mirror of
https://github.com/arendst/Tasmota.git
synced 2025-04-24 23:07:17 +00:00
IR: Extended the A/C protocol with Command/iFeel/SensorTemp (#18612)
* IR: Added support for new A/C protocol fields New fields introduced in IRremoteESP8266 v2.8.5: 1. Command (one of: [Control | Config | iFeel | Timer], default: Control) - allows to set IR command type (e.g. iFeel for no-beep ambient sensor report) 2. iFeel (boolean, default: false) - configures whether iFeel (ambient sensor temp. is used by the A/C unit) 3. SensorTemp - the ambient sensor temperature reading to send Note: - Fan setting via int is now 6-value (breaking change -> 6 is the new max) * IR A/C: Model now pretty-printed to JSON Model string is the default (falls back to int if n/a) * IR A/C: Added separate JSON resp. per command type * No change for "kControlCommand" (default/legacy). * SensorReport/Timer/Config commands only contain relevant JSON nodes Additionally: Clock has time semantics (input: minutes, output: HH:MM), except for "config" mode, where state.clock is used as ConfigKey and state.sleep is used as ConfigValue. --------- Co-authored-by: Mateusz Bronk <2566147+mbronk@users.noreply.github.com>
This commit is contained in:
parent
fec3a21c8a
commit
6a7b2b0c6e
@ -123,6 +123,7 @@
|
||||
#define D_JSON_MODEL "Model"
|
||||
#define D_JSON_MOISTURE "Moisture"
|
||||
#define D_JSON_MQTT_COUNT "MqttCount"
|
||||
#define D_JSON_NA "n/a"
|
||||
#define D_JSON_NO "No"
|
||||
#define D_JSON_NOISE "Noise"
|
||||
#define D_JSON_NONE "None"
|
||||
@ -535,6 +536,7 @@
|
||||
#define D_JSON_IRHVAC_VENDOR "Vendor"
|
||||
#define D_JSON_IRHVAC_PROTOCOL "Protocol"
|
||||
#define D_JSON_IRHVAC_MODEL "Model"
|
||||
#define D_JSON_IRHVAC_COMMAND "Command"
|
||||
#define D_JSON_IRHVAC_POWER "Power"
|
||||
#define D_JSON_IRHVAC_MODE "Mode"
|
||||
#define D_JSON_IRHVAC_FANSPEED "FanSpeed"
|
||||
@ -551,6 +553,10 @@
|
||||
#define D_JSON_IRHVAC_CLEAN "Clean"
|
||||
#define D_JSON_IRHVAC_SLEEP "Sleep"
|
||||
#define D_JSON_IRHVAC_CLOCK "Clock"
|
||||
#define D_JSON_IRHVAC_IFEEL "iFeel"
|
||||
#define D_JSON_IRHVAC_CONFIG_KEY "ConfigKey"
|
||||
#define D_JSON_IRHVAC_CONFIG_VALUE "ConfigValue"
|
||||
#define D_JSON_IRHVAC_SENSOR_TEMP "SensorTemp"
|
||||
#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"
|
||||
|
@ -221,37 +221,77 @@ void IrReceiveInit(void)
|
||||
irrecv->enableIRIn(); // Start the receiver
|
||||
}
|
||||
|
||||
namespace {
|
||||
void addFloatToJson(JsonGeneratorObject& json, const char* key, float value, float noValueConstant = NAN) {
|
||||
if (!isnan(noValueConstant) && value == noValueConstant) {
|
||||
//The "no sensor value" may not be straightforward (e.g.-100.0), hence replacing with explicit n/a
|
||||
json.add(key, PSTR(D_JSON_NA));
|
||||
return;
|
||||
}
|
||||
char s[6]; // Range: -99.9 <> 999.9 should be fine for any sensible temperature value :)
|
||||
ext_snprintf_P(s, sizeof(s), PSTR("%-1_f"), &value);
|
||||
json.addStrRaw(key, s);
|
||||
}
|
||||
|
||||
void addModelToJson(JsonGeneratorObject& json, const char* key, decode_type_t protocol, const int16_t model) {
|
||||
String modelStr = irutils::modelToStr(protocol, model);
|
||||
if (modelStr != kUnknownStr) {
|
||||
json.add(key, modelStr);
|
||||
} else { // Fallback to int value
|
||||
json.add(key, model);
|
||||
}
|
||||
}
|
||||
} // namespace {
|
||||
|
||||
String sendACJsonState(const stdAc::state_t &state) {
|
||||
JsonGeneratorObject json;
|
||||
json.add(PSTR(D_JSON_IRHVAC_VENDOR), typeToString(state.protocol));
|
||||
json.add(PSTR(D_JSON_IRHVAC_MODEL), state.model);
|
||||
addModelToJson(json, PSTR(D_JSON_IRHVAC_MODEL), state.protocol, state.model);
|
||||
json.add(PSTR(D_JSON_IRHVAC_COMMAND), IRac::commandTypeToString(state.command));
|
||||
|
||||
json.add(PSTR(D_JSON_IRHVAC_MODE), IRac::opmodeToString(state.mode));
|
||||
// Home Assistant wants power to be off if mode is also off.
|
||||
if (state.mode == stdAc::opmode_t::kOff) {
|
||||
json.add(PSTR(D_JSON_IRHVAC_POWER), IRac::boolToString(false));
|
||||
} else {
|
||||
json.add(PSTR(D_JSON_IRHVAC_POWER), IRac::boolToString(state.power));
|
||||
}
|
||||
json.add(PSTR(D_JSON_IRHVAC_CELSIUS), IRac::boolToString(state.celsius));
|
||||
if (floorf(state.degrees) == state.degrees) {
|
||||
json.add(PSTR(D_JSON_IRHVAC_TEMP), (int32_t) floorf(state.degrees)); // integer
|
||||
} else {
|
||||
// TODO can do better here
|
||||
json.addStrRaw(PSTR(D_JSON_IRHVAC_TEMP), String(state.degrees, 1).c_str()); // non-integer, limit to only 1 sub-digit
|
||||
}
|
||||
switch (state.command) {
|
||||
case stdAc::ac_command_t::kSensorTempReport:
|
||||
addFloatToJson(json, PSTR(D_JSON_IRHVAC_SENSOR_TEMP), state.sensorTemperature, kNoTempValue);
|
||||
break;
|
||||
case stdAc::ac_command_t::kConfigCommand:
|
||||
// Note: for `kConfigCommand` the semantics of clock/sleep is abused IRremoteESP8266 lib-side for key/value pair
|
||||
// Ref: lib/lib_basic/IRremoteESP8266/IRremoteESP8266/src/IRac.cpp[L3062-3066]
|
||||
json.add(PSTR(D_JSON_IRHVAC_CONFIG_KEY), state.clock);
|
||||
json.add(PSTR(D_JSON_IRHVAC_CONFIG_VALUE), state.sleep);
|
||||
break;
|
||||
case stdAc::ac_command_t::kTimerCommand:
|
||||
json.add(PSTR(D_JSON_IRHVAC_POWER), IRac::boolToString(state.power));
|
||||
if(state.clock != -1) { json.add(PSTR(D_JSON_IRHVAC_CLOCK), irutils::minsToString(state.clock)); }
|
||||
json.add(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
|
||||
break;
|
||||
case stdAc::ac_command_t::kControlCommand:
|
||||
default:
|
||||
json.add(PSTR(D_JSON_IRHVAC_MODE), IRac::opmodeToString(state.mode));
|
||||
// Home Assistant wants power to be off if mode is also off.
|
||||
if (state.mode == stdAc::opmode_t::kOff) {
|
||||
json.add(PSTR(D_JSON_IRHVAC_POWER), IRac::boolToString(false));
|
||||
} else {
|
||||
json.add(PSTR(D_JSON_IRHVAC_POWER), IRac::boolToString(state.power));
|
||||
}
|
||||
json.add(PSTR(D_JSON_IRHVAC_CELSIUS), IRac::boolToString(state.celsius));
|
||||
addFloatToJson(json, PSTR(D_JSON_IRHVAC_TEMP), state.degrees);
|
||||
|
||||
json.add(PSTR(D_JSON_IRHVAC_FANSPEED), IRac::fanspeedToString(state.fanspeed));
|
||||
json.add(PSTR(D_JSON_IRHVAC_SWINGV), IRac::swingvToString(state.swingv));
|
||||
json.add(PSTR(D_JSON_IRHVAC_SWINGH), IRac::swinghToString(state.swingh));
|
||||
json.add(PSTR(D_JSON_IRHVAC_QUIET), IRac::boolToString(state.quiet));
|
||||
json.add(PSTR(D_JSON_IRHVAC_TURBO), IRac::boolToString(state.turbo));
|
||||
json.add(PSTR(D_JSON_IRHVAC_ECONO), IRac::boolToString(state.econo));
|
||||
json.add(PSTR(D_JSON_IRHVAC_LIGHT), IRac::boolToString(state.light));
|
||||
json.add(PSTR(D_JSON_IRHVAC_FILTER), IRac::boolToString(state.filter));
|
||||
json.add(PSTR(D_JSON_IRHVAC_CLEAN), IRac::boolToString(state.clean));
|
||||
json.add(PSTR(D_JSON_IRHVAC_BEEP), IRac::boolToString(state.beep));
|
||||
json.add(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
|
||||
json.add(PSTR(D_JSON_IRHVAC_FANSPEED), IRac::fanspeedToString(state.fanspeed));
|
||||
json.add(PSTR(D_JSON_IRHVAC_SWINGV), IRac::swingvToString(state.swingv));
|
||||
json.add(PSTR(D_JSON_IRHVAC_SWINGH), IRac::swinghToString(state.swingh));
|
||||
json.add(PSTR(D_JSON_IRHVAC_QUIET), IRac::boolToString(state.quiet));
|
||||
json.add(PSTR(D_JSON_IRHVAC_TURBO), IRac::boolToString(state.turbo));
|
||||
json.add(PSTR(D_JSON_IRHVAC_ECONO), IRac::boolToString(state.econo));
|
||||
json.add(PSTR(D_JSON_IRHVAC_LIGHT), IRac::boolToString(state.light));
|
||||
json.add(PSTR(D_JSON_IRHVAC_FILTER), IRac::boolToString(state.filter));
|
||||
json.add(PSTR(D_JSON_IRHVAC_CLEAN), IRac::boolToString(state.clean));
|
||||
json.add(PSTR(D_JSON_IRHVAC_BEEP), IRac::boolToString(state.beep));
|
||||
json.add(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
|
||||
if(state.clock != -1) { json.add(PSTR(D_JSON_IRHVAC_CLOCK), state.clock); }
|
||||
json.add(PSTR(D_JSON_IRHVAC_IFEEL), IRac::boolToString(state.iFeel));
|
||||
addFloatToJson(json, PSTR(D_JSON_IRHVAC_SENSOR_TEMP), state.sensorTemperature, kNoTempValue);
|
||||
break;
|
||||
}
|
||||
|
||||
String payload = json.toString(); // copy string before returning, the original is on the stack
|
||||
return payload;
|
||||
@ -393,7 +433,7 @@ StateModes strToStateMode(class JsonParserToken token, StateModes 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::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium, stdAc::fanspeed_t::kMediumHigh,
|
||||
stdAc::fanspeed_t::kHigh, stdAc::fanspeed_t::kMax };
|
||||
|
||||
uint32_t IrRemoteCmndIrHvacJson(void)
|
||||
@ -408,6 +448,7 @@ uint32_t IrRemoteCmndIrHvacJson(void)
|
||||
// from: https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/CommonAcControl/CommonAcControl.ino
|
||||
state.protocol = decode_type_t::UNKNOWN;
|
||||
state.model = 1; // Some A/C's have different models. Let's try using just 1.
|
||||
state.command = stdAc::ac_command_t::kControlCommand;
|
||||
state.mode = stdAc::opmode_t::kAuto; // Run in cool mode initially.
|
||||
state.power = false; // Initially start with the unit off.
|
||||
state.celsius = true; // Use Celsius for units of temp. False = Fahrenheit
|
||||
@ -424,6 +465,8 @@ uint32_t IrRemoteCmndIrHvacJson(void)
|
||||
state.sleep = -1; // Don't set any sleep time or modes.
|
||||
state.clean = false; // Turn off any Cleaning options if we can.
|
||||
state.clock = -1; // Don't set any current time if we can avoid it.
|
||||
state.iFeel = false;
|
||||
state.sensorTemperature = kNoTempValue; // Don't set any sensor (ambient) temperature if not provided
|
||||
|
||||
JsonParserToken val;
|
||||
if (val = root[PSTR(D_JSON_IRHVAC_VENDOR)]) { state.protocol = strToDecodeType(val.getStr()); }
|
||||
@ -431,18 +474,20 @@ uint32_t IrRemoteCmndIrHvacJson(void)
|
||||
if (decode_type_t::UNKNOWN == state.protocol) { return IE_UNSUPPORTED_HVAC; }
|
||||
if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; }
|
||||
|
||||
// for fan speed, we also support 1-5 values
|
||||
if (val = root[PSTR(D_JSON_IRHVAC_MODEL)]) { state.model = IRac::strToModel(val.getStr()); }
|
||||
if (val = root[PSTR(D_JSON_IRHVAC_COMMAND)]) { state.command = IRac::strToCommandType(val.getStr()); }
|
||||
|
||||
// for fan speed, we also support 1-6 values
|
||||
JsonParserToken tok_fan_speed = root[PSTR(D_JSON_IRHVAC_FANSPEED)];
|
||||
if (tok_fan_speed) {
|
||||
uint32_t fan_speed = tok_fan_speed.getUInt();
|
||||
if ((fan_speed >= 1) && (fan_speed <= 5)) {
|
||||
if ((fan_speed >= 1) && (fan_speed <= 6)) {
|
||||
state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]);
|
||||
} else {
|
||||
state.fanspeed = IRac::strToFanspeed(tok_fan_speed.getStr());
|
||||
}
|
||||
}
|
||||
|
||||
if (val = root[PSTR(D_JSON_IRHVAC_MODEL)]) { state.model = IRac::strToModel(val.getStr()); }
|
||||
if (val = root[PSTR(D_JSON_IRHVAC_MODE)]) { state.mode = IRac::strToOpmode(val.getStr()); }
|
||||
if (val = root[PSTR(D_JSON_IRHVAC_SWINGV)]) { state.swingv = IRac::strToSwingV(val.getStr()); }
|
||||
if (val = root[PSTR(D_JSON_IRHVAC_SWINGH)]) { state.swingh = IRac::strToSwingH(val.getStr()); }
|
||||
@ -465,9 +510,18 @@ uint32_t IrRemoteCmndIrHvacJson(void)
|
||||
state.quiet = strToBool(root[PSTR(D_JSON_IRHVAC_QUIET)], state.quiet);
|
||||
state.clean = strToBool(root[PSTR(D_JSON_IRHVAC_CLEAN)], state.clean);
|
||||
|
||||
// optional timer and clock
|
||||
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'
|
||||
if (state.command == stdAc::ac_command_t::kConfigCommand) {
|
||||
// Note: for `kConfigCommand` the semantics of clock/sleep is abused IRremoteESP8266 lib-side for key/value pair
|
||||
state.clock = root.getInt(PSTR(D_JSON_IRHVAC_CONFIG_KEY), state.clock);
|
||||
state.sleep = root.getInt(PSTR(D_JSON_IRHVAC_CONFIG_VALUE), state.sleep);
|
||||
} else {
|
||||
// optional timer and clock (Note: different json field names w/ time semantics)
|
||||
state.clock = root.getInt(PSTR(D_JSON_IRHVAC_CLOCK), state.clock);
|
||||
state.sleep = root.getInt(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
|
||||
}
|
||||
|
||||
state.iFeel = strToBool(root[PSTR(D_JSON_IRHVAC_IFEEL)], state.iFeel);
|
||||
state.sensorTemperature = root.getFloat(PSTR(D_JSON_IRHVAC_SENSOR_TEMP), state.sensorTemperature);
|
||||
|
||||
if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->pause(); }
|
||||
if (stateMode == StateModes::SEND_ONLY || stateMode == StateModes::SEND_STORE) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user