Add support for TP-Link's Kasa Smart light bulbs

This commit is contained in:
Devin Wendt 2023-02-26 04:59:04 +00:00 committed by Adam Honse
parent b1f2231bc7
commit de43a02a1b
14 changed files with 950 additions and 0 deletions

View file

@ -0,0 +1,329 @@
/*----------------------------------------------*\
| Driver for Kasa Smart light bulbs |
| |
| Devin Wendt (umbreon222@gmail.com), 2/16/2023 |
\*----------------------------------------------*/
#include "KasaSmartController.h"
#include "json.hpp"
#include "hsv.h"
using json = nlohmann::json;
KasaSmartController::KasaSmartController(std::string ipAddress, std::string name)
{
this->name = name;
/*------------------------------------------------*\
| Fill in location string with device's IP address |
\*------------------------------------------------*/
location = "IP: " + ipAddress;
/*---------------------------------------------------------*\
| Create a TCP client sending to the device's IP, port 9999 |
\*---------------------------------------------------------*/
port.tcp_client(ipAddress.c_str(), "9999");
}
bool KasaSmartController::Initialize()
{
is_initialized = false;
retry_count = 0;
/*--------------*\
| Try to connect |
\*--------------*/
while(!port.connected && !port.tcp_client_connect() && retry_count < KASA_SMART_MAX_CONNECTION_ATTEMPTS)
{
++retry_count;
}
if(!port.connected)
{
/*----------------*\
| Couldn't connect |
\*----------------*/
return is_initialized;
}
const std::string system_info_query(KASA_SMART_SYSTEM_INFO_QUERY);
std::string system_info_json;
bool command_sent = KasaSmartController::SendCommand(system_info_query, system_info_json);
port.tcp_close();
if(!command_sent || system_info_json.empty())
{
/*---------------------------------------*\
| Send command failed or no data returned |
\*---------------------------------------*/
return is_initialized;
}
json system_information;
try
{
system_information = json::parse(system_info_json);
}
catch (json::parse_error&)
{
/*-----------------------*\
| Can't parse system info |
\*-----------------------*/
return is_initialized;
}
std::string device_type;
if(system_information["system"]["get_sysinfo"].contains("type"))
{
device_type = system_information["system"]["get_sysinfo"]["type"];
}
else if(system_information["system"]["get_sysinfo"].contains("mic_type"))
{
device_type = system_information["system"]["get_sysinfo"]["mic_type"];
}
else
{
/*----------------------*\
| Can't find device type |
\*----------------------*/
return is_initialized;
}
std::transform(device_type.begin(), device_type.end(), device_type.begin(),
[](unsigned char c){ return std::tolower(c); });
if(device_type.find("smartbulb") == std::string::npos)
{
/*----------------------------*\
| Device type not a smart bulb |
\*----------------------------*/
return is_initialized;
}
if(system_information["system"]["get_sysinfo"].contains("is_color") && system_information["system"]["get_sysinfo"]["is_color"] != 1)
{
/*--------------------------------*\
| Smart bulb doesn't support color |
\*--------------------------------*/
return is_initialized;
}
firmware_version = system_information["system"]["get_sysinfo"]["sw_ver"];
module_name = system_information["system"]["get_sysinfo"]["model"];
device_id = system_information["system"]["get_sysinfo"]["deviceId"];
is_initialized = true;
return is_initialized;
}
KasaSmartController::~KasaSmartController()
{
if(port.connected)
{
port.tcp_close();
}
}
std::string KasaSmartController::GetLocation()
{
return(location);
}
std::string KasaSmartController::GetName()
{
return(name);
}
std::string KasaSmartController::GetVersion()
{
return(module_name + " " + firmware_version);
}
std::string KasaSmartController::GetManufacturer()
{
return("Kasa Smart");
}
std::string KasaSmartController::GetUniqueID()
{
return(device_id);
}
void KasaSmartController::SetColor(unsigned char red, unsigned char green, unsigned char blue)
{
if(!is_initialized)
{
return;
}
RGBColor color = ToRGBColor(red, green, blue);
hsv_t hsv;
rgb2hsv(color, &hsv);
/*------------------------------------------*\
| Normalize case where hue is "-1" undefined |
\*------------------------------------------*/
unsigned int normalized_hue = hsv.hue;
if(hsv.hue == -1)
{
normalized_hue = 0;
}
/*--------------------------------------------------*\
| Kasa smart lights take values out of 100 for these |
\*--------------------------------------------------*/
unsigned int normalized_saturation = hsv.saturation * 100 / 255;
unsigned int normalized_value = hsv.value * 100 / 255;
/*-------------------*\
| Open TCP connection |
\*-------------------*/
if(!port.connected && !port.tcp_client_connect() && ++retry_count >= KASA_SMART_MAX_CONNECTION_ATTEMPTS)
{
is_initialized = false;
return;
}
/*----------------------------*\
| Hack to handle/emulate black |
\*----------------------------*/
if(normalized_saturation == 0 && normalized_value == 0)
{
TurnOff();
return;
}
/*------------------------------*\
| Format set light state command |
\*------------------------------*/
const std::string set_lightstate_command_format(KASA_SMART_SET_LIGHT_STATE_COMMAND_FORMAT);
int size = std::snprintf(nullptr, 0, set_lightstate_command_format.c_str(), normalized_hue, normalized_saturation, normalized_value) + 1;
if(size <= 0)
{
port.tcp_close();
return;
}
char* buf = new char[size];
std::snprintf(buf, size, set_lightstate_command_format.c_str(), normalized_hue, normalized_saturation, normalized_value);
std::string set_lightstate_command(buf, buf + size - 1);
delete[] buf;
/*-----------------------------*\
| Send command, ignore response |
\*-----------------------------*/
std::string response;
KasaSmartController::SendCommand(set_lightstate_command, response);
port.tcp_close();
}
void KasaSmartController::TurnOff()
{
if(!is_initialized)
{
return;
}
const std::string turn_off_command(KASA_SMART_OFF_COMMAND);
if(!port.connected && !port.tcp_client_connect() && ++retry_count >= KASA_SMART_MAX_CONNECTION_ATTEMPTS)
{
is_initialized = false;
return;
}
std::string response;
KasaSmartController::SendCommand(turn_off_command, response);
port.tcp_close();
}
bool KasaSmartController::SendCommand(std::string command, std::string &response)
{
const unsigned char* encrypted_payload = KasaSmartController::Encrypt(command);
port.tcp_client_write((char*)encrypted_payload, command.length() + sizeof(unsigned long));
delete[] encrypted_payload;
unsigned char* receive_buffer = new unsigned char[KASA_SMART_RECEIVE_BUFFER_SIZE];
int response_length = port.tcp_listen((char*)receive_buffer, KASA_SMART_RECEIVE_BUFFER_SIZE);
if(response_length > KASA_SMART_RECEIVE_BUFFER_SIZE) {
/*-------------------------------------------------------------*\
| Small fail safes to prevent decrypting bad or empty responses |
\*-------------------------------------------------------------*/
return false;
}
unsigned long received_length = response_length;
unsigned long response_full_length = 0;
if(response_length > 0)
{
response_full_length = ntohl(*(unsigned long*)receive_buffer);
}
if(response_full_length > KASA_SMART_RECEIVE_BUFFER_SIZE) {
return false;
}
/*--------------------------*\
| Fetch entirety of response |
\*--------------------------*/
while(received_length < response_full_length)
{
received_length += port.tcp_listen((char*)receive_buffer + received_length, KASA_SMART_RECEIVE_BUFFER_SIZE - received_length);
}
if(received_length > 0)
{
/*------------------------------------------------*\
| Decrypt payload data preceeding the payload size |
\*------------------------------------------------*/
KasaSmartController::Decrypt(receive_buffer + sizeof(unsigned long), received_length - sizeof(unsigned long), response);
}
delete[] receive_buffer;
return true;
}
unsigned char* KasaSmartController::Encrypt(const std::string request)
{
/*----------------------------------------------------------------*\
| "Encrypted" payload consists of size as a uint32 + XOR'd payload |
\*----------------------------------------------------------------*/
unsigned long size = htonl(request.length());
int payload_size = request.length() + sizeof(size);
unsigned char* payload = new unsigned char[payload_size];
memcpy(payload, &size, sizeof(size));
unsigned char* request_data = new unsigned char[request.length()];
memcpy(request_data, request.data(), request.length());
KasaSmartController::XorPayload(request_data, request.length());
memcpy(payload + sizeof(size), request_data, request.length());
delete[] request_data;
return payload;
}
std::string KasaSmartController::Decrypt(const unsigned char* encrypted, int length, std::string &response)
{
unsigned char* temp_encrypted = new unsigned char[length];
memcpy(temp_encrypted, encrypted, length);
KasaSmartController::XorEncryptedPayload(temp_encrypted, length);
for(int i = 0; i < length; ++i)
{
response += temp_encrypted[i];
}
delete[] temp_encrypted;
return response;
}
void KasaSmartController::XorPayload(unsigned char* encrypted, int length)
{
unsigned char key = KASA_SMART_INITIALIZATION_VECTOR;
for(int i = 0; i < length; ++i)
{
key ^= encrypted[i];
encrypted[i] = key;
}
}
void KasaSmartController::XorEncryptedPayload(unsigned char* encrypted, int length)
{
unsigned char key = KASA_SMART_INITIALIZATION_VECTOR;
for(int i = 0; i < length; ++i)
{
unsigned char plain_byte = key ^ encrypted[i];
key = encrypted[i];
encrypted[i] = plain_byte;
}
}

