mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[mffan] Initial contribution (#16786)
* Added entry for binding mffan Signed-off-by: mark-brooks-180 <mark.brooks.180@gmail.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
44d749c379
commit
98b9aca21e
@ -212,6 +212,7 @@
|
||||
/bundles/org.openhab.binding.meteoalerte/ @clinique
|
||||
/bundles/org.openhab.binding.meteoblue/ @9037568
|
||||
/bundles/org.openhab.binding.meteostick/ @cdjackson
|
||||
/bundles/org.openhab.binding.mffan/ @mark-brooks-180
|
||||
/bundles/org.openhab.binding.miele/ @kgoderis @jlaur
|
||||
/bundles/org.openhab.binding.mielecloud/ @BjoernLange
|
||||
/bundles/org.openhab.binding.mihome/ @pboos
|
||||
|
@ -1056,6 +1056,11 @@
|
||||
<artifactId>org.openhab.binding.meteostick</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.mffan</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.miele</artifactId>
|
||||
|
13
bundles/org.openhab.binding.mffan/NOTICE
Normal file
13
bundles/org.openhab.binding.mffan/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
69
bundles/org.openhab.binding.mffan/README.md
Normal file
69
bundles/org.openhab.binding.mffan/README.md
Normal file
@ -0,0 +1,69 @@
|
||||
# MfFan Binding
|
||||
|
||||
This binding is used to enable communications between openHAB and "Modern Forms" or "WAC Lighting" WIFI connected, smart, ceiling fans.
|
||||
|
||||
## Supported Things
|
||||
|
||||
The binding currently supports the following thing:
|
||||
|
||||
| Thing | ID | |
|
||||
|---------------|-------------|----------------------------------------------------------------|
|
||||
| mffan | mffan | Smart fans consisting of fan and optional integrated LED light |
|
||||
|
||||
## Discovery
|
||||
|
||||
Auto discovery is not supported at this time.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|-----------------|---------|---------------------------------------|---------|----------|----------|
|
||||
| hostname | text | Hostname or IP address of the device | N/A | yes | no |
|
||||
| refreshInterval | integer | Interval the device is polled in sec. | 120 | no | yes |
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|------------------|------------------------|------------|-------------------------------------|
|
||||
| fan-on | Switch | RW | Channel that turns the fan on/off. |
|
||||
| fan-speed | String | RW | Controls the fan's rate of rotation.|
|
||||
| fan-direction | String | RW | Controls the direction of the fan. |
|
||||
| wind-on | Switch | RW | Turn the fan's "wind mode" on/off. |
|
||||
| wind-level | String | RW | The amount of wind produced. |
|
||||
| light-on | Switch | RW | Turns the light on/off |
|
||||
| light-intensity | Number:Dimensionless | RW | Controls the intensity of the light |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
### Thing Configuration
|
||||
|
||||
```java
|
||||
mffan:mffan:db0bd2eb4d [label="Greatroom Fan", ipAddress="fan.greatroom.local", pollingPeriod = "120"]
|
||||
```
|
||||
|
||||
### Item Configuration
|
||||
|
||||
```java
|
||||
Switch Greatroom_Fan_Fan { channel="mffan:mffan:db0bd2eb4d:fan-on" }
|
||||
String Greatroom_Fan_Fan_Direction {channel="mffan:mffan:db0bd2eb4d:fan-direction" }
|
||||
String Greatroom_Fan_Fan_Speed {channel="mffan:mffan:db0bd2eb4d:fan-speed" }
|
||||
Switch Greatroom_Fan_Light {channel="mffan:mffan:db0bd2eb4d:light-on" }
|
||||
Dimmer Greatroom_Fan_Light_Intensity {channel="mffan:mffan:db0bd2eb4d:light-intensity" }
|
||||
Switch Greatroom_Fan_Wind {channel="mffan:mffan:db0bd2eb4d:wind-on" }
|
||||
String Greatroom_Fan_Wind_Level {channel="mffan:mffan:db0bd2eb4d:wind-level" }
|
||||
```
|
||||
|
||||
### Sitemap Configuration
|
||||
|
||||
```perl
|
||||
Group icon=fan_ceiling label="Fan" item=Greatroom_Fan {
|
||||
Switch icon=switch label="Fan On/Off" item=Greatroom_Fan_Fan
|
||||
Selection label="Fan Speed" item=Greatroom_Fan_Fan_Speed
|
||||
Selection label="Fan Direction" item=Greatroom_Fan_Fan_Direction
|
||||
Switch icon=switch label="Window On/Off" item=Greatroom_Fan_Wind
|
||||
Selection label="Wind Level" item=Greatroom_Fan_Wind_Level
|
||||
Switch icon=switch label="Light On/Off" item=Greatroom_Fan_Light
|
||||
Slider label="Light Intensity" item=Greatroom_Fan_Light_Intensity
|
||||
}
|
||||
```
|
17
bundles/org.openhab.binding.mffan/pom.xml
Normal file
17
bundles/org.openhab.binding.mffan/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.mffan</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: MfFan Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.mffan-${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-mffan" description="MfFan Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mffan/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.mffan.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link MfFanBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Mark Brooks - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MfFanBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "mffan";
|
||||
private static final String THING_MFFAN_ID = "mffan";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_MFFAN = new ThingTypeUID(BINDING_ID, THING_MFFAN_ID);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MFFAN);
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_FAN_ON = "fan-on";
|
||||
public static final String CHANNEL_FAN_SPEED = "fan-speed";
|
||||
public static final String CHANNEL_FAN_DIRECTION = "fan-direction";
|
||||
public static final String CHANNEL_WIND_ON = "wind-on";
|
||||
public static final String CHANNEL_WIND_LEVEL = "wind-level";
|
||||
public static final String CHANNEL_LIGHT_ON = "light-on";
|
||||
public static final String CHANNEL_LIGHT_INTENSITY = "light-intensity";
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.mffan.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link MfFanConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Mark Brooks - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MfFanConfiguration {
|
||||
|
||||
private String ipAddress;
|
||||
private Integer pollingPeriod;
|
||||
|
||||
public MfFanConfiguration() {
|
||||
this.ipAddress = "";
|
||||
this.pollingPeriod = 120;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return this.ipAddress.trim();
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public Integer getPollingPeriod() {
|
||||
return this.pollingPeriod;
|
||||
}
|
||||
|
||||
public void setPollingPeriod(Integer pollingPeriod) {
|
||||
this.pollingPeriod = pollingPeriod;
|
||||
}
|
||||
|
||||
public static boolean validateConfig(@Nullable MfFanConfiguration config) {
|
||||
if (config == null || config.getIpAddress().isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return config.getPollingPeriod() >= 10;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.mffan.internal;
|
||||
|
||||
import static org.openhab.binding.mffan.internal.MfFanBindingConstants.THING_TYPE_MFFAN;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mffan.internal.handler.MfFanHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link MfFanHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Mark Brooks - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.mffan", service = ThingHandlerFactory.class)
|
||||
public class MfFanHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MFFAN);
|
||||
private HttpClientFactory httpClientFactory;
|
||||
|
||||
@Activate
|
||||
public MfFanHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
@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_MFFAN.equals(thingTypeUID)) {
|
||||
return new MfFanHandler(thing, this.httpClientFactory);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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.mffan.internal.api;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link FanRestApi} is implements provides access to the smart fan's REST services.
|
||||
*
|
||||
* @author Mark Brooks - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FanRestApi {
|
||||
private final Logger logger = LoggerFactory.getLogger(FanRestApi.class);
|
||||
|
||||
private final String ipAddress;
|
||||
private final String url;
|
||||
private final HttpClient client;
|
||||
private final Gson gson;
|
||||
|
||||
public FanRestApi(String ipAddress, HttpClientFactory httpClientFactory) {
|
||||
this.ipAddress = ipAddress;
|
||||
this.url = String.format("http://%s/mf", this.ipAddress);
|
||||
this.client = httpClientFactory.getCommonHttpClient();
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto getShadowBuffer() throws RestApiException {
|
||||
return doPost("{\"queryDynamicShadowData\" : 1}");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto setFanPower(boolean power) throws RestApiException {
|
||||
return doPost(String.format("{\"fanOn\" : %s}", String.valueOf(power)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto setFanSpeed(int speed) throws RestApiException {
|
||||
return doPost(String.format("{\"fanSpeed\" : %d}", speed));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto setFanDirection(ShadowBufferDto.FanDirection direction) throws RestApiException {
|
||||
return doPost(String.format("{\"fanDirection\" : \"%s\"}", direction.name()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto setWindPower(boolean power) throws RestApiException {
|
||||
return doPost(String.format("{\"wind\" : %s}", String.valueOf(power)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto setWindSpeed(int speed) throws RestApiException {
|
||||
return doPost(String.format("{\"windSpeed\" : %d}", speed));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto setLightPower(boolean power) throws RestApiException {
|
||||
return doPost(String.format("{\"lightOn\" : %s}", String.valueOf(power)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ShadowBufferDto setLightIntensity(int intensity) throws RestApiException {
|
||||
return doPost(String.format("{\"lightBrightness\" : %d}", intensity));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ShadowBufferDto doPost(String payloadJson) throws RestApiException {
|
||||
try {
|
||||
this.logger.debug("Performing Post: 'URL: {}, Payload: '{}'", this.url, payloadJson);
|
||||
Request postRequest = this.client.POST(this.url);
|
||||
postRequest.timeout(10, TimeUnit.SECONDS);
|
||||
postRequest.header(HttpHeader.ACCEPT, MediaType.APPLICATION_JSON);
|
||||
postRequest.header(HttpHeader.CONTENT_TYPE, MediaType.APPLICATION_JSON);
|
||||
postRequest.content(new StringContentProvider(payloadJson, Charset.forName(StandardCharsets.UTF_8.name())));
|
||||
ContentResponse postResponse = postRequest.send();
|
||||
this.logger.debug("Response status: {}", postResponse.getStatus());
|
||||
if (postResponse.getStatus() == 200) {
|
||||
this.logger.trace("Post Response Content = '{}'", postResponse.getContentAsString());
|
||||
return this.gson.fromJson(postResponse.getContentAsString(), ShadowBufferDto.class);
|
||||
}
|
||||
} catch (JsonSyntaxException | InterruptedException | TimeoutException | ExecutionException e) {
|
||||
this.logger.warn("Exception on post: {}", e.getMessage());
|
||||
throw new RestApiException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.mffan.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RestApiException} is an exception thrown from the REST API.
|
||||
*
|
||||
* @author Mark Brooks - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RestApiException extends Exception {
|
||||
private static final long serialVersionUID = -6340681561578357625L;
|
||||
|
||||
public RestApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RestApiException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 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.mffan.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link ShadowBufferDto} shadow buffer data transport object.
|
||||
*
|
||||
* @author Mark Brooks - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadowBufferDto {
|
||||
@Expose
|
||||
private String clientId;
|
||||
|
||||
@Expose
|
||||
private Integer cloudPort;
|
||||
|
||||
@Expose
|
||||
private Boolean lightOn;
|
||||
|
||||
@Expose
|
||||
private Boolean fanOn;
|
||||
|
||||
@Expose
|
||||
private Integer lightBrightness;
|
||||
|
||||
@Expose
|
||||
private Integer fanSpeed;
|
||||
|
||||
@Expose
|
||||
private FanDirection fanDirection;
|
||||
|
||||
@Expose
|
||||
private Boolean wind;
|
||||
|
||||
@Expose
|
||||
private Integer windSpeed;
|
||||
|
||||
@Expose
|
||||
private Boolean rfPairModeActive;
|
||||
|
||||
@Expose
|
||||
private Boolean resetRfPairList;
|
||||
|
||||
@Expose
|
||||
private Boolean factoryReset;
|
||||
|
||||
@Expose
|
||||
private Boolean awayModeEnabled;
|
||||
|
||||
@Expose
|
||||
private Integer fanTimer;
|
||||
|
||||
@Expose
|
||||
private Integer lightTimer;
|
||||
|
||||
@Expose
|
||||
private Boolean decommission;
|
||||
|
||||
@Expose
|
||||
private String schedule;
|
||||
|
||||
@Expose
|
||||
private Boolean adaptiveLearning;
|
||||
|
||||
@Expose
|
||||
private String userData;
|
||||
|
||||
@Expose
|
||||
private String timezone;
|
||||
|
||||
@Expose
|
||||
@SerializedName("FrCodes")
|
||||
private String frCodes;
|
||||
|
||||
@Expose
|
||||
private Boolean cdebug;
|
||||
|
||||
@Expose
|
||||
private Boolean feedbackToneMute;
|
||||
|
||||
public enum FanDirection {
|
||||
forward,
|
||||
reverse
|
||||
}
|
||||
|
||||
public ShadowBufferDto() {
|
||||
super();
|
||||
this.clientId = "";
|
||||
this.cloudPort = 0;
|
||||
this.lightOn = false;
|
||||
this.fanOn = false;
|
||||
this.lightBrightness = 0;
|
||||
this.fanSpeed = 0;
|
||||
this.fanDirection = FanDirection.forward;
|
||||
this.wind = false;
|
||||
this.windSpeed = 0;
|
||||
this.rfPairModeActive = false;
|
||||
this.resetRfPairList = false;
|
||||
this.factoryReset = false;
|
||||
this.awayModeEnabled = false;
|
||||
this.fanTimer = 0;
|
||||
this.lightTimer = 0;
|
||||
this.decommission = false;
|
||||
this.schedule = "";
|
||||
this.adaptiveLearning = false;
|
||||
this.userData = "";
|
||||
this.timezone = "";
|
||||
this.frCodes = "";
|
||||
this.cdebug = false;
|
||||
this.feedbackToneMute = false;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
public Integer getCloudPort() {
|
||||
return this.cloudPort;
|
||||
}
|
||||
|
||||
public Boolean getLightOn() {
|
||||
return this.lightOn;
|
||||
}
|
||||
|
||||
public Boolean getFanOn() {
|
||||
return this.fanOn;
|
||||
}
|
||||
|
||||
public OnOffType getFanOnAsOnOffType() {
|
||||
return OnOffType.from(this.fanOn);
|
||||
}
|
||||
|
||||
public Integer getLightBrightness() {
|
||||
return this.lightBrightness;
|
||||
}
|
||||
|
||||
public Integer getFanSpeed() {
|
||||
return this.fanSpeed;
|
||||
}
|
||||
|
||||
public FanDirection getFanDirection() {
|
||||
return this.fanDirection;
|
||||
}
|
||||
|
||||
public Boolean getWind() {
|
||||
return this.wind;
|
||||
}
|
||||
|
||||
public Integer getWindSpeed() {
|
||||
return this.windSpeed;
|
||||
}
|
||||
|
||||
public Boolean getRfPairModeActive() {
|
||||
return this.rfPairModeActive;
|
||||
}
|
||||
|
||||
public Boolean getResetRfPairList() {
|
||||
return this.resetRfPairList;
|
||||
}
|
||||
|
||||
public Boolean getFactoryReset() {
|
||||
return this.factoryReset;
|
||||
}
|
||||
|
||||
public Boolean getAwayModeEnabled() {
|
||||
return this.awayModeEnabled;
|
||||
}
|
||||
|
||||
public Integer getFanTimer() {
|
||||
return this.fanTimer;
|
||||
}
|
||||
|
||||
public Integer getLightTimer() {
|
||||
return this.lightTimer;
|
||||
}
|
||||
|
||||
public Boolean getDecommission() {
|
||||
return this.decommission;
|
||||
}
|
||||
|
||||
public String getSchedule() {
|
||||
return this.schedule;
|
||||
}
|
||||
|
||||
public Boolean getAdaptiveLearning() {
|
||||
return this.adaptiveLearning;
|
||||
}
|
||||
|
||||
public String getUserData() {
|
||||
return this.userData;
|
||||
}
|
||||
|
||||
public String getTimezone() {
|
||||
return this.timezone;
|
||||
}
|
||||
|
||||
public String getFrCodes() {
|
||||
return this.frCodes;
|
||||
}
|
||||
|
||||
public Boolean getCdebug() {
|
||||
return this.cdebug;
|
||||
}
|
||||
|
||||
public Boolean getFeedbackToneMute() {
|
||||
return this.feedbackToneMute;
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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.mffan.internal.handler;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mffan.internal.MfFanBindingConstants;
|
||||
import org.openhab.binding.mffan.internal.MfFanConfiguration;
|
||||
import org.openhab.binding.mffan.internal.api.FanRestApi;
|
||||
import org.openhab.binding.mffan.internal.api.RestApiException;
|
||||
import org.openhab.binding.mffan.internal.api.ShadowBufferDto;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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.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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MfFanHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Mark Brooks - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MfFanHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MfFanHandler.class);
|
||||
|
||||
@NonNullByDefault({} /* non-null if initialized */)
|
||||
private MfFanConfiguration config;
|
||||
|
||||
@NonNullByDefault({} /* non-null if initialized */)
|
||||
private FanRestApi api;
|
||||
|
||||
@NonNullByDefault({} /* non-null if initialized */)
|
||||
private ScheduledFuture<?> pollingJob;
|
||||
|
||||
private HttpClientFactory httpClientFactory;
|
||||
|
||||
public MfFanHandler(Thing thing, HttpClientFactory httpClientFactory) {
|
||||
super(thing);
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.logger.debug("Initializing MfFan handler '{}'", getThing().getUID());
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
this.config = getConfigAs(MfFanConfiguration.class);
|
||||
if (!MfFanConfiguration.validateConfig(this.config)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid configuration detected.");
|
||||
return;
|
||||
}
|
||||
this.api = new FanRestApi(this.config.getIpAddress(), this.httpClientFactory);
|
||||
this.pollingJob = this.scheduler.scheduleWithFixedDelay(() -> getShadowBufferAndUpdate(), 0,
|
||||
this.config.getPollingPeriod(), TimeUnit.SECONDS);
|
||||
this.logger.debug("Polling job scheduled to run every {} sec. for '{}'", this.config.getPollingPeriod(),
|
||||
getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.logger.debug("Disposing MF fan handler '{}'", getThing().getUID());
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
this.pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
try {
|
||||
if (command instanceof RefreshType) {
|
||||
update(MfFanHandler.this.api.getShadowBuffer());
|
||||
} else if (channelUID.getId().equals(MfFanBindingConstants.CHANNEL_FAN_ON)) {
|
||||
if (command instanceof OnOffType onOffCommand) {
|
||||
update(MfFanHandler.this.api.setFanPower(onOffCommand == OnOffType.ON));
|
||||
}
|
||||
} else if (channelUID.getId().equals(MfFanBindingConstants.CHANNEL_FAN_SPEED)) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
update(MfFanHandler.this.api.setFanSpeed(Integer.valueOf(stringCommand.toString())));
|
||||
}
|
||||
} else if (channelUID.getId().equals(MfFanBindingConstants.CHANNEL_FAN_DIRECTION)) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
update(MfFanHandler.this.api
|
||||
.setFanDirection(ShadowBufferDto.FanDirection.valueOf(stringCommand.toString())));
|
||||
}
|
||||
} else if (channelUID.getId().equals(MfFanBindingConstants.CHANNEL_LIGHT_ON)) {
|
||||
if (command instanceof OnOffType onOffCommand) {
|
||||
update(MfFanHandler.this.api.setLightPower(onOffCommand == OnOffType.ON));
|
||||
}
|
||||
} else if (channelUID.getId().equals(MfFanBindingConstants.CHANNEL_LIGHT_INTENSITY)) {
|
||||
if (command instanceof QuantityType quantityCommand) {
|
||||
update(MfFanHandler.this.api.setLightIntensity(quantityCommand.intValue()));
|
||||
}
|
||||
} else if (channelUID.getId().equals(MfFanBindingConstants.CHANNEL_WIND_ON)) {
|
||||
if (command instanceof OnOffType onOffCommand) {
|
||||
update(MfFanHandler.this.api.setWindPower(onOffCommand == OnOffType.ON));
|
||||
}
|
||||
} else if (channelUID.getId().equals(MfFanBindingConstants.CHANNEL_WIND_LEVEL)) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
update(MfFanHandler.this.api.setWindSpeed(Integer.valueOf(stringCommand.toString())));
|
||||
}
|
||||
} else {
|
||||
MfFanHandler.this.logger.warn("Skipping command. Unidentified channel id '{}'", channelUID.getId());
|
||||
}
|
||||
} catch (@SuppressWarnings("unused") RestApiException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, String
|
||||
.format("Could not control device at IP address %s", MfFanHandler.this.config.getIpAddress()));
|
||||
}
|
||||
}
|
||||
|
||||
private void getShadowBufferAndUpdate() {
|
||||
try {
|
||||
update(MfFanHandler.this.api.getShadowBuffer());
|
||||
} catch (@SuppressWarnings("unused") RestApiException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, String
|
||||
.format("Could not control device at IP address %s", MfFanHandler.this.config.getIpAddress()));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void update(@Nullable ShadowBufferDto dto) {
|
||||
MfFanHandler.this.logger.debug("Updating data '{}'", getThing().getUID());
|
||||
if (dto != null) {
|
||||
updateState(MfFanBindingConstants.CHANNEL_FAN_ON, OnOffType.from(dto.getFanOn().booleanValue()));
|
||||
updateState(MfFanBindingConstants.CHANNEL_FAN_SPEED, StringType.valueOf(String.valueOf(dto.getFanSpeed())));
|
||||
updateState(MfFanBindingConstants.CHANNEL_FAN_DIRECTION, StringType.valueOf(dto.getFanDirection().name()));
|
||||
updateState(MfFanBindingConstants.CHANNEL_WIND_ON, OnOffType.from(dto.getWind().booleanValue()));
|
||||
updateState(MfFanBindingConstants.CHANNEL_WIND_LEVEL,
|
||||
StringType.valueOf(String.valueOf(dto.getWindSpeed())));
|
||||
updateState(MfFanBindingConstants.CHANNEL_LIGHT_ON, OnOffType.from(dto.getLightOn().booleanValue()));
|
||||
updateState(MfFanBindingConstants.CHANNEL_LIGHT_INTENSITY, new DecimalType(dto.getLightBrightness()));
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Null shadow buffer returned.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="mffan" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
|
||||
<type>binding</type>
|
||||
<name>Modern Forms Fan Binding</name>
|
||||
<description>This is the binding for "Modern Forms", and "WAC Lighting" smart ceiling fans.</description>
|
||||
<connection>local</connection>
|
||||
<countries>us</countries>
|
||||
<discovery-methods>
|
||||
<discovery-method>
|
||||
<service-type>manual</service-type>
|
||||
</discovery-method>
|
||||
</discovery-methods>
|
||||
</addon:addon>
|
@ -0,0 +1,47 @@
|
||||
|
||||
thing-type.mffan.mffan.label = Modern Forms Fan
|
||||
thing-type.mffan.mffan.description = Modern Forms and WAC Lighting Smart Ceiling Fans
|
||||
|
||||
thing-type.config.mffan.mffan.ipAddress.label = IP or Host
|
||||
thing-type.config.mffan.mffan.ipAddress.description = IP address or host name of the fan.
|
||||
thing-type.config.mffan.mffan.pollingPeriod.label = Refresh Interval
|
||||
thing-type.config.mffan.mffan.pollingPeriod.description = Interval the device is polled in seconds.
|
||||
|
||||
channel-type.mffan.fan-on.label = Fan
|
||||
channel-type.mffan.fan-on.description = Fan on/off
|
||||
|
||||
|
||||
channel-type.mffan.fan-speed.label = Fan Speed
|
||||
channel-type.mffan.fan-speed.description = The fan's rotational rate.
|
||||
channel-type.mffan.fan-speed.state.option.1 = Speed 1
|
||||
channel-type.mffan.fan-speed.state.option.2 = Speed 2
|
||||
channel-type.mffan.fan-speed.state.option.3 = Speed 3
|
||||
channel-type.mffan.fan-speed.state.option.4 = Speed 4
|
||||
channel-type.mffan.fan-speed.state.option.5 = Speed 5
|
||||
channel-type.mffan.fan-speed.state.option.6 = Speed 6
|
||||
|
||||
channel-type.mffan.fan-direction.label = Fan Direction
|
||||
channel-type.mffan.fan-direction.description = The fan's direction of rotation: Forward (Summer), Reverse (Winter).
|
||||
channel-type.mffan.fan-direction.state.option.forward = Forward
|
||||
channel-type.mffan.fan-direction.state.option.reverse = Reverse
|
||||
|
||||
channel-type.mffan.wind-on.label = Wind
|
||||
channel-type.mffan.wind-on.description = Wind (sometimes referred to as "Breeze Mode") on/off.
|
||||
|
||||
channel-type.mffan.wind-level.label = Wind Level
|
||||
channel-type.mffan.wind-level.description = The amount of the wind being produced.
|
||||
channel-type.mffan.wind-level.state.option.1 = Level 1
|
||||
channel-type.mffan.wind-level.state.option.2 = Level 2
|
||||
channel-type.mffan.wind-level.state.option.3 = Level 3
|
||||
|
||||
channel-type.mffan.light-on.label = Light
|
||||
channel-type.mffan.light-on.description = Light on/off.
|
||||
|
||||
channel-type.mffan.light-intensity.label = Light Intensity
|
||||
channel-type.mffan.light-intensity.description = The light intensity.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mffan"
|
||||
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="mffan">
|
||||
|
||||
<label>Modern Forms Fan</label>
|
||||
<description>Modern Forms and WAC Lighting Smart Ceiling Fans</description>
|
||||
|
||||
<channels>
|
||||
<channel id="fan-on" typeId="fan-on"/>
|
||||
<channel id="fan-speed" typeId="fan-speed"/>
|
||||
<channel id="fan-direction" typeId="fan-direction"/>
|
||||
<channel id="wind-on" typeId="wind-on"/>
|
||||
<channel id="wind-level" typeId="wind-level"/>
|
||||
<channel id="light-on" typeId="light-on"/>
|
||||
<channel id="light-intensity" typeId="light-intensity"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<label>IP or Host</label>
|
||||
<description>IP address or host name of the fan.</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="pollingPeriod" type="integer" unit="s" min="10">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Interval the device is polled in seconds.</description>
|
||||
<default>120</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="fan-on">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Fan</label>
|
||||
<description>Fan on/off.</description>
|
||||
</channel-type>
|
||||
<channel-type id="fan-speed">
|
||||
<item-type>String</item-type>
|
||||
<label>Fan Speed</label>
|
||||
<description>The fan's rotational rate.</description>
|
||||
<state readOnly="false" pattern="Fan %s">
|
||||
<options>
|
||||
<option value="1">Speed 1</option>
|
||||
<option value="2">Speed 2</option>
|
||||
<option value="3">Speed 3</option>
|
||||
<option value="4">Speed 4</option>
|
||||
<option value="5">Speed 5</option>
|
||||
<option value="6">Speed 6</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="fan-direction">
|
||||
<item-type>String</item-type>
|
||||
<label>Fan Direction</label>
|
||||
<description>The fan's direction of rotation: Forward (Summer), Reverse (Winter).</description>
|
||||
<state readOnly="false" pattern="Direction %s">
|
||||
<options>
|
||||
<option value="forward">Forward</option>
|
||||
<option value="reverse">Reverse</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="wind-on">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Wind</label>
|
||||
<description>Wind (sometimes referred to as "Breeze Mode") on/off.</description>
|
||||
</channel-type>
|
||||
<channel-type id="wind-level">
|
||||
<item-type>String</item-type>
|
||||
<label>Wind Level</label>
|
||||
<description>The amount of the wind being produced.</description>
|
||||
<state readOnly="false" pattern="Wind %s">
|
||||
<options>
|
||||
<option value="1">Level 1</option>
|
||||
<option value="2">Level 2</option>
|
||||
<option value="3">Level 3</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="light-on">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Light</label>
|
||||
<description>Light on/off.</description>
|
||||
</channel-type>
|
||||
<channel-type id="light-intensity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Light Intensity</label>
|
||||
<description>The light intensity.</description>
|
||||
<state readOnly="false" min="1" max="100" step="1" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -246,6 +246,7 @@
|
||||
<module>org.openhab.binding.meteoalerte</module>
|
||||
<module>org.openhab.binding.meteoblue</module>
|
||||
<module>org.openhab.binding.meteostick</module>
|
||||
<module>org.openhab.binding.mffan</module>
|
||||
<module>org.openhab.binding.miele</module>
|
||||
<module>org.openhab.binding.mielecloud</module>
|
||||
<module>org.openhab.binding.mihome</module>
|
||||
|
Loading…
Reference in New Issue
Block a user