// Copyright 2017-2021 David Conran #include "IRutils.h" #ifndef UNIT_TEST #include #endif #define __STDC_LIMIT_MACROS #include #include #include #include #ifndef ARDUINO #include #endif #include "IRrecv.h" #include "IRremoteESP8266.h" #include "IRsend.h" #include "IRtext.h" // On the ESP8266 platform we need to use a set of ..._P functions // to handle the strings stored in the flash address space. #ifndef STRCASECMP #if defined(ESP8266) #define STRCASECMP(LHS, RHS) \ strcasecmp_P(LHS, reinterpret_cast(RHS)) #else // ESP8266 #define STRCASECMP strcasecmp #endif // ESP8266 #endif // STRCASECMP #ifndef STRLEN #if defined(ESP8266) #define STRLEN(PTR) strlen_P(PTR) #else // ESP8266 #define STRLEN(PTR) strlen(PTR) #endif // ESP8266 #endif // STRLEN #ifndef FPSTR #define FPSTR(X) X #endif // FPSTR /// Reverse the order of the requested least significant nr. of bits. /// @param[in] input Bit pattern/integer to reverse. /// @param[in] nbits Nr. of bits to reverse. (LSB -> MSB) /// @return The reversed bit pattern. uint64_t reverseBits(uint64_t input, uint16_t nbits) { if (nbits <= 1) return input; // Reversing <= 1 bits makes no change at all. // Cap the nr. of bits to rotate to the max nr. of bits in the input. nbits = std::min(nbits, static_cast((sizeof(input) * 8))); uint64_t output = 0; for (uint16_t i = 0; i < nbits; i++) { output <<= 1; output |= (input & 1); input >>= 1; } // Merge any remaining unreversed bits back to the top of the reversed bits. return (input << nbits) | output; } /// Convert a uint64_t (unsigned long long) to a string. /// Arduino String/toInt/Serial.print() can't handle printing 64 bit values. /// @param[in] input The value to print /// @param[in] base The output base. /// @returns A String representation of the integer. /// @note Based on Arduino's Print::printNumber() String uint64ToString(uint64_t input, uint8_t base) { String result = ""; // prevent issues if called with base <= 1 if (base < 2) base = 10; // Check we have a base that we can actually print. // i.e. [0-9A-Z] == 36 if (base > 36) base = 10; // Reserve some string space to reduce fragmentation. // 16 bytes should store a uint64 in hex text which is the likely worst case. // 64 bytes would be the worst case (base 2). result.reserve(16); do { char c = input % base; input /= base; if (c < 10) c += '0'; else c += 'A' - 10; result = c + result; } while (input); return result; } /// Convert a int64_t (signed long long) to a string. /// Arduino String/toInt/Serial.print() can't handle printing 64 bit values. /// @param[in] input The value to print /// @param[in] base The output base. /// @returns A String representation of the integer. String int64ToString(int64_t input, uint8_t base) { if (input < 0) { // Using String(kDashStr) to keep compatible with old arduino // frameworks. Not needed with 3.0.2. ///> @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1639#issuecomment-944906016 return String(kDashStr) + uint64ToString(-input, base); } return uint64ToString(input, base); } #ifdef ARDUINO /// Print a uint64_t/unsigned long long to the Serial port /// Serial.print() can't handle printing long longs. (uint64_t) /// @param[in] input The value to print /// @param[in] base The output base. void serialPrintUint64(uint64_t input, uint8_t base) { Serial.print(uint64ToString(input, base)); } #endif /// Convert a C-style string to a decode_type_t. /// @param[in] str A C-style string containing a protocol name or number. /// @return A decode_type_t enum. (decode_type_t::UNKNOWN if no match.) decode_type_t strToDecodeType(const char * const str) { auto *ptr = reinterpret_cast(kAllProtocolNamesStr); uint16_t length = STRLEN(ptr); for (uint16_t i = 0; length; i++) { if (!STRCASECMP(str, ptr)) return (decode_type_t)i; ptr += length + 1; length = STRLEN(ptr); } // Handle integer values of the type by converting to a string and back again. decode_type_t result = strToDecodeType( typeToString((decode_type_t)atoi(str)).c_str()); if (result > 0) return result; return decode_type_t::UNKNOWN; } /// Convert a protocol type (enum etc) to a human readable string. /// @param[in] protocol Nr. (enum) of the protocol. /// @param[in] isRepeat A flag indicating if it is a repeat message. /// @return A String containing the protocol name. kUnknownStr if no match. String typeToString(const decode_type_t protocol, const bool isRepeat) { String result = ""; result.reserve(30); // Size of longest protocol name + " (Repeat)" if (protocol > kLastDecodeType || protocol == decode_type_t::UNKNOWN) { result = kUnknownStr; } else { auto *ptr = reinterpret_cast(kAllProtocolNamesStr); for (uint16_t i = 0; i <= protocol && STRLEN(ptr); i++) { if (i == protocol) { result = FPSTR(ptr); break; } ptr += STRLEN(ptr) + 1; } } if (isRepeat) { result += kSpaceLBraceStr; result += kRepeatStr; result += ')'; } return result; } /// Does the given protocol use a complex state as part of the decode? /// @param[in] protocol The decode_type_t protocol we are enquiring about. /// @return True if the protocol uses a state array. False if just an integer. bool hasACState(const decode_type_t protocol) { switch (protocol) { // This is kept sorted by name case AMCOR: case ARGO: case BLUESTARHEAVY: case BOSCH144: case CARRIER_AC84: case CARRIER_AC128: case CORONA_AC: case DAIKIN: case DAIKIN128: case DAIKIN152: case DAIKIN160: case DAIKIN176: case DAIKIN2: case DAIKIN200: case DAIKIN216: case DAIKIN312: case ELECTRA_AC: case FUJITSU_AC: case GREE: case HAIER_AC: case HAIER_AC_YRW02: case HAIER_AC160: case HAIER_AC176: case HITACHI_AC: case HITACHI_AC1: case HITACHI_AC2: case HITACHI_AC3: case HITACHI_AC264: case HITACHI_AC296: case HITACHI_AC344: case HITACHI_AC424: case KELON168: case KELVINATOR: case MIRAGE: case MITSUBISHI136: case MITSUBISHI112: case MITSUBISHI_AC: case MITSUBISHI_HEAVY_88: case MITSUBISHI_HEAVY_152: case MWM: case NEOCLIMA: case PANASONIC_AC: case RHOSS: case SAMSUNG_AC: case SANYO_AC: case SANYO_AC88: case SANYO_AC152: case SHARP_AC: case TCL96AC: case TCL112AC: case TEKNOPOINT: case TOSHIBA_AC: case TROTEC: case TROTEC_3550: case VOLTAS: case WHIRLPOOL_AC: case YORK: return true; default: return false; } } /// Return the corrected length of a 'raw' format array structure /// after over-large values are converted into multiple entries. /// @param[in] results A ptr to a decode_results structure. /// @return The corrected length. uint16_t getCorrectedRawLength(const decode_results * const results) { uint16_t extended_length = results->rawlen - 1; for (uint16_t i = 0; i < results->rawlen - 1; i++) { uint32_t usecs = results->rawbuf[i] * kRawTick; // Add two extra entries for multiple larger than UINT16_MAX it is. extended_length += (usecs / (UINT16_MAX + 1)) * 2; } return extended_length; } /// Return a String containing the key values of a decode_results structure /// in a C/C++ code style format. /// @param[in] results A ptr to a decode_results structure. /// @return A String containing the code-ified result. String resultToSourceCode(const decode_results * const results) { String output = ""; const uint16_t length = getCorrectedRawLength(results); const bool hasState = hasACState(results->decode_type); // Reserve some space for the string to reduce heap fragmentation. // "uint16_t rawData[9999] = {}; // LONGEST_PROTOCOL\n" = ~55 chars. // "NNNN, " = ~7 chars on average per raw entry // Protocols with a `state`: // "uint8_t state[NN] = {};\n" = ~25 chars // "0xNN, " = 6 chars per byte. // Protocols without a `state`: // " DEADBEEFDEADBEEF\n" // "uint32_t address = 0xDEADBEEF;\n" // "uint32_t command = 0xDEADBEEF;\n" // "uint64_t data = 0xDEADBEEFDEADBEEF;" = ~116 chars max. output.reserve(55 + (length * 7) + hasState ? 25 + (results->bits / 8) * 6 : 116); // Start declaration output += F("uint16_t "); // variable type output += F("rawData["); // array name output += uint64ToString(length, 10); // array size output += F("] = {"); // Start declaration // Dump data for (uint16_t i = 1; i < results->rawlen; i++) { uint32_t usecs; for (usecs = results->rawbuf[i] * kRawTick; usecs > UINT16_MAX; usecs -= UINT16_MAX) { output += uint64ToString(UINT16_MAX); if (i % 2) output += F(", 0, "); else output += F(", 0, "); } output += uint64ToString(usecs, 10); if (i < results->rawlen - 1) output += kCommaSpaceStr; // ',' not needed on the last one if (i % 2 == 0) output += ' '; // Extra if it was even. } // End declaration output += F("};"); // Comment output += F(" // "); output += typeToString(results->decode_type, results->repeat); // Only display the value if the decode type doesn't have an A/C state. if (!hasState) output += ' ' + uint64ToString(results->value, 16); output += F("\n"); // Now dump "known" codes if (results->decode_type != UNKNOWN) { if (hasState) { #if DECODE_AC uint16_t nbytes = ceil(static_cast(results->bits) / 8.0); output += F("uint8_t state["); output += uint64ToString(nbytes); output += F("] = {"); for (uint16_t i = 0; i < nbytes; i++) { output += F("0x"); if (results->state[i] < 0x10) output += '0'; output += uint64ToString(results->state[i], 16); if (i < nbytes - 1) output += kCommaSpaceStr; } output += F("};\n"); #endif // DECODE_AC } else { // Simple protocols // Some protocols have an address &/or command. // NOTE: It will ignore the atypical case when a message has been // decoded but the address & the command are both 0. if (results->address > 0 || results->command > 0) { output += F("uint32_t address = 0x"); output += uint64ToString(results->address, 16); output += F(";\n"); output += F("uint32_t command = 0x"); output += uint64ToString(results->command, 16); output += F(";\n"); } // Most protocols have data output += F("uint64_t data = 0x"); output += uint64ToString(results->value, 16); output += F(";\n"); } } return output; } /// Dump out the decode_results structure. /// @param[in] results A ptr to a decode_results structure. /// @return A String containing the legacy information format. /// @deprecated This is only for those that want this legacy format. String resultToTimingInfo(const decode_results * const results) { String output = ""; String value = ""; // Reserve some space for the string to reduce heap fragmentation. // "Raw Timing[NNNN]:\n\n" = 19 chars // " +123456, " / "-123456, " = ~12 chars on avg per raw entry. output.reserve(19 + 12 * results->rawlen); // Should be less than this. value.reserve(6); // Max value should be 2^17 = 131072 output += F("Raw Timing["); output += uint64ToString(results->rawlen - 1, 10); output += F("]:\n"); for (uint16_t i = 1; i < results->rawlen; i++) { if (i % 2 == 0) output += kDashStr; // even else output += F(" +"); // odd value = uint64ToString(results->rawbuf[i] * kRawTick); // Space pad the value till it is at least 6 chars long. while (value.length() < 6) value = ' ' + value; output += value; if (i < results->rawlen - 1) output += kCommaSpaceStr; // ',' not needed for last one if (!(i % 8)) output += '\n'; // Newline every 8 entries. } output += '\n'; return output; } /// Convert the decode_results structure's value/state to simple hexadecimal. /// @param[in] result A ptr to a decode_results structure. /// @return A String containing the output. String resultToHexidecimal(const decode_results * const result) { String output = F("0x"); // Reserve some space for the string to reduce heap fragmentation. output.reserve(2 * kStateSizeMax + 2); // Should cover worst cases. if (hasACState(result->decode_type)) { #if DECODE_AC for (uint16_t i = 0; result->bits > i * 8; i++) { if (result->state[i] < 0x10) output += '0'; // Zero pad output += uint64ToString(result->state[i], 16); } #endif // DECODE_AC } else { output += uint64ToString(result->value, 16); } return output; } /// Dump out the decode_results structure into a human readable format. /// @param[in] results A ptr to a decode_results structure. /// @return A String containing the output. String resultToHumanReadableBasic(const decode_results * const results) { String output = ""; // Reserve some space for the string to reduce heap fragmentation. // "Protocol : LONGEST_PROTOCOL_NAME (Repeat)\n" // "Code : 0x (NNNN Bits)\n" = 70 chars output.reserve(2 * kStateSizeMax + 70); // Should cover most cases. // Show Encoding standard output += kProtocolStr; output += F(" : "); output += typeToString(results->decode_type, results->repeat); output += '\n'; // Show Code & length output += kCodeStr; output += F(" : "); output += resultToHexidecimal(results); output += kSpaceLBraceStr; output += uint64ToString(results->bits); output += ' '; output += kBitsStr; output += F(")\n"); return output; } /// Convert a decode_results into an array suitable for `sendRaw()`. /// @param[in] decode A ptr to a decode_results structure that contains a mesg. /// @return A PTR to a dynamically allocated uint16_t sendRaw compatible array. /// @note The returned array needs to be delete[]'ed/free()'ed (deallocated) /// after use by caller. uint16_t* resultToRawArray(const decode_results * const decode) { uint16_t *result = new uint16_t[getCorrectedRawLength(decode)]; if (result != NULL) { // The memory was allocated successfully. // Convert the decode data. uint16_t pos = 0; for (uint16_t i = 1; i < decode->rawlen; i++) { uint32_t usecs = decode->rawbuf[i] * kRawTick; while (usecs > UINT16_MAX) { // Keep truncating till it fits. result[pos++] = UINT16_MAX; result[pos++] = 0; // A 0 in a sendRaw() array basically means skip. usecs -= UINT16_MAX; } result[pos++] = usecs; } } return result; } /// Sum all the bytes of an array and return the least significant 8-bits of /// the result. /// @param[in] start A ptr to the start of the byte array to calculate over. /// @param[in] length How many bytes to use in the calculation. /// @param[in] init Starting value of the calculation to use. (Default is 0) /// @return The 8-bit calculated result of all the bytes and init value. uint8_t sumBytes(const uint8_t * const start, const uint16_t length, const uint8_t init) { uint8_t checksum = init; const uint8_t *ptr; for (ptr = start; ptr - start < length; ptr++) checksum += *ptr; return checksum; } /// Calculate a rolling XOR of all the bytes of an array. /// @param[in] start A ptr to the start of the byte array to calculate over. /// @param[in] length How many bytes to use in the calculation. /// @param[in] init Starting value of the calculation to use. (Default is 0) /// @return The 8-bit calculated result of all the bytes and init value. uint8_t xorBytes(const uint8_t * const start, const uint16_t length, const uint8_t init) { uint8_t checksum = init; const uint8_t *ptr; for (ptr = start; ptr - start < length; ptr++) checksum ^= *ptr; return checksum; } /// Count the number of bits of a certain type in an array. /// @param[in] start A ptr to the start of the byte array to calculate over. /// @param[in] length How many bytes to use in the calculation. /// @param[in] ones Count the binary nr of `1` bits. False is count the `0`s. /// @param[in] init Starting value of the calculation to use. (Default is 0) /// @return The nr. of bits found of the given type found in the array. uint16_t countBits(const uint8_t * const start, const uint16_t length, const bool ones, const uint16_t init) { uint16_t count = init; for (uint16_t offset = 0; offset < length; offset++) for (uint8_t currentbyte = *(start + offset); currentbyte; currentbyte >>= 1) if (currentbyte & 1) count++; if (ones || length == 0) return count; else return (length * 8) - count; } /// Count the number of bits of a certain type in an Integer. /// @param[in] data The value you want bits counted for. Starting from the LSB. /// @param[in] length How many bits to use in the calculation? Starts at the LSB /// @param[in] ones Count the binary nr of `1` bits. False is count the `0`s. /// @param[in] init Starting value of the calculation to use. (Default is 0) /// @return The nr. of bits found of the given type found in the Integer. uint16_t countBits(const uint64_t data, const uint8_t length, const bool ones, const uint16_t init) { uint16_t count = init; uint8_t bitsSoFar = length; for (uint64_t remainder = data; remainder && bitsSoFar; remainder >>= 1, bitsSoFar--) if (remainder & 1) count++; if (ones || length == 0) return count; else return length - count; } /// Invert/Flip the bits in an Integer. /// @param[in] data The Integer that will be inverted. /// @param[in] nbits How many bits are to be inverted. Starting from the LSB. /// @return An Integer with the appropriate bits inverted/flipped. uint64_t invertBits(const uint64_t data, const uint16_t nbits) { // No change if we are asked to invert no bits. if (nbits == 0) return data; uint64_t result = ~data; // If we are asked to invert all the bits or more than we have, it's simple. if (nbits >= sizeof(data) * 8) return result; // Mask off any unwanted bits and return the result. return (result & ((1ULL << nbits) - 1)); } /// Convert degrees Celsius to degrees Fahrenheit. float celsiusToFahrenheit(const float deg) { return (deg * 9.0) / 5.0 + 32.0; } /// Convert degrees Fahrenheit to degrees Celsius. float fahrenheitToCelsius(const float deg) { return (deg - 32.0) * 5.0 / 9.0; } namespace irutils { /// Create a String with a colon separated "label: value" pair suitable for /// Humans. /// @param[in] value The value to come after the label. /// @param[in] label The label to precede the value. /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addLabeledString(const String value, const String label, const bool precomma) { String result = ""; // ", " + ": " = 4 chars result.reserve(4 + value.length() + label.length()); if (precomma) result += kCommaSpaceStr; result += label; result += kColonSpaceStr; return result + value; } /// Create a String with a colon separated flag suitable for Humans. /// e.g. "Power: On" /// @param[in] value The value to come after the label. /// @param[in] label The label to precede the value. /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addBoolToString(const bool value, const String label, const bool precomma) { return addLabeledString(value ? kOnStr : kOffStr, label, precomma); } /// Create a String with a colon separated toggle flag suitable for Humans. /// e.g. "Light: Toggle", "Light: -" /// @param[in] toggle The value of the toggle to come after the label. /// @param[in] label The label to precede the value. /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addToggleToString(const bool toggle, const String label, const bool precomma) { return addLabeledString(toggle ? kToggleStr : kDashStr, label, precomma); } /// Create a String with a colon separated labeled Integer suitable for /// Humans. /// e.g. "Foo: 23" /// @param[in] value The value to come after the label. /// @param[in] label The label to precede the value. /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addIntToString(const uint16_t value, const String label, const bool precomma) { return addLabeledString(uint64ToString(value), label, precomma); } /// Create a String with a colon separated labeled Integer suitable for /// Humans. /// e.g. "Foo: 23" /// @param[in] value The value to come after the label. /// @param[in] label The label to precede the value. /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addSignedIntToString(const int16_t value, const String label, const bool precomma) { return addLabeledString(int64ToString(value), label, precomma); } /// Generate the model string for a given Protocol/Model pair. /// @param[in] protocol The IR protocol. /// @param[in] model The model number for that protocol. /// @return The resulting String. /// @note After adding a new model you should update IRac::strToModel() too. String modelToStr(const decode_type_t protocol, const int16_t model) { switch (protocol) { case decode_type_t::FUJITSU_AC: switch (model) { case fujitsu_ac_remote_model_t::ARRAH2E: return kArrah2eStr; case fujitsu_ac_remote_model_t::ARDB1: return kArdb1Str; case fujitsu_ac_remote_model_t::ARREB1E: return kArreb1eStr; case fujitsu_ac_remote_model_t::ARJW2: return kArjw2Str; case fujitsu_ac_remote_model_t::ARRY4: return kArry4Str; case fujitsu_ac_remote_model_t::ARREW4E: return kArrew4eStr; default: return kUnknownStr; } break; case decode_type_t::GREE: switch (model) { case gree_ac_remote_model_t::YAW1F: return kYaw1fStr; case gree_ac_remote_model_t::YBOFB: return kYbofbStr; case gree_ac_remote_model_t::YX1FSF: return kYx1fsfStr; default: return kUnknownStr; } break; case decode_type_t::HAIER_AC176: switch (model) { case haier_ac176_remote_model_t::V9014557_A: return kV9014557AStr; case haier_ac176_remote_model_t::V9014557_B: return kV9014557BStr; default: return kUnknownStr; } break; case decode_type_t::HITACHI_AC1: switch (model) { case hitachi_ac1_remote_model_t::R_LT0541_HTA_A: return kRlt0541htaaStr; case hitachi_ac1_remote_model_t::R_LT0541_HTA_B: return kRlt0541htabStr; default: return kUnknownStr; } break; case decode_type_t::LG: case decode_type_t::LG2: switch (model) { case lg_ac_remote_model_t::GE6711AR2853M: return kGe6711ar2853mStr; case lg_ac_remote_model_t::AKB75215403: return kAkb75215403Str; case lg_ac_remote_model_t::AKB74955603: return kAkb74955603Str; case lg_ac_remote_model_t::AKB73757604: return kAkb73757604Str; case lg_ac_remote_model_t::LG6711A20083V: return kLg6711a20083vStr; default: return kUnknownStr; } break; case decode_type_t::MIRAGE: switch (model) { case mirage_ac_remote_model_t::KKG9AC1: return kKkg9ac1Str; case mirage_ac_remote_model_t::KKG29AC1: return kKkg29ac1Str; default: return kUnknownStr; } break; case decode_type_t::PANASONIC_AC: switch (model) { case panasonic_ac_remote_model_t::kPanasonicLke: return kLkeStr; case panasonic_ac_remote_model_t::kPanasonicNke: return kNkeStr; case panasonic_ac_remote_model_t::kPanasonicDke: return kDkeStr; case panasonic_ac_remote_model_t::kPanasonicJke: return kJkeStr; case panasonic_ac_remote_model_t::kPanasonicCkp: return kCkpStr; case panasonic_ac_remote_model_t::kPanasonicRkr: return kRkrStr; default: return kUnknownStr; } break; case decode_type_t::SHARP_AC: switch (model) { case sharp_ac_remote_model_t::A907: return kA907Str; case sharp_ac_remote_model_t::A705: return kA705Str; case sharp_ac_remote_model_t::A903: return kA903Str; default: return kUnknownStr; } break; case decode_type_t::TCL112AC: switch (model) { case tcl_ac_remote_model_t::TAC09CHSD: return kTac09chsdStr; case tcl_ac_remote_model_t::GZ055BE1: return kGz055be1Str; default: return kUnknownStr; } break; case decode_type_t::VOLTAS: switch (model) { case voltas_ac_remote_model_t::kVoltas122LZF: return k122lzfStr; default: return kUnknownStr; } break; case decode_type_t::WHIRLPOOL_AC: switch (model) { case whirlpool_ac_remote_model_t::DG11J13A: return kDg11j13aStr; case whirlpool_ac_remote_model_t::DG11J191: return kDg11j191Str; default: return kUnknownStr; } break; case decode_type_t::ARGO: switch (model) { case argo_ac_remote_model_t::SAC_WREM2: return kArgoWrem2Str; case argo_ac_remote_model_t::SAC_WREM3: return kArgoWrem3Str; default: return kUnknownStr; } break; case decode_type_t::TOSHIBA_AC: switch (model) { case toshiba_ac_remote_model_t::kToshibaGenericRemote_A: return kToshibaGenericRemoteAStr; case toshiba_ac_remote_model_t::kToshibaGenericRemote_B: return kToshibaGenericRemoteBStr; default: return kUnknownStr; } default: return kUnknownStr; } } /// Create a String of human output for a given protocol model number. /// e.g. "Model: JKE" /// @param[in] protocol The IR protocol. /// @param[in] model The model number for that protocol. /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addModelToString(const decode_type_t protocol, const int16_t model, const bool precomma) { String result = ""; // ", Model: NNN (BlahBlahEtc)" = ~40 chars for longest model name. result.reserve(40); result += addIntToString(model, kModelStr, precomma); result += kSpaceLBraceStr; result += modelToStr(protocol, model); return result + ')'; } /// Create a String of human output for a given temperature. /// e.g. "Temp: 25C" /// @param[in] degrees The temperature in degrees. /// @param[in] celsius Is the temp Celsius or Fahrenheit. /// true is C, false is F /// @param[in] precomma Should the output string start with ", " or not? /// @param[in] isSensorTemp Is the value a room (ambient) temp. or target? /// @return The resulting String. String addTempToString(const uint16_t degrees, const bool celsius, const bool precomma, const bool isSensorTemp) { String result = addIntToString(degrees, (isSensorTemp)? kSensorTempStr : kTempStr, precomma); result += celsius ? 'C' : 'F'; return result; } /// Create a String of human output for a given temperature. /// e.g. "Temp: 25.5C" /// @param[in] degrees The temperature in degrees. /// @param[in] celsius Is the temp Celsius or Fahrenheit. /// true is C, false is F /// @param[in] precomma Should the output string start with ", " or not? /// @param[in] isSensorTemp Is the value a room (ambient) temp. or target? /// @return The resulting String. String addTempFloatToString(const float degrees, const bool celsius, const bool precomma, const bool isSensorTemp) { String result = ""; result.reserve(21); // Assuming ", Sensor Temp: XXX.5F" is the largest. result += addIntToString(degrees, (isSensorTemp)? kSensorTempStr : kTempStr, precomma); // Is it a half degree? if (static_cast(2 * degrees) & 1) result += F(".5"); result += celsius ? 'C' : 'F'; return result; } /// Create a String of human output for the given operating mode. /// e.g. "Mode: 1 (Cool)" /// @param[in] mode The operating mode to display. /// @param[in] automatic The numeric value for Auto mode. /// @param[in] cool The numeric value for Cool mode. /// @param[in] heat The numeric value for Heat mode. /// @param[in] dry The numeric value for Dry mode. /// @param[in] fan The numeric value for Fan mode. /// @return The resulting String. String addModeToString(const uint8_t mode, const uint8_t automatic, const uint8_t cool, const uint8_t heat, const uint8_t dry, const uint8_t fan) { String result = ""; result.reserve(22); // ", Mode: NNN (UNKNOWN)" result += addIntToString(mode, kModeStr); result += kSpaceLBraceStr; if (mode == automatic) result += kAutoStr; else if (mode == cool) result += kCoolStr; else if (mode == heat) result += kHeatStr; else if (mode == dry) result += kDryStr; else if (mode == fan) result += kFanStr; else result += kUnknownStr; return result + ')'; } /// Create a String of the 3-letter day of the week from a numerical day of /// the week. e.g. "Day: 1 (Mon)" /// @param[in] day_of_week A numerical version of the sequential day of the /// week. e.g. Saturday = 7 etc. /// @param[in] offset Days to offset by. /// e.g. For different day starting the week. /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addDayToString(const uint8_t day_of_week, const int8_t offset, const bool precomma) { String result = ""; result.reserve(19); // ", Day: N (UNKNOWN)" result += addIntToString(day_of_week, kDayStr, precomma); result += kSpaceLBraceStr; result += dayToString(day_of_week, offset); return result + ')'; } /// Create a String of the 3-letter day of the week from a numerical day of /// the week. e.g. "Mon" /// @param[in] day_of_week A numerical version of the sequential day of the /// week. e.g. Sunday = 1, Monday = 2, ..., Saturday = 7 /// @param[in] offset Days to offset by. /// e.g. For different day starting the week. /// @return The resulting String. String dayToString(const uint8_t day_of_week, const int8_t offset) { if ((uint8_t)(day_of_week + offset) < 7) #if UNIT_TEST return String(kThreeLetterDayOfWeekStr).substr( (day_of_week + offset) * 3, 3); #else // UNIT_TEST return String(kThreeLetterDayOfWeekStr).substring( (day_of_week + offset) * 3, (day_of_week + offset) * 3 + 3); #endif // UNIT_TEST else return kUnknownStr; } /// Create a String of human output for the given fan speed. /// e.g. "Fan: 0 (Auto)" /// @param[in] speed The numeric speed of the fan to display. /// @param[in] high The numeric value for High speed. (second highest) /// @param[in] low The numeric value for Low speed. /// @param[in] automatic The numeric value for Auto speed. /// @param[in] quiet The numeric value for Quiet speed. /// @param[in] medium The numeric value for Medium speed. /// @param[in] maximum The numeric value for Highest speed. (if > high) /// @param[in] medium_high The numeric value for third-highest speed. /// (if > medium) /// @return The resulting String. String addFanToString(const uint8_t speed, const uint8_t high, const uint8_t low, const uint8_t automatic, const uint8_t quiet, const uint8_t medium, const uint8_t maximum, const uint8_t medium_high) { String result = ""; result.reserve(21); // ", Fan: NNN (UNKNOWN)" result += addIntToString(speed, kFanStr); result += kSpaceLBraceStr; if (speed == high) result += kHighStr; else if (speed == low) result += kLowStr; else if (speed == automatic) result += kAutoStr; else if (speed == quiet) result += kQuietStr; else if (speed == medium) result += kMediumStr; else if (speed == maximum) result += kMaximumStr; else if (speed == medium_high) result += kMedHighStr; else result += kUnknownStr; return result + ')'; } /// Create a String of human output for the given horizontal swing setting. /// e.g. "Swing(H): 0 (Auto)" /// @param[in] position The numeric position of the swing to display. /// @param[in] automatic The numeric value for Auto position. /// @param[in] maxleft The numeric value for most left position. /// @param[in] left The numeric value for Left position. /// @param[in] middle The numeric value for Middle position. /// @param[in] right The numeric value for Right position. /// @param[in] maxright The numeric value for most right position. /// @param[in] off The numeric value for Off position. /// @param[in] leftright The numeric value for "left right" position. /// @param[in] rightleft The numeric value for "right left" position. /// @param[in] threed The numeric value for 3D setting. /// @param[in] wide The numeric value for Wide position. /// @return The resulting String. String addSwingHToString(const uint8_t position, const uint8_t automatic, const uint8_t maxleft, const uint8_t left, const uint8_t middle, const uint8_t right, const uint8_t maxright, const uint8_t off, const uint8_t leftright, const uint8_t rightleft, const uint8_t threed, const uint8_t wide) { String result = ""; result.reserve(30); // ", Swing(H): NNN (Left Right)" result += addIntToString(position, kSwingHStr); result += kSpaceLBraceStr; if (position == automatic) { result += kAutoStr; } else if (position == left) { result += kLeftStr; } else if (position == middle) { result += kMiddleStr; } else if (position == right) { result += kRightStr; } else if (position == maxleft) { result += kMaxLeftStr; } else if (position == maxright) { result += kMaxRightStr; } else if (position == off) { result += kOffStr; } else if (position == leftright) { result += kLeftStr; result += ' '; result += kRightStr; } else if (position == rightleft) { result += kRightStr; result += ' '; result += kLeftStr; } else if (position == threed) { result += k3DStr; } else if (position == wide) { result += kWideStr; } else { result += kUnknownStr; } return result + ')'; } /// Create a String of human output for the given vertical swing setting. /// e.g. "Swing(V): 0 (Auto)" /// @param[in] position The numeric position of the swing to display. /// @param[in] automatic The numeric value for Auto position. /// @param[in] highest The numeric value for Highest position. /// @param[in] high The numeric value for High position. /// @param[in] uppermiddle The numeric value for Upper Middle position. /// @param[in] middle The numeric value for Middle position. /// @param[in] lowermiddle The numeric value for Lower Middle position. /// @param[in] low The numeric value for Low position. /// @param[in] lowest The numeric value for Low position. /// @param[in] off The numeric value for Off position. /// @param[in] swing The numeric value for Swing setting. /// @param[in] breeze The numeric value for Breeze setting. /// @param[in] circulate The numeric value for Circulate setting. /// @return The resulting String. String addSwingVToString(const uint8_t position, const uint8_t automatic, const uint8_t highest, const uint8_t high, const uint8_t uppermiddle, const uint8_t middle, const uint8_t lowermiddle, const uint8_t low, const uint8_t lowest, const uint8_t off, const uint8_t swing, const uint8_t breeze, const uint8_t circulate) { String result = ""; result.reserve(31); // ", Swing(V): NNN (Upper Middle)" result += addIntToString(position, kSwingVStr); result += kSpaceLBraceStr; if (position == automatic) { result += kAutoStr; } else if (position == highest) { result += kHighestStr; } else if (position == high) { result += kHighStr; } else if (position == middle) { result += kMiddleStr; } else if (position == low) { result += kLowStr; } else if (position == lowest) { result += kLowestStr; } else if (position == off) { result += kOffStr; } else if (position == uppermiddle) { result += kUpperStr; result += ' '; result += kMiddleStr; } else if (position == lowermiddle) { result += kLowerStr; result += ' '; result += kMiddleStr; } else if (position == swing) { result += kSwingStr; } else if (position == breeze) { result += kBreezeStr; } else if (position == circulate) { result += kCirculateStr; } else { result += kUnknownStr; } return result + ')'; } /// @brief Create a String of human output for the given timer setting. /// e.g. "Timer Mode: 2 (Schedule 1)" /// @param[in] timerMode The numeric value of the timer mode to display. /// @param[in] noTimer The numeric value for no timer (off) /// @param[in] delayTimer The numeric value for delay (sleep) timer /// @param[in] schedule1 The numeric value for schedule timer #1 /// @param[in] schedule2 The numeric value for schedule timer #2 /// @param[in] schedule3 The numeric value for schedule timer #3 /// @param[in] precomma Should the output string start with ", " or not? /// @return String representation String addTimerModeToString(const uint8_t timerMode, const uint8_t noTimer, const uint8_t delayTimer, const uint8_t schedule1, const uint8_t schedule2, const uint8_t schedule3, const bool precomma) { String result = ""; result.reserve(28); // ", Timer Mode: 2 (Schedule 1)" result += addIntToString(timerMode, kTimerModeStr, precomma); result += kSpaceLBraceStr; if (timerMode == noTimer) { result += kOffStr; } else if (timerMode == delayTimer) { result += kSleepTimerStr; } else if (timerMode == schedule1) { result += kScheduleStr; result += '1'; } else if (timerMode == schedule2) { result += kScheduleStr; result += '2'; } else if (timerMode == schedule3) { result += kScheduleStr; result += '3'; } else { result += kUnknownStr; } return result + ')'; } /// @brief Create a String of human output for the given channel /// e.g. "[CH#0]" /// @param channel The numeric value of the channel to display. /// @return String representation String channelToString(const uint8_t channel) { String result = ""; result.reserve(6); // "[CH#4]" result += "["; result += kChStr; result += uint64ToString(channel); result += "]"; return result; } /// @brief Create a String of human output for the given command type /// e.g. "IFeel Report" /// @param irCommandType The numeric value of the command type to display. /// @param acControlCmd The numeric value of the "control" (default) command /// @param iFeelReportCmd The numeric value of the sensor temperature command /// @param timerCmd The numeric value of the timer config IR command /// @param configCmd The numeric value of the config param set IR command /// @return String representation String irCommandTypeToString(uint8_t irCommandType, uint8_t acControlCmd, uint8_t iFeelReportCmd, uint8_t timerCmd, uint8_t configCmd) { String result = ""; result.reserve(12); // "IFeel Report" if (irCommandType == acControlCmd) { result += kCommandStr; } else if (irCommandType == iFeelReportCmd) { result += kIFeelReportStr; } else if (irCommandType == timerCmd) { result += kTimerStr; } else if (irCommandType == configCmd) { result += kConfigCommandStr; } else { result += kUnknownStr; } return result; } /// @brief Create a String of the 3-letter day of the week bitmap // e.g. 0b0000101 is "Sun | Tue" /// @param[in] daysBitmap The bitmap representing days of week to represent /// e.g bit[0]=Sunday, bit[1]=Monday, ... /// @param[in] offset Days to offset by. /// e.g. For different day starting the week. /// @return String representation. String daysBitmaskToString(uint8_t daysBitmap, uint8_t offset) { String result = ""; result.reserve(27); // Sun|Mon|Tue|Wed|Thu|Fri|Sat for (uint8_t i = 0; i < 7; ++i) { if (((daysBitmap >> i) & 0b1) == 0b1) { if (result.length() > 0) { result += "|"; } result += irutils::dayToString(i, offset); } } return result; } /// Escape any special HTML (unsafe) characters in a string. e.g. anti-XSS. /// @param[in] unescaped A String containing text to make HTML safe. /// @return A string that is HTML safe. String htmlEscape(const String unescaped) { String result = ""; uint16_t ulen = unescaped.length(); result.reserve(ulen); // The result will be at least the size of input. for (size_t i = 0; i < ulen; i++) { char c = unescaped[i]; switch (c) { // ';!-"<>=&#{}() are all unsafe. case '\'': result += F("'"); break; case ';': result += F(";"); break; case '!': result += F("!"); break; case '-': result += F("‐"); break; case '\"': result += F("""); break; case '<': result += F("<"); break; case '>': result += F(">"); break; case '=': result += F("&#equals;"); break; case '&': result += F("&"); break; case '#': result += F("#"); break; case '{': result += F("{"); break; case '}': result += F("}"); break; case '(': result += F("("); break; case ')': result += F(")"); break; default: result += c; } } return result; } /// Convert a nr. of milliSeconds into a Human-readable string. /// e.g. "1 Day 6 Hours 34 Minutes 17 Seconds" /// @param[in] msecs Nr. of milliSeconds (ms). /// @return A human readable string. String msToString(uint32_t const msecs) { uint32_t totalseconds = msecs / 1000; if (totalseconds == 0) return kNowStr; // Note: uint32_t can only hold up to 45 days, so uint8_t is safe. uint8_t days = totalseconds / (60 * 60 * 24); uint8_t hours = (totalseconds / (60 * 60)) % 24; uint8_t minutes = (totalseconds / 60) % 60; uint8_t seconds = totalseconds % 60; String result = ""; result.reserve(42); // "99 Days, 23 Hours, 59 Minutes, 59 Seconds" if (days) result += uint64ToString(days) + ' ' + String((days > 1) ? kDaysStr : kDayStr); if (hours) { if (result.length()) result += ' '; result += uint64ToString(hours) + ' ' + String((hours > 1) ? kHoursStr : kHourStr); } if (minutes) { if (result.length()) result += ' '; result += uint64ToString(minutes) + ' ' + String( (minutes > 1) ? kMinutesStr : kMinuteStr); } if (seconds) { if (result.length()) result += ' '; result += uint64ToString(seconds) + ' ' + String( (seconds > 1) ? kSecondsStr : kSecondStr); } return result; } /// Convert a nr. of minutes into a 24h clock format Human-readable string. /// e.g. "23:59" /// @param[in] mins Nr. of Minutes. /// @return A human readable string. String minsToString(const uint16_t mins) { String result = ""; result.reserve(5); // 23:59 is the typical worst case. if (mins / 60 < 10) result += '0'; // Zero pad the hours result += uint64ToString(mins / 60) + kTimeSep; if (mins % 60 < 10) result += '0'; // Zero pad the minutes. result += uint64ToString(mins % 60); return result; } /// Sum all the nibbles together in a series of bytes. /// @param[in] start A ptr to the start of the byte array to calculate over. /// @param[in] length How many bytes to use in the calculation. /// @param[in] init Starting value of the calculation to use. (Default is 0) /// @return The 8-bit calculated result of all the bytes and init value. uint8_t sumNibbles(const uint8_t * const start, const uint16_t length, const uint8_t init) { uint8_t sum = init; const uint8_t *ptr; for (ptr = start; ptr - start < length; ptr++) sum += (*ptr >> 4) + (*ptr & 0xF); return sum; } /// Sum all the nibbles together in an integer. /// @param[in] data The integer to be summed. /// @param[in] count The number of nibbles to sum. Starts from LSB. Max of 16. /// @param[in] init Starting value of the calculation to use. (Default is 0) /// @param[in] nibbleonly true, the result is 4 bits. false, it's 8 bits. /// @return The 4/8-bit calculated result of all the nibbles and init value. uint8_t sumNibbles(const uint64_t data, const uint8_t count, const uint8_t init, const bool nibbleonly) { uint8_t sum = init; uint64_t copy = data; const uint8_t nrofnibbles = (count < 16) ? count : (64 / 4); for (uint8_t i = 0; i < nrofnibbles; i++, copy >>= 4) sum += copy & 0xF; return nibbleonly ? sum & 0xF : sum; } /// Sum all the bytes together in an integer. /// @param[in] data The integer to be summed. /// @param[in] count The number of bytes to sum. Starts from LSB. Max of 8. /// @param[in] init Starting value of the calculation to use. (Default is 0) /// @param[in] byteonly true, the result is 8 bits. false, it's 16 bits. /// @return The 8/16-bit calculated result of all the bytes and init value. uint16_t sumBytes(const uint64_t data, const uint8_t count, const uint8_t init, const bool byteonly) { uint16_t sum = init; uint64_t copy = data; const uint8_t nrofbytes = (count < 8) ? count : (64 / 8); for (uint8_t i = 0; i < nrofbytes; i++, copy >>= 8) sum += (copy & 0xFF); return byteonly ? sum & 0xFF : sum; } /// Convert a byte of Binary Coded Decimal(BCD) into an Integer. /// @param[in] bcd The BCD value. /// @return A normal Integer value. uint8_t bcdToUint8(const uint8_t bcd) { if (bcd > 0x99) return 255; // Too big. return (bcd >> 4) * 10 + (bcd & 0xF); } /// Convert an Integer into a byte of Binary Coded Decimal(BCD). /// @param[in] integer The number to convert. /// @return An 8-bit BCD value. uint8_t uint8ToBcd(const uint8_t integer) { if (integer > 99) return 255; // Too big. return ((integer / 10) << 4) + (integer % 10); } /// Return the value of `position`th bit of an Integer. /// @param[in] data Value to be examined. /// @param[in] position Nr. of the Nth bit to be examined. `0` is the LSB. /// @param[in] size Nr. of bits in data. /// @return The bit's value. bool getBit(const uint64_t data, const uint8_t position, const uint8_t size) { if (position >= size) return false; // Outside of range. return data & (1ULL << position); } /// Return the value of `position`th bit of an Integer. /// @param[in] data Value to be examined. /// @param[in] position Nr. of the Nth bit to be examined. `0` is the LSB. /// @return The bit's value. bool getBit(const uint8_t data, const uint8_t position) { if (position >= 8) return false; // Outside of range. return data & (1 << position); } /// Return the value of an Integer with the `position`th bit changed. /// @param[in] data Value to be changed. /// @param[in] position Nr. of the bit to be changed. `0` is the LSB. /// @param[in] on Value to set the position'th bit to. /// @param[in] size Nr. of bits in data. /// @return A suitably modified integer. uint64_t setBit(const uint64_t data, const uint8_t position, const bool on, const uint8_t size) { if (position >= size) return data; // Outside of range. uint64_t mask = 1ULL << position; if (on) return data | mask; else return data & ~mask; } /// Return the value of an Integer with the `position`th bit changed. /// @param[in] data Value to be changed. /// @param[in] position Nr. of the bit to be changed. `0` is the LSB. /// @param[in] on Value to set the position'th bit to. /// @return A suitably modified integer. uint8_t setBit(const uint8_t data, const uint8_t position, const bool on) { if (position >= 8) return data; // Outside of range. uint8_t mask = 1 << position; if (on) return data | mask; else return data & ~mask; } /// Alter the value of an Integer with the `position`th bit changed. /// @param[in,out] data A pointer to the 8-bit integer to be changed. /// @param[in] position Nr. of the bit to be changed. `0` is the LSB. /// @param[in] on Value to set the position'th bit to. void setBit(uint8_t * const data, const uint8_t position, const bool on) { uint8_t mask = 1 << position; if (on) *data |= mask; else *data &= ~mask; } /// Alter the value of an Integer with the `position`th bit changed. /// @param[in,out] data A pointer to the 32-bit integer to be changed. /// @param[in] position Nr. of the bit to be changed. `0` is the LSB. /// @param[in] on Value to set the position'th bit to. void setBit(uint32_t * const data, const uint8_t position, const bool on) { uint32_t mask = static_cast(1) << position; if (on) *data |= mask; else *data &= ~mask; } /// Alter the value of an Integer with the `position`th bit changed. /// @param[in,out] data A pointer to the 64-bit integer to be changed. /// @param[in] position Nr. of the bit to be changed. `0` is the LSB. /// @param[in] on Value to set the position'th bit to. void setBit(uint64_t * const data, const uint8_t position, const bool on) { uint64_t mask = static_cast(1) << position; if (on) *data |= mask; else *data &= ~mask; } /// Alter an uint8_t value by overwriting an arbitrary given number of bits. /// @param[in,out] dst A pointer to the value to be changed. /// @param[in] offset Nr. of bits from the Least Significant Bit to be ignored /// @param[in] nbits Nr of bits of data to be placed into the destination. /// @param[in] data The value to be placed. void setBits(uint8_t * const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) { if (offset >= 8 || !nbits) return; // Short circuit as it won't change. // Calculate the mask for the supplied value. uint8_t mask = UINT8_MAX >> (8 - ((nbits > 8) ? 8 : nbits)); // Calculate the mask & clear the space for the data. // Clear the destination bits. *dst &= ~(uint8_t)(mask << offset); // Merge in the data. *dst |= ((data & mask) << offset); } /// Alter an uint32_t value by overwriting an arbitrary given number of bits. /// @param[in,out] dst A pointer to the value to be changed. /// @param[in] offset Nr. of bits from the Least Significant Bit to be ignored /// @param[in] nbits Nr of bits of data to be placed into the destination. /// @param[in] data The value to be placed. void setBits(uint32_t * const dst, const uint8_t offset, const uint8_t nbits, const uint32_t data) { if (offset >= 32 || !nbits) return; // Short circuit as it won't change. // Calculate the mask for the supplied value. uint32_t mask = UINT32_MAX >> (32 - ((nbits > 32) ? 32 : nbits)); // Calculate the mask & clear the space for the data. // Clear the destination bits. *dst &= ~(mask << offset); // Merge in the data. *dst |= ((data & mask) << offset); } /// Alter an uint64_t value by overwriting an arbitrary given number of bits. /// @param[in,out] dst A pointer to the value to be changed. /// @param[in] offset Nr. of bits from the Least Significant Bit to be ignored /// @param[in] nbits Nr of bits of data to be placed into the destination. /// @param[in] data The value to be placed. void setBits(uint64_t * const dst, const uint8_t offset, const uint8_t nbits, const uint64_t data) { if (offset >= 64 || !nbits) return; // Short circuit as it won't change. // Calculate the mask for the supplied value. uint64_t mask = UINT64_MAX >> (64 - ((nbits > 64) ? 64 : nbits)); // Calculate the mask & clear the space for the data. // Clear the destination bits. *dst &= ~(mask << offset); // Merge in the data. *dst |= ((data & mask) << offset); } /// Create byte pairs where the second byte of the pair is a bit /// inverted/flipped copy of the first/previous byte of the pair. /// @param[in,out] ptr A pointer to the start of array to modify. /// @param[in] length The byte size of the array. /// @note A length of `<= 1` will do nothing. /// @return A ptr to the modified array. uint8_t * invertBytePairs(uint8_t *ptr, const uint16_t length) { for (uint16_t i = 1; i < length; i += 2) { // Code done this way to avoid a compiler warning bug. uint8_t inv = ~*(ptr + i - 1); *(ptr + i) = inv; } return ptr; } /// Check an array to see if every second byte of a pair is a bit /// inverted/flipped copy of the first/previous byte of the pair. /// @param[in] ptr A pointer to the start of array to check. /// @param[in] length The byte size of the array. /// @note A length of `<= 1` will always return true. /// @return true, if every second byte is inverted. Otherwise false. bool checkInvertedBytePairs(const uint8_t * const ptr, const uint16_t length) { for (uint16_t i = 1; i < length; i += 2) { // Code done this way to avoid a compiler warning bug. uint8_t inv = ~*(ptr + i - 1); if (*(ptr + i) != inv) return false; } return true; } /// Perform a low level bit manipulation sanity check for the given cpu /// architecture and the compiler operation. Calls to this should return /// 0 if everything is as expected, anything else means the library won't work /// as expected. /// @return A bit mask value of potential issues. /// 0: (e.g. 0b00000000) Everything appears okay. /// 0th bit set: (0b1) Unexpected bit field/packing encountered. /// Try a different compiler. /// 1st bit set: (0b10) Unexpected Endianness. Try a different compiler flag /// or use a CPU different architecture. /// e.g. A result of 3 (0b11) would mean both a bit field and an Endianness /// issue has been found. uint8_t lowLevelSanityCheck(void) { const uint64_t kExpectedBitFieldResult = 0x8000012340000039ULL; volatile uint32_t EndianTest = 0x12345678; const uint8_t kBitFieldError = 0b01; const uint8_t kEndiannessError = 0b10; uint8_t result = 0; union bitpackdata { struct { uint64_t lowestbit:1; // 0th bit uint64_t next7bits:7; // 1-7th bits uint64_t _unused_1:20; // 8-27th bits // Cross the 32 bit boundary. uint64_t crossbits:16; // 28-43rd bits uint64_t _usused_2:18; // 44-61st bits uint64_t highest2bits:2; // 62-63rd bits }; uint64_t all; }; bitpackdata data; data.lowestbit = true; data.next7bits = 0b0011100; // 0x1C data._unused_1 = 0; data.crossbits = 0x1234; data._usused_2 = 0; data.highest2bits = 0b10; // 2 if (data.all != kExpectedBitFieldResult) result |= kBitFieldError; // Check that we are using Little Endian for integers #if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) if (BYTE_ORDER != LITTLE_ENDIAN) result |= kEndiannessError; #endif #if defined(__IEEE_BIG_ENDIAN) || defined(__IEEE_BYTES_BIG_ENDIAN) result |= kEndiannessError; #endif // Brute force check for little endian. if (*((uint8_t*)(&EndianTest)) != 0x78) // NOLINT(readability/casting) result |= kEndiannessError; return result; } } // namespace irutils