View file

@ -0,0 +1,64 @@
/*---------------------------------------------------------*\
| Definitions for Kasa Smart |
| |
| Devin Wendt (umbreon222@gmail.com), 2/16/2023 |
\*---------------------------------------------------------*/
#include "RGBController.h"
#include "net_port.h"
#include <string>
#include <thread>
#include <vector>
#pragma once
enum
{
KASA_SMART_MODE_DIRECT = 0x00,
KASA_SMART_MODE_OFF = 0x01
};
#define KASA_SMART_INITIALIZATION_VECTOR 0xAB
#define KASA_SMART_RECEIVE_BUFFER_SIZE 4096
#define KASA_SMART_MAX_CONNECTION_ATTEMPTS 3
/*-------------------------*\
| Kasa Smart Light Commands |
\*-------------------------*/
#define KASA_SMART_SYSTEM_INFO_QUERY "{\"system\": {\"get_sysinfo\": {}}}"
#define KASA_SMART_OFF_COMMAND "{\"smartlife.iot.smartbulb.lightingservice\": {\"transition_light_state\": {\"transition_period\": 0, \"on_off\":0, \"mode\":\"normal\"}}}"
const char KASA_SMART_SET_LIGHT_STATE_COMMAND_FORMAT[] = "{\"smartlife.iot.smartbulb.lightingservice\": {\"transition_light_state\": {\"transition_period\": 0, \"on_off\""
":1, \"mode\":\"normal\", \"hue\": %u, \"saturation\": %u, \"brightness\": %u, \"color_temp\": 0}}}";
class KasaSmartController
{
public:
KasaSmartController(std::string ipAddress, std::string name);
~KasaSmartController();
std::string GetLocation();
std::string GetName();
std::string GetVersion();
std::string GetManufacturer();
std::string GetUniqueID();
bool Initialize();
void SetColor(unsigned char red, unsigned char green, unsigned char blue);
void TurnOff();
private:
net_port port;
std::string name;
bool is_initialized;
unsigned int retry_count;
std::string firmware_version;
std::string module_name;
std::string device_id;
std::string location;
bool SendCommand(std::string command, std::string &response);
static unsigned char* Encrypt(const std::string request);
static std::string Decrypt(const unsigned char*, int length, std::string &response);
static void XorPayload(unsigned char* encrypted, int length);
static void XorEncryptedPayload(unsigned char* encrypted, int length);
};

