Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for stateful ACs using IRremoteESP8266 #9472

Merged
merged 12 commits into from
Oct 7, 2020
4 changes: 4 additions & 0 deletions tasmota/i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,10 @@
#define D_JSON_IRHVAC_CLEAN "Clean"
#define D_JSON_IRHVAC_SLEEP "Sleep"
#define D_JSON_IRHVAC_CLOCK "Clock"
#define D_JSON_IRHVAC_STATE_MODE "StateMode"
#define D_JSON_IRHVAC_STATE_MODE_SEND_ONLY "SendOnly"
#define D_JSON_IRHVAC_STATE_MODE_STORE_ONLY "StoreOnly"
#define D_JSON_IRHVAC_STATE_MODE_SEND_STORE "SendStore"
#define D_JSON_IRRECEIVED "IrReceived"

// Commands xdrv_06_snfbridge.ino
Expand Down
47 changes: 40 additions & 7 deletions tasmota/xdrv_05_irremote_full.ino
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ protected:

IRsend *irsend = nullptr;
bool irsend_active = false;
// some ACs send toggle messages rather than state. we need to help IRremoteESP8266 keep track of the state
// have a flag that is a variable, can be later used to convert this functionality to an option (as in SetOptionXX)
bool irhvac_stateful = true;
stdAc::state_t irac_prev_state; // this implementations only keeps one state so if you use a single tasmota-ir device to command more than one AC it might not work

// different modes on how to handle state when sending HVAC commands. needed for ACs with a differential/toggle protocol.
enum class StateModes { SEND_ONLY, // just send the IR signal, this is the default. expect the state to update when the IR receiver gets the command that IR transmitter sent.
STORE_ONLY, // just update the state to what is provided, this is when one needs to sync actual and stored states.
SEND_STORE }; // send IR signal but also update stored state. this is for use cases when there is just one transmitter and there is no receiver in the device.
StateModes strToStateMode(class JsonParserToken token, StateModes def); // declate to prevent errors related to ino files

void IrSendInit(void)
{
Expand Down Expand Up @@ -262,11 +272,12 @@ String sendIRJsonState(const struct decode_results &results) {
json += ",\"" D_JSON_IR_REPEAT "\":";
json += results.repeat;

stdAc::state_t ac_result;
if (IRAcUtils::decodeToState(&results, &ac_result, nullptr)) {
stdAc::state_t new_state;
if (IRAcUtils::decodeToState(&results, &new_state, irhvac_stateful && irac_prev_state.protocol == results.decode_type ? &irac_prev_state : nullptr)) {
// we have a decoded state
json += ",\"" D_CMND_IRHVAC "\":";
json += sendACJsonState(ac_result);
json += sendACJsonState(new_state);
irac_prev_state = new_state; // store for next time
}

return json;
Expand Down Expand Up @@ -361,14 +372,27 @@ bool strToBool(class JsonParserToken token, bool def) {
}
}

StateModes strToStateMode(class JsonParserToken token, StateModes def) {
if (token.isStr()) {
const char * str = token.getStr();
if (!strcasecmp_P(str, PSTR(D_JSON_IRHVAC_STATE_MODE_SEND_ONLY)))
return StateModes::SEND_ONLY;
else if (!strcasecmp_P(str, PSTR(D_JSON_IRHVAC_STATE_MODE_STORE_ONLY)))
return StateModes::STORE_ONLY;
else if (!strcasecmp_P(str, PSTR(D_JSON_IRHVAC_STATE_MODE_SEND_STORE)))
return StateModes::SEND_STORE;
}
return def;
}

// used to convert values 0-5 to fanspeed_t
const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto,
stdAc::fanspeed_t::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium,
stdAc::fanspeed_t::kHigh, stdAc::fanspeed_t::kMax };

uint32_t IrRemoteCmndIrHvacJson(void)
{
stdAc::state_t state, prev;
stdAc::state_t state;

//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data);
JsonParser parser(XdrvMailbox.data);
Expand Down Expand Up @@ -420,6 +444,10 @@ uint32_t IrRemoteCmndIrHvacJson(void)
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("model %d, mode %d, fanspeed %d, swingv %d, swingh %d"),
// state.model, state.mode, state.fanspeed, state.swingv, state.swingh);

// if and how we should handle the state for IRremote
StateModes stateMode = StateModes::SEND_ONLY; // default
if (irhvac_stateful && (val = root[PSTR(D_JSON_IRHVAC_STATE_MODE)])) { stateMode = strToStateMode(val, stateMode); }

// decode booleans
state.power = strToBool(root[PSTR(D_JSON_IRHVAC_POWER)], state.power);
state.celsius = strToBool(root[PSTR(D_JSON_IRHVAC_CELSIUS)], state.celsius);
Expand All @@ -435,9 +463,14 @@ uint32_t IrRemoteCmndIrHvacJson(void)
state.sleep = root.getInt(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
//if (json[D_JSON_IRHVAC_CLOCK]) { state.clock = json[D_JSON_IRHVAC_CLOCK]; } // not sure it's useful to support 'clock'

IRac ac(Pin(GPIO_IRSEND));
bool success = ac.sendAc(state, &prev);
if (!success) { return IE_SYNTAX_IRHVAC; }
if (stateMode == StateModes::SEND_ONLY || stateMode == StateModes::SEND_STORE) {
IRac ac(Pin(GPIO_IRSEND));
bool success = ac.sendAc(state, irhvac_stateful && irac_prev_state.protocol == state.protocol ? &irac_prev_state : nullptr);
if (!success) { return IE_SYNTAX_IRHVAC; }
}
if (stateMode == StateModes::STORE_ONLY || stateMode == StateModes::SEND_STORE) { // store state in memory
irac_prev_state = state;
}

Response_P(PSTR("{\"" D_CMND_IRHVAC "\":%s}"), sendACJsonState(state).c_str());
return IE_RESPONSE_PROVIDED;
Expand Down