@@ -55,6 +55,7 @@ public class HassApiImpl implements HueApi {
5555 private final HassAvailabilityListener availabilityListener ;
5656 private final ObjectMapper mapper ;
5757 private final String baseUrl ;
58+ private final String z2mBaseTopic ;
5859
5960 private final Object lightMapLock = new Object ();
6061 private Map <String , State > availableStates ;
@@ -63,12 +64,13 @@ public class HassApiImpl implements HueApi {
6364 private boolean nameToStatesMapInvalidated ;
6465
6566 public HassApiImpl (String origin , HttpResourceProvider httpResourceProvider , HassAreaRegistry hassAreaRegistry ,
66- HassAvailabilityListener availabilityListener , RateLimiter rateLimiter ) {
67+ HassAvailabilityListener availabilityListener , RateLimiter rateLimiter , String z2mBaseTopic ) {
6768 baseUrl = origin + "/api" ;
6869 this .httpResourceProvider = httpResourceProvider ;
6970 this .hassAreaRegistry = hassAreaRegistry ;
7071 this .availabilityListener = availabilityListener ;
7172 this .rateLimiter = rateLimiter ;
73+ this .z2mBaseTopic = z2mBaseTopic ;
7274 mapper = new ObjectMapper ();
7375 mapper .disable (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES );
7476 mapper .setDefaultPropertyInclusion (JsonInclude .Include .NON_NULL );
@@ -167,11 +169,61 @@ public void allowFastSceneUpdate(String groupId) {
167169 private void putStateInternal (PutCall putCall ) {
168170 String id = putCall .getId ();
169171 assertSupportedStateType (id );
172+
173+ // Determine if this is a silent attribute update (light is off, and we aren't explicitly turning it on)
174+ boolean isOffUpdate = (putCall .getOn () == null && isLightOff (id )) || Boolean .FALSE .equals (putCall .getOn ());
175+ boolean hasAttributesToUpdate = putCall .getBri () != null || putCall .getCt () != null || (putCall .getX () != null && putCall .getY () != null );
176+
177+ if (isOffUpdate && hasAttributesToUpdate && this .z2mBaseTopic != null ) {
178+ State state = getAndAssertLightExists (id );
179+ String friendlyName = state .getAttributes ().getFriendly_name ();
180+
181+ if (friendlyName != null ) {
182+ publishZ2mMqttUpdate (putCall , friendlyName );
183+
184+ // If on is null, we only wanted a silent update. We can safely return.
185+ if (putCall .getOn () == null ) {
186+ return ;
187+ }
188+ // If putCall.getOn() == false, we fall through to let HA run the actual turn_off service
189+ }
190+ }
191+
170192 ChangeState changeState = getChangeState (putCall );
171193 changeState .setEntity_id (id );
172194 httpResourceProvider .postResource (getUpdateUrl (putCall ), getBody (changeState ));
173195 }
174196
197+ private void publishZ2mMqttUpdate (PutCall putCall , String friendlyName ) {
198+ List <String > payloadParts = new ArrayList <>();
199+
200+ // Hardcode the null state to bypass JSON serializer dropping it
201+ payloadParts .add ("\" state\" :null" );
202+
203+ if (putCall .getBri () != null ) {
204+ payloadParts .add ("\" brightness\" :" + hueToHassBrightness (putCall .getBri ()));
205+ }
206+ if (putCall .getCt () != null ) {
207+ payloadParts .add ("\" color_temp\" :" + miredsToKelvin (putCall .getCt ()));
208+ }
209+ if (putCall .getX () != null && putCall .getY () != null ) {
210+ Double [] xy = getXyColor (putCall );
211+ payloadParts .add ("\" color\" :{\" x\" :" + xy [0 ] + ",\" y\" :" + xy [1 ] + "}" );
212+ }
213+ if (putCall .getTransitionTime () != null ) {
214+ payloadParts .add ("\" transition\" :" + convertToSeconds (putCall .getTransitionTime ()));
215+ }
216+
217+ // Combine the parts into a raw JSON string
218+ String rawZ2mPayload = "{" + String .join ("," , payloadParts ) + "}" ;
219+
220+ MqttPublish publish = new MqttPublish ();
221+ publish .setTopic (this .z2mBaseTopic + "/" + friendlyName + "/set" );
222+ publish .setPayload (rawZ2mPayload );
223+
224+ httpResourceProvider .postResource (createUrl ("/services/mqtt/publish" ), getBody (publish ));
225+ }
226+
175227 private ChangeState getChangeState (PutCall putCall ) {
176228 ChangeState changeState = new ChangeState ();
177229 changeState .setBrightness (hueToHassBrightness (putCall .getBri ()));
@@ -641,4 +693,10 @@ private static final class CreateScene {
641693 String scene_id ;
642694 Map <String , ChangeState > entities ;
643695 }
696+
697+ @ Data
698+ private static final class MqttPublish {
699+ String topic ;
700+ String payload ;
701+ }
644702}
0 commit comments