diff --git a/README.md b/README.md index ae37117..fba0e87 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ barely tested, and some are known as inoperable. * Remote Control N2 * TRADFRI on/off switch * TRADFRI remote control + * LEPTITER Recessed spot light * Sound-Controller * SYMFONISK Sound Controller * Blinds-Controller diff --git a/dirigera-client-api/src/main/java/de/dvdgeisler/iot/dirigera/client/api/model/device/light/LightColorAttributes.java b/dirigera-client-api/src/main/java/de/dvdgeisler/iot/dirigera/client/api/model/device/light/LightColorAttributes.java index 59ed67e..617a90c 100644 --- a/dirigera-client-api/src/main/java/de/dvdgeisler/iot/dirigera/client/api/model/device/light/LightColorAttributes.java +++ b/dirigera-client-api/src/main/java/de/dvdgeisler/iot/dirigera/client/api/model/device/light/LightColorAttributes.java @@ -20,8 +20,18 @@ public class LightColorAttributes { public Float saturation; @JsonProperty("colorTemperature") public Integer temperature; + + /** + * The minimal color temperature in Kelvin. + * @implNote higher Kelvin indicates lower color temperature + */ @JsonProperty("colorTemperatureMin") public Integer temperatureMin; + + /** + * The maximal color temperature in Kelvin. + * @implNote lower Kelvin indicates higher color temperature + */ @JsonProperty("colorTemperatureMax") public Integer temperatureMax; @JsonProperty("colorMode") diff --git a/dirigera-client-api/src/test/java/de/dvdgeisler/iot/dirigera/client/api/model/device/light/LEPTITERRecessedspotlight.java b/dirigera-client-api/src/test/java/de/dvdgeisler/iot/dirigera/client/api/model/device/light/LEPTITERRecessedspotlight.java new file mode 100644 index 0000000..3d3be64 --- /dev/null +++ b/dirigera-client-api/src/test/java/de/dvdgeisler/iot/dirigera/client/api/model/device/light/LEPTITERRecessedspotlight.java @@ -0,0 +1,66 @@ +package de.dvdgeisler.iot.dirigera.client.api.model.device.light; + +import de.dvdgeisler.iot.dirigera.client.api.model.device.Device; +import de.dvdgeisler.iot.dirigera.client.api.model.device.DeviceTest; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class LEPTITERRecessedspotlight extends DeviceTest { + final static String JSON = """ + { + "id" : "7e69e364-17b6-4c15-9c18-1835d0e44d87_1", + "type" : "light", + "deviceType" : "light", + "createdAt" : "2022-11-13T12:58:16.000Z", + "isReachable" : true, + "lastSeen" : "2022-11-22T17:57:24.000Z", + "attributes" : { + "customName" : "Bathroom light", + "firmwareVersion" : "2.3.087", + "hardwareVersion" : "1", + "manufacturer" : "IKEA of Sweden", + "model" : "LEPTITER Recessed spot light", + "productCode" : "T1820", + "serialNumber" : "000D6FFFFE12EC29", + "isOn" : false, + "startupOnOff" : "startOn", + "lightLevel" : 20, + "colorMode" : "temperature", + "colorTemperature" : 2500, + "colorTemperatureMax" : 2202, + "colorTemperatureMin" : 4000, + "identifyPeriod" : 0, + "identifyStarted" : "2000-01-01T00:00:00.000Z", + "permittingJoin" : false, + "otaPolicy" : "autoUpdate", + "otaProgress" : 0, + "otaScheduleEnd" : "00:00", + "otaScheduleStart" : "00:00", + "otaState" : "readyToCheck", + "otaStatus" : "upToDate" + }, + "capabilities" : { + "canSend" : [ ], + "canReceive" : [ "customName", "isOn", "lightLevel", "colorTemperature" ] + }, + "room" : { + "id" : "90b00dc0-6a6a-477b-82f1-f7ac47c6cd6d", + "name" : "Bathroom", + "color" : "ikea_lilac_no_3", + "icon" : "rooms_bathtub" + }, + "deviceSet" : [ ], + "remoteLinks" : [ "57e25312-9e04-4fcc-adc3-2e57c9c89be0_1" ], + "isHidden" : false + } + """; + + public LEPTITERRecessedspotlight() { + super(JSON); + } + + @Override + public void validateDeserialize(final Device device) { + assertInstanceOf(LightDevice.class, device); + } +} \ No newline at end of file diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/MqttBridge.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/MqttBridge.java index aedb848..ce5d97d 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/MqttBridge.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/MqttBridge.java @@ -27,8 +27,7 @@ public MqttBridge( @Value("${dirigera.mqtt.reconnect:true}") final Boolean reconnect, @Value("${dirigera.mqtt.timeout:10}") final Integer timeout, final DirigeraApi api) throws MqttException { - super(String.format("tcp://%s:%d", host, port), - Objects.requireNonNull(api.status().map(s -> s.id).block())); + super(String.format("tcp://%s:%d", host, port), clientId(api)); final MqttConnectOptions options; this.api = api; this.eventHandler = new HashMap<>(); @@ -122,4 +121,9 @@ public <_Device extends Device> void removeDevice(final _Device device) { .flatMap(factory -> factory.removeDevice(this, this.api, device)) .forEach(this::publishMessage); } + + private static String clientId(final DirigeraApi api) { + api.pairIfRequired().block(); + return api.status().map(s -> s.id).block(); + } } diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/HassEventHandler.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/HassEventHandler.java index 529d932..06e9636 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/HassEventHandler.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/HassEventHandler.java @@ -2,24 +2,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.PrettyPrinter; import com.fasterxml.jackson.databind.ObjectMapper; import de.dvdgeisler.iot.dirigera.client.api.DirigeraApi; -import de.dvdgeisler.iot.dirigera.client.api.model.device.light.LightDevice; import de.dvdgeisler.iot.dirigera.client.mqtt.MqttBridge; import de.dvdgeisler.iot.dirigera.client.mqtt.MqttBridgeMessage; import de.dvdgeisler.iot.dirigera.client.mqtt.MqttEventHandler; -import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.Device; import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.DeviceAvailability; -import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.light.LightStatus; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.core.publisher.Mono; -import java.io.IOException; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -27,8 +20,6 @@ import static de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.DeviceAvailabilityState.OFFLINE; import static de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.DeviceAvailabilityState.ONLINE; -import static java.lang.Math.max; -import static java.lang.Math.min; public abstract class HassEventHandler<_Device extends de.dvdgeisler.iot.dirigera.client.api.model.device.Device, _DeviceState> implements MqttEventHandler<_Device> { private final static Logger log = LoggerFactory.getLogger(HassEventHandler.class); @@ -56,22 +47,35 @@ protected String topic(final MqttBridge mqtt, final _Device device, final String .collect(Collectors.joining("/")); } - protected MqttBridgeMessage<_Device> build(final MqttBridge mqtt, final _Device device, final String topic, final T payload) { + protected MqttBridgeMessage<_Device> build( + final MqttBridge mqtt, + final _Device device, + final String topic, + final T payload) { try { return new MqttBridgeMessage<>( device, this, this.topic(mqtt, device, topic), - new MqttMessage(this.objectMapper.writeValueAsBytes(payload))); + new MqttMessage(this.objectMapper.writeValueAsBytes(payload)){{ + this.setQos(1); + this.setRetained(true); + }}); } catch (JsonProcessingException e) { log.error(e.getMessage()); } return null; } - protected void subscribe(final MqttBridge mqtt, final DirigeraApi api, final _Device device) { + protected void subscribe(final MqttBridge mqtt, final DirigeraApi api, final _Device device, final String topicSuffix) { + final String topic; + try { - mqtt.subscribe(this.topic(mqtt, device, "set"), (topic, message) -> { + topic = this.topic(mqtt, device, topicSuffix); + log.info("Subscribe to device: topic={}, id={}, name={}, category={}, type={}", + topic, device.id, device.attributes.state.customName, device.deviceType, device.type); + + mqtt.subscribe(this.topic(mqtt, device, topicSuffix), (t, message) -> { final _DeviceState state; try { diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/LightEventHandler.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/LightEventHandler.java index b0e0ca0..5a8438d 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/LightEventHandler.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/LightEventHandler.java @@ -51,8 +51,8 @@ public void setDevice(final MqttBridge mqtt, final DirigeraApi api, final LightD Optional.ofNullable(status.color_temp) .filter(t -> canReceive(device, "colorTemperature")) - .map(t -> max(t, getMaxTemperatureMireds(device))) // todo: getMaxTemperatureMireds may return null - .map(t -> min(t, getMinTemperatureMireds(device))) // todo: getMinTemperatureMireds may return null + .flatMap(t -> Optional.ofNullable(getMaxTemperatureMireds(device)).map(maxT -> min(t, maxT))) + .flatMap(t -> Optional.ofNullable(getMinTemperatureMireds(device)).map(minT -> max(t, minT))) .map(LightUtils::miredsToKelvin) .map(t -> api.device.light.setTemperature(device, t)) .ifPresent(Mono::block); @@ -81,7 +81,7 @@ public Stream> addDevice( final List colorModes; colorModes = colorModes(device); - this.subscribe(mqtt, api, device); + this.subscribe(mqtt, api, device, TOPIC_SET); return Stream.of(this.build(mqtt, device, TOPIC_CONFIG, new LightConfig( device.id, diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/MotionSensorEventHandler.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/MotionSensorEventHandler.java index 9c8f58e..9a490b6 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/MotionSensorEventHandler.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/MotionSensorEventHandler.java @@ -50,7 +50,6 @@ public Stream> addDevice( final DirigeraApi api, final MotionSensorDevice device) { - this.subscribe(mqtt, api, device); return Stream.of(this.build(mqtt, device, TOPIC_CONFIG, new MotionSensorConfig( device.id, diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/OutletEventHandler.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/OutletEventHandler.java index 91263ea..38b1935 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/OutletEventHandler.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/OutletEventHandler.java @@ -55,7 +55,7 @@ public Stream> addDevice( final DirigeraApi api, final OutletDevice device) { - this.subscribe(mqtt, api, device); + this.subscribe(mqtt, api, device, TOPIC_SET); return Stream.of(this.build(mqtt, device, TOPIC_CONFIG, new OutletConfig( device.id, diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/DeviceAvailability.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/DeviceAvailability.java index d1faaae..f4dc62a 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/DeviceAvailability.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/DeviceAvailability.java @@ -1,8 +1,6 @@ package de.dvdgeisler.iot.dirigera.client.mqtt.hass.model; -import de.dvdgeisler.iot.dirigera.client.api.model.device.Device; - public class DeviceAvailability { public String topic; public String payload_available; diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightColor.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightColor.java index 7c7eda6..0237f6c 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightColor.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightColor.java @@ -1,7 +1,6 @@ package de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.light; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; @JsonInclude(JsonInclude.Include.NON_NULL) public class LightColor { diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightUtils.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightUtils.java index 742691b..7d3b07a 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightUtils.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/light/LightUtils.java @@ -24,7 +24,7 @@ public static Integer miredsToKelvin(final Integer kelvin) { } public static Integer getMaxTemperatureMireds(final LightDevice device) { - return Optional.ofNullable(getMaxTemperatureKelvin(device)) + return Optional.ofNullable(getMinTemperatureKelvin(device)) .map(LightUtils::kelvinToMireds) .orElse(null); } @@ -34,12 +34,12 @@ public static Integer getMaxTemperatureKelvin(final LightDevice device) { .map(d -> d.attributes) .map(d -> d.state) .map(d -> d.color) - .map(d -> d.temperatureMax) + .map(d -> d.temperatureMin) .orElse(null); } public static Integer getMinTemperatureMireds(final LightDevice device) { - return Optional.ofNullable(getTemperatureMireds(device)) + return Optional.ofNullable(getMaxTemperatureKelvin(device)) .map(LightUtils::miredsToKelvin) .orElse(null); } @@ -49,7 +49,7 @@ public static Integer getMinTemperatureKelvin(final LightDevice device) { .map(d -> d.attributes) .map(d -> d.state) .map(d -> d.color) - .map(d -> d.temperatureMin) + .map(d -> d.temperatureMax) .orElse(null); } diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/motionsensor/MotionSensorUtils.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/motionsensor/MotionSensorUtils.java index 99ca5a3..cd69e44 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/motionsensor/MotionSensorUtils.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/motionsensor/MotionSensorUtils.java @@ -1,7 +1,6 @@ package de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.motionsensor; import de.dvdgeisler.iot.dirigera.client.api.model.device.motionsensor.MotionSensorDevice; -import de.dvdgeisler.iot.dirigera.client.api.model.device.outlet.OutletDevice; import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.DeviceState; import java.util.Optional; diff --git a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/outlet/OutletConfig.java b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/outlet/OutletConfig.java index 29ef755..e466140 100644 --- a/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/outlet/OutletConfig.java +++ b/dirigera-client-mqtt/src/main/java/de/dvdgeisler/iot/dirigera/client/mqtt/hass/model/outlet/OutletConfig.java @@ -3,10 +3,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.Device; import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.DeviceAvailability; -import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.DeviceState; -import de.dvdgeisler.iot.dirigera.client.mqtt.hass.model.light.LightColorMode; - -import java.util.List; @JsonInclude(JsonInclude.Include.NON_NULL) public class OutletConfig {