| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- // Copyright 2017 Jonny Graham, David Conran
- #include "ir_Fujitsu.h"
- #include <algorithm>
- #ifndef ARDUINO
- #include <string>
- #endif
- #include "IRsend.h"
- #include "IRutils.h"
- // Fujitsu A/C support added by Jonny Graham & David Conran
- // Equipment it seems compatible with:
- // * Fujitsu ASYG30LFCA with remote AR-RAH2E
- // * Fujitsu AST9RSGCW with remote AR-DB1
- // * <Add models (A/C & remotes) you've gotten it working with here>
- // Ref:
- // These values are based on averages of measurements
- const uint16_t kFujitsuAcHdrMark = 3324;
- const uint16_t kFujitsuAcHdrSpace = 1574;
- const uint16_t kFujitsuAcBitMark = 448;
- const uint16_t kFujitsuAcOneSpace = 1182;
- const uint16_t kFujitsuAcZeroSpace = 390;
- const uint16_t kFujitsuAcMinGap = 8100;
- #if SEND_FUJITSU_AC
- // Send a Fujitsu A/C message.
- //
- // Args:
- // data: An array of bytes containing the IR command.
- // nbytes: Nr. of bytes of data in the array. Typically one of:
- // kFujitsuAcStateLength
- // kFujitsuAcStateLength - 1
- // kFujitsuAcStateLengthShort
- // kFujitsuAcStateLengthShort - 1
- // repeat: Nr. of times the message is to be repeated.
- // (Default = kFujitsuAcMinRepeat).
- //
- // Status: BETA / Appears to be working.
- //
- void IRsend::sendFujitsuAC(unsigned char data[], uint16_t nbytes,
- uint16_t repeat) {
- sendGeneric(kFujitsuAcHdrMark, kFujitsuAcHdrSpace, kFujitsuAcBitMark,
- kFujitsuAcOneSpace, kFujitsuAcBitMark, kFujitsuAcZeroSpace,
- kFujitsuAcBitMark, kFujitsuAcMinGap, data, nbytes, 38, false,
- repeat, 50);
- }
- #endif // SEND_FUJITSU_AC
- // Code to emulate Fujitsu A/C IR remote control unit.
- // Initialise the object.
- IRFujitsuAC::IRFujitsuAC(uint16_t pin, fujitsu_ac_remote_model_t model)
- : _irsend(pin) {
- setModel(model);
- stateReset();
- }
- void IRFujitsuAC::setModel(fujitsu_ac_remote_model_t model) {
- _model = model;
- switch (model) {
- case ARDB1:
- _state_length = kFujitsuAcStateLength - 1;
- _state_length_short = kFujitsuAcStateLengthShort - 1;
- break;
- default:
- _state_length = kFujitsuAcStateLength;
- _state_length_short = kFujitsuAcStateLengthShort;
- }
- }
- // Reset the state of the remote to a known good state/sequence.
- void IRFujitsuAC::stateReset() {
- _temp = 24;
- _fanSpeed = kFujitsuAcFanHigh;
- _mode = kFujitsuAcModeCool;
- _swingMode = kFujitsuAcSwingBoth;
- _cmd = kFujitsuAcCmdTurnOn;
- buildState();
- }
- // Configure the pin for output.
- void IRFujitsuAC::begin() { _irsend.begin(); }
- #if SEND_FUJITSU_AC
- // Send the current desired state to the IR LED.
- void IRFujitsuAC::send() {
- getRaw();
- _irsend.sendFujitsuAC(remote_state, getStateLength());
- }
- #endif // SEND_FUJITSU_AC
- void IRFujitsuAC::buildState() {
- remote_state[0] = 0x14;
- remote_state[1] = 0x63;
- remote_state[2] = 0x00;
- remote_state[3] = 0x10;
- remote_state[4] = 0x10;
- bool fullCmd = false;
- switch (_cmd) {
- case kFujitsuAcCmdTurnOff:
- remote_state[5] = 0x02;
- break;
- case kFujitsuAcCmdStepHoriz:
- remote_state[5] = 0x79;
- break;
- case kFujitsuAcCmdStepVert:
- remote_state[5] = 0x6C;
- break;
- default:
- switch (_model) {
- case ARRAH2E:
- remote_state[5] = 0xFE;
- break;
- case ARDB1:
- remote_state[5] = 0xFC;
- break;
- }
- fullCmd = true;
- break;
- }
- if (fullCmd) { // long codes
- uint8_t tempByte = _temp - kFujitsuAcMinTemp;
- // Nr. of bytes in the message after this byte.
- remote_state[6] = _state_length - 7;
- remote_state[7] = 0x30;
- remote_state[8] = (_cmd == kFujitsuAcCmdTurnOn) | (tempByte << 4);
- remote_state[9] = _mode | 0 << 4; // timer off
- remote_state[10] = _fanSpeed | _swingMode << 4;
- remote_state[11] = 0; // timerOff values
- remote_state[12] = 0; // timerOff/On values
- remote_state[13] = 0; // timerOn values
- if (_model == ARRAH2E)
- remote_state[14] = 0x20;
- else
- remote_state[14] = 0x00;
- uint8_t checksum = 0;
- uint8_t checksum_complement = 0;
- if (_model == ARRAH2E) {
- checksum = sumBytes(remote_state + _state_length_short,
- _state_length - _state_length_short - 1);
- } else if (_model == ARDB1) {
- checksum = sumBytes(remote_state, _state_length - 1);
- checksum_complement = 0x9B;
- }
- // and negate the checksum and store it in the last byte.
- remote_state[_state_length - 1] = checksum_complement - checksum;
- } else { // short codes
- if (_model == ARRAH2E)
- // The last byte is the inverse of penultimate byte
- remote_state[_state_length_short - 1] =
- ~remote_state[_state_length_short - 2];
- // Zero the rest of the state.
- for (uint8_t i = _state_length_short; i < kFujitsuAcStateLength; i++)
- remote_state[i] = 0;
- }
- }
- uint8_t IRFujitsuAC::getStateLength() {
- buildState(); // Force an update of the internal state.
- if ((_model == ARRAH2E && remote_state[5] != 0xFE) ||
- (_model == ARDB1 && remote_state[5] != 0xFC))
- return _state_length_short;
- else
- return _state_length;
- }
- // Return a pointer to the internal state date of the remote.
- uint8_t* IRFujitsuAC::getRaw() {
- buildState();
- return remote_state;
- }
- void IRFujitsuAC::buildFromState(const uint16_t length) {
- switch (length) {
- case kFujitsuAcStateLength - 1:
- case kFujitsuAcStateLengthShort - 1:
- setModel(ARDB1);
- break;
- default:
- setModel(ARRAH2E);
- }
- switch (remote_state[6]) {
- case 8:
- setModel(ARDB1);
- break;
- case 9:
- setModel(ARRAH2E);
- break;
- }
- setTemp((remote_state[8] >> 4) + kFujitsuAcMinTemp);
- if (remote_state[8] & 0x1)
- setCmd(kFujitsuAcCmdTurnOn);
- else
- setCmd(kFujitsuAcCmdStayOn);
- setMode(remote_state[9] & 0b111);
- setFanSpeed(remote_state[10] & 0b111);
- setSwing(remote_state[10] >> 4);
- switch (remote_state[5]) {
- case kFujitsuAcCmdTurnOff:
- case kFujitsuAcCmdStepHoriz:
- case kFujitsuAcCmdStepVert:
- setCmd(remote_state[5]);
- break;
- }
- }
- bool IRFujitsuAC::setRaw(const uint8_t newState[], const uint16_t length) {
- if (length > kFujitsuAcStateLength) return false;
- for (uint16_t i = 0; i < kFujitsuAcStateLength; i++) {
- if (i < length)
- remote_state[i] = newState[i];
- else
- remote_state[i] = 0;
- }
- buildFromState(length);
- return true;
- }
- // Set the requested power state of the A/C to off.
- void IRFujitsuAC::off() { _cmd = kFujitsuAcCmdTurnOff; }
- void IRFujitsuAC::stepHoriz() {
- switch (_model) {
- case ARDB1:
- break; // This remote doesn't have a horizontal option.
- default:
- _cmd = kFujitsuAcCmdStepHoriz;
- }
- }
- void IRFujitsuAC::stepVert() { _cmd = kFujitsuAcCmdStepVert; }
- // Set the requested command of the A/C.
- void IRFujitsuAC::setCmd(uint8_t cmd) {
- switch (cmd) {
- case kFujitsuAcCmdTurnOff:
- case kFujitsuAcCmdTurnOn:
- case kFujitsuAcCmdStayOn:
- case kFujitsuAcCmdStepVert:
- _cmd = cmd;
- break;
- case kFujitsuAcCmdStepHoriz:
- if (_model != ARDB1) // AR-DB1 remote doesn't have step horizontal.
- _cmd = cmd;
- // FALLTHRU
- default:
- _cmd = kFujitsuAcCmdStayOn;
- break;
- }
- }
- uint8_t IRFujitsuAC::getCmd() { return _cmd; }
- bool IRFujitsuAC::getPower() { return _cmd != kFujitsuAcCmdTurnOff; }
- // Set the temp. in deg C
- void IRFujitsuAC::setTemp(uint8_t temp) {
- temp = std::max((uint8_t)kFujitsuAcMinTemp, temp);
- temp = std::min((uint8_t)kFujitsuAcMaxTemp, temp);
- _temp = temp;
- }
- uint8_t IRFujitsuAC::getTemp() { return _temp; }
- // Set the speed of the fan
- void IRFujitsuAC::setFanSpeed(uint8_t fanSpeed) {
- if (fanSpeed > kFujitsuAcFanQuiet)
- fanSpeed = kFujitsuAcFanHigh; // Set the fan to maximum if out of range.
- _fanSpeed = fanSpeed;
- }
- uint8_t IRFujitsuAC::getFanSpeed() { return _fanSpeed; }
- // Set the requested climate operation mode of the a/c unit.
- void IRFujitsuAC::setMode(uint8_t mode) {
- if (mode > kFujitsuAcModeHeat)
- mode = kFujitsuAcModeHeat; // Set the mode to maximum if out of range.
- _mode = mode;
- }
- uint8_t IRFujitsuAC::getMode() { return _mode; }
- // Set the requested swing operation mode of the a/c unit.
- void IRFujitsuAC::setSwing(uint8_t swingMode) {
- switch (_model) {
- case ARDB1:
- // Set the mode to max if out of range
- if (swingMode > kFujitsuAcSwingVert) swingMode = kFujitsuAcSwingVert;
- break;
- case ARRAH2E:
- default:
- // Set the mode to max if out of range
- if (swingMode > kFujitsuAcSwingBoth) swingMode = kFujitsuAcSwingBoth;
- }
- _swingMode = swingMode;
- }
- uint8_t IRFujitsuAC::getSwing() { return _swingMode; }
- bool IRFujitsuAC::validChecksum(uint8_t state[], uint16_t length) {
- uint8_t sum = 0;
- uint8_t sum_complement = 0;
- uint8_t checksum = state[length - 1];
- switch (length) {
- case kFujitsuAcStateLengthShort: // ARRAH2E
- return state[length - 1] == (uint8_t)~state[length - 2];
- case kFujitsuAcStateLength - 1: // ARDB1
- sum = sumBytes(state, length - 1);
- sum_complement = 0x9B;
- break;
- case kFujitsuAcStateLength: // ARRAH2E
- sum = sumBytes(state + kFujitsuAcStateLengthShort,
- length - 1 - kFujitsuAcStateLengthShort);
- break;
- default: // Includes ARDB1 short.
- return true; // Assume the checksum is valid for other lengths.
- }
- return checksum == (uint8_t)(sum_complement - sum); // Does it match?
- }
- // Convert the internal state into a human readable string.
- #ifdef ARDUINO
- String IRFujitsuAC::toString() {
- String result = "";
- #else
- std::string IRFujitsuAC::toString() {
- std::string result = "";
- #endif // ARDUINO
- result += "Power: ";
- if (getPower())
- result += "On";
- else
- result += "Off";
- result += ", Mode: " + uint64ToString(getMode());
- switch (getMode()) {
- case kFujitsuAcModeAuto:
- result += " (AUTO)";
- break;
- case kFujitsuAcModeCool:
- result += " (COOL)";
- break;
- case kFujitsuAcModeHeat:
- result += " (HEAT)";
- break;
- case kFujitsuAcModeDry:
- result += " (DRY)";
- break;
- case kFujitsuAcModeFan:
- result += " (FAN)";
- break;
- default:
- result += " (UNKNOWN)";
- }
- result += ", Temp: " + uint64ToString(getTemp()) + "C";
- result += ", Fan: " + uint64ToString(getFanSpeed());
- switch (getFanSpeed()) {
- case kFujitsuAcFanAuto:
- result += " (AUTO)";
- break;
- case kFujitsuAcFanHigh:
- result += " (HIGH)";
- break;
- case kFujitsuAcFanMed:
- result += " (MED)";
- break;
- case kFujitsuAcFanLow:
- result += " (LOW)";
- break;
- case kFujitsuAcFanQuiet:
- result += " (QUIET)";
- break;
- }
- result += ", Swing: ";
- switch (getSwing()) {
- case kFujitsuAcSwingOff:
- result += "Off";
- break;
- case kFujitsuAcSwingVert:
- result += "Vert";
- break;
- case kFujitsuAcSwingHoriz:
- result += "Horiz";
- break;
- case kFujitsuAcSwingBoth:
- result += "Vert + Horiz";
- break;
- default:
- result += "UNKNOWN";
- }
- result += ", Command: ";
- switch (getCmd()) {
- case kFujitsuAcCmdStepHoriz:
- result += "Step vane horizontally";
- break;
- case kFujitsuAcCmdStepVert:
- result += "Step vane vertically";
- break;
- default:
- result += "N/A";
- }
- return result;
- }
- #if DECODE_FUJITSU_AC
- // Decode a Fujitsu AC IR message if possible.
- // Places successful decode information in the results pointer.
- // Args:
- // results: Ptr to the data to decode and where to store the decode result.
- // nbits: The number of data bits to expect. Typically kFujitsuAcBits.
- // strict: Flag to indicate if we strictly adhere to the specification.
- // Returns:
- // boolean: True if it can decode it, false if it can't.
- //
- // Status: ALPHA / Untested.
- //
- // Ref:
- //
- bool IRrecv::decodeFujitsuAC(decode_results* results, uint16_t nbits,
- bool strict) {
- uint16_t offset = kStartOffset;
- uint16_t dataBitsSoFar = 0;
- // Have we got enough data to successfully decode?
- if (results->rawlen < (2 * kFujitsuAcMinBits) + kHeader + kFooter - 1)
- return false; // Can't possibly be a valid message.
- // Compliance
- if (strict) {
- switch (nbits) {
- case kFujitsuAcBits:
- case kFujitsuAcBits - 8:
- case kFujitsuAcMinBits:
- case kFujitsuAcMinBits + 8:
- break;
- default:
- return false; // Must be called with the correct nr. of bits.
- }
- }
- // Header
- if (!matchMark(results->rawbuf[offset++], kFujitsuAcHdrMark)) return false;
- if (!matchSpace(results->rawbuf[offset++], kFujitsuAcHdrSpace)) return false;
- // Data (Fixed signature)
- match_result_t data_result =
- matchData(&(results->rawbuf[offset]), kFujitsuAcMinBits - 8,
- kFujitsuAcBitMark, kFujitsuAcOneSpace, kFujitsuAcBitMark,
- kFujitsuAcZeroSpace, kTolerance, kMarkExcess, false);
- if (data_result.success == false) return false; // Fail
- if (data_result.data != 0x1010006314) return false; // Signature failed.
- dataBitsSoFar += kFujitsuAcMinBits - 8;
- offset += data_result.used;
- results->state[0] = 0x14;
- results->state[1] = 0x63;
- results->state[2] = 0x00;
- results->state[3] = 0x10;
- results->state[4] = 0x10;
- // Keep reading bytes until we either run out of message or state to fill.
- for (uint16_t i = 5;
- offset <= results->rawlen - 16 && i < kFujitsuAcStateLength;
- i++, dataBitsSoFar += 8, offset += data_result.used) {
- data_result = matchData(
- &(results->rawbuf[offset]), 8, kFujitsuAcBitMark, kFujitsuAcOneSpace,
- kFujitsuAcBitMark, kFujitsuAcZeroSpace, kTolerance, kMarkExcess, false);
- if (data_result.success == false) break; // Fail
- results->state[i] = data_result.data;
- }
- // Footer
- if (offset > results->rawlen ||
- !matchMark(results->rawbuf[offset++], kFujitsuAcBitMark))
- return false;
- // The space is optional if we are out of capture.
- if (offset < results->rawlen &&
- !matchAtLeast(results->rawbuf[offset], kFujitsuAcMinGap))
- return false;
- // Compliance
- if (strict) {
- if (dataBitsSoFar != nbits) return false;
- }
- results->decode_type = FUJITSU_AC;
- results->bits = dataBitsSoFar;
- // Compliance
- switch (dataBitsSoFar) {
- case kFujitsuAcMinBits:
- // Check if this values indicate that this should have been a long state
- // message.
- if (results->state[5] == 0xFC) return false;
- return true; // Success
- case kFujitsuAcMinBits + 8:
- // Check if this values indicate that this should have been a long state
- // message.
- if (results->state[5] == 0xFE) return false;
- // The last byte needs to be the inverse of the penultimate byte.
- if (results->state[5] != (uint8_t)~results->state[6]) return false;
- return true; // Success
- case kFujitsuAcBits - 8:
- // Long messages of this size require this byte be correct.
- if (results->state[5] != 0xFC) return false;
- break;
- case kFujitsuAcBits:
- // Long messages of this size require this byte be correct.
- if (results->state[5] != 0xFE) return false;
- break;
- default:
- return false; // Unexpected size.
- }
- if (!IRFujitsuAC::validChecksum(results->state, dataBitsSoFar / 8))
- return false;
- // Success
- return true; // All good.
- }
- #endif // DECODE_FUJITSU_AC
|