esp32_ethernet_milight_hub/lib/MiLightState/GroupState.h
2021-01-15 22:49:01 +01:00

225 lines
7 KiB
C++

#include <stddef.h>
#include <inttypes.h>
#include <MiLightRemoteType.h>
#include <MiLightStatus.h>
#include <MiLightRadioConfig.h>
#include <GroupStateField.h>
#include <ArduinoJson.h>
#include <BulbId.h>
#include <ParsedColor.h>
#ifndef _GROUP_STATE_H
#define _GROUP_STATE_H
// enable to add debugging on state
// #define DEBUG_STATE
enum BulbMode {
BULB_MODE_WHITE,
BULB_MODE_COLOR,
BULB_MODE_SCENE,
BULB_MODE_NIGHT
};
enum class IncrementDirection : unsigned {
INCREASE = 1,
DECREASE = -1U
};
class GroupState {
public:
static const GroupStateField ALL_PHYSICAL_FIELDS[];
GroupState();
GroupState(const GroupState& other);
GroupState& operator=(const GroupState& other);
// Convenience constructor that patches transient state from a previous GroupState,
// and defaults with JSON state
GroupState(const GroupState* previousState, JsonObject jsonState);
void initFields();
bool operator==(const GroupState& other) const;
bool isEqualIgnoreDirty(const GroupState& other) const;
void print(Stream& stream) const;
bool isSetField(GroupStateField field) const;
uint16_t getFieldValue(GroupStateField field) const;
uint16_t getParsedFieldValue(GroupStateField field) const;
void setFieldValue(GroupStateField field, uint16_t value);
bool clearField(GroupStateField field);
bool isSetScratchField(GroupStateField field) const;
uint16_t getScratchFieldValue(GroupStateField field) const;
void setScratchFieldValue(GroupStateField field, uint16_t value);
// 1 bit
bool isSetState() const;
MiLightStatus getState() const;
bool setState(const MiLightStatus on);
// Return true if status is ON or if the field is unset (i.e., defaults to ON)
bool isOn() const;
// 7 bits
bool isSetBrightness() const;
uint8_t getBrightness() const;
bool setBrightness(uint8_t brightness);
bool clearBrightness();
// 8 bits
bool isSetHue() const;
uint16_t getHue() const;
bool setHue(uint16_t hue);
// 7 bits
bool isSetSaturation() const;
uint8_t getSaturation() const;
bool setSaturation(uint8_t saturation);
// 5 bits
bool isSetMode() const;
bool isSetEffect() const;
uint8_t getMode() const;
bool setMode(uint8_t mode);
// 7 bits
bool isSetKelvin() const;
uint8_t getKelvin() const;
uint16_t getMireds() const;
bool setKelvin(uint8_t kelvin);
bool setMireds(uint16_t mireds);
// 3 bits
bool isSetBulbMode() const;
BulbMode getBulbMode() const;
bool setBulbMode(BulbMode mode);
// 1 bit
bool isSetNightMode() const;
bool isNightMode() const;
bool setNightMode(bool nightMode);
bool isDirty() const;
inline bool setDirty();
bool clearDirty();
bool isMqttDirty() const;
inline bool setMqttDirty();
bool clearMqttDirty();
// Clears all of the fields in THIS GroupState that have different values
// than the provided group state.
bool clearNonMatchingFields(const GroupState& other);
// Patches this state with ONLY the set fields in the other.
void patch(const GroupState& other);
// Patches this state with the fields defined in the JSON state. Returns
// true if there were any changes.
bool patch(JsonObject state);
// It's a little weird to need to pass in a BulbId here. The purpose is to
// support fields like DEVICE_ID, which aren't otherweise available to the
// state in this class. The alternative is to have every GroupState object
// keep a reference to its BulbId, which feels too heavy-weight.
void applyField(JsonObject state, const BulbId& bulbId, GroupStateField field) const;
void applyState(JsonObject state, const BulbId& bulbId, std::vector<GroupStateField>& fields) const;
// Attempt to keep track of increment commands in such a way that we can
// know what state it's in. When we get an increment command (like "increase
// brightness"):
// 1. If there is no value in the scratch state: assume real state is in
// the furthest value from the direction of the command. For example,
// if we get "increase," assume the value was 0.
// 2. If there is a value in the scratch state, apply the command to it.
// For example, if we get "decrease," subtract 1 from the scratch.
// 3. When scratch reaches a known extreme (either min or max), set the
// persistent field to that value
// 4. If there is already a known value for the state, apply it rather
// than messing with scratch state.
//
// returns true if a (real, not scratch) state change was made
bool applyIncrementCommand(GroupStateField field, IncrementDirection dir);
// Helpers that convert raw state values
// Return true if hue is set. If saturation is not set, will assume 100.
bool isSetColor() const;
ParsedColor getColor() const;
void load(Stream& stream);
void dump(Stream& stream) const;
void debugState(char const *debugMessage) const;
static const GroupState& defaultState(MiLightRemoteType remoteType);
static GroupState initDefaultRgbState();
static GroupState initDefaultWhiteState();
static bool isPhysicalField(GroupStateField field);
private:
static const size_t DATA_LONGS = 2;
union StateData {
uint32_t rawData[DATA_LONGS];
struct Fields {
uint32_t
_state : 1,
_brightness : 7,
_hue : 8,
_saturation : 7,
_mode : 4,
_bulbMode : 3,
_isSetState : 1,
_isSetHue : 1;
uint32_t
_kelvin : 7,
_isSetBrightness : 1,
_isSetSaturation : 1,
_isSetMode : 1,
_isSetKelvin : 1,
_isSetBulbMode : 1,
_brightnessColor : 7,
_brightnessMode : 7,
_isSetBrightnessColor : 1,
_isSetBrightnessMode : 1,
_dirty : 1,
_mqttDirty : 1,
_isSetNightMode : 1,
_isNightMode : 1;
} fields;
};
// Transient scratchpad that is never persisted. Used to track and compute state for
// protocols that only have increment commands (like CCT).
union TransientData {
uint16_t rawData;
struct Fields {
uint16_t
_isSetKelvinScratch : 1,
_kelvinScratch : 7,
_isSetBrightnessScratch : 1,
_brightnessScratch : 8;
} fields;
};
StateData state;
TransientData scratchpad;
// State is constructed from individual command packets. A command packet is parsed in
// isolation, and the result is patched onto previous state. There are a few cases where
// it's necessary to know some things from the previous state, so we keep a reference to
// it here.
const GroupState* previousState;
void applyColor(JsonObject state, uint8_t r, uint8_t g, uint8_t b) const;
void applyColor(JsonObject state) const;
// Apply OpenHAB-style color, e.g., {"color":"0,0,0"}
void applyOhColor(JsonObject state) const;
// Apply hex color, e.g., {"color":"#FF0000"}
void applyHexColor(JsonObject state) const;
};
extern const BulbId DEFAULT_BULB_ID;
#endif