View file

@ -0,0 +1,55 @@
#include "Detector.h"
#include "KasaSmartController.h"
#include "RGBController.h"
#include "RGBController_KasaSmart.h"
#include "SettingsManager.h"
#include <vector>
#include <cctype>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
/******************************************************************************************\
* *
* DetectKasaSmartControllers *
* *
* Detect Kasa Smart devices *
* *
\******************************************************************************************/
void DetectKasaSmartControllers()
{
json kasa_smart_settings;
/*---------------------------------------------*\
| Get Kasa Smart settings from settings manager |
\*---------------------------------------------*/
kasa_smart_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("KasaSmartDevices");
/*---------------------------------------------*\
| If the Wiz settings contains devices, process |
\*---------------------------------------------*/
if(kasa_smart_settings.contains("devices"))
{
for(unsigned int device_idx = 0; device_idx < kasa_smart_settings["devices"].size(); device_idx++)
{
if(kasa_smart_settings["devices"][device_idx].contains("ip"))
{
std::string kasa_smart_ip = kasa_smart_settings["devices"][device_idx]["ip"];
std::string name = kasa_smart_settings["devices"][device_idx]["name"];
KasaSmartController* controller = new KasaSmartController(kasa_smart_ip, name);
if(!controller->Initialize())
{
continue;
}
RGBController_KasaSmart* rgb_controller = new RGBController_KasaSmart(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
}
} /* DetectKasaSmartControllers() */
REGISTER_DETECTOR("KasaSmart", DetectKasaSmartControllers);

View file

@ -0,0 +1,113 @@
/*-----------------------------------------*\
| RGBController_KasaSmart.cpp |
| |
| Generic RGB Interface for KasaSmart |
| |
| Devin Wendt (umbreon222) 2/16/2023 |
\*-----------------------------------------*/
#include "RGBController_KasaSmart.h"
/**------------------------------------------------------------------*\
@name Kasa Smart Bulbs
@category Light
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectKasaSmartControllers
@comment
\*-------------------------------------------------------------------*/
RGBController_KasaSmart::RGBController_KasaSmart(KasaSmartController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetManufacturer() + " " + controller->GetName();
vendor = controller->GetManufacturer();
type = DEVICE_TYPE_LIGHT;
version = controller->GetVersion();
description = "Kasa Smart Device";
serial = controller->GetUniqueID();
location = controller->GetLocation();
mode Direct;
Direct.name = "Direct";
Direct.value = KASA_SMART_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Off;
Off.name = "Off";
Off.value = KASA_SMART_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
SetupZones();
}
RGBController_KasaSmart::~RGBController_KasaSmart()
{
delete controller;
}
void RGBController_KasaSmart::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";
leds.push_back(new_led);
SetupColors();
}
void RGBController_KasaSmart::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*-------------------------------------------*\
| This device does not support resizing zones |
\*-------------------------------------------*/
}
void RGBController_KasaSmart::DeviceUpdateLEDs()
{
if(modes[active_mode].value != KASA_SMART_MODE_DIRECT)
{
return;
}
unsigned char red = RGBGetRValue(colors[0]);
unsigned char grn = RGBGetGValue(colors[0]);
unsigned char blu = RGBGetBValue(colors[0]);
controller->SetColor(red, grn, blu);
}
void RGBController_KasaSmart::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_KasaSmart::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_KasaSmart::DeviceUpdateMode()
{
if(modes[active_mode].value == KASA_SMART_MODE_OFF)
{
controller->TurnOff();
return;
}
}

View file

@ -0,0 +1,31 @@
/*-----------------------------------------*\
| RGBController_KasaSmart.h |
| |
| Generic RGB Interface for Kasa Smart |
| |
| Devin Wendt (umbreon222) 2/16/2023 |
\*-----------------------------------------*/
#pragma once
#include "RGBController.h"
#include "KasaSmartController.h"
class RGBController_KasaSmart : public RGBController
{
public:
RGBController_KasaSmart(KasaSmartController* controller_ptr);
~RGBController_KasaSmart();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
KasaSmartController* controller;
};

View file

@ -219,6 +219,8 @@ HEADERS +=
qt/OpenRGBDialog2.h \
qt/OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsEntry.h \
qt/OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsPage.h \
qt/OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsEntry.h \
qt/OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsPage.h \
qt/OpenRGBPluginContainer.h \
qt/OpenRGBPluginsPage/OpenRGBPluginsEntry.h \
qt/OpenRGBPluginsPage/OpenRGBPluginsList.h \
@ -469,6 +471,8 @@ HEADERS +=
Controllers/IntelArcA770LEController/RGBController_IntelArcA770LE.h \
Controllers/IonicoController/IonicoController.h \
Controllers/IonicoController/RGBController_Ionico.h \
Controllers/KasaSmartController/KasaSmartController.h \
Controllers/KasaSmartController/RGBController_KasaSmart.h \
Controllers/KeychronKeyboardController/KeychronKeyboardController.h \
Controllers/KeychronKeyboardController/RGBController_KeychronKeyboard.h \
Controllers/LEDStripController/LEDStripController.h \
@ -748,6 +752,8 @@ SOURCES +=
qt/OpenRGBDialog2.cpp \
qt/OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsEntry.cpp \
qt/OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsPage.cpp \
qt/OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsEntry.cpp \
qt/OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsPage.cpp \
qt/OpenRGBPluginContainer.cpp \
qt/OpenRGBPluginsPage/OpenRGBPluginsEntry.cpp \
qt/OpenRGBPluginsPage/OpenRGBPluginsList.cpp \
@ -1062,6 +1068,9 @@ SOURCES +=
Controllers/IonicoController/IonicoControllerDetect.cpp \
Controllers/IonicoController/RGBController_Ionico.cpp \
Controllers/IntelArcA770LEController/RGBController_IntelArcA770LE.cpp \
Controllers/KasaSmartController/KasaSmartController.cpp \
Controllers/KasaSmartController/KasaSmartControllerDetect.cpp \
Controllers/KasaSmartController/RGBController_KasaSmart.cpp \
Controllers/KeychronKeyboardController/KeychronKeyboardController.cpp \
Controllers/KeychronKeyboardController/KeychronKeyboardControllerDetect.cpp \
Controllers/KeychronKeyboardController/RGBController_KeychronKeyboard.cpp \
@ -1364,6 +1373,8 @@ FORMS +=
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.ui \
qt/OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsEntry.ui \
qt/OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsPage.ui \
qt/OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsEntry.ui \
qt/OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsPage.ui \
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsEntry.ui \
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsPage.ui \
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsPage.ui \

View file

@ -437,6 +437,11 @@ OpenRGBDialog2::OpenRGBDialog2(QWidget *parent) : QMainWindow(parent), ui(new Op
\*-----------------------------------------------------*/
AddE131SettingsPage();
/*-----------------------------------------------------*\
| Add the Kasa Smart settings page |
\*-----------------------------------------------------*/
AddKasaSmartSettingsPage();
/*-----------------------------------------------------*\
| Add the LIFX settings page |
\*-----------------------------------------------------*/
@ -766,6 +771,34 @@ void OpenRGBDialog2::AddE131SettingsPage()
ui->SettingsTabBar->tabBar()->setTabButton(ui->SettingsTabBar->tabBar()->count() - 1, QTabBar::LeftSide, SettingsTabLabel);
}
void OpenRGBDialog2::AddKasaSmartSettingsPage()
{
/*-----------------------------------------------------*\
| Create the Settings page |
\*-----------------------------------------------------*/
KasaSmartSettingsPage = new OpenRGBKasaSmartSettingsPage();
ui->SettingsTabBar->addTab(KasaSmartSettingsPage, "");
QString SettingsLabelString;
if(OpenRGBThemeManager::IsDarkTheme())
{
SettingsLabelString = "light_dark.png";
}
else
{
SettingsLabelString = "light.png";
}
/*-----------------------------------------------------*\
| Create the tab label |
\*-----------------------------------------------------*/
TabLabel* SettingsTabLabel = new TabLabel(SettingsLabelString, tr("Kasa Smart Devices"), (char *)"Kasa Smart Devices", (char *)context);
ui->SettingsTabBar->tabBar()->setTabButton(ui->SettingsTabBar->tabBar()->count() - 1, QTabBar::LeftSide, SettingsTabLabel);
}
void OpenRGBDialog2::AddLIFXSettingsPage()
{
/*-----------------------------------------------------*\

View file

@ -11,6 +11,7 @@
#include "OpenRGBSettingsPage.h"
#include "OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.h"
#include "OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsPage.h"
#include "OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsPage.h"
#include "OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsPage.h"
#include "OpenRGBPhilipsHueSettingsPage/OpenRGBPhilipsHueSettingsPage.h"
#include "OpenRGBPhilipsWizSettingsPage/OpenRGBPhilipsWizSettingsPage.h"
@ -84,6 +85,7 @@ private:
OpenRGBSettingsPage *SettingsPage;
OpenRGBE131SettingsPage *E131SettingsPage;
OpenRGBElgatoKeyLightSettingsPage *ElgatoKeyLightSettingsPage;
OpenRGBKasaSmartSettingsPage *KasaSmartSettingsPage;
OpenRGBLIFXSettingsPage *LIFXSettingsPage;
OpenRGBPhilipsHueSettingsPage *PhilipsHueSettingsPage;
OpenRGBPhilipsWizSettingsPage *PhilipsWizSettingsPage;
@ -111,6 +113,7 @@ private:
void AddSettingsPage();
void AddE131SettingsPage();
void AddElgatoKeyLightSettingsPage();
void AddKasaSmartSettingsPage();
void AddLIFXSettingsPage();
void AddPhilipsHueSettingsPage();
void AddPhilipsWizSettingsPage();

View file

@ -0,0 +1,24 @@
#include "OpenRGBKasaSmartSettingsEntry.h"
#include "ui_OpenRGBKasaSmartSettingsEntry.h"
using namespace Ui;
OpenRGBKasaSmartSettingsEntry::OpenRGBKasaSmartSettingsEntry(QWidget *parent) :
QWidget(parent),
ui(new Ui::OpenRGBKasaSmartSettingsEntryUi)
{
ui->setupUi(this);
}
OpenRGBKasaSmartSettingsEntry::~OpenRGBKasaSmartSettingsEntry()
{
delete ui;
}
void OpenRGBKasaSmartSettingsEntry::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
}

View file

@ -0,0 +1,24 @@
#ifndef OPENRGBKASASMARTSETTINGSENTRY_H
#define OPENRGBKASASMARTSETTINGSENTRY_H
#include "ui_OpenRGBKasaSmartSettingsEntry.h"
#include <QWidget>
namespace Ui {
class OpenRGBKasaSmartSettingsEntry;
}
class Ui::OpenRGBKasaSmartSettingsEntry : public QWidget
{
Q_OBJECT
public:
explicit OpenRGBKasaSmartSettingsEntry(QWidget *parent = nullptr);
~OpenRGBKasaSmartSettingsEntry();
Ui::OpenRGBKasaSmartSettingsEntryUi *ui;
private slots:
void changeEvent(QEvent *event);
};
#endif // OPENRGBKASASMARTSETTINGSENTRY_H

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenRGBKasaSmartSettingsEntryUi</class>
<widget class="QWidget" name="OpenRGBKasaSmartSettingsEntryUi">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>190</width>
<height>89</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="5">
<widget class="QLineEdit" name="NameEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="IPLabel">
<property name="text">
<string>IP:</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="IPEdit"/>
</item>
<item row="1" column="4">
<widget class="QLabel" name="NameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>IPEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,120 @@
#include "OpenRGBKasaSmartSettingsPage.h"
#include "ui_OpenRGBKasaSmartSettingsPage.h"
#include "ResourceManager.h"
using namespace Ui;
OpenRGBKasaSmartSettingsPage::OpenRGBKasaSmartSettingsPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::OpenRGBKasaSmartSettingsPageUi)
{
ui->setupUi(this);
json KasaSmart_settings;
/*-------------------------------------------------*\
| Get KasaSmart settings |
\*-------------------------------------------------*/
KasaSmart_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("KasaSmartDevices");
/*-------------------------------------------------*\
| If the Wiz settings contains devices, process |
\*-------------------------------------------------*/
if(KasaSmart_settings.contains("devices"))
{
for(unsigned int device_idx = 0; device_idx < KasaSmart_settings["devices"].size(); device_idx++)
{
OpenRGBKasaSmartSettingsEntry* entry = new OpenRGBKasaSmartSettingsEntry;
if(KasaSmart_settings["devices"][device_idx].contains("ip"))
{
entry->ui->IPEdit->setText(QString::fromStdString(KasaSmart_settings["devices"][device_idx]["ip"]));
}
if(KasaSmart_settings["devices"][device_idx].contains("name"))
{
entry->ui->IPEdit->setText(QString::fromStdString(KasaSmart_settings["devices"][device_idx]["name"]));
}
entries.push_back(entry);
QListWidgetItem* item = new QListWidgetItem;
item->setSizeHint(entry->sizeHint());
ui->KasaSmartDeviceList->addItem(item);
ui->KasaSmartDeviceList->setItemWidget(item, entry);
ui->KasaSmartDeviceList->show();
}
}
}
OpenRGBKasaSmartSettingsPage::~OpenRGBKasaSmartSettingsPage()
{
delete ui;
}
void OpenRGBKasaSmartSettingsPage::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
}
void Ui::OpenRGBKasaSmartSettingsPage::on_AddKasaSmartDeviceButton_clicked()
{
OpenRGBKasaSmartSettingsEntry* entry = new OpenRGBKasaSmartSettingsEntry;
entry->ui->NameEdit->setText(QString("KasaSmart%1").arg(entries.size()));
entries.push_back(entry);
QListWidgetItem* item = new QListWidgetItem;
item->setSizeHint(entry->sizeHint());
ui->KasaSmartDeviceList->addItem(item);
ui->KasaSmartDeviceList->setItemWidget(item, entry);
ui->KasaSmartDeviceList->show();
}
void Ui::OpenRGBKasaSmartSettingsPage::on_RemoveKasaSmartDeviceButton_clicked()
{
int cur_row = ui->KasaSmartDeviceList->currentRow();
if(cur_row < 0)
{
return;
}
QListWidgetItem* item = ui->KasaSmartDeviceList->takeItem(cur_row);
ui->KasaSmartDeviceList->removeItemWidget(item);
delete item;
delete entries[cur_row];
entries.erase(entries.begin() + cur_row);
}
void Ui::OpenRGBKasaSmartSettingsPage::on_SaveKasaSmartConfigurationButton_clicked()
{
json KasaSmart_settings;
/*-------------------------------------------------*\
| Get KasaSmart settings |
\*-------------------------------------------------*/
KasaSmart_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("KasaSmartDevices");
KasaSmart_settings["devices"].clear();
for(unsigned int device_idx = 0; device_idx < entries.size(); device_idx++)
{
/*-------------------------------------------------*\
| Required parameters |
\*-------------------------------------------------*/
KasaSmart_settings["devices"][device_idx]["ip"] = entries[device_idx]->ui->IPEdit->text().toStdString();
KasaSmart_settings["devices"][device_idx]["name"] = entries[device_idx]->ui->NameEdit->text().toStdString();
}
ResourceManager::get()->GetSettingsManager()->SetSettings("KasaSmartDevices", KasaSmart_settings);
ResourceManager::get()->GetSettingsManager()->SaveSettings();
}

