diff --git a/component.yaml b/component.yaml new file mode 100644 index 0000000..5b5ab02 --- /dev/null +++ b/component.yaml @@ -0,0 +1,89 @@ +ota: + password: "e82fe9bada398051d779151a591a1e2e" + # Needed to get round interrupt issue + on_begin: + then: + - lambda: balboagl->pause(); + +external_components: + - source: + type: git + url: https://github.com/netmindz/balboa_GL_ML_spa_control.git + ref: ESPHome + refresh: 0s + + +BalboaGL: + # Pin values for T-RSC3 + hardware_uart: UART1 + rx_pin: 3 + tx_pin: 10 + enable_pin: 5 + panel_select_pin: 6 + +climate: + - platform: BalboaGL + name: Temp + update_interval: 220ms + +# # Text sensors with general information. +text_sensor: + # Expose ESPHome version as sensor. + - platform: version + name: ESPHome Version + # Expose WiFi information as sensors. + - platform: wifi_info + ip_address: + name: IP + ssid: + name: SSID + bssid: + name: BSSID + + - platform: BalboaGL + state: + name: State + raw: + name: Raw Data + # lcd: + # name: LCD Display + +# Sensors with general information. +sensor: + # Uptime sensor. + - platform: uptime + name: Uptime + + - platform: BalboaGL + commandQueue: + name: Command Queue + + # WiFi Signal sensor. + - platform: wifi_signal + name: WiFi Signal + update_interval: 60s + +switch: + - platform: BalboaGL + name: Light + + +select: + - platform: BalboaGL + pump1: + name: Pump1 + options: + "HIGH" + pump2: + name: Pump2 + options: + "HIGH" + +button: + - platform: BalboaGL + up: + name: "UP" + down: + name: "Down" + mode: + name: "Mode" diff --git a/components/BalboaGL/ESPBalboaGL.cpp b/components/BalboaGL/ESPBalboaGL.cpp new file mode 100755 index 0000000..7063638 --- /dev/null +++ b/components/BalboaGL/ESPBalboaGL.cpp @@ -0,0 +1,177 @@ +#include +#include "ESPBalboaGL.h" +using namespace esphome; + +void telnetSend(String msg) { + ESP_LOGI(TAG, msg.c_str()); +} + +void log(const char *format, ...) { + char loc_buf[64]; + char * temp = loc_buf; + va_list arg; + va_list copy; + va_start(arg, format); + va_copy(copy, arg); + int len = vsnprintf(temp, sizeof(loc_buf), format, copy); + va_end(copy); + if(len < 0) { + va_end(arg); + }; + if(len >= sizeof(loc_buf)){ + temp = (char*) malloc(len+1); + if(temp == NULL) { + va_end(arg); + } + len = vsnprintf(temp, len+1, format, arg); + } + va_end(arg); + std::string str = reinterpret_cast(temp); + ESP_LOGI(TAG, str.c_str()); + if(temp != loc_buf){ + free(temp); + } + +} +/** + * Create a new BalboaGL object + * + * Args: + * hw_serial: pointer to an Arduino HardwareSerial instance + * poll_interval: polling interval in milliseconds + */ +BalboaGL::BalboaGL( + HardwareSerial* hw_serial +) : + hw_serial_{hw_serial} +{ + +} + +void BalboaGL::check_logger_conflict_() { +#ifdef USE_LOGGER + if (this->get_hw_serial_() == logger::global_logger->get_hw_serial()) { + ESP_LOGW(TAG, " You're using the same serial port for logging" + " and the BalboaGL component. Please disable" + " logging over the serial port by setting" + " logger:baud_rate to 0."); + } +#endif +} + +void BalboaGL::loop() { + // ESP_LOGV(TAG, "Loop called."); + int sanity = 0; + do { + this->spa->readSerial(); + // if(status.rawData != lastRaw) { + // ESP_LOGD(TAG, "Raw: %s", status.rawData.c_str()); + // lastRaw = status.rawData; + // // this->publish_state(); + // } + sanity++; + } + while((status.commandQueue > 0) && (sanity < 10)); + if(status.commandQueue > 0) { + this->high_freq_.start(); // no wait on main loop + } + else { + this->high_freq_.stop(); + } + +// if(status.commandQueue > 0) ESP_LOGV(TAG, "q:%u s:%u", status.commandQueue, sanity); +} + +void BalboaGL::setup() { + // This will be called by App.setup() + // this->banner(); + ESP_LOGCONFIG(TAG, "Setting up UART..."); + if (!this->get_hw_serial_()) { + ESP_LOGCONFIG( + TAG, + "No HardwareSerial was provided. " + "Software serial ports are unsupported by this component." + ); + this->mark_failed(); + return; + } + this->check_logger_conflict_(); + + ESP_LOGCONFIG(TAG, "Initialize new balboaGL object."); + + ESP_LOGI(TAG, "Serial begin rx,tx = %u,%u", this->rx_pin, this->tx_pin); + hw_serial_->begin(115200, SERIAL_8N1, rx_pin, tx_pin); + // hw_serial_->setHwFlowCtrlMode(UART_HW_FLOWCTRL_RTS); + hw_serial_->setPins(this->rx_pin, this->tx_pin, -1, this->rts_pin); + // hw_serial_->setMode(UART_MODE_RS485_HALF_DUPLEX); + this->spa = new balboaGL(hw_serial_, rts_pin, panel_select_pin); + this->spa->attachPanelInterrupt(); + if(delay_time > -1) this->spa->set_delay_time(delay_time); + +// ESP_LOGCONFIG( +// TAG, +// "hw_serial(%p) is &Serial(%p)? %s", +// this->get_hw_serial_(), +// &Serial, +// YESNO(this->get_hw_serial_() == &Serial) +// ); + +// ESP_LOGCONFIG(TAG, "Calling hp->connect(%p)", this->get_hw_serial_()); + +// if (hp->connect(this->get_hw_serial_(), this->baud_, -1, -1)) { +// hp->sync(); +// } +// else { +// ESP_LOGCONFIG( +// TAG, +// "Connection to HeatPump failed." +// " Marking BalboaGL component as failed." +// ); +// this->mark_failed(); +// } + + ESP_LOGCONFIG(TAG, "End of seutp"); + this->dump_config(); +} + +void BalboaGL::pause() { + ESP_LOGI(TAG, "pause"); + this->spa->detachPanelInterrupt(); +} + +void BalboaGL::dump_config() { + // this->banner(); + ESP_LOGI(TAG, " rx,tx = %u,%u", this->rx_pin, this->tx_pin); + ESP_LOGI(TAG, " rts_pin = %u", this->rts_pin); + ESP_LOGI(TAG, " panel_select_pin = %u", this->panel_select_pin); +// ESP_LOGI(TAG, " Supports HEAT: %s", YESNO(true)); +// ESP_LOGI(TAG, " Supports COOL: %s", YESNO(true)); +// ESP_LOGI(TAG, " Supports AWAY mode: %s", YESNO(false)); +// ESP_LOGI(TAG, " Saved heat: %.1f", heat_setpoint.value_or(-1)); +// ESP_LOGI(TAG, " Saved cool: %.1f", cool_setpoint.value_or(-1)); +// ESP_LOGI(TAG, " Saved auto: %.1f", auto_setpoint.value_or(-1)); +} + +// void BalboaGL::dump_state() { +// LOG_CLIMATE("", "BalboaGL Climate", this); +// ESP_LOGI(TAG, "HELLO"); +// } + +void BalboaGL::set_rx_pin(int pin) { + this->rx_pin = pin; +} + +void BalboaGL::set_tx_pin(int pin) { + this->tx_pin = pin; +} +void BalboaGL::set_rts_pin(int pin) { + this->rts_pin = pin; +} + +void BalboaGL::set_panel_select_pin(int pin) { + this->panel_select_pin = pin; +} + +void BalboaGL::set_delay_time(int delay) { + this->delay_time; +} diff --git a/components/BalboaGL/ESPBalboaGL.h b/components/BalboaGL/ESPBalboaGL.h new file mode 100755 index 0000000..0722b7e --- /dev/null +++ b/components/BalboaGL/ESPBalboaGL.h @@ -0,0 +1,96 @@ +#ifndef ESPMHP_H +#define ESPMHP_H + +#include "esphome.h" +#include "esphome/core/preferences.h" +// #include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/core/log.h" +#include "esp_log.h" + +#include "ESPBalboaGL.h" + +static const char* TAG = "BalboaGL"; // Logging tag + +// // Perform measurements or read nameplate values on your tub to define the power [kW] +// // for each device in order to calculate tub power usage +// const float POWER_HEATER = 2.8; +// const float POWER_PUMP_CIRCULATION = 0.3; +// const float POWER_PUMP1_LOW = 0.31; +// const float POWER_PUMP1_HIGH = 1.3; +// const float POWER_PUMP2_LOW = 0.3; +// const float POWER_PUMP2_HIGH = 0.6; + +// // Tweak for your tub - would be nice to auto-learn in the future to allow for outside temp etc +// const int MINUTES_PER_DEGC = 45; + +const uint32_t POLL_INTERVAL_DEFAULT = 10000; + +#include "balboaGL.h" + +using namespace esphome; + +class BalboaGL : public Component { + public: + BalboaGL( + HardwareSerial* hw_serial + ); + + // print the current configuration + void dump_config() override; + + // Set up the component, initializing the balboaGL object. + void setup() override; + + // This is called every poll_interval. + void loop() override; + + // Debugging function to print the object's state. + // void dump_state(); + + void set_rx_pin(int pin); + + void set_tx_pin(int pin); + + void set_rts_pin(int pin); + + void set_panel_select_pin(int pin); + + void set_delay_time(int delay); + + float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } + + void pause(); + + balboaGL* get_spa() { + return spa; + } + + protected: + + //Accessor method for the HardwareSerial pointer + HardwareSerial* get_hw_serial_() { + return this->hw_serial_; + } + + //Print a warning message if we're using the sole hardware UART on an + //ESP8266 or UART0 on ESP32 + void check_logger_conflict_(); + + int rx_pin = -1; + int tx_pin = -1; + int rts_pin = -1; + int panel_select_pin = -1; + + HighFrequencyLoopRequester high_freq_; + + int delay_time = -1; + + private: + // Retrieve the HardwareSerial pointer from friend and subclasses. + HardwareSerial *hw_serial_; + + balboaGL* spa; + String lastRaw = "0"; +}; +#endif \ No newline at end of file diff --git a/components/BalboaGL/__init__.py b/components/BalboaGL/__init__.py new file mode 100644 index 0000000..b23bdac --- /dev/null +++ b/components/BalboaGL/__init__.py @@ -0,0 +1,107 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.logger import HARDWARE_UART_TO_SERIAL +from esphome.const import ( + CONF_ID, + CONF_HARDWARE_UART, + CONF_UPDATE_INTERVAL, + CONF_RX_PIN, + CONF_TX_PIN, + PLATFORM_ESP32, +) +from esphome.core import CORE, coroutine + +AUTO_LOAD = ["climate","switch","select","sensor","text_sensor"] + +CODEOWNERS = ["@netmindz"] + +CONF_SUPPORTS = "supports" + +CONF_ENABLE_PIN = "enable_pin" +CONF_PANEL_SELECT_PIN = "panel_select_pin" + +CONF_DELAY_TIME = "delay_time" + +balboagl_ns = cg.esphome_ns.namespace("balboagl") +BalboaGL = cg.global_ns.class_( + "BalboaGL", cg.Component +) + + +def valid_uart(uart): + if CORE.is_esp32: + uarts = [ "UART1", "UART2"] + else: + raise NotImplementedError + + return cv.one_of(*uarts, upper=True)(uart) + + +CONF_BALBOA_ID = "balboa_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BalboaGL), + cv.Optional(CONF_HARDWARE_UART, default="UART1"): valid_uart, + # If polling interval is greater than 9 seconds, the HeatPump library + # reconnects, but doesn't then follow up with our data request. + # cv.Optional(CONF_UPDATE_INTERVAL, default="100ms"): cv.All( + # cv.update_interval, cv.Range(max=cv.TimePeriod(milliseconds=9000)) + # ), + cv.Required(CONF_RX_PIN): cv.int_range(), + cv.Required(CONF_TX_PIN): cv.int_range(), + cv.Required(CONF_PANEL_SELECT_PIN): cv.int_range(), + cv.Optional(CONF_ENABLE_PIN): cv.int_range(), + cv.Optional(CONF_DELAY_TIME): cv.int_range(), + # Optionally override the supported ClimateTraits. + cv.Optional(CONF_SUPPORTS, default={}): cv.Schema( + { + } + ), + }) +).extend(cv.COMPONENT_SCHEMA) + + +@coroutine +def to_code(config): + serial = cg.global_ns.Serial1 + # HARDWARE_UART_TO_SERIAL[PLATFORM_ESP32][config[CONF_HARDWARE_UART]] + var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f"&{serial}")) + + cg.add_define("tubUART 1") # TODO make dynamic + + if CONF_RX_PIN in config: + cg.add(var.set_rx_pin(config[CONF_RX_PIN])) + + if CONF_TX_PIN in config: + cg.add(var.set_tx_pin(config[CONF_TX_PIN])) + + if CONF_ENABLE_PIN in config: + cg.add(var.set_rts_pin(config[CONF_ENABLE_PIN])) + + if CONF_PANEL_SELECT_PIN in config: + cg.add(var.set_panel_select_pin(config[CONF_PANEL_SELECT_PIN])) + + if CONF_DELAY_TIME in config: + cg.add(var.set_delay_time(config[CONF_DELAY_TIME])) + + + yield cg.register_component(var, config) + + cg.add_library( + name="ArduinoQueue", + repository="https://github.com/EinarArnason/ArduinoQueue.git", + version="1.2.5", + ) + + cg.add_library( + name="CircularBuffer", # TODO: should really pull in a dep of balboaGL + repository="https://github.com/rlogiacco/CircularBuffer.git", + version="1.3.3", + ) + cg.add_library( + name="balboaGL", + repository="https://github.com/netmindz/balboaGL.git", + version="af714231217e5f35be7c4bb77a3e417a2bcbff49", + ) diff --git a/components/BalboaGL/button/BalboaGLButton.h b/components/BalboaGL/button/BalboaGLButton.h new file mode 100644 index 0000000..1df7877 --- /dev/null +++ b/components/BalboaGL/button/BalboaGLButton.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "balboaGL.h" + +using namespace esphome; + +class UpButton : public PollingComponent, public button::Button { + public: + void set_spa(balboaGL* spa) { + this->spa = spa; + } + void press_action() { + this->spa->buttonPressUp(); + } + void update() {} + private: + balboaGL* spa; + +}; + +class DownButton : public PollingComponent, public button::Button { + public: + void set_spa(balboaGL* spa) { + this->spa = spa; + } + void press_action() { + this->spa->buttonPressDown(); + } + void update() {} + private: + balboaGL* spa; + +}; + +class ModeButton : public PollingComponent, public button::Button { + public: + void set_spa(balboaGL* spa) { + this->spa = spa; + } + void press_action() { + this->spa->buttonPressMode(); + } + void update() {} + private: + balboaGL* spa; + +}; diff --git a/components/BalboaGL/button/__init__.py b/components/BalboaGL/button/__init__.py new file mode 100644 index 0000000..437ca35 --- /dev/null +++ b/components/BalboaGL/button/__init__.py @@ -0,0 +1,48 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ( + ICON_LIGHTBULB, +) +from .. import balboagl_ns, CONF_BALBOA_ID, BalboaGL + +AUTO_LOAD = ["button"] + +DEPENDENCIES = ["BalboaGL"] + +UpButton = cg.esphome_ns.class_("UpButton", button.Button, cg.PollingComponent) +DownButton = cg.esphome_ns.class_("DownButton", button.Button, cg.PollingComponent) +ModeButton = cg.esphome_ns.class_("ModeButton", button.Button, cg.PollingComponent) + +CONF_UP = "up" +CONF_DOWN = "down" +CONF_MODE = "mode" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_BALBOA_ID): cv.use_id(BalboaGL), + + cv.Optional(CONF_UP): button.button_schema(UpButton, + + ), + cv.Optional(CONF_DOWN): button.button_schema(DownButton, + + ), + cv.Optional(CONF_MODE): button.button_schema(ModeButton, + + ), + } +) + +async def setup_conf(config, key): + if key in config: + conf = config[key] + var = await button.new_button(conf) + await cg.register_component(var, conf) + paren = await cg.get_variable(config[CONF_BALBOA_ID]) + cg.add(cg.RawStatement(f"{var}->set_spa({paren}->get_spa());")) + +async def to_code(config): + await setup_conf(config, CONF_UP) + await setup_conf(config, CONF_DOWN) + await setup_conf(config, CONF_MODE) \ No newline at end of file diff --git a/components/BalboaGL/climate/BalboaGLClimate.cpp b/components/BalboaGL/climate/BalboaGLClimate.cpp new file mode 100644 index 0000000..ec55200 --- /dev/null +++ b/components/BalboaGL/climate/BalboaGLClimate.cpp @@ -0,0 +1,421 @@ +#include +#include "BalboaGLClimate.h" +#include "../ESPBalboaGL.h" +using namespace esphome; + + +void BalboaGLClimate::update() { + // This will be called every "update_interval" milliseconds. + // ESP_LOGV(TAG, "BalboaGLClimate Update called."); + + static String lastRaw = "0"; + if((status.rawData != lastRaw) || (status.temp != this->current_temperature)) { + ESP_LOGD(TAG, "Raw: %s", status.rawData.c_str()); + ESP_LOGD(TAG, "Current Temp: %f", status.temp); + ESP_LOGD(TAG, "Target Temp: %f", status.targetTemp); + this->current_temperature = status.temp; + this->target_temperature = status.targetTemp; + switch(status.mode) { + case MODE_IDX_STD: + this->custom_preset = std::string("STD"); + break; + case MODE_IDX_ECO: + this->custom_preset = std::string("ECO"); + break; + case MODE_IDX_SLP: + this->custom_preset = std::string("Sleep"); + break; + default: + this->custom_preset = std::string("UNKNOWN"); + break; + } + + if(status.mode == MODE_IDX_STD) { + if(status.heater) { + this->mode = climate::CLIMATE_MODE_HEAT; + } + else { + this->mode = climate::CLIMATE_MODE_AUTO; + } + } + else { + this->mode = climate::CLIMATE_MODE_OFF; + } + + lastRaw = status.rawData; + this->publish_state(); + } + +} + +/** + * Get our supported traits. + * + * Note: + * Many of the following traits are only available in the 1.5.0 dev train of + * ESPHome, particularly the Dry operation mode, and several of the fan modes. + * + * Returns: + * This class' supported climate::ClimateTraits. + */ +climate::ClimateTraits BalboaGLClimate::traits() { + return traits_; +} + +/** + * Modify our supported traits. + * + * Returns: + * A reference to this class' supported climate::ClimateTraits. + */ +climate::ClimateTraits& BalboaGLClimate::config_traits() { + return traits_; +} + +/** + * Implement control of a BalboaGL. + * + * Maps HomeAssistant/ESPHome modes to Mitsubishi modes. + */ +void BalboaGLClimate::control(const climate::ClimateCall &call) { + ESP_LOGV(TAG, "Control called."); + + bool updated = false; +// bool has_mode = call.get_mode().has_value(); + bool has_temp = call.get_target_temperature().has_value(); +// if (has_mode){ +// this->mode = *call.get_mode(); +// } +// switch (this->mode) { +// case climate::CLIMATE_MODE_COOL: +// hp->setModeSetting("COOL"); +// hp->setPowerSetting("ON"); + +// if (has_mode){ +// if (cool_setpoint.has_value() && !has_temp) { +// hp->setTemperature(cool_setpoint.value()); +// this->target_temperature = cool_setpoint.value(); +// } +// this->action = climate::CLIMATE_ACTION_IDLE; +// updated = true; +// } +// break; +// case climate::CLIMATE_MODE_HEAT: +// hp->setModeSetting("HEAT"); +// hp->setPowerSetting("ON"); +// if (has_mode){ +// if (heat_setpoint.has_value() && !has_temp) { +// hp->setTemperature(heat_setpoint.value()); +// this->target_temperature = heat_setpoint.value(); +// } +// this->action = climate::CLIMATE_ACTION_IDLE; +// updated = true; +// } +// break; +// case climate::CLIMATE_MODE_DRY: +// hp->setModeSetting("DRY"); +// hp->setPowerSetting("ON"); +// if (has_mode){ +// this->action = climate::CLIMATE_ACTION_DRYING; +// updated = true; +// } +// break; +// case climate::CLIMATE_MODE_HEAT_COOL: +// hp->setModeSetting("AUTO"); +// hp->setPowerSetting("ON"); +// if (has_mode){ +// if (auto_setpoint.has_value() && !has_temp) { +// hp->setTemperature(auto_setpoint.value()); +// this->target_temperature = auto_setpoint.value(); +// } +// this->action = climate::CLIMATE_ACTION_IDLE; +// } +// updated = true; +// break; +// case climate::CLIMATE_MODE_FAN_ONLY: +// hp->setModeSetting("FAN"); +// hp->setPowerSetting("ON"); +// if (has_mode){ +// this->action = climate::CLIMATE_ACTION_FAN; +// updated = true; +// } +// break; +// case climate::CLIMATE_MODE_OFF: +// default: +// if (has_mode){ +// hp->setPowerSetting("OFF"); +// this->action = climate::CLIMATE_ACTION_OFF; +// updated = true; +// } +// break; +// } + + if (has_temp){ + ESP_LOGI( + "control", "Sending target temp: %.1f", + *call.get_target_temperature() + ); + this->spa->setTemp(*call.get_target_temperature()); + updated = true; + } + + +// ESP_LOGD(TAG, "control - Was SPA updated? %s", YESNO(updated)); + + // send the update back to esphome: + this->publish_state(); +} + +// void BalboaGL::hpSettingsChanged() { +// heatpumpSettings currentSettings = hp->getSettings(); + +// if (currentSettings.power == NULL) { +// /* +// * We should always get a valid pointer here once the HeatPump +// * component fully initializes. If HeatPump hasn't read the settings +// * from the unit yet (hp->connect() doesn't do this, sadly), we'll need +// * to punt on the update. Likely not an issue when run in callback +// * mode, but that isn't working right yet. +// */ +// ESP_LOGW(TAG, "Waiting for HeatPump to read the settings the first time."); +// esphome::delay(10); +// return; +// } + +// /* +// * ************ HANDLE POWER AND MODE CHANGES *********** +// * https://github.com/geoffdavis/HeatPump/blob/stream/src/HeatPump.h#L125 +// * const char* POWER_MAP[2] = {"OFF", "ON"}; +// * const char* MODE_MAP[5] = {"HEAT", "DRY", "COOL", "FAN", "AUTO"}; +// */ +// if (strcmp(currentSettings.power, "ON") == 0) { +// if (strcmp(currentSettings.mode, "HEAT") == 0) { +// this->mode = climate::CLIMATE_MODE_HEAT; +// if (heat_setpoint != currentSettings.temperature) { +// heat_setpoint = currentSettings.temperature; +// save(currentSettings.temperature, heat_storage); +// } +// this->action = climate::CLIMATE_ACTION_IDLE; +// } else if (strcmp(currentSettings.mode, "DRY") == 0) { +// this->mode = climate::CLIMATE_MODE_DRY; +// this->action = climate::CLIMATE_ACTION_DRYING; +// } else if (strcmp(currentSettings.mode, "COOL") == 0) { +// this->mode = climate::CLIMATE_MODE_COOL; +// if (cool_setpoint != currentSettings.temperature) { +// cool_setpoint = currentSettings.temperature; +// save(currentSettings.temperature, cool_storage); +// } +// this->action = climate::CLIMATE_ACTION_IDLE; +// } else if (strcmp(currentSettings.mode, "FAN") == 0) { +// this->mode = climate::CLIMATE_MODE_FAN_ONLY; +// this->action = climate::CLIMATE_ACTION_FAN; +// } else if (strcmp(currentSettings.mode, "AUTO") == 0) { +// this->mode = climate::CLIMATE_MODE_HEAT_COOL; +// if (auto_setpoint != currentSettings.temperature) { +// auto_setpoint = currentSettings.temperature; +// save(currentSettings.temperature, auto_storage); +// } +// this->action = climate::CLIMATE_ACTION_IDLE; +// } else { +// ESP_LOGW( +// TAG, +// "Unknown climate mode value %s received from HeatPump", +// currentSettings.mode +// ); +// } +// } else { +// this->mode = climate::CLIMATE_MODE_OFF; +// this->action = climate::CLIMATE_ACTION_OFF; +// } + +// ESP_LOGI(TAG, "Climate mode is: %i", this->mode); + +// /* +// * ******* HANDLE FAN CHANGES ******** +// * +// * const char* FAN_MAP[6] = {"AUTO", "QUIET", "1", "2", "3", "4"}; +// */ +// if (strcmp(currentSettings.fan, "QUIET") == 0) { +// this->fan_mode = climate::CLIMATE_FAN_DIFFUSE; +// } else if (strcmp(currentSettings.fan, "1") == 0) { +// this->fan_mode = climate::CLIMATE_FAN_LOW; +// } else if (strcmp(currentSettings.fan, "2") == 0) { +// this->fan_mode = climate::CLIMATE_FAN_MEDIUM; +// } else if (strcmp(currentSettings.fan, "3") == 0) { +// this->fan_mode = climate::CLIMATE_FAN_MIDDLE; +// } else if (strcmp(currentSettings.fan, "4") == 0) { +// this->fan_mode = climate::CLIMATE_FAN_HIGH; +// } else { //case "AUTO" or default: +// this->fan_mode = climate::CLIMATE_FAN_AUTO; +// } +// ESP_LOGI(TAG, "Fan mode is: %i", this->fan_mode); + +// /* ******** HANDLE MITSUBISHI VANE CHANGES ******** +// * const char* VANE_MAP[7] = {"AUTO", "1", "2", "3", "4", "5", "SWING"}; +// */ +// if (strcmp(currentSettings.vane, "SWING") == 0) { +// this->swing_mode = climate::CLIMATE_SWING_VERTICAL; +// } +// else { +// this->swing_mode = climate::CLIMATE_SWING_OFF; +// } +// ESP_LOGI(TAG, "Swing mode is: %i", this->swing_mode); + + + +// /* +// * ******** HANDLE TARGET TEMPERATURE CHANGES ******** +// */ +// this->target_temperature = currentSettings.temperature; +// ESP_LOGI(TAG, "Target temp is: %f", this->target_temperature); + +// /* +// * ******** Publish state back to ESPHome. ******** +// */ +// this->publish_state(); +// } + +// /** +// * Report changes in the current temperature sensed by the HeatPump. +// */ +// void BalboaGL::hpStatusChanged(heatpumpStatus currentStatus) { +// this->current_temperature = currentStatus.roomTemperature; +// switch (this->mode) { +// case climate::CLIMATE_MODE_HEAT: +// if (currentStatus.operating) { +// this->action = climate::CLIMATE_ACTION_HEATING; +// } +// else { +// this->action = climate::CLIMATE_ACTION_IDLE; +// } +// break; +// case climate::CLIMATE_MODE_COOL: +// if (currentStatus.operating) { +// this->action = climate::CLIMATE_ACTION_COOLING; +// } +// else { +// this->action = climate::CLIMATE_ACTION_IDLE; +// } +// break; +// case climate::CLIMATE_MODE_HEAT_COOL: +// this->action = climate::CLIMATE_ACTION_IDLE; +// if (currentStatus.operating) { +// if (this->current_temperature > this->target_temperature) { +// this->action = climate::CLIMATE_ACTION_COOLING; +// } else if (this->current_temperature < this->target_temperature) { +// this->action = climate::CLIMATE_ACTION_HEATING; +// } +// } +// break; +// case climate::CLIMATE_MODE_DRY: +// if (currentStatus.operating) { +// this->action = climate::CLIMATE_ACTION_DRYING; +// } +// else { +// this->action = climate::CLIMATE_ACTION_IDLE; +// } +// break; +// case climate::CLIMATE_MODE_FAN_ONLY: +// this->action = climate::CLIMATE_ACTION_FAN; +// break; +// default: +// this->action = climate::CLIMATE_ACTION_OFF; +// } + +// this->publish_state(); +// } + +// void BalboaGL::set_remote_temperature(float temp) { +// ESP_LOGD(TAG, "Setting remote temp: %.1f", temp); +// this->hp->setRemoteTemperature(temp); +// } + +void BalboaGLClimate::setup() { + // This will be called by App.setup() + // this->banner(); + + ESP_LOGCONFIG(TAG, "Initialize new balboaGLCliamte object."); + + this->current_temperature = NAN; + this->target_temperature = NAN; + this->fan_mode = climate::CLIMATE_FAN_OFF; + this->swing_mode = climate::CLIMATE_SWING_OFF; + this->traits_.set_supports_action(false); + this->traits_.set_supports_current_temperature(true); + this->traits_.add_supported_custom_preset(std::string("STD")); + this->traits_.add_supported_custom_preset(std::string("ECO")); + this->traits_.add_supported_custom_preset(std::string("Sleep")); + this->traits_.add_supported_custom_preset(std::string("UNKNOWN")); + + this->visual_min_temperature_override_ = 26; + this->visual_max_temperature_override_ = 40; + this->visual_target_temperature_step_override_ = 0.5; + +// ESP_LOGCONFIG( +// TAG, +// "hw_serial(%p) is &Serial(%p)? %s", +// this->get_hw_serial_(), +// &Serial, +// YESNO(this->get_hw_serial_() == &Serial) +// ); + +// ESP_LOGCONFIG(TAG, "Calling hp->connect(%p)", this->get_hw_serial_()); + +// if (hp->connect(this->get_hw_serial_(), this->baud_, -1, -1)) { +// hp->sync(); +// } +// else { +// ESP_LOGCONFIG( +// TAG, +// "Connection to HeatPump failed." +// " Marking BalboaGL component as failed." +// ); +// this->mark_failed(); +// } + +// // create various setpoint persistence: +// cool_storage = global_preferences->make_preference(this->get_object_id_hash() + 1); +// heat_storage = global_preferences->make_preference(this->get_object_id_hash() + 2); +// auto_storage = global_preferences->make_preference(this->get_object_id_hash() + 3); + +// // load values from storage: +// cool_setpoint = load(cool_storage); +// heat_setpoint = load(heat_storage); +// auto_setpoint = load(auto_storage); + + ESP_LOGCONFIG(TAG, "End of seutp"); + this->dump_config(); +} + +// /** +// * The ESP only has a few bytes of rtc storage, so instead +// * of storing floats directly, we'll store the number of +// * TEMPERATURE_STEPs from MIN_TEMPERATURE. +// **/ +// void BalboaGL::save(float value, ESPPreferenceObject& storage) { +// uint8_t steps = (value - ESPMHP_MIN_TEMPERATURE) / ESPMHP_TEMPERATURE_STEP; +// storage.save(&steps); +// } + +// optional BalboaGL::load(ESPPreferenceObject& storage) { +// uint8_t steps = 0; +// if (!storage.load(&steps)) { +// return {}; +// } +// return ESPMHP_MIN_TEMPERATURE + (steps * ESPMHP_TEMPERATURE_STEP); +// } + +void BalboaGLClimate::dump_config() { + // this->banner(); +// ESP_LOGI(TAG, " Supports HEAT: %s", YESNO(true)); +// ESP_LOGI(TAG, " Supports COOL: %s", YESNO(true)); +// ESP_LOGI(TAG, " Supports AWAY mode: %s", YESNO(false)); +// ESP_LOGI(TAG, " Saved heat: %.1f", heat_setpoint.value_or(-1)); +// ESP_LOGI(TAG, " Saved cool: %.1f", cool_setpoint.value_or(-1)); +// ESP_LOGI(TAG, " Saved auto: %.1f", auto_setpoint.value_or(-1)); +} + +// void BalboaGL::dump_state() { +// LOG_CLIMATE("", "BalboaGL Climate", this); +// ESP_LOGI(TAG, "HELLO"); +// } diff --git a/components/BalboaGL/climate/BalboaGLClimate.h b/components/BalboaGL/climate/BalboaGLClimate.h new file mode 100644 index 0000000..c3c5791 --- /dev/null +++ b/components/BalboaGL/climate/BalboaGLClimate.h @@ -0,0 +1,53 @@ +#ifndef GLCLIMATE_H +#define GLCLIMATE_H + +#include "esphome.h" +#include "esphome/core/preferences.h" +#include "esphome/components/climate/climate.h" +#include "esphome/core/log.h" +#include "esp_log.h" +#include "../ESPBalboaGL.h" + +#include "balboaGL.h" + +using namespace esphome; + +class BalboaGLClimate : public PollingComponent, public climate::Climate { + public: + void set_spa(balboaGL* spa) { + this->spa = spa; + } + // print the current configuration + void dump_config() override; + + // Set up the component, initializing the balboaGL object. + void setup() override; + + // This is called every poll_interval. + void update() override; + + // Configure the climate object with traits that we support. + climate::ClimateTraits traits() override; + + // Get a mutable reference to the traits that we support. + climate::ClimateTraits& config_traits(); + + // Debugging function to print the object's state. + // void dump_state(); + + // Handle a request from the user to change settings. + void control(const climate::ClimateCall &call) override; + + // // Use the temperature from an external sensor. Use + // // set_remote_temp(0) to switch back to the internal sensor. + // void set_remote_temperature(float); + + protected: + // The ClimateTraits supported by this HeatPump. + climate::ClimateTraits traits_; + + private: + balboaGL* spa; +}; + +#endif \ No newline at end of file diff --git a/components/BalboaGL/climate/__init__.py b/components/BalboaGL/climate/__init__.py new file mode 100644 index 0000000..0e1e499 --- /dev/null +++ b/components/BalboaGL/climate/__init__.py @@ -0,0 +1,78 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate +from esphome.const import ( + CONF_ID, + CONF_UPDATE_INTERVAL, + CONF_MODE, + CONF_FAN_MODE, + CONF_SWING_MODE, +) +from esphome.core import CORE +from .. import balboagl_ns, CONF_BALBOA_ID, BalboaGL + +AUTO_LOAD = ["climate"] + +DEPENDENCIES = ["BalboaGL"] + + +CONF_SUPPORTS = "supports" +DEFAULT_CLIMATE_MODES = ["HEAT","AUTO"] +DEFAULT_FAN_MODES = ["OFF"] +DEFAULT_SWING_MODES = ["OFF"] + +BalboaGLClimate = cg.esphome_ns.class_( + "BalboaGLClimate", climate.Climate, cg.PollingComponent +) + + +CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BalboaGLClimate), + cv.GenerateID(CONF_BALBOA_ID): cv.use_id(BalboaGL), + # If polling interval is greater than 9 seconds, the HeatPump library + # reconnects, but doesn't then follow up with our data request. + cv.Optional(CONF_UPDATE_INTERVAL, default="100ms"): cv.All( + cv.update_interval, cv.Range(max=cv.TimePeriod(milliseconds=9000)) + ), + # Optionally override the supported ClimateTraits. + cv.Optional(CONF_SUPPORTS, default={}): cv.Schema( + { + cv.Optional(CONF_MODE, default=DEFAULT_CLIMATE_MODES): + cv.ensure_list(climate.validate_climate_mode), + cv.Optional(CONF_FAN_MODE, default=DEFAULT_FAN_MODES): + cv.ensure_list(climate.validate_climate_fan_mode), + cv.Optional(CONF_SWING_MODE, default=DEFAULT_SWING_MODES): + cv.ensure_list(climate.validate_climate_swing_mode), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + supports = config[CONF_SUPPORTS] + traits = var.config_traits() + + for mode in supports[CONF_MODE]: + if mode == "OFF": + continue + cg.add(traits.add_supported_mode(climate.CLIMATE_MODES[mode])) + + for mode in supports[CONF_FAN_MODE]: + cg.add(traits.add_supported_fan_mode(climate.CLIMATE_FAN_MODES[mode])) + + for mode in supports[CONF_SWING_MODE]: + cg.add(traits.add_supported_swing_mode( + climate.CLIMATE_SWING_MODES[mode] + )) + + + + await cg.register_component(var, config) + await climate.register_climate(var, config) + + paren = await cg.get_variable(config[CONF_BALBOA_ID]) + cg.add(cg.RawStatement(f"{var}->set_spa({paren}->get_spa());")) \ No newline at end of file diff --git a/components/BalboaGL/select/BalboaGLSelect.h b/components/BalboaGL/select/BalboaGLSelect.h new file mode 100644 index 0000000..32dc62b --- /dev/null +++ b/components/BalboaGL/select/BalboaGLSelect.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/select/select.h" + +#include "balboaGL.h" + +namespace esphome { + namespace balboa_select { + + class BalboaGLPump1Select : public Component, public select::Select { + public: + void set_spa(balboaGL* spa) { + this->spa = spa; + } + void control(const std::string &value) override { + + } + private: + balboaGL* spa; + + + }; + + class BalboaGLPump2Select : public Component, public select::Select { + public: + void set_spa(balboaGL* spa) { + this->spa = spa; + } + void control(const std::string &value) override { + + } + private: + balboaGL* spa; + + }; + + } +} \ No newline at end of file diff --git a/components/BalboaGL/select/__init__.py b/components/BalboaGL/select/__init__.py new file mode 100644 index 0000000..c8e5c36 --- /dev/null +++ b/components/BalboaGL/select/__init__.py @@ -0,0 +1,47 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.const import ( + CONF_ID, + CONF_OPTIONS, +) +from .. import balboagl_ns, CONF_BALBOA_ID, BalboaGL + + + +CONF_PUMP1 = "pump1" +CONF_PUMP2 = "pump2" + +select_ns = cg.esphome_ns.namespace('balboa_select') +Pump1Select = select_ns.class_('BalboaGLPump1Select', select.Select, cg.Component) +Pump2Select = select_ns.class_('BalboaGLPump2Select', select.Select, cg.Component) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(CONF_BALBOA_ID): cv.use_id(BalboaGL), + cv.Optional(CONF_PUMP1): select.SELECT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(Pump1Select), + cv.Required(CONF_OPTIONS): cv.All( + cv.ensure_list(cv.string_strict), cv.Length(min=1) + ), + }), + cv.Optional(CONF_PUMP2): select.SELECT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(Pump2Select), + cv.Required(CONF_OPTIONS): cv.All( + cv.ensure_list(cv.string_strict), cv.Length(min=1) + ), + }), + +}) + +async def setup_conf(config, key): + if key in config: + conf = config[key] + options_map = conf[CONF_OPTIONS] + var = await select.new_select(conf, options=["off","high"]) + await cg.register_component(var, conf) + paren = await cg.get_variable(config[CONF_BALBOA_ID]) + cg.add(cg.RawStatement(f"{var}->set_spa({paren}->get_spa());")) + +async def to_code(config): + await setup_conf(config, CONF_PUMP1) + await setup_conf(config, CONF_PUMP2) diff --git a/components/BalboaGL/sensor/BalboaGLSesnor.h b/components/BalboaGL/sensor/BalboaGLSesnor.h new file mode 100644 index 0000000..4e3e04c --- /dev/null +++ b/components/BalboaGL/sensor/BalboaGLSesnor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome.h" +#include "esphome/components/sensor/sensor.h" + +// #include "balboaGL.h" + +using namespace esphome; + +class BalboaGLCommandQueueSensor : public PollingComponent, public sensor::Sensor { + public: + void update() override { + if(status.commandQueue != this->last_value) { + this->last_value = status.commandQueue; + publish_state(status.commandQueue); + } + } + private: + u_int8_t last_value; +}; + diff --git a/components/BalboaGL/sensor/__init__.py b/components/BalboaGL/sensor/__init__.py new file mode 100644 index 0000000..d607c67 --- /dev/null +++ b/components/BalboaGL/sensor/__init__.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import CONF_ID +from esphome.core import coroutine +from esphome.const import ( + ENTITY_CATEGORY_DIAGNOSTIC, +) + +from .. import balboagl_ns, CONF_BALBOA_ID, BalboaGL + +AUTO_LOAD = ["sensor"] + +DEPENDENCIES = ["BalboaGL"] + +CONF_QUEUE = "commandQueue" + +CommandQueueInfo = cg.esphome_ns.class_('BalboaGLCommandQueueSensor', sensor.Sensor, cg.PollingComponent) + +CONFIG_SCHEMA = cv.Schema( + { + cv.Optional(CONF_QUEUE): sensor.sensor_schema(CommandQueueInfo, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC + + ).extend(cv.polling_component_schema("1s")), + + # cv.Optional(CONF_RAW): text_sensor.TEXT_SENSOR_SCHEMA.extend({ + # cv.GenerateID(): cv.declare_id(RawInfo), + # }), + # cv.Optional(CONF_LCD): text_sensor.TEXT_SENSOR_SCHEMA.extend({ + # cv.GenerateID(): cv.declare_id(LCDInfo), + # }), + } +) + +async def setup_conf(config, key): + if key in config: + conf = config[key] + var = await sensor.new_sensor(conf) + await cg.register_component(var, conf) + +async def to_code(config): + await setup_conf(config, CONF_QUEUE) diff --git a/components/BalboaGL/switch/BalboaGLSwitch.h b/components/BalboaGL/switch/BalboaGLSwitch.h new file mode 100644 index 0000000..dd42f95 --- /dev/null +++ b/components/BalboaGL/switch/BalboaGLSwitch.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +#include "balboaGL.h" + +static const char* SWITCH_TAG = "BalboaGLSwitch"; // Logging tag + +using namespace esphome; + + class BalboaGLLightSwitch : public Component, public switch_::Switch { + public: + void set_spa(balboaGL* spa) { + this->spa = spa; + } + void write_state(bool state) override { + if(state) { + ESP_LOGI(SWITCH_TAG, "LightSwitch write_state(true)"); + } + else { + ESP_LOGI(SWITCH_TAG, "LightSwitch write_state(false)"); + } + // This will be called every time the user requests a state change. + + spa->setLight(state); + + // Acknowledge new state by publishing it + // publish_state(state); + ESP_LOGD(SWITCH_TAG, "LightSwitch write_state complete"); + } + void loop() override { + if(status.light != this->last_state) { + this->last_state = status.light; + publish_state(this->last_state); + } + } + + private: + balboaGL* spa; + bool last_state; + }; \ No newline at end of file diff --git a/components/BalboaGL/switch/__init__.py b/components/BalboaGL/switch/__init__.py new file mode 100644 index 0000000..47f0ec1 --- /dev/null +++ b/components/BalboaGL/switch/__init__.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_ID + +from .. import balboagl_ns, CONF_BALBOA_ID, BalboaGL + +DEPENDENCIES = ["BalboaGL"] + +LightSwitch = cg.esphome_ns.class_('BalboaGLLightSwitch', switch.Switch, cg.Component) + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LightSwitch), + cv.GenerateID(CONF_BALBOA_ID): cv.use_id(BalboaGL), + } +).extend(cv.COMPONENT_SCHEMA) + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + + paren = await cg.get_variable(config[CONF_BALBOA_ID]) + cg.add(cg.RawStatement(f"{var}->set_spa({paren}->get_spa());")) diff --git a/components/BalboaGL/text_sensor/BalboaGLTextSesnor.h b/components/BalboaGL/text_sensor/BalboaGLTextSesnor.h new file mode 100644 index 0000000..2e7bd20 --- /dev/null +++ b/components/BalboaGL/text_sensor/BalboaGLTextSesnor.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome.h" +#include "esphome/components/text_sensor/text_sensor.h" + +using namespace esphome; + +class BalboaGLStateSensor : public PollingComponent, public text_sensor::TextSensor { + public: + void setup() override { + this->last_value = "Awaiting data"; + } + void update() override { + if(status.state != this->last_value) { + this->last_value = status.state; + publish_state(last_value.c_str()); + } + } + // std::string unique_id() override { return "state"; } + private: + String last_value; + +}; + +class BalboaGLRawSensor : public PollingComponent, public text_sensor::TextSensor { + public: + void setup() override { + this->last_value = "no data"; + } + void update() override { + if(status.rawData != this->last_value) { + this->last_value = status.rawData; + publish_state(last_value.c_str()); + } + } + // std::string unique_id() override { return "raw"; } + private: + String last_value; + +}; + +class BalboaGLLCDSensor : public PollingComponent, public text_sensor::TextSensor { + public: + void setup() override { + this->last_value = "unknown"; + } + void update() override { + if(String(status.lcd) != this->last_value) { + this->last_value = String(status.lcd); + publish_state(last_value.c_str()); + } + } + // std::string unique_id() override { return "lcd"; } + private: + String last_value; + +}; + diff --git a/components/BalboaGL/text_sensor/__init__.py b/components/BalboaGL/text_sensor/__init__.py new file mode 100644 index 0000000..7533a08 --- /dev/null +++ b/components/BalboaGL/text_sensor/__init__.py @@ -0,0 +1,47 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_DIAGNOSTIC +) + +from .. import balboagl_ns, CONF_BALBOA_ID, BalboaGL + +AUTO_LOAD = ["text_sensor"] + +DEPENDENCIES = ["BalboaGL"] + +CONF_STATE = "state" +CONF_RAW = "raw" +CONF_LCD = "lcd" + + +StateInfo = cg.esphome_ns.class_('BalboaGLStateSensor', text_sensor.TextSensor, cg.PollingComponent) +RawInfo = cg.esphome_ns.class_('BalboaGLRawSensor', text_sensor.TextSensor, cg.PollingComponent) +LCDInfo = cg.esphome_ns.class_('BalboaGLLCDSensor', text_sensor.TextSensor, cg.PollingComponent) + +CONFIG_SCHEMA = cv.Schema( + { + cv.Optional(CONF_STATE): text_sensor.text_sensor_schema(StateInfo, + ).extend(cv.polling_component_schema("1s")), + + cv.Optional(CONF_RAW): text_sensor.text_sensor_schema(RawInfo, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ).extend(cv.polling_component_schema("1s")), + + cv.Optional(CONF_LCD): text_sensor.text_sensor_schema(LCDInfo, + ).extend(cv.polling_component_schema("1s")), + } +) + +async def setup_conf(config, key): + if key in config: + conf = config[key] + var = await text_sensor.new_text_sensor(conf) + await cg.register_component(var, conf) + +async def to_code(config): + await setup_conf(config, CONF_STATE) + await setup_conf(config, CONF_RAW) + await setup_conf(config, CONF_LCD) diff --git a/sensor/src/sensor.cpp b/sensor/src/sensor.cpp index bc0608f..3aa0b52 100644 --- a/sensor/src/sensor.cpp +++ b/sensor/src/sensor.cpp @@ -57,10 +57,10 @@ Adafruit_NeoPixel pixels(1, 4, NEO_GRB + NEO_KHZ800); #elif ESP32 #define tub Serial1 -#define RX_PIN 19 -#define TX_PIN 23 +#define RX_PIN 17 +#define TX_PIN 16 #define RTS_PIN_DEF 22 // RS485 direction control, RequestToSend TX or RX, required for MAX485 board. -#define PIN_5_PIN_DEF 18 +#define PIN_5_PIN_DEF 21 #else SoftwareSerial tub; #define RX_PIN D6