diff --git a/lib/MiLight/CctPacketFormatter.cpp b/lib/MiLight/CctPacketFormatter.cpp index 32f882b5..3b9adcfc 100644 --- a/lib/MiLight/CctPacketFormatter.cpp +++ b/lib/MiLight/CctPacketFormatter.cpp @@ -199,7 +199,10 @@ BulbId CctPacketFormatter::parsePacket(const uint8_t* packet, JsonObject result) REMOTE_TYPE_CCT ); - if (onOffGroupId < 255) { + // Night mode + if (command & 0x10) { + result["command"] = "night_mode"; + } else if (onOffGroupId < 255) { result["state"] = cctCommandToStatus(command) == ON ? "ON" : "OFF"; } else if (command == CCT_BRIGHTNESS_DOWN) { result["command"] = "brightness_down"; diff --git a/lib/MiLight/RgbwPacketFormatter.cpp b/lib/MiLight/RgbwPacketFormatter.cpp index 6ead54b0..454ba299 100644 --- a/lib/MiLight/RgbwPacketFormatter.cpp +++ b/lib/MiLight/RgbwPacketFormatter.cpp @@ -96,7 +96,15 @@ void RgbwPacketFormatter::updateColorWhite() { void RgbwPacketFormatter::enableNightMode() { uint8_t button = STATUS_COMMAND(OFF, groupId); - command(button, 0); + // Bulbs must be OFF for night mode to work in RGBW. + // Turn it off if it isn't already off. + const GroupState* state = stateStore->get(deviceId, groupId, REMOTE_TYPE_RGBW); + if (state == NULL || state->getState() == MiLightStatus::ON) { + command(button, 0); + } + + // Night mode command has 0x10 bit set, but is otherwise + // a repeat of the OFF command. command(button | 0x10, 0); } @@ -116,9 +124,8 @@ BulbId RgbwPacketFormatter::parsePacket(const uint8_t* packet, JsonObject result // the last packet sent, not the current one, and that can be wrong for // on/off commands. bulbId.groupId = GROUP_FOR_STATUS_COMMAND(command); - } else if (command >= RGBW_ALL_MAX_LEVEL && command <= RGBW_GROUP_4_MIN_LEVEL) { + } else if (command & 0x10) { if ((command % 2) == 0) { - result["state"] = "ON"; result["command"] = "night_mode"; } else { result["command"] = "set_white"; diff --git a/lib/MiLightState/GroupState.cpp b/lib/MiLightState/GroupState.cpp index 082387b5..2442658a 100644 --- a/lib/MiLightState/GroupState.cpp +++ b/lib/MiLightState/GroupState.cpp @@ -709,10 +709,15 @@ void GroupState::patch(const GroupState& other) { for (size_t i = 0; i < size(ALL_PHYSICAL_FIELDS); ++i) { GroupStateField field = ALL_PHYSICAL_FIELDS[i]; + // Handle night mode separately. Should always set this field. + if (field == GroupStateField::BULB_MODE && other.isNightMode()) { + setFieldValue(field, other.getFieldValue(field)); + } + // Otherwise... // Conditions: // * Only set anything if field is set in other state // * Do not patch anything other than STATE if bulb is off - if (other.isSetField(field) && (field == GroupStateField::STATE || isOn())) { + else if (other.isSetField(field) && (field == GroupStateField::STATE || isOn())) { setFieldValue(field, other.getFieldValue(field)); } } diff --git a/test/remote/helpers/state_helpers.rb b/test/remote/helpers/state_helpers.rb index 68efc9f3..59a61898 100644 --- a/test/remote/helpers/state_helpers.rb +++ b/test/remote/helpers/state_helpers.rb @@ -1,4 +1,5 @@ module StateHelpers + ALL_REMOTE_TYPES = %w(rgb rgbw rgb_cct cct fut089 fut091) def states_are_equal(desired_state, retrieved_state) expect(retrieved_state).to include(*desired_state.keys) expect(retrieved_state.select { |x| desired_state.include?(x) } ).to eq(desired_state) diff --git a/test/remote/spec/state_spec.rb b/test/remote/spec/state_spec.rb index c418e68f..053ded6f 100644 --- a/test/remote/spec/state_spec.rb +++ b/test/remote/spec/state_spec.rb @@ -34,20 +34,33 @@ end context 'night mode command' do - it 'should affect state when bulb is off' do - state = @client.patch_state({'command' => 'night_mode'}, @id_params) - - expect(state['bulb_mode']).to eq('night') - expect(state['effect']).to eq('night_mode') + StateHelpers::ALL_REMOTE_TYPES + .reject { |x| %w(rgb).include?(x) } # Night mode not supported for these types + .each do |type| + it "should affect state when bulb is OFF for #{type}" do + params = @id_params.merge(type: type) + @client.delete_state(params) + state = @client.patch_state({'command' => 'night_mode'}, params) + + expect(state['bulb_mode']).to eq('night') + expect(state['effect']).to eq('night_mode') + end end - it 'should affect state when bulb is on' do - @client.patch_state({'status' => 'ON'}, @id_params) - state = @client.patch_state({'command' => 'night_mode'}, @id_params) - - expect(state['status']).to eq('ON') - expect(state['bulb_mode']).to eq('night') - expect(state['effect']).to eq('night_mode') + StateHelpers::ALL_REMOTE_TYPES + .reject { |x| %w(rgb).include?(x) } # Night mode not supported for these types + .each do |type| + it "should affect state when bulb is ON for #{type}" do + params = @id_params.merge(type: type) + @client.delete_state(params) + @client.patch_state({'status' => 'ON'}, params) + state = @client.patch_state({'command' => 'night_mode'}, params) + + # RGBW bulbs have to be OFF in order for night mode to take affect + expect(state['status']).to eq('ON') if type != 'rgbw' + expect(state['bulb_mode']).to eq('night') + expect(state['effect']).to eq('night_mode') + end end it 'should revert to previous mode when status is toggled' do