From 7806973c1e23bdd7c3946a379cf1faeabae06753 Mon Sep 17 00:00:00 2001 From: KlausMu Date: Mon, 16 Sep 2024 21:04:58 +0200 Subject: [PATCH] new device air conditioner --- .../ESP32/infrared_sender_hal_esp32.cpp | 101 +++--- .../device_airconditioner.cpp | 290 ++++++++++++++++++ .../device_airconditioner.h | 61 ++++ .../gui_airconditioner.cpp | 244 +++++++++++++++ .../gui_airconditioner.h | 7 + Platformio/src/main.cpp | 4 + 6 files changed, 641 insertions(+), 66 deletions(-) create mode 100644 Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.cpp create mode 100644 Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.h create mode 100644 Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.cpp create mode 100644 Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.h diff --git a/Platformio/hardware/ESP32/infrared_sender_hal_esp32.cpp b/Platformio/hardware/ESP32/infrared_sender_hal_esp32.cpp index c4559cff..4f491194 100644 --- a/Platformio/hardware/ESP32/infrared_sender_hal_esp32.cpp +++ b/Platformio/hardware/ESP32/infrared_sender_hal_esp32.cpp @@ -28,24 +28,38 @@ enum IRprotocols { IR_PROTOCOL_SAMSUNG36 = 6 }; void sendIRcode_HAL(int protocol, std::list commandPayloads, std::string additionalPayload) { + + // first determine if data was provided by commandPayload or by additionalPayload. Only one of these will be used. + std::string::size_type sz = 0; // alias of size_t + std::string dataStr; + uint64_t data; + if (commandPayloads.empty() && (additionalPayload == "")) { + Serial.printf("execute: cannot send IR command, because both data and payload are empty\r\n"); + return; + } else { + if (additionalPayload != "") { + dataStr = additionalPayload; + } else { + auto current = commandPayloads.begin(); + dataStr = *current; + } + } + switch (protocol) { case IR_PROTOCOL_GC: { - auto current = commandPayloads.begin(); - std::string arrayStr = *current; // first create array of needed size - std::string::difference_type size = std::count(arrayStr.begin(), arrayStr.end(), ','); + std::string::difference_type size = std::count(dataStr.begin(), dataStr.end(), ','); size += 1; uint16_t *buf = new uint16_t[size]; // now get comma separated values and fill array int pos = 0; - std::stringstream ss(arrayStr); + std::stringstream ss(dataStr); while(ss.good()) { - std::string dataStr; - std::getline(ss, dataStr, ','); + std::string valueStr; + std::getline(ss, valueStr, ','); // https://cplusplus.com/reference/string/stoull/ - std::string::size_type sz = 0; // alias of size_t - const uint64_t data = std::stoull(dataStr, &sz, 0); - // Serial.printf(" next string value %s (%" PRIu64 ")\r\n", dataStr.c_str(), data); + data = std::stoull(valueStr, &sz, 0); + // Serial.printf(" next string value %s (%" PRIu64 ")\r\n", valueStr.c_str(), data); buf[pos] = data; pos += 1; } @@ -56,87 +70,42 @@ void sendIRcode_HAL(int protocol, std::list commandPayloads, std::s } case IR_PROTOCOL_NEC: { - auto current = commandPayloads.begin(); - std::string dataStr = *current; - // https://cplusplus.com/reference/string/stoull/ - std::string::size_type sz = 0; // alias of size_t - const uint64_t data = std::stoull(dataStr, &sz, 0); + data = std::stoull(dataStr, &sz, 0); Serial.printf("execute: will send IR NEC, data %s (%" PRIu64 ")\r\n", dataStr.c_str(), data); IrSender.sendNEC(data); break; } case IR_PROTOCOL_SAMSUNG: { - auto current = commandPayloads.begin(); - std::string dataStr = *current; - // https://cplusplus.com/reference/string/stoull/ - std::string::size_type sz = 0; // alias of size_t - const uint64_t data = std::stoull(dataStr, &sz, 0); + data = std::stoull(dataStr, &sz, 0); Serial.printf("execute: will send IR SAMSUNG, data %s (%" PRIu64 ")\r\n", dataStr.c_str(), data); IrSender.sendSAMSUNG(data); break; } case IR_PROTOCOL_SONY: { - std::string::size_type sz = 0; // alias of size_t - uint64_t data; - if (commandPayloads.empty() && (additionalPayload == "")) { - Serial.printf("execute: cannot send IR SONY, because both data and payload are empty\r\n"); - } else { - if (additionalPayload != "") { - data = std::stoull(additionalPayload, &sz, 0); - } else { - auto current = commandPayloads.begin(); - data = std::stoull(*current, &sz, 0); - } - Serial.printf("execute: will send IR SONY 15 bit, data (%" PRIu64 ")\r\n", data); - IrSender.sendSony(data, 15); - } + data = std::stoull(dataStr, &sz, 0); + Serial.printf("execute: will send IR SONY 15 bit, data %s (%" PRIu64 ")\r\n", dataStr.c_str(), data); + IrSender.sendSony(data, 15); break; } case IR_PROTOCOL_RC5: { - std::string::size_type sz = 0; // alias of size_t - uint64_t data; - if (commandPayloads.empty() && (additionalPayload == "")) { - Serial.printf("execute: cannot send IR RC5, because both data and payload are empty\r\n"); - } else { - if (additionalPayload != "") { - data = std::stoull(additionalPayload, &sz, 0); - } else { - auto current = commandPayloads.begin(); - data = std::stoull(*current, &sz, 0); - } - Serial.printf("execute: will send IR RC5, data (%" PRIu64 ")\r\n", data); - IrSender.sendRC5(IrSender.encodeRC5X(0x00, data)); - } + data = std::stoull(dataStr, &sz, 0); + Serial.printf("execute: will send IR RC5, data %s (%" PRIu64 ")\r\n", dataStr.c_str(), data); + IrSender.sendRC5(IrSender.encodeRC5X(0x00, data)); break; } case IR_PROTOCOL_DENON: { - std::string::size_type sz = 0; // alias of size_t - uint64_t data; - if (commandPayloads.empty() && (additionalPayload == "")) { - Serial.printf("execute: cannot send IR DENON 48 bit, because both data and payload are empty\r\n"); - } else { - if (additionalPayload != "") { - data = std::stoull(additionalPayload, &sz, 0); - } else { - auto current = commandPayloads.begin(); - data = std::stoull(*current, &sz, 0); - } - Serial.printf("execute: will send IR DENON 48 bit, data (%" PRIu64 ")\r\n", data); - IrSender.sendDenon(data, 48); - } + data = std::stoull(dataStr, &sz, 0); + Serial.printf("execute: will send IR DENON 48 bit, data %s (%" PRIu64 ")\r\n", dataStr.c_str(), data); + IrSender.sendDenon(data, 48); break; } case IR_PROTOCOL_SAMSUNG36: { - auto current = commandPayloads.begin(); - std::string dataStr = *current; - // https://cplusplus.com/reference/string/stoull/ - std::string::size_type sz = 0; // alias of size_t - const uint64_t data = std::stoull(dataStr, &sz, 0); + data = std::stoull(dataStr, &sz, 0); Serial.printf("execute: will send IR SAMSUNG36, data %s (%" PRIu64 ")\r\n", dataStr.c_str(), data); IrSender.sendSamsung36(data); break; diff --git a/Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.cpp b/Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.cpp new file mode 100644 index 00000000..58b1306f --- /dev/null +++ b/Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.cpp @@ -0,0 +1,290 @@ +#include "applicationInternal/commandHandler.h" +#include "applicationInternal/omote_log.h" +#include "applicationInternal/hardware/hardwarePresenter.h" +#include "device_airconditioner.h" + +uint16_t AIRCONDITIONER_COMMAND; + +void register_device_airconditioner() { + register_command(&AIRCONDITIONER_COMMAND , makeCommandData(IR, {std::to_string(IR_PROTOCOL_NEC)})); + + airConditionerPAC_N81.init(); +} + +AirConditionerPAC_N81 airConditionerPAC_N81; + +void AirConditionerPAC_N81::init() { + // this is the initial state we assume after startup. + // Nibble 1: 1 + // Nibble 2: 2 + // Nibble 3: fan level + airconditioner_state.fan = 2; // 1=high, 2=medium, 4=low + // Nibble 4: mode + airconditioner_state.mode = 8; // 8=A/C, 2=Dehumidify, 1=Fan only + // Nibble 5: timer + airconditioner_state.timer_value = 0; // Hours until the timer action (on/off) will be executed + // Nibble 6: | |CF|T |ON| + // 4 bits + airconditioner_state.unitF = false; + airconditioner_state.timer = false; // Enables the timer. When unit is off it serves as the switch-on timer, if the unit is on it switches off after the interval + airconditioner_state.on = false; + // Nibble 7+8: temp + airconditioner_state.temperature = 24; + +/* + // print this message + omote_log_d("AC inital state:\r\n"); + dl_print_msg(&airconditioner_state); + // convert it to hex command + unsigned long msg_on_hex = dl_assemble_msg(&airconditioner_state); + omote_log_d("AC hex command: 0x%lx\r\n", msg_on_hex); + // dec 304611600 hex 0x12280110 + + // sanity check: decode it back and print it + omote_log_d("Sanity check: hex converted back to message\r\n"); + dl_aircon_msg_t msg_on_decoded; + dl_decode_msg(msg_on_hex, &msg_on_decoded); + dl_print_msg(&msg_on_decoded); +*/ +} + +/* +this is how the AC changes temperature when unit changes between C and F: +F C F +89 -> 31 -> 89 +90 -> 32 -> 90 +61 -> 16 -> 61 +*/ + +void AirConditionerPAC_N81::set_onoff(bool state) { + airconditioner_state.on = state; +} +void AirConditionerPAC_N81::set_mode_next() { + switch (airconditioner_state.mode) { + case 1: {airconditioner_state.mode = 2; break;} + case 2: {airconditioner_state.mode = 8; break;} + case 8: {airconditioner_state.mode = 1; break;} + } +} +void AirConditionerPAC_N81::set_fan_next() { + switch (airconditioner_state.fan) { + case 1: {airconditioner_state.fan = 4; break;} + case 2: {airconditioner_state.fan = 1; break;} + case 4: {airconditioner_state.fan = 2; break;} + } +} +void AirConditionerPAC_N81::set_temp_up() { + if (!airconditioner_state.unitF) { + // Celsius + if (airconditioner_state.temperature < maxTempC) { + airconditioner_state.temperature++; + } + } else { + // Fahrenheit + if (airconditioner_state.temperature < maxTempF) { + airconditioner_state.temperature++; + } + } +} +void AirConditionerPAC_N81::set_temp_down() { + if (!airconditioner_state.unitF) { + // Celsius + if (airconditioner_state.temperature > minTempC) { + airconditioner_state.temperature--; + } + } else { + // Fahrenheit + if (airconditioner_state.temperature > minTempF) { + airconditioner_state.temperature--; + } + } +} +void AirConditionerPAC_N81::set_CF_next() { + if (airconditioner_state.unitF) { + // change from Fahrenheit to Celsius + airconditioner_state.temperature = (float(airconditioner_state.temperature - 32) * 5/9 + 0.5); + airconditioner_state.unitF = false; + } else { + // change from Celsius to Fahrenheit + airconditioner_state.temperature = (float(airconditioner_state.temperature ) * 9/5 + 0.5) + 32; + airconditioner_state.unitF = true; + } +} +void AirConditionerPAC_N81::set_timer(bool state) { + airconditioner_state.timer = state; +} +void AirConditionerPAC_N81::set_timer_value(int timer_value) { + if ((timer_value >=0) && (timer_value <= 12)) { + airconditioner_state.timer_value = timer_value; + } +} + +std::string AirConditionerPAC_N81::get_onoff_str() { + if (airconditioner_state.on) { + return "on"; + } else { + return "off"; + } +} +std::string AirConditionerPAC_N81::get_mode_str() { + switch (airconditioner_state.mode) { + case 1: {return "fan only"; break;} + case 2: {return "dehumidify"; break;} + case 8: {return "A/C"; break;} + } + return ""; +} +std::string AirConditionerPAC_N81::get_fan_str() { + switch (airconditioner_state.fan) { + case 1: {return "high"; break;} + case 2: {return "medium"; break;} + case 4: {return "low"; break;} + } + return ""; +} +int AirConditionerPAC_N81::get_temp() { + return airconditioner_state.temperature; +} +std::string AirConditionerPAC_N81::get_temp_str() { + return std::to_string(airconditioner_state.temperature); +} +std::string AirConditionerPAC_N81::get_CF_str() { + if (airconditioner_state.unitF) { + return "F"; + } else { + return "C"; + } +} +std::string AirConditionerPAC_N81::get_timer_str() { + if (airconditioner_state.timer) { + return "timer on"; + } else { + return "timer off"; + } +} +std::string AirConditionerPAC_N81::get_timer_value_str() { + return std::to_string(airconditioner_state.timer_value); +} + + +// https://github.com/zeroflow/ESPAircon +/* + * DeLonghi PAC N81 IR Commands + * + * 32 Bit Value + * NEC Encoding + * + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | 0x01 | 0x02 | |Lo|Md|Hi|Ac| |Dh|Bl| + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | Timer | |CF|T |ON| Temperature | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * 0x01 and 0x02 is a fixed prefix + * Lo / Md / Hi are bits for the fan setting + * Cl / Dh / Bl are mode switches + * Ac Airconditioning + * Dh Dehumidify + * Bl Blow (Fan Mode) + * Timer is the set value for timer control. + * Set to 1 even if not active via the bit + * CF Celsius / Fahrenheit Setting. Low C, High F + * T Timer Low Off, High On + * On On/Off Switch + * + * Temperature 8-Bit Temperature Value + * + * 0-16 means 16-32°C + * 61-89 means 61-89°F + * + * Both the temperature and timer have their bit-order reversed. + */ + +unsigned long AirConditionerPAC_N81::dl_assemble_msg(dl_aircon_msg_t* msg){ + unsigned long buf = 0x12000000; + + if (!msg->unitF){ + // msg->temperature = constrain(msg->temperature, 16, 32); + msg->temperature = (msg->temperature) < minTempC ? minTempC : ((msg->temperature) > maxTempC ? maxTempC : (msg->temperature)); + buf |= bit_reverse(msg->temperature-16); + }else{ + // msg->temperature = constrain(msg->temperature, 61, 89); + msg->temperature = (msg->temperature) < minTempF ? minTempF : ((msg->temperature) > maxTempF ? maxTempF : (msg->temperature)); + buf |= bit_reverse(msg->temperature); + } + + buf |= bit_reverse(msg->timer_value) << 8; + + if (msg->on) buf |= 0x1 << 8; + if (msg->timer) buf |= 0x1 << 9; + if (msg->unitF) buf |= 0x1 << 10; + + if (msg->mode == 8 || msg->mode == 2 || msg->mode == 1){ + buf |= msg->mode << 16; + }else{ + buf |= 0x8 << 16; + } + + if (msg->fan == 4 || msg->fan == 2 || msg->fan == 1){ + buf |= msg->fan << 20; + }else{ + buf |= 0x2 << 20; + } + + return buf; +} + +void AirConditionerPAC_N81::dl_decode_msg(unsigned long code, dl_aircon_msg_t* msg){ + + msg->on = ( (code) >> 8 ) & 0x01; + msg->timer = ( (code) >> 9 ) & 0x01; + msg->unitF = ( (code) >> 10 ) & 0x01; + + msg->temperature = bit_reverse(code & 0xFF); + if (!msg->unitF) { //degC has a -16 offset + msg->temperature += 16; + } + + msg->timer_value = bit_reverse((code >> 12) & 0xF) >> 4; + msg->mode = (code >> 16) & 0xF; + msg->fan = (code >> 20) & 0xF; +} + +void AirConditionerPAC_N81::dl_print_msg(dl_aircon_msg_t *msg){ + omote_log_d("Airconditioner settings:\r\n"); + + omote_log_d(" %s: %d %s\r\n", get_onoff_str().c_str(), msg->temperature, get_CF_str().c_str()); + omote_log_d(" %s: %s\r\n", get_mode_str().c_str(), get_fan_str().c_str()); + omote_log_d(" %s: %d\r\n", get_timer_str().c_str(), msg->timer_value); + +} + +// Reverse the order of bits in a byte. +// I.e. MSB is swapped with LSB, etc. +unsigned char AirConditionerPAC_N81::bit_reverse( unsigned char x ) +{ + x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa); + x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc); + x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0); + return x; +} + +void AirConditionerPAC_N81::sendIRcommand() { + // print this message + dl_print_msg(&airconditioner_state); + // convert it to hex command + unsigned long msg_on_hex = dl_assemble_msg(&airconditioner_state); + //omote_log_d("AC hex command: 0x%lx\r\n", msg_on_hex); + char buffer[12]; + sprintf(buffer, "0x%lx", msg_on_hex); + //omote_log_d("buffer: %s\r\n", buffer); + executeCommand(AIRCONDITIONER_COMMAND, std::string(buffer)); + +/* + // sanity check: decode it back and print it + omote_log_d("Sanity check: hex converted back to message\r\n"); + dl_aircon_msg_t msg_on_decoded; + dl_decode_msg(msg_on_hex, &msg_on_decoded); + dl_print_msg(&msg_on_decoded); +*/ +} \ No newline at end of file diff --git a/Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.h b/Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.h new file mode 100644 index 00000000..7069230e --- /dev/null +++ b/Platformio/src/devices_pool/misc/device_airconditioner/device_airconditioner.h @@ -0,0 +1,61 @@ +#pragma once + +extern uint16_t AIRCONDITIONER_COMMAND; + +void register_device_airconditioner(); + +// https://github.com/zeroflow/ESPAircon +// De'Longhi PAC N81 +class AirConditionerPAC_N81 { + +private: + typedef struct { + bool on; + int mode; + int fan; + int temperature; + bool unitF; + bool timer; + int timer_value; + } dl_aircon_msg_t; + dl_aircon_msg_t airconditioner_state; + + unsigned long dl_assemble_msg(dl_aircon_msg_t* msg); + void dl_decode_msg(unsigned long code, dl_aircon_msg_t* msg); + void dl_print_msg(dl_aircon_msg_t *msg); + unsigned char bit_reverse( unsigned char x ); + + uint8_t minTempC = 16; + uint8_t maxTempC = 32; + uint8_t minTempF = 61; + uint8_t maxTempF = 89; + +public: + // set default state + void init(); + // setter + void set_onoff(bool state); + void set_mode_next(); + void set_fan_next(); + void set_temp_up(); + void set_temp_down(); + void set_CF_next(); + void set_timer(bool state); + void set_timer_value(int time_value); + // getter + std::string get_onoff_str(); + std::string get_mode_str(); + std::string get_fan_str(); + int get_temp(); + std::string get_temp_str(); + std::string get_CF_str(); + std::string get_timer_str(); + std::string get_timer_value_str(); + // send IR command + void sendIRcommand(); + +protected: + +}; + +extern AirConditionerPAC_N81 airConditionerPAC_N81; diff --git a/Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.cpp b/Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.cpp new file mode 100644 index 00000000..347cab45 --- /dev/null +++ b/Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.cpp @@ -0,0 +1,244 @@ +#include +#include "applicationInternal/gui/guiBase.h" +#include "applicationInternal/gui/guiRegistry.h" +#include "applicationInternal/hardware/hardwarePresenter.h" +#include "applicationInternal/scenes/sceneRegistry.h" +#include "applicationInternal/commandHandler.h" +#include "applicationInternal/keys.h" +#include "applicationInternal/omote_log.h" +#include "devices/misc/device_airconditioner/gui_airconditioner.h" +#include "devices/misc/device_airconditioner/device_airconditioner.h" +#include "scenes/scene__default.h" + +uint16_t GUI_AIRCONDITIONER_ACTIVATE; + +std::map key_repeatModes_airconditioner = {}; +std::map key_commands_short_airconditioner = {}; +std::map key_commands_long_airconditioner = {}; + +lv_obj_t* onoffLabel; +lv_obj_t* tempLabel; +lv_obj_t* modeLabel; +lv_obj_t* speedLabel; +lv_obj_t* timerLabel; +lv_obj_t* CFLabel; + +void update_onoff_label(); +void update_temp_label(); +void update_mode_label(); +void update_speed_label(); +void update_timer_label(); +void update_CF_label(); + +static void airConditioner_event_cb(lv_event_t* e) { + lv_obj_t* target = lv_event_get_target(e); + lv_obj_t* cont = lv_event_get_current_target(e); + if (target == cont) return; // stop if container was clicked + + int user_data = (intptr_t)(target->user_data); + + switch (user_data) { + case 0: { + if (lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED)) { + omote_log_i("Air conditioner: on\r\n"); + airConditionerPAC_N81.set_onoff(true); + } else { + omote_log_i("Air conditioner: off\r\n"); + airConditionerPAC_N81.set_onoff(false); + } + update_onoff_label(); + airConditionerPAC_N81.sendIRcommand(); + break; + } + case 1: { + omote_log_i("Air conditioner: minus\r\n"); + airConditionerPAC_N81.set_temp_down(); + update_temp_label(); + airConditionerPAC_N81.sendIRcommand(); + break; + } + case 2: { + omote_log_i("Air conditioner: plus\r\n"); + airConditionerPAC_N81.set_temp_up(); + update_temp_label(), + airConditionerPAC_N81.sendIRcommand(); + break; + } + case 3: { + omote_log_i("Air conditioner: mode\r\n"); + airConditionerPAC_N81.set_mode_next(); + update_mode_label(); + airConditionerPAC_N81.sendIRcommand(); + break; + } + case 4: { + omote_log_i("Air conditioner: speed\r\n"); + airConditionerPAC_N81.set_fan_next(); + update_speed_label(); + airConditionerPAC_N81.sendIRcommand(); + break; + } + case 5: { + if (lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED)) { + omote_log_i("Air conditioner: timer on\r\n"); + airConditionerPAC_N81.set_timer(true); + } else { + omote_log_i("Air conditioner: timer off\r\n"); + airConditionerPAC_N81.set_timer(false); + } + update_timer_label(); + airConditionerPAC_N81.sendIRcommand(); + break; + } + case 6: { + // This event does not help, because it is not fired when a value in the dropdown was selected. Use airConditioner_dropdown_cb() + omote_log_i("Air conditioner: timer dropdown opened or closed\r\n"); + break; + } + case 7: { + omote_log_i("Air conditioner: C/F\r\n"); + airConditionerPAC_N81.set_CF_next(); + update_temp_label(); + update_CF_label(); + airConditionerPAC_N81.sendIRcommand(); + break; + } + } + +} + +static void airConditioner_dropdown_cb(lv_event_t* e) { + lv_obj_t* target = lv_event_get_target(e); + uint16_t selected_index = lv_dropdown_get_selected(target); + + omote_log_i("Air conditioner: timer dropdown selected %d\r\n", selected_index); + airConditionerPAC_N81.set_timer_value(selected_index); + airConditionerPAC_N81.sendIRcommand(); +} + +void update_onoff_label() { + lv_label_set_text(onoffLabel, airConditionerPAC_N81.get_onoff_str().c_str()); +} +void update_temp_label() { + lv_label_set_text(tempLabel, (airConditionerPAC_N81.get_temp_str() + "°" + airConditionerPAC_N81.get_CF_str()).c_str()); +} +void update_mode_label() { + lv_label_set_text(modeLabel, airConditionerPAC_N81.get_mode_str().c_str()); +} +void update_speed_label() { + lv_label_set_text(speedLabel, airConditionerPAC_N81.get_fan_str().c_str()); +} +void update_timer_label() { + lv_label_set_text(timerLabel, airConditionerPAC_N81.get_timer_str().c_str()); +} +void update_CF_label() { + lv_label_set_text(CFLabel, airConditionerPAC_N81.get_CF_str().c_str()); +} + +void create_label(lv_obj_t** label, lv_obj_t* tab, lv_coord_t x_ofs, lv_coord_t y_ofs) { + *label = lv_label_create(tab); + lv_obj_set_style_text_font(*label, &lv_font_montserrat_12, LV_PART_MAIN); + lv_obj_align(*label, LV_ALIGN_TOP_LEFT, x_ofs, y_ofs); +} + +void create_button(lv_obj_t* tab, lv_coord_t width, lv_coord_t height, lv_coord_t x_ofs, lv_coord_t y_ofs, std::string text, uint8_t user_data) { + lv_obj_t* obj; + lv_obj_t* buttonLabel; + + obj = lv_btn_create(tab); + lv_obj_align(obj, LV_ALIGN_TOP_LEFT, x_ofs, y_ofs); + lv_obj_set_size(obj, width, height); + lv_obj_set_style_bg_color(obj, color_primary, LV_PART_MAIN); + lv_obj_set_style_radius(obj, 14, LV_PART_MAIN); + lv_obj_set_style_shadow_color(obj, lv_color_hex(0x404040), LV_PART_MAIN); + lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE); // Clicking a button causes an event in its container + lv_obj_set_user_data(obj,(void *)(intptr_t)user_data); // Add user data so we can identify which button caused the container event + buttonLabel = lv_label_create(obj); + lv_label_set_text(buttonLabel, text.c_str()); + lv_obj_set_style_text_font(buttonLabel, &lv_font_montserrat_12, LV_PART_MAIN); + lv_obj_center(buttonLabel); +} + +void create_switch(lv_obj_t* tab, lv_coord_t width, lv_coord_t height, lv_coord_t x_ofs, lv_coord_t y_ofs, uint8_t user_data) { + lv_obj_t* obj = lv_switch_create(tab); + lv_obj_align(obj, LV_ALIGN_TOP_LEFT, x_ofs, y_ofs); + lv_obj_set_size(obj, width, height); + lv_obj_set_style_bg_color(obj, lv_color_hex(0x505050), LV_PART_MAIN); + lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE); // Clicking a switch causes an event in its container + lv_obj_set_user_data(obj,(void *)(intptr_t)user_data); // Add user data so we can identify which button caused the container event +} + +void create_dropdownTimer(lv_obj_t* tab, lv_coord_t width, lv_coord_t height, lv_coord_t x_ofs, lv_coord_t y_ofs, uint8_t user_data) { + lv_obj_t * dropdownTimer = lv_dropdown_create(tab); + lv_dropdown_set_options(dropdownTimer, "0 h\n" "1 h\n" "2 h\n" "3 h\n" "4 h\n" "5 h\n" "6 h\n" "7 h\n" "8 h\n" "9 h\n" "10 h\n" "11 h\n" "12 h"); + lv_obj_align(dropdownTimer, LV_ALIGN_TOP_LEFT, x_ofs, y_ofs); + lv_obj_set_size(dropdownTimer, width, height); + lv_obj_set_style_pad_top(dropdownTimer, 3, LV_PART_MAIN); + lv_obj_add_event_cb(dropdownTimer, airConditioner_dropdown_cb, LV_EVENT_VALUE_CHANGED, (void *)(intptr_t)user_data); + // This event does not help, because it is not fired when a value in the dropdown was selected. Use airConditioner_dropdown_cb() + // lv_obj_add_flag(dropdownTimer, LV_OBJ_FLAG_EVENT_BUBBLE); // Clicking a dropdown causes a event in its container + // lv_obj_set_user_data(dropdownTimer,(void *)(intptr_t)user_data); // Add user data so we can identify which button caused the container event +} + +void create_tab_content_airconditioner(lv_obj_t* tab) { + + lv_obj_t* menuLabel = lv_label_create(tab); + lv_label_set_text(menuLabel, "Air Conditioner"); + lv_obj_set_style_text_font(menuLabel, &lv_font_montserrat_12, LV_PART_MAIN); + lv_obj_align(menuLabel, LV_ALIGN_TOP_MID, 0, 0); + + uint8_t buttons_width = 60; + uint8_t buttons_height = 25; + uint8_t buttons_startx = 10; + uint8_t buttons_starty = 30; + uint8_t buttons_space = 35; + create_switch (tab, buttons_width, buttons_height, buttons_startx, buttons_starty , 0); + create_button (tab, buttons_width, buttons_height, buttons_startx, buttons_starty + 1*buttons_space, "-" , 1); + create_button (tab, buttons_width, buttons_height, buttons_startx +140, buttons_starty + 1*buttons_space, "+" , 2); + create_button (tab, buttons_width, buttons_height, buttons_startx, buttons_starty + 2*buttons_space, "mode" , 3); + create_button (tab, buttons_width, buttons_height, buttons_startx, buttons_starty + 3*buttons_space, "speed" , 4); + create_switch (tab, buttons_width, buttons_height, buttons_startx, buttons_starty + 4*buttons_space, 5); + create_dropdownTimer(tab, buttons_width, buttons_height, buttons_startx +140, buttons_starty + 4*buttons_space, 6); + create_button (tab, buttons_width, buttons_height, buttons_startx, buttons_starty + 5*buttons_space, "C/F" , 7); + + uint8_t labels_startx = 83; + uint8_t labels_starty = 35; + uint8_t labels_space = 35; + create_label(&onoffLabel, tab, labels_startx, labels_starty ); update_onoff_label(); + create_label(&tempLabel, tab, labels_startx, labels_starty + 1*labels_space); update_temp_label(); + create_label(&CFLabel, tab, labels_startx, labels_starty + 5*labels_space); update_CF_label(); + create_label(&modeLabel, tab, labels_startx, labels_starty + 2*labels_space); update_mode_label(); + create_label(&speedLabel, tab, labels_startx, labels_starty + 3*labels_space); update_speed_label(); + create_label(&timerLabel, tab, labels_startx, labels_starty + 4*labels_space); update_timer_label(); + + // Create a shared event for all button inside container + lv_obj_add_event_cb(tab, airConditioner_event_cb, LV_EVENT_CLICKED, NULL); + +} + +void notify_tab_before_delete_airconditioner(void) { + // remember to set all pointers to lvgl objects to NULL if they might be accessed from outside. + // They must check if object is NULL and must not use it if so + +} + +void gui_setKeys_airconditioner() { + key_commands_short_airconditioner = { + {KEY_STOP, SCENE_SELECTION}, + }; +} + +void register_gui_airconditioner(void){ + + register_gui( + std::string(tabName_airconditioner), + & create_tab_content_airconditioner, + & notify_tab_before_delete_airconditioner, + & gui_setKeys_airconditioner, + & key_repeatModes_airconditioner, + & key_commands_short_airconditioner, + & key_commands_long_airconditioner + ); + + register_command(&GUI_AIRCONDITIONER_ACTIVATE, makeCommandData(GUI, {std::to_string(MAIN_GUI_LIST), std::string(tabName_airconditioner)})); +} diff --git a/Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.h b/Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.h new file mode 100644 index 00000000..a6f3e912 --- /dev/null +++ b/Platformio/src/devices_pool/misc/device_airconditioner/gui_airconditioner.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +const char * const tabName_airconditioner = "Air Conditioner"; +extern uint16_t GUI_AIRCONDITIONER_ACTIVATE; +void register_gui_airconditioner(void); diff --git a/Platformio/src/main.cpp b/Platformio/src/main.cpp index d1681d9d..394a1383 100644 --- a/Platformio/src/main.cpp +++ b/Platformio/src/main.cpp @@ -25,6 +25,7 @@ //#include "devices/mediaPlayer/device_shield/device_shield.h" // misc #include "devices/misc/device_smarthome/device_smarthome.h" +//#include "devices/misc/device_airconditioner/device_airconditioner.h" // register gui and keys #include "applicationInternal/gui/guiBase.h" #include "applicationInternal/gui/guiRegistry.h" @@ -35,6 +36,7 @@ #include "devices/AVreceiver/device_yamahaAmp/gui_yamahaAmp.h" #include "devices/mediaPlayer/device_appleTV/gui_appleTV.h" #include "devices/misc/device_smarthome/gui_smarthome.h" +//#include "devices/misc/device_airconditioner/gui_airconditioner.h" #include "applicationInternal/keys.h" #include "applicationInternal/gui/guiStatusUpdate.h" // register scenes @@ -95,6 +97,7 @@ int main(int argc, char *argv[]) { //register_device_shield(); // misc register_device_smarthome(); + //register_device_airconditioner(); #if (ENABLE_KEYBOARD_MQTT == 1) register_device_keyboard_mqtt(); @@ -111,6 +114,7 @@ int main(int argc, char *argv[]) { register_gui_appleTV(); register_gui_numpad(); register_gui_smarthome(); + //register_gui_airconditioner(); register_gui_yamahaAmp(); // Only show these GUIs in the main gui list. If you don't set this explicitely, by default all registered guis are shown. #if (USE_SCENE_SPECIFIC_GUI_LIST != 0)