#include "light_json_schema.h" #include "color_mode.h" #include "light_output.h" #include "esphome/core/progmem.h" #ifdef USE_JSON namespace esphome::light { // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema // Get JSON string for color mode. // ColorMode enum values are sparse bitmasks (0, 1, 3, 7, 11, 19, 35, 39, 47, 51) which would // generate a large jump table. Converting to bit index (0-9) allows a compact switch. static ProgmemStr get_color_mode_json_str(ColorMode mode) { switch (ColorModeBitPolicy::to_bit(mode)) { case 1: return ESPHOME_F("onoff"); case 2: return ESPHOME_F("brightness"); case 3: return ESPHOME_F("white"); case 4: return ESPHOME_F("color_temp"); case 5: return ESPHOME_F("cwww"); case 6: return ESPHOME_F("rgb"); case 7: return ESPHOME_F("rgbw"); case 8: return ESPHOME_F("rgbct"); case 9: return ESPHOME_F("rgbww"); default: return nullptr; } } void LightJSONSchema::dump_json(LightState &state, JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (state.supports_effects()) { root[ESPHOME_F("effect")] = state.get_effect_name().c_str(); root[ESPHOME_F("effect_index")] = state.get_current_effect_index(); root[ESPHOME_F("effect_count")] = state.get_effect_count(); } auto values = state.remote_values; const auto color_mode = values.get_color_mode(); const auto *mode_str = get_color_mode_json_str(color_mode); if (mode_str != nullptr) { root[ESPHOME_F("color_mode")] = mode_str; } if (color_mode & ColorCapability::ON_OFF) root[ESPHOME_F("state")] = (values.get_state() != 0.0f) ? "ON" : "OFF"; if (color_mode & ColorCapability::BRIGHTNESS) root[ESPHOME_F("brightness")] = to_uint8_scale(values.get_brightness()); JsonObject color = root[ESPHOME_F("color")].to(); if (color_mode & ColorCapability::RGB) { float color_brightness = values.get_color_brightness(); color[ESPHOME_F("r")] = to_uint8_scale(color_brightness * values.get_red()); color[ESPHOME_F("g")] = to_uint8_scale(color_brightness * values.get_green()); color[ESPHOME_F("b")] = to_uint8_scale(color_brightness * values.get_blue()); } if (color_mode & ColorCapability::WHITE) { uint8_t white_val = to_uint8_scale(values.get_white()); color[ESPHOME_F("w")] = white_val; root[ESPHOME_F("white_value")] = white_val; // legacy API } if (color_mode & ColorCapability::COLOR_TEMPERATURE) { // this one isn't under the color subkey for some reason root[ESPHOME_F("color_temp")] = uint32_t(values.get_color_temperature()); } if (color_mode & ColorCapability::COLD_WARM_WHITE) { color[ESPHOME_F("c")] = to_uint8_scale(values.get_cold_white()); color[ESPHOME_F("w")] = to_uint8_scale(values.get_warm_white()); } } void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { if (root[ESPHOME_F("state")].is()) { auto val = parse_on_off(root[ESPHOME_F("state")]); switch (val) { case PARSE_ON: call.set_state(true); break; case PARSE_OFF: call.set_state(false); break; case PARSE_TOGGLE: call.set_state(!state.remote_values.is_on()); break; case PARSE_NONE: break; } } if (root[ESPHOME_F("brightness")].is()) { call.set_brightness(float(root[ESPHOME_F("brightness")]) / 255.0f); } if (root[ESPHOME_F("color")].is()) { JsonObject color = root[ESPHOME_F("color")]; // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. float max_rgb = 0.0f; if (color[ESPHOME_F("r")].is()) { float r = float(color[ESPHOME_F("r")]) / 255.0f; max_rgb = fmaxf(max_rgb, r); call.set_red(r); } if (color[ESPHOME_F("g")].is()) { float g = float(color[ESPHOME_F("g")]) / 255.0f; max_rgb = fmaxf(max_rgb, g); call.set_green(g); } if (color[ESPHOME_F("b")].is()) { float b = float(color[ESPHOME_F("b")]) / 255.0f; max_rgb = fmaxf(max_rgb, b); call.set_blue(b); } if (color[ESPHOME_F("r")].is() || color[ESPHOME_F("g")].is() || color[ESPHOME_F("b")].is()) { call.set_color_brightness(max_rgb); } if (color[ESPHOME_F("c")].is()) { call.set_cold_white(float(color[ESPHOME_F("c")]) / 255.0f); } if (color[ESPHOME_F("w")].is()) { // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // white channel in RGBWW. if (color[ESPHOME_F("c")].is()) { call.set_warm_white(float(color[ESPHOME_F("w")]) / 255.0f); } else { call.set_white(float(color[ESPHOME_F("w")]) / 255.0f); } } } if (root[ESPHOME_F("white_value")].is()) { // legacy API call.set_white(float(root[ESPHOME_F("white_value")]) / 255.0f); } if (root[ESPHOME_F("color_temp")].is()) { call.set_color_temperature(float(root[ESPHOME_F("color_temp")])); } } void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { LightJSONSchema::parse_color_json(state, call, root); if (root[ESPHOME_F("flash")].is()) { auto length = uint32_t(float(root[ESPHOME_F("flash")]) * 1000); call.set_flash_length(length); } if (root[ESPHOME_F("transition")].is()) { auto length = uint32_t(float(root[ESPHOME_F("transition")]) * 1000); call.set_transition_length(length); } if (root[ESPHOME_F("effect")].is()) { const char *effect = root[ESPHOME_F("effect")]; call.set_effect(effect, strlen(effect)); } if (root[ESPHOME_F("effect_index")].is()) { uint32_t effect_index = root[ESPHOME_F("effect_index")]; call.set_effect(effect_index); } } } // namespace esphome::light #endif