diff --git a/Controllers/LIFXController/LIFXController.cpp b/Controllers/LIFXController/LIFXController.cpp index 3f9a81cb..e68b6aaf 100644 --- a/Controllers/LIFXController/LIFXController.cpp +++ b/Controllers/LIFXController/LIFXController.cpp @@ -16,19 +16,22 @@ using json = nlohmann::json; using namespace std::chrono_literals; -LIFXController::LIFXController(std::string ip, std::string name) +LIFXController::LIFXController(std::string ip, std::string name, bool multizone, bool extended_multizone) { - this->name = name; + this->name = name; + zone_count = 1; + this->multizone = multizone; + this->extended_multizone = extended_multizone; /*-----------------------------------------------------------------*\ | Fill in location string with device's IP address | \*-----------------------------------------------------------------*/ - location = "IP: " + ip; + location = "IP: " + ip; /*-----------------------------------------------------------------*\ | Open a UDP client sending to the device's IP, port 56700 | \*-----------------------------------------------------------------*/ - port.udp_client(ip.c_str(), "56700"); + port.udp_client(ip.c_str(), LIFX_UDP_PORT); } LIFXController::~LIFXController() @@ -53,7 +56,7 @@ std::string LIFXController::GetVersion() std::string LIFXController::GetManufacturer() { - return("LIFX"); + return(LIFX_MANUFACTURER); } std::string LIFXController::GetUniqueID() @@ -61,97 +64,417 @@ std::string LIFXController::GetUniqueID() return(module_mac); } -void LIFXController::SetColor(unsigned char red, unsigned char green, unsigned char blue) +unsigned int LIFXController::GetZoneCount() +{ + return(zone_count); +} + +void LIFXController::SetColors(std::vector colors) +{ + /*-------------------------*\ + | Non-multizone lifx device | + \*-------------------------*/ + if(!multizone) + { + SetColor(colors[0]); + + return; + } + + /*-------------------------------------------*\ + | Multizone lifx device with extended support | + \*-------------------------------------------*/ + if(extended_multizone) + { + SetZoneColors(colors); + + return; + } + + /*----------------------------------------------*\ + | Multizone lifx device without extended support | + \*----------------------------------------------*/ + for(size_t i = 0; i < zone_count; i++) + { + /*-----------------------------------------------------------------*\ + | Utilize caching to avoid setting all zones when 1 zone is changed | + \*-----------------------------------------------------------------*/ + if(cached_colors[i] == colors[i]) + { + continue; + } + + SetZoneColor(colors[i], i); + cached_colors[i] = colors[i]; + } +} + +void LIFXController::FetchZoneCount() +{ + if(!multizone) + { + return; + } + + /*---------------------------*\ + | Send get color zones packet | + \*---------------------------*/ + data_buf_size = LIFX_PACKET_HEADER_LENGTH + LIFX_GET_COLOR_ZONES_PACKET_LENGTH; + data = new unsigned char[data_buf_size]; + memset(data, 0, data_buf_size); + + HeaderPacketSetDefaults(LIFX_PACKET_TYPE_GET_COLOR_ZONES); + + GetColorZonesPacketSetStartIndex(0); + GetColorZonesPacketSetEndIndex(0); + + port.udp_write((char*)data, data_buf_size); + delete[] data; + + /*----------------------------*\ + | Listen for state zone packet | + \*----------------------------*/ + data_buf_size = LIFX_PACKET_HEADER_LENGTH + LIFX_STATE_ZONE_PACKET_LENGTH; + data = new unsigned char[data_buf_size]; + memset(data, 0, data_buf_size); + + port.set_receive_timeout(5, 0); + port.udp_listen((char*)data, data_buf_size); + + /*-----------------*\ + | Validate response | + \*-----------------*/ + if(HeaderPacketGetSize() != data_buf_size || HeaderPacketGetProtocol() != LIFX_PROTOCOL || HeaderPacketGetPacketType() != LIFX_PACKET_TYPE_STATE_ZONE) + { + return; + } + + zone_count = StateZonePacketGetZonesCount(); + delete[] data; +} + +void LIFXController::SetColor(RGBColor color) +{ + /*---------------------*\ + | Send set color packet | + \*---------------------*/ + data_buf_size = LIFX_PACKET_HEADER_LENGTH + LIFX_SET_COLOR_PACKET_LENGTH; + data = new unsigned char[data_buf_size]; + memset(data, 0, data_buf_size); + + HeaderPacketSetDefaults(LIFX_PACKET_TYPE_SET_COLOR); + + hsbk_t hsbk; + RGBColorToHSBK(color, &hsbk); + + SetColorPacketSetHSBK(&hsbk); + SetColorPacketSetDuration(0); + + port.udp_write((char*)data, data_buf_size); + delete[] data; +} + +void LIFXController::SetZoneColor(RGBColor color, unsigned int zone) +{ + /*---------------------------*\ + | Send set color zones packet | + \*---------------------------*/ + data_buf_size = LIFX_PACKET_HEADER_LENGTH + LIFX_SET_COLOR_ZONES_PACKET_LENGTH; + data = new unsigned char[data_buf_size]; + memset(data, 0, data_buf_size); + + HeaderPacketSetDefaults(LIFX_PACKET_TYPE_SET_COLOR_ZONES); + + SetColorZonesPacketSetStartIndex(zone); + SetColorZonesPacketSetEndIndex(zone); + + hsbk_t hsbk; + RGBColorToHSBK(color, &hsbk); + + SetColorZonesPacketSetHSBK(&hsbk); + SetColorZonesPacketSetDuration(0); + SetColorZonesPacketSetApply(LIFX_MULTIZONE_APPLICATION_REQUEST_APPLY); + + port.udp_write((char*)data, data_buf_size); + delete[] data; +} + +void LIFXController::SetZoneColors(std::vector colors) +{ + /*------------------------------------*\ + | Send set extended color zones packet | + \*------------------------------------*/ + data_buf_size = LIFX_PACKET_HEADER_LENGTH + LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_LENGTH; + data = new unsigned char[data_buf_size]; + memset(data, 0, data_buf_size); + + HeaderPacketSetDefaults(LIFX_PACKET_TYPE_SET_EXTENDED_COLOR_ZONES); + + SetExtendedColorZonesPacketSetDuration(0); + SetExtendedColorZonesPacketSetApply(LIFX_MULTIZONE_APPLICATION_REQUEST_APPLY); + SetExtendedColorZonesPacketSetZoneIndex(0); + SetExtendedColorZonesPacketSetColors(colors); + + port.udp_write((char*)data, data_buf_size); + delete[] data; +} + +void LIFXController::RGBColorToHSBK(RGBColor color, hsbk_t* hsbk) { - RGBColor color = ToRGBColor(red, green, blue); hsv_t hsv; rgb2hsv(color, &hsv); - data = data_buf; - memset( data, 0, 49 ); - - source = 2; - sequence = 1; - - unsigned char target[8] = {0}; - - FrameHeader( 49, true, false, 0, source ); - FrameAddress( target, false, false, sequence ); - ProtocolAddress( 102 ); - - unsigned char * set_color = &data[36]; - - unsigned short hue = hsv.hue * (65536/360); - unsigned short saturation = hsv.saturation * (65536/256); - unsigned short brightness = hsv.value * (65536/256); - unsigned short kelvin = 3500; - unsigned int duration = 0; - - memcpy(&set_color[LIFX_SET_COLOR_OFFSET_HUE], &hue, sizeof(unsigned short)); - memcpy(&set_color[LIFX_SET_COLOR_OFFSET_SATURATION], &saturation, sizeof(unsigned short)); - memcpy(&set_color[LIFX_SET_COLOR_OFFSET_BRIGHTNESS], &brightness, sizeof(unsigned short)); - memcpy(&set_color[LIFX_SET_COLOR_OFFSET_KELVIN], &kelvin, sizeof(unsigned short)); - memcpy(&set_color[LIFX_SET_COLOR_OFFSET_DURATION], &duration, sizeof(unsigned int)); - - port.udp_write((char *)data, 49); + hsbk->hue = hsv.hue * (USHRT_MAX/360); + hsbk->saturation = hsv.saturation * (USHRT_MAX/256); + hsbk->brightness = hsv.value * (USHRT_MAX/256); + hsbk->kelvin = DEFAULT_KELVIN; } -void LIFXController::FrameHeader - ( - unsigned short size, - bool addressable, - bool tagged, - unsigned char origin, - unsigned int source - ) +/*----------------------------*\ +| Header packet helper methods | +\*----------------------------*/ +void LIFXController::HeaderPacketSetDefaults(unsigned short packet_type) { - unsigned short protocol = 1024; - memcpy(&data[LIFX_FRAME_HEADER_OFFSET_SIZE], &size, sizeof(unsigned short)); - memcpy(&data[LIFX_FRAME_HEADER_OFFSET_PROTOCOL], &protocol, sizeof(unsigned short)); + /*-----*\ + | Frame | + \*-----*/ + HeaderPacketSetSize(data_buf_size); + HeaderPacketSetProtocol(); + HeaderPacketSetAddressable(true); + HeaderPacketSetTagged(false); + HeaderPacketSetOrigin(0); + HeaderPacketSetSource(2); + /*-------------*\ + | Frame address | + \*-------------*/ + unsigned char target[TARGET_LENGTH] = {0}; + HeaderPacketSetTarget(target); + HeaderPacketSetResponseRequired(false); + HeaderPacketSetAcknowledgeRequired(false); + HeaderPacketSetSequence(++sequence); + + /*---------------*\ + | Protocol header | + \*---------------*/ + HeaderPacketSetPacketType(packet_type); +} + +unsigned short LIFXController::HeaderPacketGetSize() +{ + return data[LIFX_HEADER_PACKET_OFFSET_SIZE]; +} + +void LIFXController::HeaderPacketSetSize(unsigned short size) +{ + memcpy(&data[LIFX_HEADER_PACKET_OFFSET_SIZE], &size, sizeof(unsigned short)); +} + +unsigned short LIFXController::HeaderPacketGetProtocol() +{ + unsigned short protocol; + memcpy(&protocol, &data[LIFX_HEADER_PACKET_OFFSET_PROTOCOL], sizeof(unsigned short)); + return protocol & 0x0FFF; +} + +void LIFXController::HeaderPacketSetProtocol(unsigned short protocol) +{ + data[LIFX_HEADER_PACKET_OFFSET_PROTOCOL] = protocol & 0xFF; + unsigned char current = data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN]; + data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN] = (current & 0xF0) | ((protocol >> 8) & 0x0F); +} + +void LIFXController::HeaderPacketSetAddressable(bool addressable) +{ if(addressable) { - data[LIFX_FRAME_HEADER_OFFSET_FLAGS] |= (1 << 4); + data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN] |= 0x10; } + else + { + data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN] &= ~0x10; + } +} + +void LIFXController::HeaderPacketSetTagged(bool tagged) +{ if(tagged) { - data[LIFX_FRAME_HEADER_OFFSET_FLAGS] |= (1 << 5); + data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN] |= 0x20; } - - data[LIFX_FRAME_HEADER_OFFSET_FLAGS] |= (origin << 6); - - memcpy(&data[LIFX_FRAME_HEADER_OFFSET_SOURCE], &source, sizeof(unsigned int)); -} - -void LIFXController::FrameAddress - ( - unsigned char * target, - bool res_required, - bool ack_required, - unsigned char sequence - ) -{ - memcpy(&data[LIFX_FRAME_ADDRESS_OFFSET_TARGET], target, 8); - - data[LIFX_FRAME_ADDRESS_OFFSET_FLAGS] = 0; - - if(res_required) + else { - data[LIFX_FRAME_ADDRESS_OFFSET_FLAGS] |= (1 << 0); + data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN] &= ~0x20; } - - if(ack_required) - { - data[LIFX_FRAME_ADDRESS_OFFSET_FLAGS] |= (1 << 1); - } - - data[LIFX_FRAME_ADDRESS_OFFSET_SEQUENCE] = sequence; } -void LIFXController::ProtocolAddress - ( - unsigned short type - ) +void LIFXController::HeaderPacketSetOrigin(unsigned char origin) { - memcpy(&data[LIFX_PROTOCOL_HEADER_OFFSET_TYPE], &type, sizeof(unsigned short)); + data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN] = + (data[LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN] & 0xFC) | (origin & 0x03); +} + +void LIFXController::HeaderPacketSetSource(unsigned int source) +{ + memcpy(&data[LIFX_HEADER_PACKET_OFFSET_SOURCE], &source, sizeof(unsigned int)); +} + +void LIFXController::HeaderPacketSetTarget(unsigned char* target) +{ + memcpy(&data[LIFX_HEADER_PACKET_OFFSET_TARGET], target, TARGET_LENGTH); +} + +void LIFXController::HeaderPacketSetResponseRequired(bool response_required) +{ + if(response_required) + { + data[LIFX_HEADER_PACKET_OFFSET_RESPONSE_REQUIRED_ACKNOWLEDGE_REQUIRED] |= 0x01; + } + else + { + data[LIFX_HEADER_PACKET_OFFSET_RESPONSE_REQUIRED_ACKNOWLEDGE_REQUIRED] &= ~0x01; + } +} + +void LIFXController::HeaderPacketSetAcknowledgeRequired(bool acknowledge_required) +{ + if(acknowledge_required) + { + data[LIFX_HEADER_PACKET_OFFSET_RESPONSE_REQUIRED_ACKNOWLEDGE_REQUIRED] |= 0x02; + } + else + { + data[LIFX_HEADER_PACKET_OFFSET_RESPONSE_REQUIRED_ACKNOWLEDGE_REQUIRED] &= ~0x02; + } +} + +void LIFXController::HeaderPacketSetSequence(unsigned char sequence) +{ + data[LIFX_HEADER_PACKET_OFFSET_SEQUENCE] = sequence; +} + +unsigned short LIFXController::HeaderPacketGetPacketType() +{ + unsigned short packet_type_value; + memcpy(&packet_type_value, &data[LIFX_HEADER_PACKET_OFFSET_PACKET_TYPE], sizeof(unsigned short)); + + return packet_type_value; +} + +void LIFXController::HeaderPacketSetPacketType(unsigned short packet_type) +{ + memcpy(&data[LIFX_HEADER_PACKET_OFFSET_PACKET_TYPE], &packet_type, sizeof(unsigned short)); +} + +/*-------------------------------*\ +| Set color packet helper methods | +\*-------------------------------*/ +void LIFXController::SetColorPacketSetHSBK(hsbk_t* hsbk) +{ + memcpy(&data[LIFX_SET_COLOR_PACKET_OFFSET_HUE], &hsbk->hue, sizeof(unsigned short)); + memcpy(&data[LIFX_SET_COLOR_PACKET_OFFSET_SATURATION], &hsbk->saturation, sizeof(unsigned short)); + memcpy(&data[LIFX_SET_COLOR_PACKET_OFFSET_BRIGHTNESS], &hsbk->brightness, sizeof(unsigned short)); + memcpy(&data[LIFX_SET_COLOR_PACKET_OFFSET_KELVIN], &hsbk->kelvin, sizeof(unsigned short)); +} + +void LIFXController::SetColorPacketSetDuration(unsigned int duration) +{ + memcpy(&data[LIFX_SET_COLOR_PACKET_OFFSET_DURATION], &duration, sizeof(unsigned int)); +} + +/*-------------------------------------*\ +| Set color zones packet helper methods | +\*-------------------------------------*/ +void LIFXController::SetColorZonesPacketSetStartIndex(unsigned char start_index) +{ + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_START_INDEX], &start_index, sizeof(unsigned char)); +} + +void LIFXController::SetColorZonesPacketSetEndIndex(unsigned char end_index) +{ + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_END_INDEX], &end_index, sizeof(unsigned char)); +} + +void LIFXController::SetColorZonesPacketSetHSBK(hsbk_t* hsbk) +{ + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_HUE], &hsbk->hue, sizeof(unsigned short)); + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_SATURATION], &hsbk->saturation, sizeof(unsigned short)); + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_BRIGHTNESS], &hsbk->brightness, sizeof(unsigned short)); + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_KELVIN], &hsbk->kelvin, sizeof(unsigned short)); +} + +void LIFXController::SetColorZonesPacketSetDuration(unsigned int duration) +{ + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_DURATION], &duration, sizeof(unsigned int)); +} + +void LIFXController::SetColorZonesPacketSetApply(unsigned char apply) +{ + memcpy(&data[LIFX_SET_COLOR_ZONES_PACKET_OFFSET_APPLY], &apply, sizeof(unsigned char)); +} + +/*-------------------------------------*\ +| Get color zones packet helper methods | +\*-------------------------------------*/ +void LIFXController::GetColorZonesPacketSetStartIndex(unsigned char start_index) +{ + memcpy(&data[LIFX_GET_COLOR_ZONES_PACKET_OFFSET_START_INDEX], &start_index, sizeof(unsigned char)); +} + +void LIFXController::GetColorZonesPacketSetEndIndex(unsigned char end_index) +{ + memcpy(&data[LIFX_GET_COLOR_ZONES_PACKET_OFFSET_END_INDEX], &end_index, sizeof(unsigned char)); +} + +/*--------------------------------*\ +| State zone packet helper methods | +\*--------------------------------*/ +unsigned char LIFXController::StateZonePacketGetZonesCount() +{ + unsigned char zones_count; + memcpy(&zones_count, &data[LIFX_STATE_ZONE_PACKET_OFFSET_ZONES_COUNT], sizeof(unsigned char)); + + return zones_count; +} + +/*----------------------------------------------*\ +| Set extended color zones packet helper methods | +\*----------------------------------------------*/ +void LIFXController::SetExtendedColorZonesPacketSetDuration(unsigned int duration) +{ + memcpy(&data[LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_DURATION], &duration, sizeof(unsigned int)); +} + +void LIFXController::SetExtendedColorZonesPacketSetApply(unsigned char apply) +{ + memcpy(&data[LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_APPLY], &apply, sizeof(unsigned char)); +} + +void LIFXController::SetExtendedColorZonesPacketSetZoneIndex(unsigned short zone_index) +{ + memcpy(&data[LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_ZONE_INDEX], &zone_index, sizeof(unsigned short)); +} + +void LIFXController::SetExtendedColorZonesPacketSetColors(std::vector colors) +{ + unsigned char colors_count = colors.size(); + memcpy(&data[LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_COLORS_COUNT], &colors_count, sizeof(unsigned char)); + + for(size_t i = 0; i < colors.size(); i++) + { + hsbk_t hsbk; + RGBColorToHSBK(colors[i], &hsbk); + + size_t current_color_offset = LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_COLORS + (i * HSBK_LENGTH); + + size_t hue_offset = current_color_offset; + size_t saturation_offset = hue_offset + sizeof(unsigned short); + size_t brightness_offset = saturation_offset + sizeof(unsigned short); + size_t kelvin_offset = brightness_offset + sizeof(unsigned short); + + memcpy(&data[hue_offset], &hsbk.hue, sizeof(unsigned short)); + memcpy(&data[saturation_offset], &hsbk.saturation, sizeof(unsigned short)); + memcpy(&data[brightness_offset], &hsbk.brightness, sizeof(unsigned short)); + memcpy(&data[kelvin_offset], &hsbk.kelvin, sizeof(unsigned short)); + } } diff --git a/Controllers/LIFXController/LIFXController.h b/Controllers/LIFXController/LIFXController.h index 10dbdab8..05936dcd 100644 --- a/Controllers/LIFXController/LIFXController.h +++ b/Controllers/LIFXController/LIFXController.h @@ -12,85 +12,229 @@ #pragma once #include -#include #include +#include #include "RGBController.h" #include "net_port.h" +#define LIFX_MANUFACTURER "LIFX" +#define LIFX_UDP_PORT "56700" +#define LIFX_PROTOCOL 1024 +#define TARGET_LENGTH 8 +#define DEFAULT_KELVIN 3500 +#define HSBK_LENGTH 8 + +/*---------------------*\ +| Packet size constants | +\*---------------------*/ +#define LIFX_PACKET_HEADER_LENGTH 36 +#define LIFX_SET_COLOR_PACKET_LENGTH 13 +#define LIFX_SET_COLOR_ZONES_PACKET_LENGTH 15 +#define LIFX_GET_COLOR_ZONES_PACKET_LENGTH 2 +#define LIFX_STATE_ZONE_PACKET_LENGTH 10 +#define LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_LENGTH 664 + +/*---------------------------------------------------------------------------*\ +| https://lan.developer.lifx.com/docs/field-types#multizoneapplicationrequest | +\*---------------------------------------------------------------------------*/ enum { - LIFX_FRAME_HEADER_OFFSET_SIZE = 0, /* 2 bytes, size of the entire message in bytes */ - LIFX_FRAME_HEADER_OFFSET_PROTOCOL = 2, /* Protocol number, must be 1024 */ - LIFX_FRAME_HEADER_OFFSET_FLAGS = 3, /* Bits 0-3 are part of Protocol */ - /* Bit 4, addressable flag */ - /* Bit 5, tagged flag */ - /* Bit 6/7, origin value */ - LIFX_FRAME_HEADER_OFFSET_SOURCE = 4, /* Source identifier, unique value set by client*/ - LIFX_FRAME_ADDRESS_OFFSET_TARGET = 8, /* 6 byte device address (MAC) or zero */ - /* Last two bytes should be 0 */ - LIFX_FRAME_ADDRESS_OFFSET_FLAGS = 22, /* Bit 0, res_required flag */ - /* Bit 1, ack_required flag */ - LIFX_FRAME_ADDRESS_OFFSET_SEQUENCE = 23, /* Wrap around message sequence number */ - LIFX_PROTOCOL_HEADER_OFFSET_TYPE = 32, /* Message type determines the payload used */ + LIFX_MULTIZONE_APPLICATION_REQUEST_NO_APPLY = 0, + LIFX_MULTIZONE_APPLICATION_REQUEST_APPLY = 1, + LIFX_MULTIZONE_APPLICATION_REQUEST_APPLY_ONLY = 2 }; + +/*----------------------------------------------------------------*\ +| https://lan.developer.lifx.com/docs/representing-color-with-hsbk | +\*----------------------------------------------------------------*/ +typedef struct +{ + unsigned short hue; /* 0-360 value normalized to 0-65535 */ + unsigned short saturation; /* 0-1 value normalized to 0-65535 */ + unsigned short brightness; /* 0-1 value normalized to 0-65535 */ + unsigned short kelvin; /* 0-65535 value */ + /* Note: Devices may only support a subset of the full range. */ +} hsbk_t; + +/*-----------------*\ +| LIFX packet types | +\*-----------------*/ enum { - LIFX_SET_COLOR_OFFSET_HUE = 1, /* 16-bit hue value */ - LIFX_SET_COLOR_OFFSET_SATURATION = 3, /* 16-bit saturation value */ - LIFX_SET_COLOR_OFFSET_BRIGHTNESS = 5, /* 16-bit brightness value */ - LIFX_SET_COLOR_OFFSET_KELVIN = 7, /* 16-bit kelvin value */ - LIFX_SET_COLOR_OFFSET_DURATION = 9, /* 32-bit brightness value */ + LIFX_PACKET_TYPE_SET_COLOR = 102, + LIFX_PACKET_TYPE_SET_COLOR_ZONES = 501, + LIFX_PACKET_TYPE_GET_COLOR_ZONES = 502, + LIFX_PACKET_TYPE_STATE_ZONE = 503, + LIFX_PACKET_TYPE_SET_EXTENDED_COLOR_ZONES = 510 +}; + +/*-----------------------------------------------------*\ +| LIFX header packet offsets | +| https://lan.developer.lifx.com/docs/encoding-a-packet | +\*-----------------------------------------------------*/ +enum +{ + LIFX_HEADER_PACKET_OFFSET_SIZE = 0, /* 2 bytes, size of the entire message in bytes */ + LIFX_HEADER_PACKET_OFFSET_PROTOCOL = 2, /* Protocol number, must be 1024 */ + LIFX_HEADER_PACKET_OFFSET_ADDRESSABLE_TAGGED_ORIGIN = 3, /* Bits 0-3 are part of Protocol */ + /* Bit 4, addressable flag */ + /* Bit 5, tagged flag */ + /* Bit 6/7, origin value */ + LIFX_HEADER_PACKET_OFFSET_SOURCE = 4, /* Source identifier, unique value set by client */ + LIFX_HEADER_PACKET_OFFSET_TARGET = 8, /* 6 byte device address (MAC) or zero */ + /* Last two bytes should be 0 */ + LIFX_HEADER_PACKET_OFFSET_RESPONSE_REQUIRED_ACKNOWLEDGE_REQUIRED = 22, /* Bit 0, res_required flag */ + /* Bit 1, ack_required flag */ + LIFX_HEADER_PACKET_OFFSET_SEQUENCE = 23, /* Wrap around message sequence number */ + LIFX_HEADER_PACKET_OFFSET_PACKET_TYPE = 32 /* Message type determines the payload used */ +}; + +/*---------------------------------------------------------------------------*\ +| LIFX set color packet offsets | +| https://lan.developer.lifx.com/docs/changing-a-device#setcolor---packet-102 | +\*---------------------------------------------------------------------------*/ +enum +{ + /* 1 byte, reserved */ + LIFX_SET_COLOR_PACKET_OFFSET_HUE = LIFX_PACKET_HEADER_LENGTH + 1, /* 2 bytes, hue as a 0-65535 value */ + LIFX_SET_COLOR_PACKET_OFFSET_SATURATION = LIFX_PACKET_HEADER_LENGTH + 3, /* 2 bytes, saturation as a 0-65535 value */ + LIFX_SET_COLOR_PACKET_OFFSET_BRIGHTNESS = LIFX_PACKET_HEADER_LENGTH + 5, /* 2 bytes, brightness as a 0-65535 value */ + LIFX_SET_COLOR_PACKET_OFFSET_KELVIN = LIFX_PACKET_HEADER_LENGTH + 7, /* 2 bytes, kelvin as a 0-65535 value. */ + /* Note: The actual max for this is device specific */ + LIFX_SET_COLOR_PACKET_OFFSET_DURATION = LIFX_PACKET_HEADER_LENGTH + 9, /* 4 bytes, transition time in ms */ +}; + +/*--------------------------------------------------------------------------------*\ +| LIFX set color zones packet offsets | +| https://lan.developer.lifx.com/docs/changing-a-device#setcolorzones---packet-501 | +\*--------------------------------------------------------------------------------*/ +enum +{ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_START_INDEX = LIFX_PACKET_HEADER_LENGTH + 0, /* 1 byte, the first zone in the segment we are changing */ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_END_INDEX = LIFX_PACKET_HEADER_LENGTH + 1, /* 1 byte, the last zone in the segment we are changing */ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_HUE = LIFX_PACKET_HEADER_LENGTH + 2, /* 2 bytes, hue as a 0-65535 value */ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_SATURATION = LIFX_PACKET_HEADER_LENGTH + 4, /* 2 bytes, saturation as a 0-65535 value */ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_BRIGHTNESS = LIFX_PACKET_HEADER_LENGTH + 6, /* 2 bytes, brightness as a 0-65535 value */ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_KELVIN = LIFX_PACKET_HEADER_LENGTH + 8, /* 2 bytes, kelvin as a 0-65535 value. */ + /* Note: The actual max for this is device specific */ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_DURATION = LIFX_PACKET_HEADER_LENGTH + 10, /* 4 bytes, transition time in ms */ + LIFX_SET_COLOR_ZONES_PACKET_OFFSET_APPLY = LIFX_PACKET_HEADER_LENGTH + 14 /* 1 byte, multizone application request */ +}; + +/*-------------------------------------------------------------------------------------------*\ +| LIFX get color zones packet offsets | +| https://lan.developer.lifx.com/docs/querying-the-device-for-data#getcolorzones---packet-502 | +\*-------------------------------------------------------------------------------------------*/ +enum +{ + LIFX_GET_COLOR_ZONES_PACKET_OFFSET_START_INDEX = LIFX_PACKET_HEADER_LENGTH + 0, /* 1 byte, The first zone you want to get information from */ + LIFX_GET_COLOR_ZONES_PACKET_OFFSET_END_INDEX = LIFX_PACKET_HEADER_LENGTH + 1, /* 1 byte, The second zone you want to get information from */ +}; + +/*-------------------------------------------------------------------------------*\ +| LIFX state zone packet offsets | +| https://lan.developer.lifx.com/docs/information-messages#statezone---packet-503 | +\*-------------------------------------------------------------------------------*/ +enum +{ + LIFX_STATE_ZONE_PACKET_OFFSET_ZONES_COUNT = LIFX_PACKET_HEADER_LENGTH + 0, /* 1 byte, the total number of zones on the strip. */ + LIFX_STATE_ZONE_PACKET_OFFSET_ZONE_INDEX = LIFX_PACKET_HEADER_LENGTH + 1, /* 1 byte, the zone this packet refers to. */ + LIFX_STATE_ZONE_PACKET_OFFSET_HUE = LIFX_PACKET_HEADER_LENGTH + 2, /* 2 bytes, hue as a 0-65535 value */ + LIFX_STATE_ZONE_PACKET_OFFSET_SATURATION = LIFX_PACKET_HEADER_LENGTH + 4, /* 2 bytes, saturation as a 0-65535 value */ + LIFX_STATE_ZONE_PACKET_OFFSET_BRIGHTNESS = LIFX_PACKET_HEADER_LENGTH + 6, /* 2 bytes, brightness as a 0-65535 value */ + LIFX_STATE_ZONE_PACKET_OFFSET_KELVIN = LIFX_PACKET_HEADER_LENGTH + 8, /* 2 bytes, kelvin as a 0-65535 value. */ + /* Note: The actual max for this is device specific */ +}; + +/*----------------------------------------------------------------------------------------*\ +| LIFX set extended color zones packet offsets | +| https://lan.developer.lifx.com/docs/changing-a-device#setextendedcolorzones---packet-510 | +\*----------------------------------------------------------------------------------------*/ +enum +{ + LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_DURATION = LIFX_PACKET_HEADER_LENGTH + 0, /* 4 bytes, transition time in ms */ + LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_APPLY = LIFX_PACKET_HEADER_LENGTH + 4, /* 1 byte, multizone application request */ + LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_ZONE_INDEX = LIFX_PACKET_HEADER_LENGTH + 5, /* 2 bytes, The first zone to apply colors from. */ + /* If the light has more than 82 zones, then */ + /* send multiple messages with different indices */ + /* to update the whole device. */ + LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_COLORS_COUNT = LIFX_PACKET_HEADER_LENGTH + 7, /* 1 byte, The number of colors in the colors field */ + LIFX_SET_EXTENDED_COLOR_ZONES_PACKET_OFFSET_COLORS = LIFX_PACKET_HEADER_LENGTH + 8, /* 656 bytes (82 * 4 * 2), 82 HSBK values to change */ + /* the device with */ }; class LIFXController { public: - LIFXController(std::string ip, std::string name); + LIFXController(std::string ip, std::string name, bool multizone, bool extended_multizone); ~LIFXController(); - std::string GetLocation(); - std::string GetName(); - std::string GetVersion(); - std::string GetManufacturer(); - std::string GetUniqueID(); + std::string GetLocation(); + std::string GetName(); + std::string GetVersion(); + std::string GetManufacturer(); + std::string GetUniqueID(); + unsigned int GetZoneCount(); - void SetColor(unsigned char red, unsigned char green, unsigned char blue); + void FetchZoneCount(); + void SetColors(std::vector colors); private: - unsigned char data_buf[49]; - unsigned char* data; - unsigned char sequence; - unsigned int source; - std::string name; - std::string firmware_version; - std::string module_name; - std::string module_mac; - std::string location; - net_port port; + RGBColor cached_colors[UCHAR_MAX]; + unsigned int zone_count; + size_t data_buf_size; + unsigned char* data; + unsigned char sequence; + std::string name; + std::string firmware_version; + std::string module_name; + std::string module_mac; + std::string location; + net_port port; + bool multizone; + bool extended_multizone; - /*-----------------------------------------------------*\ - | Functions for filling in LIFX header | - \*-----------------------------------------------------*/ - void FrameHeader - ( - unsigned short size, - bool addressable, - bool tagged, - unsigned char origin, - unsigned int source - ); + void SetColor(RGBColor color); + void SetZoneColor(RGBColor color, unsigned int zone); + void SetZoneColors(std::vector colors); + void RGBColorToHSBK(RGBColor color, hsbk_t* hsbk); - void FrameAddress - ( - unsigned char * target, - bool res_required, - bool ack_required, - unsigned char sequence - ); + /*---------------------*\ + | Packet helper methods | + \*---------------------*/ + void HeaderPacketSetDefaults(unsigned short packet_type); + unsigned short HeaderPacketGetSize(); + void HeaderPacketSetSize(unsigned short size); + unsigned short HeaderPacketGetProtocol(); + void HeaderPacketSetProtocol(unsigned short protocol=LIFX_PROTOCOL); + void HeaderPacketSetAddressable(bool addressable=true); + void HeaderPacketSetTagged(bool tagged=false); + void HeaderPacketSetOrigin(unsigned char origin=0); + void HeaderPacketSetSource(unsigned int source=2); + void HeaderPacketSetTarget(unsigned char* target); + void HeaderPacketSetResponseRequired(bool response_required=false); + void HeaderPacketSetAcknowledgeRequired(bool acknowledge_required=false); + void HeaderPacketSetSequence(unsigned char sequence); + unsigned short HeaderPacketGetPacketType(); + void HeaderPacketSetPacketType(unsigned short packet_type); - void ProtocolAddress - ( - unsigned short type - ); + void SetColorPacketSetDuration(unsigned int duration=0); + void SetColorPacketSetHSBK(hsbk_t* hsbk); + + void SetColorZonesPacketSetStartIndex(unsigned char start_index); + void SetColorZonesPacketSetEndIndex(unsigned char end_index); + void SetColorZonesPacketSetHSBK(hsbk_t* hsbk); + void SetColorZonesPacketSetDuration(unsigned int duration=0); + void SetColorZonesPacketSetApply(unsigned char apply=LIFX_MULTIZONE_APPLICATION_REQUEST_APPLY); + + void GetColorZonesPacketSetStartIndex(unsigned char start_index=0); + void GetColorZonesPacketSetEndIndex(unsigned char end_index=0); + + unsigned char StateZonePacketGetZonesCount(); + + void SetExtendedColorZonesPacketSetDuration(unsigned int duration=0); + void SetExtendedColorZonesPacketSetApply(unsigned char apply=LIFX_MULTIZONE_APPLICATION_REQUEST_APPLY); + void SetExtendedColorZonesPacketSetZoneIndex(unsigned short zone_index=0); + void SetExtendedColorZonesPacketSetColors(std::vector colors); }; diff --git a/Controllers/LIFXController/LIFXControllerDetect.cpp b/Controllers/LIFXController/LIFXControllerDetect.cpp index 99c4e8fd..4e0166f5 100644 --- a/Controllers/LIFXController/LIFXControllerDetect.cpp +++ b/Controllers/LIFXController/LIFXControllerDetect.cpp @@ -40,10 +40,14 @@ void DetectLIFXControllers() { if(lifx_settings["devices"][device_idx].contains("ip")) { - std::string lifx_ip = lifx_settings["devices"][device_idx]["ip"]; - std::string name = lifx_settings["devices"][device_idx]["name"]; + std::string lifx_ip = lifx_settings["devices"][device_idx]["ip"]; + std::string name = lifx_settings["devices"][device_idx]["name"]; + bool multizone = lifx_settings["devices"][device_idx]["multizone"]; + bool extended_multizone = lifx_settings["devices"][device_idx]["extended_multizone"]; + + LIFXController* controller = new LIFXController(lifx_ip, name, multizone, extended_multizone); + controller->FetchZoneCount(); - LIFXController* controller = new LIFXController(lifx_ip, name); RGBController_LIFX* rgb_controller = new RGBController_LIFX(controller); ResourceManager::get()->RegisterRGBController(rgb_controller); diff --git a/Controllers/LIFXController/RGBController_LIFX.cpp b/Controllers/LIFXController/RGBController_LIFX.cpp index bc102e57..93bf6525 100644 --- a/Controllers/LIFXController/RGBController_LIFX.cpp +++ b/Controllers/LIFXController/RGBController_LIFX.cpp @@ -52,18 +52,54 @@ RGBController_LIFX::~RGBController_LIFX() void RGBController_LIFX::SetupZones() { zone led_zone; - led_zone.name = "RGB Light"; - led_zone.type = ZONE_TYPE_SINGLE; - led_zone.leds_min = 1; - led_zone.leds_max = 1; - led_zone.leds_count = 1; - led_zone.matrix_map = NULL; - zones.push_back(led_zone); - led new_led; - new_led.name = "RGB Light"; + unsigned int zone_count = controller->GetZoneCount(); - leds.push_back(new_led); + /*---------------------------------------------------------*\ + | If there is only one zone, set up a single LED | + \*---------------------------------------------------------*/ + if(zone_count <= 1) + { + led_zone.name = "RGB Light"; + led_zone.type = ZONE_TYPE_SINGLE; + led_zone.leds_min = 1; + led_zone.leds_max = 1; + led_zone.leds_count = 1; + led_zone.matrix_map = NULL; + zones.push_back(led_zone); + + led new_led; + new_led.name = "RGB Light"; + + leds.push_back(new_led); + } + else + { + /*---------------------------------------------------------*\ + | Set up multiple LEDs | + \*---------------------------------------------------------*/ + led_zone.name = "RGB Light Strip"; + led_zone.type = ZONE_TYPE_LINEAR; + led_zone.leds_min = 1; + led_zone.leds_max = zone_count; + led_zone.leds_count = zone_count; + led_zone.matrix_map = NULL; + + zones.push_back(led_zone); + + for(size_t zone_idx = 0; zone_idx < zone_count; zone_idx++) + { + + /*---------------------------------------------------------*\ + | Set up LEDs | + \*---------------------------------------------------------*/ + led new_led; + + new_led.name = "LED " + std::to_string(zone_idx); + + leds.push_back(new_led); + } + } SetupColors(); } @@ -77,11 +113,7 @@ void RGBController_LIFX::ResizeZone(int /*zone*/, int /*new_size*/) void RGBController_LIFX::DeviceUpdateLEDs() { - unsigned char red = RGBGetRValue(colors[0]); - unsigned char grn = RGBGetGValue(colors[0]); - unsigned char blu = RGBGetBValue(colors[0]); - - controller->SetColor(red, grn, blu); + controller->SetColors(colors); } void RGBController_LIFX::UpdateZoneLEDs(int /*zone*/) diff --git a/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.cpp b/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.cpp index 709e0e1f..b7c32ec6 100644 --- a/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.cpp +++ b/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.cpp @@ -15,6 +15,8 @@ LIFXSettingsEntry::LIFXSettingsEntry(QWidget *parent) : ui(new Ui::LIFXSettingsEntry) { ui->setupUi(this); + + connect(ui->MultizoneCheckBox, SIGNAL(stateChanged(int)), this, SLOT(on_MultizoneCheckBox_stateChanged(int))); } LIFXSettingsEntry::~LIFXSettingsEntry() @@ -40,13 +42,23 @@ void LIFXSettingsEntry::loadFromSettings(const json& data) { ui->NameEdit->setText(QString::fromStdString(data["name"])); } + if(data.contains("multizone") && data["multizone"].is_boolean()) + { + ui->MultizoneCheckBox->setCheckState(data["multizone"] == true ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + } + if(data.contains("extended_multizone") && data["extended_multizone"].is_boolean()) + { + ui->ExtendedMultizoneCheckBox->setCheckState(data["extended_multizone"] == true ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + } } json LIFXSettingsEntry::saveSettings() { json result; - result["ip"] = ui->IPEdit->text().toStdString(); - result["name"] = ui->NameEdit->text().toStdString(); + result["ip"] = ui->IPEdit->text().toStdString(); + result["name"] = ui->NameEdit->text().toStdString(); + result["multizone"] = ui->MultizoneCheckBox->checkState() == Qt::Checked; + result["extended_multizone"] = ui->ExtendedMultizoneCheckBox->checkState() == Qt::Checked; return result; } @@ -55,6 +67,19 @@ void LIFXSettingsEntry::setName(QString name) ui->NameEdit->setText(name); } +void LIFXSettingsEntry::on_MultizoneCheckBox_stateChanged(int checkState) +{ + if (checkState == Qt::Checked) + { + ui->ExtendedMultizoneCheckBox->setEnabled(true); + } + else + { + ui->ExtendedMultizoneCheckBox->setEnabled(false); + ui->ExtendedMultizoneCheckBox->setCheckState(Qt::Unchecked); + } +} + bool LIFXSettingsEntry::isDataValid() { // stub diff --git a/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.h b/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.h index 0a817166..8d471213 100644 --- a/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.h +++ b/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.h @@ -31,5 +31,6 @@ private: Ui::LIFXSettingsEntry *ui; private slots: - void changeEvent(QEvent *event) override; + void changeEvent(QEvent *event); + void on_MultizoneCheckBox_stateChanged(int arg1); }; diff --git a/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.ui b/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.ui index 73d6f229..73b24f68 100644 --- a/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.ui +++ b/qt/ManualDevicesSettingsPage/LIFXSettingsEntry/LIFXSettingsEntry.ui @@ -6,7 +6,7 @@ 0 0 - 216 + 605 89 @@ -26,8 +26,15 @@ LIFX Device - - + + + + + + + Multizone + + @@ -36,9 +43,6 @@ - - - @@ -46,6 +50,19 @@ + + + + + + + false + + + Extended Multizone + + +