View file

@ -0,0 +1,35 @@
#ifndef OPENRGBKASASMARTSETTINGSPAGE_H
#define OPENRGBKASASMARTSETTINGSPAGE_H
#include "ui_OpenRGBKasaSmartSettingsPage.h"
#include <QWidget>
#include "OpenRGBKasaSmartSettingsEntry.h"
namespace Ui {
class OpenRGBKasaSmartSettingsPage;
}
class Ui::OpenRGBKasaSmartSettingsPage : public QWidget
{
Q_OBJECT
public:
explicit OpenRGBKasaSmartSettingsPage(QWidget *parent = nullptr);
~OpenRGBKasaSmartSettingsPage();
private slots:
void changeEvent(QEvent *event);
void on_AddKasaSmartDeviceButton_clicked();
void on_RemoveKasaSmartDeviceButton_clicked();
void on_SaveKasaSmartConfigurationButton_clicked();
private:
Ui::OpenRGBKasaSmartSettingsPageUi *ui;
std::vector<OpenRGBKasaSmartSettingsEntry*> entries;
};
#endif // OPENRGBKASASMARTSETTINGSPAGE_H

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenRGBKasaSmartSettingsPageUi</class>
<widget class="QWidget" name="OpenRGBKasaSmartSettingsPageUi">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="AddKasaSmartDeviceButton">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="RemoveKasaSmartDeviceButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="SaveKasaSmartConfigurationButton">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QListWidget" name="KasaSmartDeviceList">
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>