From 1beea0c586071957d1eb159860b543bd544cb170 Mon Sep 17 00:00:00 2001
From: David Conran <crankyoldgit@users.noreply.github.com>
Date: Sat, 9 Oct 2021 08:04:55 +1000
Subject: [PATCH] Strings finally in Flash! (#1623)

Finally convince the compiler to store the text strings into flash.

Saves approx 2k of Global ram, for a trade-off of between ~0-0.5k of extra flash space used.

e.g. _(updated)_
* IRMQTTServer example code:
  - Before:
RAM:   [=====     ]  54.1% (used 44344 bytes from 81920 bytes)
Flash: [=====     ]  54.2% (used 566209 bytes from 1044464 bytes)
Bin file size = 570368
  - After:
RAM:   [=====     ]  51.3% (used 41992 bytes from 81920 bytes)
Flash: [=====     ]  54.2% (used 566201 bytes from 1044464 bytes)
Bin file size = 570352

* IRrecvDumpV2 example code:
  - Before:
RAM:   [====      ]  37.9% (used 31044 bytes from 81920 bytes)
Flash: [====      ]  35.6% (used 372025 bytes from 1044464 bytes)
Bin file size = 376176
  - After:
RAM:   [====      ]  35.5% (used 29072 bytes from 81920 bytes)
Flash: [====      ]  35.7% (used 372525 bytes from 1044464 bytes)
Bin file size = 376672

Fixes #1614
Fixes #1493

Co-authored with @mcspr
---
 keywords.txt               |  47 ++++-
 src/IRac.cpp               | 248 +++++++++++++++------------
 src/IRtext.cpp             | 340 ++++++++++++++++++++-----------------
 src/IRtext.h               | 322 ++++++++++++++++++-----------------
 src/IRutils.cpp            |  52 ++++--
 src/ir_Electra.cpp         |   3 +-
 src/ir_Goodweather.cpp     |  10 +-
 src/ir_Sharp.cpp           |  11 +-
 tools/generate_irtext_h.sh |  16 +-
 tools/mkkeywords           |  13 +-
 10 files changed, 605 insertions(+), 457 deletions(-)

diff --git a/keywords.txt b/keywords.txt
index fefce8d9d..60e714c5d 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -95,6 +95,7 @@ sharp_ac_remote_model_t	KEYWORD1
 state_t	KEYWORD1
 swingh_t	KEYWORD1
 swingv_t	KEYWORD1
+tcl_ac_remote_model_t	KEYWORD1
 voltas_ac_remote_model_t	KEYWORD1
 whirlpool_ac_remote_model_t	KEYWORD1
 
@@ -193,6 +194,7 @@ decodeAirwell	KEYWORD2
 decodeAiwaRCT501	KEYWORD2
 decodeAmcor	KEYWORD2
 decodeArgo	KEYWORD2
+decodeArris	KEYWORD2
 decodeBose	KEYWORD2
 decodeCOOLIX	KEYWORD2
 decodeCarrierAC	KEYWORD2
@@ -298,6 +300,7 @@ enableIROut	KEYWORD2
 enableOffTimer	KEYWORD2
 enableOnTimer	KEYWORD2
 enableSleepTimer	KEYWORD2
+encodeArris	KEYWORD2
 encodeDoshisha	KEYWORD2
 encodeJVC	KEYWORD2
 encodeLG	KEYWORD2
@@ -481,6 +484,7 @@ isSwingH	KEYWORD2
 isSwingV	KEYWORD2
 isSwingVStep	KEYWORD2
 isSwingVToggle	KEYWORD2
+isTcl	KEYWORD2
 isTimeCommand	KEYWORD2
 isTimerActive	KEYWORD2
 isTurboToggle	KEYWORD2
@@ -541,6 +545,7 @@ sendAirwell	KEYWORD2
 sendAiwaRCT501	KEYWORD2
 sendAmcor	KEYWORD2
 sendArgo	KEYWORD2
+sendArris	KEYWORD2
 sendBose	KEYWORD2
 sendCOOLIX	KEYWORD2
 sendCarrierAC	KEYWORD2
@@ -791,6 +796,7 @@ teco	KEYWORD2
 ticksHigh	KEYWORD2
 ticksLow	KEYWORD2
 toString	KEYWORD2
+toggleArrisRelease	KEYWORD2
 toggleRC5	KEYWORD2
 toggleRC6	KEYWORD2
 toggleSwingHoriz	KEYWORD2
@@ -857,6 +863,7 @@ ARJW2	LITERAL1
 ARRAH2E	LITERAL1
 ARREB1E	LITERAL1
 ARREW4E	LITERAL1
+ARRIS	LITERAL1
 ARRY4	LITERAL1
 BOSE	LITERAL1
 CARRIER_AC	LITERAL1
@@ -891,6 +898,7 @@ DECODE_AIRWELL	LITERAL1
 DECODE_AIWA_RC_T501	LITERAL1
 DECODE_AMCOR	LITERAL1
 DECODE_ARGO	LITERAL1
+DECODE_ARRIS	LITERAL1
 DECODE_BOSE	LITERAL1
 DECODE_CARRIER_AC	LITERAL1
 DECODE_CARRIER_AC40	LITERAL1
@@ -1054,6 +1062,7 @@ GREE_SWING_MIDDLE_DOWN	LITERAL1
 GREE_SWING_MIDDLE_UP	LITERAL1
 GREE_SWING_UP	LITERAL1
 GREE_SWING_UP_AUTO	LITERAL1
+GZ055BE1	LITERAL1
 HAIER_AC	LITERAL1
 HAIER_AC176	LITERAL1
 HAIER_AC_AUTO	LITERAL1
@@ -1213,6 +1222,7 @@ PANASONIC_BITS	LITERAL1
 PIONEER	LITERAL1
 PROGMEM	LITERAL1
 PRONTO	LITERAL1
+PSTR	LITERAL1
 RAW	LITERAL1
 RAWTICK	LITERAL1
 RC5	LITERAL1
@@ -1240,6 +1250,7 @@ SEND_AIRWELL	LITERAL1
 SEND_AIWA_RC_T501	LITERAL1
 SEND_AMCOR	LITERAL1
 SEND_ARGO	LITERAL1
+SEND_ARRIS	LITERAL1
 SEND_BOSE	LITERAL1
 SEND_CARRIER_AC	LITERAL1
 SEND_CARRIER_AC40	LITERAL1
@@ -1347,6 +1358,7 @@ SONY_15_BITS	LITERAL1
 SONY_20_BITS	LITERAL1
 SONY_38K	LITERAL1
 SYMPHONY	LITERAL1
+TAC09CHSD	LITERAL1
 TCL112AC	LITERAL1
 TECHNIBEL_AC	LITERAL1
 TECO	LITERAL1
@@ -1497,6 +1509,16 @@ kArgoOneSpace	LITERAL1
 kArgoStateLength	LITERAL1
 kArgoTempDelta	LITERAL1
 kArgoZeroSpace	LITERAL1
+kArrisBits	LITERAL1
+kArrisChecksumSize	LITERAL1
+kArrisCommandSize	LITERAL1
+kArrisGapSpace	LITERAL1
+kArrisHalfClockPeriod	LITERAL1
+kArrisHdrMark	LITERAL1
+kArrisHdrSpace	LITERAL1
+kArrisOverhead	LITERAL1
+kArrisReleaseBit	LITERAL1
+kArrisReleaseToggle	LITERAL1
 kAuto	LITERAL1
 kAutoStr	LITERAL1
 kAutomaticStr	LITERAL1
@@ -2008,8 +2030,11 @@ kEyeAutoStr	LITERAL1
 kEyeStr	LITERAL1
 kFalseStr	LITERAL1
 kFan	LITERAL1
+kFanOnlyNoSpaceStr	LITERAL1
 kFanOnlyStr	LITERAL1
+kFanOnlyWithSpaceStr	LITERAL1
 kFanStr	LITERAL1
+kFan_OnlyStr	LITERAL1
 kFastStr	LITERAL1
 kFilterStr	LITERAL1
 kFixedStr	LITERAL1
@@ -3326,8 +3351,15 @@ kSharpAcSpecialTimer	LITERAL1
 kSharpAcSpecialTimerHalfHour	LITERAL1
 kSharpAcSpecialTurbo	LITERAL1
 kSharpAcStateLength	LITERAL1
-kSharpAcSwingNoToggle	LITERAL1
-kSharpAcSwingToggle	LITERAL1
+kSharpAcSwingVCoanda	LITERAL1
+kSharpAcSwingVHigh	LITERAL1
+kSharpAcSwingVIgnore	LITERAL1
+kSharpAcSwingVLast	LITERAL1
+kSharpAcSwingVLow	LITERAL1
+kSharpAcSwingVLowest	LITERAL1
+kSharpAcSwingVMid	LITERAL1
+kSharpAcSwingVOff	LITERAL1
+kSharpAcSwingVToggle	LITERAL1
 kSharpAcTimerHoursMax	LITERAL1
 kSharpAcTimerHoursOff	LITERAL1
 kSharpAcTimerIncrement	LITERAL1
@@ -3408,6 +3440,9 @@ kTcl112AcFanAuto	LITERAL1
 kTcl112AcFanHigh	LITERAL1
 kTcl112AcFanLow	LITERAL1
 kTcl112AcFanMed	LITERAL1
+kTcl112AcFanMin	LITERAL1
+kTcl112AcFanNight	LITERAL1
+kTcl112AcFanQuiet	LITERAL1
 kTcl112AcGap	LITERAL1
 kTcl112AcHdrMark	LITERAL1
 kTcl112AcHdrMarkTolerance	LITERAL1
@@ -3417,10 +3452,17 @@ kTcl112AcNormal	LITERAL1
 kTcl112AcOneSpace	LITERAL1
 kTcl112AcSpecial	LITERAL1
 kTcl112AcStateLength	LITERAL1
+kTcl112AcSwingVHigh	LITERAL1
+kTcl112AcSwingVHighest	LITERAL1
+kTcl112AcSwingVLow	LITERAL1
+kTcl112AcSwingVLowest	LITERAL1
+kTcl112AcSwingVMiddle	LITERAL1
 kTcl112AcSwingVOff	LITERAL1
 kTcl112AcSwingVOn	LITERAL1
 kTcl112AcTempMax	LITERAL1
 kTcl112AcTempMin	LITERAL1
+kTcl112AcTimerMax	LITERAL1
+kTcl112AcTimerResolution	LITERAL1
 kTcl112AcTolerance	LITERAL1
 kTcl112AcZeroSpace	LITERAL1
 kTechnibelAcBitMark	LITERAL1
@@ -3482,6 +3524,7 @@ kTempDownStr	LITERAL1
 kTempStr	LITERAL1
 kTempUpStr	LITERAL1
 kThreeLetterDayOfWeekStr	LITERAL1
+kTimeSep	LITERAL1
 kTimeoutMs	LITERAL1
 kTimerModeStr	LITERAL1
 kTimerStr	LITERAL1
diff --git a/src/IRac.cpp b/src/IRac.cpp
index 9379914b1..9623c98d7 100644
--- a/src/IRac.cpp
+++ b/src/IRac.cpp
@@ -50,6 +50,20 @@
 #include "ir_Voltas.h"
 #include "ir_Whirlpool.h"
 
+// On the ESP8266 platform we need to use a special version of string handling
+// 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<const char* const>(RHS))
+#else  // ESP8266
+#define STRCASECMP(LHS, RHS) strcasecmp(LHS, RHS)
+#endif  // ESP8266
+#endif  // STRCASECMP
+#ifndef PSTR
+#define PSTR
+#endif
+
 /// Class constructor
 /// @param[in] pin Gpio pin to use when transmitting IR messages.
 /// @param[in] inverted true, gpio output defaults to high. false, to low.
@@ -3059,31 +3073,31 @@ bool IRac::hasStateChanged(void) { return cmpStates(next, _prev); }
 /// @return The equivalent enum.
 stdAc::opmode_t IRac::strToOpmode(const char *str,
                                   const stdAc::opmode_t def) {
-  if (!strcasecmp(str, kAutoStr) ||
-      !strcasecmp(str, kAutomaticStr))
+  if (!STRCASECMP(str, kAutoStr) ||
+      !STRCASECMP(str, kAutomaticStr))
     return stdAc::opmode_t::kAuto;
-  else if (!strcasecmp(str, kOffStr) ||
-           !strcasecmp(str, kStopStr))
+  else if (!STRCASECMP(str, kOffStr) ||
+           !STRCASECMP(str, kStopStr))
     return stdAc::opmode_t::kOff;
-  else if (!strcasecmp(str, kCoolStr) ||
-           !strcasecmp(str, "COOLING"))
+  else if (!STRCASECMP(str, kCoolStr) ||
+           !STRCASECMP(str, PSTR("COOLING")))
     return stdAc::opmode_t::kCool;
-  else if (!strcasecmp(str, kHeatStr) ||
-           !strcasecmp(str, "HEATING"))
+  else if (!STRCASECMP(str, kHeatStr) ||
+           !STRCASECMP(str, PSTR("HEATING")))
     return stdAc::opmode_t::kHeat;
-  else if (!strcasecmp(str, kDryStr) ||
-           !strcasecmp(str, "DRYING") ||
-           !strcasecmp(str, "DEHUMIDIFY"))
+  else if (!STRCASECMP(str, kDryStr) ||
+           !STRCASECMP(str, PSTR("DRYING")) ||
+           !STRCASECMP(str, PSTR("DEHUMIDIFY")))
     return stdAc::opmode_t::kDry;
-  else if (!strcasecmp(str, kFanStr) ||
+  else if (!STRCASECMP(str, kFanStr) ||
           // The following Fans strings with "only" are required to help with
           // HomeAssistant & Google Home Climate integration.
           // For compatibility only.
           // Ref: https://www.home-assistant.io/integrations/google_assistant/#climate-operation-modes
-           !strcasecmp(str, kFanOnlyStr) ||
-           !strcasecmp(str, kFan_OnlyStr) ||
-           !strcasecmp(str, kFanOnlyWithSpaceStr) ||
-           !strcasecmp(str, kFanOnlyNoSpaceStr))
+           !STRCASECMP(str, kFanOnlyStr) ||
+           !STRCASECMP(str, kFan_OnlyStr) ||
+           !STRCASECMP(str, kFanOnlyWithSpaceStr) ||
+           !STRCASECMP(str, kFanOnlyNoSpaceStr))
     return stdAc::opmode_t::kFan;
   else
     return def;
@@ -3095,26 +3109,26 @@ stdAc::opmode_t IRac::strToOpmode(const char *str,
 /// @return The equivalent enum.
 stdAc::fanspeed_t IRac::strToFanspeed(const char *str,
                                       const stdAc::fanspeed_t def) {
-  if (!strcasecmp(str, kAutoStr) ||
-      !strcasecmp(str, kAutomaticStr))
+  if (!STRCASECMP(str, kAutoStr) ||
+      !STRCASECMP(str, kAutomaticStr))
     return stdAc::fanspeed_t::kAuto;
-  else if (!strcasecmp(str, kMinStr) ||
-           !strcasecmp(str, kMinimumStr) ||
-           !strcasecmp(str, kLowestStr))
+  else if (!STRCASECMP(str, kMinStr) ||
+           !STRCASECMP(str, kMinimumStr) ||
+           !STRCASECMP(str, kLowestStr))
     return stdAc::fanspeed_t::kMin;
-  else if (!strcasecmp(str, kLowStr) ||
-           !strcasecmp(str, kLoStr))
+  else if (!STRCASECMP(str, kLowStr) ||
+           !STRCASECMP(str, kLoStr))
     return stdAc::fanspeed_t::kLow;
-  else if (!strcasecmp(str, kMedStr) ||
-           !strcasecmp(str, kMediumStr) ||
-           !strcasecmp(str, kMidStr))
+  else if (!STRCASECMP(str, kMedStr) ||
+           !STRCASECMP(str, kMediumStr) ||
+           !STRCASECMP(str, kMidStr))
     return stdAc::fanspeed_t::kMedium;
-  else if (!strcasecmp(str, kHighStr) ||
-           !strcasecmp(str, kHiStr))
+  else if (!STRCASECMP(str, kHighStr) ||
+           !STRCASECMP(str, kHiStr))
     return stdAc::fanspeed_t::kHigh;
-  else if (!strcasecmp(str, kMaxStr) ||
-           !strcasecmp(str, kMaximumStr) ||
-           !strcasecmp(str, kHighestStr))
+  else if (!STRCASECMP(str, kMaxStr) ||
+           !STRCASECMP(str, kMaximumStr) ||
+           !STRCASECMP(str, kHighestStr))
     return stdAc::fanspeed_t::kMax;
   else
     return def;
@@ -3126,36 +3140,36 @@ stdAc::fanspeed_t IRac::strToFanspeed(const char *str,
 /// @return The equivalent enum.
 stdAc::swingv_t IRac::strToSwingV(const char *str,
                                   const stdAc::swingv_t def) {
-  if (!strcasecmp(str, kAutoStr) ||
-      !strcasecmp(str, kAutomaticStr) ||
-      !strcasecmp(str, kOnStr) ||
-      !strcasecmp(str, kSwingStr))
+  if (!STRCASECMP(str, kAutoStr) ||
+      !STRCASECMP(str, kAutomaticStr) ||
+      !STRCASECMP(str, kOnStr) ||
+      !STRCASECMP(str, kSwingStr))
     return stdAc::swingv_t::kAuto;
-  else if (!strcasecmp(str, kOffStr) ||
-           !strcasecmp(str, kStopStr))
+  else if (!STRCASECMP(str, kOffStr) ||
+           !STRCASECMP(str, kStopStr))
     return stdAc::swingv_t::kOff;
-  else if (!strcasecmp(str, kMinStr) ||
-           !strcasecmp(str, kMinimumStr) ||
-           !strcasecmp(str, kLowestStr) ||
-           !strcasecmp(str, kBottomStr) ||
-           !strcasecmp(str, kDownStr))
+  else if (!STRCASECMP(str, kMinStr) ||
+           !STRCASECMP(str, kMinimumStr) ||
+           !STRCASECMP(str, kLowestStr) ||
+           !STRCASECMP(str, kBottomStr) ||
+           !STRCASECMP(str, kDownStr))
     return stdAc::swingv_t::kLowest;
-  else if (!strcasecmp(str, kLowStr))
+  else if (!STRCASECMP(str, kLowStr))
     return stdAc::swingv_t::kLow;
-  else if (!strcasecmp(str, kMidStr) ||
-           !strcasecmp(str, kMiddleStr) ||
-           !strcasecmp(str, kMedStr) ||
-           !strcasecmp(str, kMediumStr) ||
-           !strcasecmp(str, kCentreStr))
+  else if (!STRCASECMP(str, kMidStr) ||
+           !STRCASECMP(str, kMiddleStr) ||
+           !STRCASECMP(str, kMedStr) ||
+           !STRCASECMP(str, kMediumStr) ||
+           !STRCASECMP(str, kCentreStr))
     return stdAc::swingv_t::kMiddle;
-  else if (!strcasecmp(str, kHighStr) ||
-           !strcasecmp(str, kHiStr))
+  else if (!STRCASECMP(str, kHighStr) ||
+           !STRCASECMP(str, kHiStr))
     return stdAc::swingv_t::kHigh;
-  else if (!strcasecmp(str, kHighestStr) ||
-           !strcasecmp(str, kMaxStr) ||
-           !strcasecmp(str, kMaximumStr) ||
-           !strcasecmp(str, kTopStr) ||
-           !strcasecmp(str, kUpStr))
+  else if (!STRCASECMP(str, kHighestStr) ||
+           !STRCASECMP(str, kMaxStr) ||
+           !STRCASECMP(str, kMaximumStr) ||
+           !STRCASECMP(str, kTopStr) ||
+           !STRCASECMP(str, kUpStr))
     return stdAc::swingv_t::kHighest;
   else
     return def;
@@ -3167,34 +3181,34 @@ stdAc::swingv_t IRac::strToSwingV(const char *str,
 /// @return The equivalent enum.
 stdAc::swingh_t IRac::strToSwingH(const char *str,
                                   const stdAc::swingh_t def) {
-  if (!strcasecmp(str, kAutoStr) ||
-      !strcasecmp(str, kAutomaticStr) ||
-      !strcasecmp(str, kOnStr) || !strcasecmp(str, kSwingStr))
+  if (!STRCASECMP(str, kAutoStr) ||
+      !STRCASECMP(str, kAutomaticStr) ||
+      !STRCASECMP(str, kOnStr) || !STRCASECMP(str, kSwingStr))
     return stdAc::swingh_t::kAuto;
-  else if (!strcasecmp(str, kOffStr) ||
-           !strcasecmp(str, kStopStr))
+  else if (!STRCASECMP(str, kOffStr) ||
+           !STRCASECMP(str, kStopStr))
     return stdAc::swingh_t::kOff;
-  else if (!strcasecmp(str, kLeftMaxStr) ||                // "LeftMax"
-           !strcasecmp(str, D_STR_LEFT " " D_STR_MAX) ||   // "Left Max"
-           !strcasecmp(str, D_STR_MAX D_STR_LEFT) ||       // "MaxLeft"
-           !strcasecmp(str, kMaxLeftStr))                  // "Max Left"
+  else if (!STRCASECMP(str, kLeftMaxStr) ||                     // "LeftMax"
+           !STRCASECMP(str, PSTR(D_STR_LEFT " " D_STR_MAX)) ||  // "Left Max"
+           !STRCASECMP(str, PSTR(D_STR_MAX D_STR_LEFT)) ||      // "MaxLeft"
+           !STRCASECMP(str, kMaxLeftStr))                       // "Max Left"
     return stdAc::swingh_t::kLeftMax;
-  else if (!strcasecmp(str, kLeftStr))
+  else if (!STRCASECMP(str, kLeftStr))
     return stdAc::swingh_t::kLeft;
-  else if (!strcasecmp(str, kMidStr) ||
-           !strcasecmp(str, kMiddleStr) ||
-           !strcasecmp(str, kMedStr) ||
-           !strcasecmp(str, kMediumStr) ||
-           !strcasecmp(str, kCentreStr))
+  else if (!STRCASECMP(str, kMidStr) ||
+           !STRCASECMP(str, kMiddleStr) ||
+           !STRCASECMP(str, kMedStr) ||
+           !STRCASECMP(str, kMediumStr) ||
+           !STRCASECMP(str, kCentreStr))
     return stdAc::swingh_t::kMiddle;
-  else if (!strcasecmp(str, kRightStr))
+  else if (!STRCASECMP(str, kRightStr))
     return stdAc::swingh_t::kRight;
-  else if (!strcasecmp(str, kRightMaxStr) ||               // "RightMax"
-           !strcasecmp(str, D_STR_RIGHT " " D_STR_MAX) ||  // "Right Max"
-           !strcasecmp(str, D_STR_MAX D_STR_RIGHT) ||      // "MaxRight"
-           !strcasecmp(str, kMaxRightStr))                 // "Max Right"
+  else if (!STRCASECMP(str, kRightMaxStr) ||                     // "RightMax"
+           !STRCASECMP(str, PSTR(D_STR_RIGHT " " D_STR_MAX)) ||  // "Right Max"
+           !STRCASECMP(str, PSTR(D_STR_MAX D_STR_RIGHT)) ||      // "MaxRight"
+           !STRCASECMP(str, kMaxRightStr))                       // "Max Right"
     return stdAc::swingh_t::kRightMax;
-  else if (!strcasecmp(str, kWideStr))
+  else if (!STRCASECMP(str, kWideStr))
     return stdAc::swingh_t::kWide;
   else
     return def;
@@ -3207,69 +3221,77 @@ stdAc::swingh_t IRac::strToSwingH(const char *str,
 /// @return The equivalent enum.
 int16_t IRac::strToModel(const char *str, const int16_t def) {
   // Gree
-  if (!strcasecmp(str, "YAW1F")) {
+  if (!STRCASECMP(str, PSTR("YAW1F"))) {
     return gree_ac_remote_model_t::YAW1F;
-  } else if (!strcasecmp(str, "YBOFB")) {
+  } else if (!STRCASECMP(str, PSTR("YBOFB"))) {
     return gree_ac_remote_model_t::YBOFB;
   // HitachiAc1 models
-  } else if (!strcasecmp(str, "R-LT0541-HTA-A")) {
+  } else if (!STRCASECMP(str, PSTR("R-LT0541-HTA-A"))) {
     return hitachi_ac1_remote_model_t::R_LT0541_HTA_A;
-  } else if (!strcasecmp(str, "R-LT0541-HTA-B")) {
+  } else if (!STRCASECMP(str, PSTR("R-LT0541-HTA-B"))) {
     return hitachi_ac1_remote_model_t::R_LT0541_HTA_B;
   // Fujitsu A/C models
-  } else if (!strcasecmp(str, "ARRAH2E")) {
+  } else if (!STRCASECMP(str, PSTR("ARRAH2E"))) {
     return fujitsu_ac_remote_model_t::ARRAH2E;
-  } else if (!strcasecmp(str, "ARDB1")) {
+  } else if (!STRCASECMP(str, PSTR("ARDB1"))) {
     return fujitsu_ac_remote_model_t::ARDB1;
-  } else if (!strcasecmp(str, "ARREB1E")) {
+  } else if (!STRCASECMP(str, PSTR("ARREB1E"))) {
     return fujitsu_ac_remote_model_t::ARREB1E;
-  } else if (!strcasecmp(str, "ARJW2")) {
+  } else if (!STRCASECMP(str, PSTR("ARJW2"))) {
     return fujitsu_ac_remote_model_t::ARJW2;
-  } else if (!strcasecmp(str, "ARRY4")) {
+  } else if (!STRCASECMP(str, PSTR("ARRY4"))) {
     return fujitsu_ac_remote_model_t::ARRY4;
   // LG A/C models
-  } else if (!strcasecmp(str, "GE6711AR2853M")) {
+  } else if (!STRCASECMP(str, PSTR("GE6711AR2853M"))) {
     return lg_ac_remote_model_t::GE6711AR2853M;
-  } else if (!strcasecmp(str, "AKB75215403")) {
+  } else if (!STRCASECMP(str, PSTR("AKB75215403"))) {
     return lg_ac_remote_model_t::AKB75215403;
-  } else if (!strcasecmp(str, "AKB74955603")) {
+  } else if (!STRCASECMP(str, PSTR("AKB74955603"))) {
     return lg_ac_remote_model_t::AKB74955603;
-  } else if (!strcasecmp(str, "AKB73757604")) {
+  } else if (!STRCASECMP(str, PSTR("AKB73757604"))) {
     return lg_ac_remote_model_t::AKB73757604;
   // Panasonic A/C families
-  } else if (!strcasecmp(str, "LKE") || !strcasecmp(str, "PANASONICLKE")) {
+  } else if (!STRCASECMP(str, PSTR("LKE")) ||
+             !STRCASECMP(str, PSTR("PANASONICLKE"))) {
     return panasonic_ac_remote_model_t::kPanasonicLke;
-  } else if (!strcasecmp(str, "NKE") || !strcasecmp(str, "PANASONICNKE")) {
+  } else if (!STRCASECMP(str, PSTR("NKE")) ||
+             !STRCASECMP(str, PSTR("PANASONICNKE"))) {
     return panasonic_ac_remote_model_t::kPanasonicNke;
-  } else if (!strcasecmp(str, "DKE") || !strcasecmp(str, "PANASONICDKE") ||
-             !strcasecmp(str, "PKR") || !strcasecmp(str, "PANASONICPKR")) {
+  } else if (!STRCASECMP(str, PSTR("DKE")) ||
+             !STRCASECMP(str, PSTR("PANASONICDKE")) ||
+             !STRCASECMP(str, PSTR("PKR")) ||
+             !STRCASECMP(str, PSTR("PANASONICPKR"))) {
     return panasonic_ac_remote_model_t::kPanasonicDke;
-  } else if (!strcasecmp(str, "JKE") || !strcasecmp(str, "PANASONICJKE")) {
+  } else if (!STRCASECMP(str, PSTR("JKE")) ||
+             !STRCASECMP(str, PSTR("PANASONICJKE"))) {
     return panasonic_ac_remote_model_t::kPanasonicJke;
-  } else if (!strcasecmp(str, "CKP") || !strcasecmp(str, "PANASONICCKP")) {
+  } else if (!STRCASECMP(str, PSTR("CKP")) ||
+             !STRCASECMP(str, PSTR("PANASONICCKP"))) {
     return panasonic_ac_remote_model_t::kPanasonicCkp;
-  } else if (!strcasecmp(str, "RKR") || !strcasecmp(str, "PANASONICRKR")) {
+  } else if (!STRCASECMP(str, PSTR("RKR")) ||
+             !STRCASECMP(str, PSTR("PANASONICRKR"))) {
     return panasonic_ac_remote_model_t::kPanasonicRkr;
   // Sharp A/C Models
-  } else if (!strcasecmp(str, "A907")) {
+  } else if (!STRCASECMP(str, PSTR("A907"))) {
     return sharp_ac_remote_model_t::A907;
-  } else if (!strcasecmp(str, "A705")) {
+  } else if (!STRCASECMP(str, PSTR("A705"))) {
     return sharp_ac_remote_model_t::A705;
-  } else if (!strcasecmp(str, "A903")) {
+  } else if (!STRCASECMP(str, PSTR("A903"))) {
     return sharp_ac_remote_model_t::A903;
   // TCL A/C Models
-  } else if (!strcasecmp(str, "TAC09CHSD")) {
+  } else if (!STRCASECMP(str, PSTR("TAC09CHSD"))) {
     return tcl_ac_remote_model_t::TAC09CHSD;
-  } else if (!strcasecmp(str, "GZ055BE1")) {
+  } else if (!STRCASECMP(str, PSTR("GZ055BE1"))) {
     return tcl_ac_remote_model_t::GZ055BE1;
   // Voltas A/C models
-  } else if (!strcasecmp(str, "122LZF")) {
+  } else if (!STRCASECMP(str, PSTR("122LZF"))) {
     return voltas_ac_remote_model_t::kVoltas122LZF;
   // Whirlpool A/C models
-  } else if (!strcasecmp(str, "DG11J13A") || !strcasecmp(str, "DG11J104") ||
-             !strcasecmp(str, "DG11J1-04")) {
+  } else if (!STRCASECMP(str, PSTR("DG11J13A")) ||
+             !STRCASECMP(str, PSTR("DG11J104")) ||
+             !STRCASECMP(str, PSTR("DG11J1-04"))) {
     return whirlpool_ac_remote_model_t::DG11J13A;
-  } else if (!strcasecmp(str, "DG11J191")) {
+  } else if (!STRCASECMP(str, PSTR("DG11J191"))) {
     return whirlpool_ac_remote_model_t::DG11J191;
   } else {
     int16_t number = atoi(str);
@@ -3285,15 +3307,15 @@ int16_t IRac::strToModel(const char *str, const int16_t def) {
 /// @param[in] def The boolean value to return if no conversion was possible.
 /// @return The equivalent boolean value.
 bool IRac::strToBool(const char *str, const bool def) {
-  if (!strcasecmp(str, kOnStr) ||
-      !strcasecmp(str, "1") ||
-      !strcasecmp(str, kYesStr) ||
-      !strcasecmp(str, kTrueStr))
+  if (!STRCASECMP(str, kOnStr) ||
+      !STRCASECMP(str, PSTR("1")) ||
+      !STRCASECMP(str, kYesStr) ||
+      !STRCASECMP(str, kTrueStr))
     return true;
-  else if (!strcasecmp(str, kOffStr) ||
-           !strcasecmp(str, "0") ||
-           !strcasecmp(str, kNoStr) ||
-           !strcasecmp(str, kFalseStr))
+  else if (!STRCASECMP(str, kOffStr) ||
+           !STRCASECMP(str, PSTR("0")) ||
+           !STRCASECMP(str, kNoStr) ||
+           !STRCASECMP(str, kFalseStr))
     return false;
   else
     return def;
diff --git a/src/IRtext.cpp b/src/IRtext.cpp
index a8959d43e..8cca392b3 100644
--- a/src/IRtext.cpp
+++ b/src/IRtext.cpp
@@ -1,9 +1,10 @@
-// Copyright 2019-2020 - David Conran (@crankyoldgit)
+// Copyright 2019-2021 - David Conran (@crankyoldgit)
 
 /// @file IRtext.cpp
 /// @warning If you add or remove an entry in this file, you should run:
 ///   '../tools/generate_irtext_h.sh' to rebuild the `IRtext.h` file.
 
+#include "IRtext.h"
 #ifndef UNIT_TEST
 #include <Arduino.h>
 #endif  // UNIT_TEST
@@ -14,185 +15,205 @@
 #define PROGMEM  // Pretend we have the PROGMEM macro even if we really don't.
 #endif
 
+#ifndef FPSTR
+#define FPSTR(X) X  // Also pretend we have flash-string helper class cast.
+#endif
+
+#define IRTEXT_CONST_BLOB_NAME(NAME)\
+    NAME ## Blob
+
+#define IRTEXT_CONST_BLOB_DECL(NAME)\
+    const char IRTEXT_CONST_BLOB_NAME(NAME) [] PROGMEM
+
+#define IRTEXT_CONST_BLOB_PTR(NAME)\
+    IRTEXT_CONST_PTR(NAME) {\
+        IRTEXT_CONST_PTR_CAST(IRTEXT_CONST_BLOB_NAME(NAME)) }
+
+#define IRTEXT_CONST_STRING(NAME, VALUE)\
+    static IRTEXT_CONST_BLOB_DECL(NAME) { VALUE };\
+    IRTEXT_CONST_PTR(NAME) PROGMEM {\
+        IRTEXT_CONST_PTR_CAST(&(IRTEXT_CONST_BLOB_NAME(NAME))[0]) }
+
 // Common
-const PROGMEM char* kUnknownStr = D_STR_UNKNOWN;  ///< "Unknown"
-const PROGMEM char* kProtocolStr = D_STR_PROTOCOL;  ///< "Protocol"
-const PROGMEM char* kPowerStr = D_STR_POWER;  ///< "Power"
-const PROGMEM char* kOnStr = D_STR_ON;  ///< "On"
-const PROGMEM char* kOffStr = D_STR_OFF;  ///< "Off"
-const PROGMEM char* kModeStr = D_STR_MODE;  ///< "Mode"
-const PROGMEM char* kToggleStr = D_STR_TOGGLE;  ///< "Toggle"
-const PROGMEM char* kTurboStr = D_STR_TURBO;  ///< "Turbo"
-const PROGMEM char* kSuperStr = D_STR_SUPER;  ///< "Super"
-const PROGMEM char* kSleepStr = D_STR_SLEEP;  ///< "Sleep"
-const PROGMEM char* kLightStr = D_STR_LIGHT;  ///< "Light"
-const PROGMEM char* kPowerfulStr = D_STR_POWERFUL;  ///< "Powerful"
-const PROGMEM char* kQuietStr = D_STR_QUIET;  ///< "Quiet"
-const PROGMEM char* kEconoStr = D_STR_ECONO;  ///< "Econo"
-const PROGMEM char* kSwingStr = D_STR_SWING;  ///< "Swing"
-const PROGMEM char* kSwingHStr = D_STR_SWINGH;  ///< "SwingH"
-const PROGMEM char* kSwingVStr = D_STR_SWINGV;  ///< "SwingV"
-const PROGMEM char* kBeepStr = D_STR_BEEP;  ///< "Beep"
-const PROGMEM char* kZoneFollowStr = D_STR_ZONEFOLLOW;  ///< "Zone Follow"
-const PROGMEM char* kFixedStr = D_STR_FIXED;  ///< "Fixed"
-const PROGMEM char* kMouldStr = D_STR_MOULD;  ///< "Mould"
-const PROGMEM char* kCleanStr = D_STR_CLEAN;  ///< "Clean"
-const PROGMEM char* kPurifyStr = D_STR_PURIFY;  ///< "Purify"
-const PROGMEM char* kTimerStr = D_STR_TIMER;  ///< "Timer"
-const PROGMEM char* kOnTimerStr = D_STR_ONTIMER;  ///< "On Timer"
-const PROGMEM char* kOffTimerStr = D_STR_OFFTIMER;  ///< "Off Timer"
-const PROGMEM char* kTimerModeStr = D_STR_TIMERMODE;  ///< "Timer Mode"
-const PROGMEM char* kClockStr = D_STR_CLOCK;  ///< "Clock"
-const PROGMEM char* kCommandStr = D_STR_COMMAND;  ///< "Command"
-const PROGMEM char* kXFanStr = D_STR_XFAN;  ///< "XFan"
-const PROGMEM char* kHealthStr = D_STR_HEALTH;  ///< "Health"
-const PROGMEM char* kModelStr = D_STR_MODEL;  ///< "Model"
-const PROGMEM char* kTempStr = D_STR_TEMP;  ///< "Temp"
-const PROGMEM char* kIFeelStr = D_STR_IFEEL;  ///< "IFeel"
-const PROGMEM char* kHumidStr = D_STR_HUMID;  ///< "Humid"
-const PROGMEM char* kSaveStr = D_STR_SAVE;  ///< "Save"
-const PROGMEM char* kEyeStr = D_STR_EYE;  ///< "Eye"
-const PROGMEM char* kFollowStr = D_STR_FOLLOW;  ///< "Follow"
-const PROGMEM char* kIonStr = D_STR_ION;  ///< "Ion"
-const PROGMEM char* kFreshStr = D_STR_FRESH;  ///< "Fresh"
-const PROGMEM char* kHoldStr = D_STR_HOLD;  ///< "Hold"
-const PROGMEM char* kButtonStr = D_STR_BUTTON;  ///< "Button"
-const PROGMEM char* k8CHeatStr = D_STR_8C_HEAT;  ///< "8C Heat"
-const PROGMEM char* k10CHeatStr = D_STR_10C_HEAT;  ///< "10C Heat"
-const PROGMEM char* kNightStr = D_STR_NIGHT;  ///< "Night"
-const PROGMEM char* kSilentStr = D_STR_SILENT;  ///< "Silent"
-const PROGMEM char* kFilterStr = D_STR_FILTER;  ///< "Filter"
-const PROGMEM char* k3DStr = D_STR_3D;  ///< "3D"
-const PROGMEM char* kCelsiusStr = D_STR_CELSIUS;  ///< "Celsius"
-const PROGMEM char* kCelsiusFahrenheitStr = D_STR_CELSIUS_FAHRENHEIT;  ///<
+IRTEXT_CONST_STRING(kUnknownStr, D_STR_UNKNOWN);  ///< "Unknown"
+IRTEXT_CONST_STRING(kProtocolStr, D_STR_PROTOCOL);  ///< "Protocol"
+IRTEXT_CONST_STRING(kPowerStr, D_STR_POWER);  ///< "Power"
+IRTEXT_CONST_STRING(kOnStr, D_STR_ON);  ///< "On"
+IRTEXT_CONST_STRING(kOffStr, D_STR_OFF);  ///< "Off"
+IRTEXT_CONST_STRING(kModeStr, D_STR_MODE);  ///< "Mode"
+IRTEXT_CONST_STRING(kToggleStr, D_STR_TOGGLE);  ///< "Toggle"
+IRTEXT_CONST_STRING(kTurboStr, D_STR_TURBO);  ///< "Turbo"
+IRTEXT_CONST_STRING(kSuperStr, D_STR_SUPER);  ///< "Super"
+IRTEXT_CONST_STRING(kSleepStr, D_STR_SLEEP);  ///< "Sleep"
+IRTEXT_CONST_STRING(kLightStr, D_STR_LIGHT);  ///< "Light"
+IRTEXT_CONST_STRING(kPowerfulStr, D_STR_POWERFUL);  ///< "Powerful"
+IRTEXT_CONST_STRING(kQuietStr, D_STR_QUIET);  ///< "Quiet"
+IRTEXT_CONST_STRING(kEconoStr, D_STR_ECONO);  ///< "Econo"
+IRTEXT_CONST_STRING(kSwingStr, D_STR_SWING);  ///< "Swing"
+IRTEXT_CONST_STRING(kSwingHStr, D_STR_SWINGH);  ///< "SwingH"
+IRTEXT_CONST_STRING(kSwingVStr, D_STR_SWINGV);  ///< "SwingV"
+IRTEXT_CONST_STRING(kBeepStr, D_STR_BEEP);  ///< "Beep"
+IRTEXT_CONST_STRING(kZoneFollowStr, D_STR_ZONEFOLLOW);  ///< "Zone Follow"
+IRTEXT_CONST_STRING(kFixedStr, D_STR_FIXED);  ///< "Fixed"
+IRTEXT_CONST_STRING(kMouldStr, D_STR_MOULD);  ///< "Mould"
+IRTEXT_CONST_STRING(kCleanStr, D_STR_CLEAN);  ///< "Clean"
+IRTEXT_CONST_STRING(kPurifyStr, D_STR_PURIFY);  ///< "Purify"
+IRTEXT_CONST_STRING(kTimerStr, D_STR_TIMER);  ///< "Timer"
+IRTEXT_CONST_STRING(kOnTimerStr, D_STR_ONTIMER);  ///< "On Timer"
+IRTEXT_CONST_STRING(kOffTimerStr, D_STR_OFFTIMER);  ///< "Off Timer"
+IRTEXT_CONST_STRING(kTimerModeStr, D_STR_TIMERMODE);  ///< "Timer Mode"
+IRTEXT_CONST_STRING(kClockStr, D_STR_CLOCK);  ///< "Clock"
+IRTEXT_CONST_STRING(kCommandStr, D_STR_COMMAND);  ///< "Command"
+IRTEXT_CONST_STRING(kXFanStr, D_STR_XFAN);  ///< "XFan"
+IRTEXT_CONST_STRING(kHealthStr, D_STR_HEALTH);  ///< "Health"
+IRTEXT_CONST_STRING(kModelStr, D_STR_MODEL);  ///< "Model"
+IRTEXT_CONST_STRING(kTempStr, D_STR_TEMP);  ///< "Temp"
+IRTEXT_CONST_STRING(kIFeelStr, D_STR_IFEEL);  ///< "IFeel"
+IRTEXT_CONST_STRING(kHumidStr, D_STR_HUMID);  ///< "Humid"
+IRTEXT_CONST_STRING(kSaveStr, D_STR_SAVE);  ///< "Save"
+IRTEXT_CONST_STRING(kEyeStr, D_STR_EYE);  ///< "Eye"
+IRTEXT_CONST_STRING(kFollowStr, D_STR_FOLLOW);  ///< "Follow"
+IRTEXT_CONST_STRING(kIonStr, D_STR_ION);  ///< "Ion"
+IRTEXT_CONST_STRING(kFreshStr, D_STR_FRESH);  ///< "Fresh"
+IRTEXT_CONST_STRING(kHoldStr, D_STR_HOLD);  ///< "Hold"
+IRTEXT_CONST_STRING(kButtonStr, D_STR_BUTTON);  ///< "Button"
+IRTEXT_CONST_STRING(k8CHeatStr, D_STR_8C_HEAT);  ///< "8C Heat"
+IRTEXT_CONST_STRING(k10CHeatStr, D_STR_10C_HEAT);  ///< "10C Heat"
+IRTEXT_CONST_STRING(kNightStr, D_STR_NIGHT);  ///< "Night"
+IRTEXT_CONST_STRING(kSilentStr, D_STR_SILENT);  ///< "Silent"
+IRTEXT_CONST_STRING(kFilterStr, D_STR_FILTER);  ///< "Filter"
+IRTEXT_CONST_STRING(k3DStr, D_STR_3D);  ///< "3D"
+IRTEXT_CONST_STRING(kCelsiusStr, D_STR_CELSIUS);  ///< "Celsius"
+IRTEXT_CONST_STRING(kCelsiusFahrenheitStr, D_STR_CELSIUS_FAHRENHEIT);  ///<
 ///< "Celsius/Fahrenheit"
-const PROGMEM char* kTempUpStr = D_STR_TEMPUP;  ///< "Temp Up"
-const PROGMEM char* kTempDownStr = D_STR_TEMPDOWN;  ///< "Temp Down"
-const PROGMEM char* kStartStr = D_STR_START;  ///< "Start"
-const PROGMEM char* kStopStr = D_STR_STOP;  ///< "Stop"
-const PROGMEM char* kMoveStr = D_STR_MOVE;  ///< "Move"
-const PROGMEM char* kSetStr = D_STR_SET;  ///< "Set"
-const PROGMEM char* kCancelStr = D_STR_CANCEL;  ///< "Cancel"
-const PROGMEM char* kUpStr = D_STR_UP;  ///< "Up"
-const PROGMEM char* kDownStr = D_STR_DOWN;  ///< "Down"
-const PROGMEM char* kChangeStr = D_STR_CHANGE;  ///< "Change"
-const PROGMEM char* kComfortStr = D_STR_COMFORT;  ///< "Comfort"
-const PROGMEM char* kSensorStr = D_STR_SENSOR;  ///< "Sensor"
-const PROGMEM char* kWeeklyTimerStr = D_STR_WEEKLYTIMER;  ///< "WeeklyTimer"
-const PROGMEM char* kWifiStr = D_STR_WIFI;  ///< "Wifi"
-const PROGMEM char* kLastStr = D_STR_LAST;  ///< "Last"
-const PROGMEM char* kFastStr = D_STR_FAST;  ///< "Fast"
-const PROGMEM char* kSlowStr = D_STR_SLOW;  ///< "Slow"
-const PROGMEM char* kAirFlowStr = D_STR_AIRFLOW;  ///< "Air Flow"
-const PROGMEM char* kStepStr = D_STR_STEP;  ///< "Step"
-const PROGMEM char* kNAStr = D_STR_NA;  ///< "N/A"
-const PROGMEM char* kInsideStr = D_STR_INSIDE;  ///< "Inside"
-const PROGMEM char* kOutsideStr = D_STR_OUTSIDE;  ///< "Outside"
-const PROGMEM char* kLoudStr = D_STR_LOUD;  ///< "Loud"
-const PROGMEM char* kLowerStr = D_STR_LOWER;  ///< "Lower"
-const PROGMEM char* kUpperStr = D_STR_UPPER;  ///< "Upper"
-const PROGMEM char* kBreezeStr = D_STR_BREEZE;  ///< "Breeze"
-const PROGMEM char* kCirculateStr = D_STR_CIRCULATE;  ///< "Circulate"
-const PROGMEM char* kCeilingStr = D_STR_CEILING;  ///< "Ceiling"
-const PROGMEM char* kWallStr = D_STR_WALL;  ///< "Wall"
-const PROGMEM char* kRoomStr = D_STR_ROOM;  ///< "Room"
-const PROGMEM char* k6thSenseStr = D_STR_6THSENSE;  ///< "6th Sense"
-const PROGMEM char* kTypeStr = D_STR_TYPE;  ///< "Type"
-const PROGMEM char* kSpecialStr = D_STR_SPECIAL;  ///< "Special"
-const PROGMEM char* kIdStr = D_STR_ID;  ///< "Id" / Device Identifier
-const PROGMEM char* kVaneStr = D_STR_VANE;  ///< "Vane"
+IRTEXT_CONST_STRING(kTempUpStr, D_STR_TEMPUP);  ///< "Temp Up"
+IRTEXT_CONST_STRING(kTempDownStr, D_STR_TEMPDOWN);  ///< "Temp Down"
+IRTEXT_CONST_STRING(kStartStr, D_STR_START);  ///< "Start"
+IRTEXT_CONST_STRING(kStopStr, D_STR_STOP);  ///< "Stop"
+IRTEXT_CONST_STRING(kMoveStr, D_STR_MOVE);  ///< "Move"
+IRTEXT_CONST_STRING(kSetStr, D_STR_SET);  ///< "Set"
+IRTEXT_CONST_STRING(kCancelStr, D_STR_CANCEL);  ///< "Cancel"
+IRTEXT_CONST_STRING(kUpStr, D_STR_UP);  ///< "Up"
+IRTEXT_CONST_STRING(kDownStr, D_STR_DOWN);  ///< "Down"
+IRTEXT_CONST_STRING(kChangeStr, D_STR_CHANGE);  ///< "Change"
+IRTEXT_CONST_STRING(kComfortStr, D_STR_COMFORT);  ///< "Comfort"
+IRTEXT_CONST_STRING(kSensorStr, D_STR_SENSOR);  ///< "Sensor"
+IRTEXT_CONST_STRING(kWeeklyTimerStr, D_STR_WEEKLYTIMER);  ///< "WeeklyTimer"
+IRTEXT_CONST_STRING(kWifiStr, D_STR_WIFI);  ///< "Wifi"
+IRTEXT_CONST_STRING(kLastStr, D_STR_LAST);  ///< "Last"
+IRTEXT_CONST_STRING(kFastStr, D_STR_FAST);  ///< "Fast"
+IRTEXT_CONST_STRING(kSlowStr, D_STR_SLOW);  ///< "Slow"
+IRTEXT_CONST_STRING(kAirFlowStr, D_STR_AIRFLOW);  ///< "Air Flow"
+IRTEXT_CONST_STRING(kStepStr, D_STR_STEP);  ///< "Step"
+IRTEXT_CONST_STRING(kNAStr, D_STR_NA);  ///< "N/A"
+IRTEXT_CONST_STRING(kInsideStr, D_STR_INSIDE);  ///< "Inside"
+IRTEXT_CONST_STRING(kOutsideStr, D_STR_OUTSIDE);  ///< "Outside"
+IRTEXT_CONST_STRING(kLoudStr, D_STR_LOUD);  ///< "Loud"
+IRTEXT_CONST_STRING(kLowerStr, D_STR_LOWER);  ///< "Lower"
+IRTEXT_CONST_STRING(kUpperStr, D_STR_UPPER);  ///< "Upper"
+IRTEXT_CONST_STRING(kBreezeStr, D_STR_BREEZE);  ///< "Breeze"
+IRTEXT_CONST_STRING(kCirculateStr, D_STR_CIRCULATE);  ///< "Circulate"
+IRTEXT_CONST_STRING(kCeilingStr, D_STR_CEILING);  ///< "Ceiling"
+IRTEXT_CONST_STRING(kWallStr, D_STR_WALL);  ///< "Wall"
+IRTEXT_CONST_STRING(kRoomStr, D_STR_ROOM);  ///< "Room"
+IRTEXT_CONST_STRING(k6thSenseStr, D_STR_6THSENSE);  ///< "6th Sense"
+IRTEXT_CONST_STRING(kTypeStr, D_STR_TYPE);  ///< "Type"
+IRTEXT_CONST_STRING(kSpecialStr, D_STR_SPECIAL);  ///< "Special"
+IRTEXT_CONST_STRING(kIdStr, D_STR_ID);  ///< "Id" / Device Identifier
+IRTEXT_CONST_STRING(kVaneStr, D_STR_VANE);  ///< "Vane"
 
-const PROGMEM char* kAutoStr = D_STR_AUTO;  ///< "Auto"
-const PROGMEM char* kAutomaticStr = D_STR_AUTOMATIC;  ///< "Automatic"
-const PROGMEM char* kManualStr = D_STR_MANUAL;  ///< "Manual"
-const PROGMEM char* kCoolStr = D_STR_COOL;  ///< "Cool"
-const PROGMEM char* kHeatStr = D_STR_HEAT;  ///< "Heat"
-const PROGMEM char* kDryStr = D_STR_DRY;  ///< "Dry"
-const PROGMEM char* kFanStr = D_STR_FAN;  ///< "Fan"
+IRTEXT_CONST_STRING(kAutoStr, D_STR_AUTO);  ///< "Auto"
+IRTEXT_CONST_STRING(kAutomaticStr, D_STR_AUTOMATIC);  ///< "Automatic"
+IRTEXT_CONST_STRING(kManualStr, D_STR_MANUAL);  ///< "Manual"
+IRTEXT_CONST_STRING(kCoolStr, D_STR_COOL);  ///< "Cool"
+IRTEXT_CONST_STRING(kHeatStr, D_STR_HEAT);  ///< "Heat"
+IRTEXT_CONST_STRING(kDryStr, D_STR_DRY);  ///< "Dry"
+IRTEXT_CONST_STRING(kFanStr, D_STR_FAN);  ///< "Fan"
 // The following Fans strings with "only" are required to help with
 // HomeAssistant & Google Home Climate integration. For compatibility only.
 // Ref: https://www.home-assistant.io/integrations/google_assistant/#climate-operation-modes
-const PROGMEM char* kFanOnlyStr = D_STR_FANONLY;  ///< "fan-only"
-const PROGMEM char* kFan_OnlyStr = D_STR_FAN_ONLY;  ///< "fan_only" (legacy use)
-const PROGMEM char* kFanOnlyWithSpaceStr = D_STR_FANSPACEONLY;  ///< "Fan Only"
-const PROGMEM char* kFanOnlyNoSpaceStr = D_STR_FANONLYNOSPACE;  ///< "FanOnly"
+IRTEXT_CONST_STRING(kFanOnlyStr, D_STR_FANONLY);  ///< "fan-only"
+IRTEXT_CONST_STRING(kFan_OnlyStr, D_STR_FAN_ONLY);  ///< "fan_only" (legacy)
+IRTEXT_CONST_STRING(kFanOnlyWithSpaceStr, D_STR_FANSPACEONLY);  ///< "Fan Only"
+IRTEXT_CONST_STRING(kFanOnlyNoSpaceStr, D_STR_FANONLYNOSPACE);  ///< "FanOnly"
 
-const PROGMEM char* kRecycleStr = D_STR_RECYCLE;  ///< "Recycle"
+IRTEXT_CONST_STRING(kRecycleStr, D_STR_RECYCLE);  ///< "Recycle"
 
-const PROGMEM char* kMaxStr = D_STR_MAX;  ///< "Max"
-const PROGMEM char* kMaximumStr = D_STR_MAXIMUM;  ///< "Maximum"
-const PROGMEM char* kMinStr = D_STR_MIN;  ///< "Min"
-const PROGMEM char* kMinimumStr = D_STR_MINIMUM;  ///< "Minimum"
-const PROGMEM char* kMedStr = D_STR_MED;  ///< "Med"
-const PROGMEM char* kMediumStr = D_STR_MEDIUM;  ///< "Medium"
+IRTEXT_CONST_STRING(kMaxStr, D_STR_MAX);  ///< "Max"
+IRTEXT_CONST_STRING(kMaximumStr, D_STR_MAXIMUM);  ///< "Maximum"
+IRTEXT_CONST_STRING(kMinStr, D_STR_MIN);  ///< "Min"
+IRTEXT_CONST_STRING(kMinimumStr, D_STR_MINIMUM);  ///< "Minimum"
+IRTEXT_CONST_STRING(kMedStr, D_STR_MED);  ///< "Med"
+IRTEXT_CONST_STRING(kMediumStr, D_STR_MEDIUM);  ///< "Medium"
 
-const PROGMEM char* kHighestStr = D_STR_HIGHEST;  ///< "Highest"
-const PROGMEM char* kHighStr = D_STR_HIGH;  ///< "High"
-const PROGMEM char* kHiStr = D_STR_HI;  ///< "Hi"
-const PROGMEM char* kMidStr = D_STR_MID;  ///< "Mid"
-const PROGMEM char* kMiddleStr = D_STR_MIDDLE;  ///< "Middle"
-const PROGMEM char* kLowStr = D_STR_LOW;  ///< "Low"
-const PROGMEM char* kLoStr = D_STR_LO;  ///< "Lo"
-const PROGMEM char* kLowestStr = D_STR_LOWEST;  ///< "Lowest"
-const PROGMEM char* kMaxRightStr = D_STR_MAXRIGHT;  ///< "Max Right"
-const PROGMEM char* kRightMaxStr = D_STR_RIGHTMAX_NOSPACE;  ///< "RightMax"
-const PROGMEM char* kRightStr = D_STR_RIGHT;  ///< "Right"
-const PROGMEM char* kLeftStr = D_STR_LEFT;  ///< "Left"
-const PROGMEM char* kMaxLeftStr = D_STR_MAXLEFT;  ///< "Max Left"
-const PROGMEM char* kLeftMaxStr = D_STR_LEFTMAX_NOSPACE;  ///< "LeftMax"
-const PROGMEM char* kWideStr = D_STR_WIDE;  ///< "Wide"
-const PROGMEM char* kCentreStr = D_STR_CENTRE;  ///< "Centre"
-const PROGMEM char* kTopStr = D_STR_TOP;  ///< "Top"
-const PROGMEM char* kBottomStr = D_STR_BOTTOM;  ///< "Bottom"
+IRTEXT_CONST_STRING(kHighestStr, D_STR_HIGHEST);  ///< "Highest"
+IRTEXT_CONST_STRING(kHighStr, D_STR_HIGH);  ///< "High"
+IRTEXT_CONST_STRING(kHiStr, D_STR_HI);  ///< "Hi"
+IRTEXT_CONST_STRING(kMidStr, D_STR_MID);  ///< "Mid"
+IRTEXT_CONST_STRING(kMiddleStr, D_STR_MIDDLE);  ///< "Middle"
+IRTEXT_CONST_STRING(kLowStr, D_STR_LOW);  ///< "Low"
+IRTEXT_CONST_STRING(kLoStr, D_STR_LO);  ///< "Lo"
+IRTEXT_CONST_STRING(kLowestStr, D_STR_LOWEST);  ///< "Lowest"
+IRTEXT_CONST_STRING(kMaxRightStr, D_STR_MAXRIGHT);  ///< "Max Right"
+IRTEXT_CONST_STRING(kRightMaxStr, D_STR_RIGHTMAX_NOSPACE);  ///< "RightMax"
+IRTEXT_CONST_STRING(kRightStr, D_STR_RIGHT);  ///< "Right"
+IRTEXT_CONST_STRING(kLeftStr, D_STR_LEFT);  ///< "Left"
+IRTEXT_CONST_STRING(kMaxLeftStr, D_STR_MAXLEFT);  ///< "Max Left"
+IRTEXT_CONST_STRING(kLeftMaxStr, D_STR_LEFTMAX_NOSPACE);  ///< "LeftMax"
+IRTEXT_CONST_STRING(kWideStr, D_STR_WIDE);  ///< "Wide"
+IRTEXT_CONST_STRING(kCentreStr, D_STR_CENTRE);  ///< "Centre"
+IRTEXT_CONST_STRING(kTopStr, D_STR_TOP);  ///< "Top"
+IRTEXT_CONST_STRING(kBottomStr, D_STR_BOTTOM);  ///< "Bottom"
 
 // Compound words/phrases/descriptions from pre-defined words.
-const PROGMEM char* kEconoToggleStr = D_STR_ECONOTOGGLE;  ///< "Econo Toggle"
-const PROGMEM char* kEyeAutoStr = D_STR_EYEAUTO;  ///< "Eye Auto"
-const PROGMEM char* kLightToggleStr = D_STR_LIGHTTOGGLE;  ///< "Light Toggle"
-const PROGMEM char* kOutsideQuietStr = D_STR_OUTSIDEQUIET;  ///< "Outside Quiet"
-const PROGMEM char* kPowerToggleStr = D_STR_POWERTOGGLE;  ///< "Power Toggle"
-const PROGMEM char* kPowerButtonStr = D_STR_POWERBUTTON;  ///< "Power Button"
-const PROGMEM char* kPreviousPowerStr = D_STR_PREVIOUSPOWER;  ///<
+IRTEXT_CONST_STRING(kEconoToggleStr, D_STR_ECONOTOGGLE);  ///< "Econo Toggle"
+IRTEXT_CONST_STRING(kEyeAutoStr, D_STR_EYEAUTO);  ///< "Eye Auto"
+IRTEXT_CONST_STRING(kLightToggleStr, D_STR_LIGHTTOGGLE);  ///< "Light Toggle"
+///< "Outside Quiet"
+IRTEXT_CONST_STRING(kOutsideQuietStr, D_STR_OUTSIDEQUIET);
+IRTEXT_CONST_STRING(kPowerToggleStr, D_STR_POWERTOGGLE);  ///< "Power Toggle"
+IRTEXT_CONST_STRING(kPowerButtonStr, D_STR_POWERBUTTON);  ///< "Power Button"
+IRTEXT_CONST_STRING(kPreviousPowerStr, D_STR_PREVIOUSPOWER);  ///<
 ///< "Previous Power"
-const PROGMEM char* kDisplayTempStr = D_STR_DISPLAYTEMP;  ///< "Display Temp"
-const PROGMEM char* kSensorTempStr = D_STR_SENSORTEMP;  ///< "Sensor Temp"
-const PROGMEM char* kSleepTimerStr = D_STR_SLEEP_TIMER;  ///< "Sleep Timer"
-const PROGMEM char* kSwingVModeStr = D_STR_SWINGVMODE;  ///< "Swing(V) Mode"
-const PROGMEM char* kSwingVToggleStr = D_STR_SWINGVTOGGLE;  ///<
+IRTEXT_CONST_STRING(kDisplayTempStr, D_STR_DISPLAYTEMP);  ///< "Display Temp"
+IRTEXT_CONST_STRING(kSensorTempStr, D_STR_SENSORTEMP);  ///< "Sensor Temp"
+IRTEXT_CONST_STRING(kSleepTimerStr, D_STR_SLEEP_TIMER);  ///< "Sleep Timer"
+IRTEXT_CONST_STRING(kSwingVModeStr, D_STR_SWINGVMODE);  ///< "Swing(V) Mode"
+IRTEXT_CONST_STRING(kSwingVToggleStr, D_STR_SWINGVTOGGLE);  ///<
 ///< "Swing(V) Toggle"
-const PROGMEM char* kTurboToggleStr = D_STR_TURBOTOGGLE;  ///< "Turbo Toggle"
+IRTEXT_CONST_STRING(kTurboToggleStr, D_STR_TURBOTOGGLE);  ///< "Turbo Toggle"
 
 // Separators
-char kTimeSep = D_CHR_TIME_SEP;  ///< ':'
-const PROGMEM char* kSpaceLBraceStr = D_STR_SPACELBRACE;  ///< " ("
-const PROGMEM char* kCommaSpaceStr = D_STR_COMMASPACE;  ///< ", "
-const PROGMEM char* kColonSpaceStr = D_STR_COLONSPACE;  ///< ": "
+const char kTimeSep = D_CHR_TIME_SEP;  ///< ':'
+IRTEXT_CONST_STRING(kSpaceLBraceStr, D_STR_SPACELBRACE);  ///< " ("
+IRTEXT_CONST_STRING(kCommaSpaceStr, D_STR_COMMASPACE);  ///< ", "
+IRTEXT_CONST_STRING(kColonSpaceStr, D_STR_COLONSPACE);  ///< ": "
 
 // IRutils
 //  - Time
-const PROGMEM char* kDayStr = D_STR_DAY;  ///< "Day"
-const PROGMEM char* kDaysStr = D_STR_DAYS;  ///< "Days"
-const PROGMEM char* kHourStr = D_STR_HOUR;  ///< "Hour"
-const PROGMEM char* kHoursStr = D_STR_HOURS;  ///< "Hours"
-const PROGMEM char* kMinuteStr = D_STR_MINUTE;  ///< "Minute"
-const PROGMEM char* kMinutesStr = D_STR_MINUTES;  ///< "Minutes"
-const PROGMEM char* kSecondStr = D_STR_SECOND;  ///< "Second"
-const PROGMEM char* kSecondsStr = D_STR_SECONDS;  ///< "Seconds"
-const PROGMEM char* kNowStr = D_STR_NOW;  ///< "Now"
-const PROGMEM char* kThreeLetterDayOfWeekStr = D_STR_THREELETTERDAYS;  ///<
+IRTEXT_CONST_STRING(kDayStr, D_STR_DAY);  ///< "Day"
+IRTEXT_CONST_STRING(kDaysStr, D_STR_DAYS);  ///< "Days"
+IRTEXT_CONST_STRING(kHourStr, D_STR_HOUR);  ///< "Hour"
+IRTEXT_CONST_STRING(kHoursStr, D_STR_HOURS);  ///< "Hours"
+IRTEXT_CONST_STRING(kMinuteStr, D_STR_MINUTE);  ///< "Minute"
+IRTEXT_CONST_STRING(kMinutesStr, D_STR_MINUTES);  ///< "Minutes"
+IRTEXT_CONST_STRING(kSecondStr, D_STR_SECOND);  ///< "Second"
+IRTEXT_CONST_STRING(kSecondsStr, D_STR_SECONDS);  ///< "Seconds"
+IRTEXT_CONST_STRING(kNowStr, D_STR_NOW);  ///< "Now"
+IRTEXT_CONST_STRING(kThreeLetterDayOfWeekStr, D_STR_THREELETTERDAYS);  ///<
 ///< "SunMonTueWedThuFriSat"
-const PROGMEM char* kYesStr = D_STR_YES;  ///< "Yes"
-const PROGMEM char* kNoStr = D_STR_NO;  ///< "No"
-const PROGMEM char* kTrueStr = D_STR_TRUE;  ///< "True"
-const PROGMEM char* kFalseStr = D_STR_FALSE;  ///< "False"
+IRTEXT_CONST_STRING(kYesStr, D_STR_YES);  ///< "Yes"
+IRTEXT_CONST_STRING(kNoStr, D_STR_NO);  ///< "No"
+IRTEXT_CONST_STRING(kTrueStr, D_STR_TRUE);  ///< "True"
+IRTEXT_CONST_STRING(kFalseStr, D_STR_FALSE);  ///< "False"
 
-const PROGMEM char* kRepeatStr = D_STR_REPEAT;  ///< "Repeat"
-const PROGMEM char* kCodeStr = D_STR_CODE;  ///< "Code"
-const PROGMEM char* kBitsStr = D_STR_BITS;  ///< "Bits"
+IRTEXT_CONST_STRING(kRepeatStr, D_STR_REPEAT);  ///< "Repeat"
+IRTEXT_CONST_STRING(kCodeStr, D_STR_CODE);  ///< "Code"
+IRTEXT_CONST_STRING(kBitsStr, D_STR_BITS);  ///< "Bits"
 
 // Protocol Names
 // Needs to be in decode_type_t order.
-const PROGMEM char *kAllProtocolNamesStr =
+IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
     D_STR_UNUSED "\x0"
     D_STR_RC5 "\x0"
     D_STR_RC6 "\x0"
@@ -302,4 +323,7 @@ const PROGMEM char *kAllProtocolNamesStr =
     D_STR_BOSE "\x0"
     D_STR_ARRIS "\x0"
     ///< New protocol strings should be added just above this line.
-    "\x0";  ///< This string requires double null termination.
+    "\x0"  ///< This string requires double null termination.
+};
+
+IRTEXT_CONST_BLOB_PTR(kAllProtocolNamesStr);
diff --git a/src/IRtext.h b/src/IRtext.h
index 20d1a958a..589cd38ef 100644
--- a/src/IRtext.h
+++ b/src/IRtext.h
@@ -12,161 +12,171 @@
 // Constant text to be shared across all object files.
 // This means there is only one copy of the character/string/text etc.
 
-extern char kTimeSep;
-extern const char* k10CHeatStr;
-extern const char* k3DStr;
-extern const char* k6thSenseStr;
-extern const char* k8CHeatStr;
-extern const char* kAirFlowStr;
-extern const char *kAllProtocolNamesStr;
-extern const char* kAutomaticStr;
-extern const char* kAutoStr;
-extern const char* kBeepStr;
-extern const char* kBitsStr;
-extern const char* kBottomStr;
-extern const char* kBreezeStr;
-extern const char* kButtonStr;
-extern const char* kCancelStr;
-extern const char* kCeilingStr;
-extern const char* kCelsiusFahrenheitStr;
-extern const char* kCelsiusStr;
-extern const char* kCentreStr;
-extern const char* kChangeStr;
-extern const char* kCirculateStr;
-extern const char* kCleanStr;
-extern const char* kClockStr;
-extern const char* kCodeStr;
-extern const char* kColonSpaceStr;
-extern const char* kComfortStr;
-extern const char* kCommandStr;
-extern const char* kCommaSpaceStr;
-extern const char* kCoolStr;
-extern const char* kDaysStr;
-extern const char* kDayStr;
-extern const char* kDisplayTempStr;
-extern const char* kDownStr;
-extern const char* kDryStr;
-extern const char* kEconoStr;
-extern const char* kEconoToggleStr;
-extern const char* kEyeAutoStr;
-extern const char* kEyeStr;
-extern const char* kFalseStr;
-extern const char* kFanOnlyNoSpaceStr;
-extern const char* kFan_OnlyStr;
-extern const char* kFanOnlyStr;
-extern const char* kFanOnlyWithSpaceStr;
-extern const char* kFanStr;
-extern const char* kFastStr;
-extern const char* kFilterStr;
-extern const char* kFixedStr;
-extern const char* kFollowStr;
-extern const char* kFreshStr;
-extern const char* kHealthStr;
-extern const char* kHeatStr;
-extern const char* kHighestStr;
-extern const char* kHighStr;
-extern const char* kHiStr;
-extern const char* kHoldStr;
-extern const char* kHoursStr;
-extern const char* kHourStr;
-extern const char* kHumidStr;
-extern const char* kIdStr;
-extern const char* kIFeelStr;
-extern const char* kInsideStr;
-extern const char* kIonStr;
-extern const char* kLastStr;
-extern const char* kLeftMaxStr;
-extern const char* kLeftStr;
-extern const char* kLightStr;
-extern const char* kLightToggleStr;
-extern const char* kLoStr;
-extern const char* kLoudStr;
-extern const char* kLowerStr;
-extern const char* kLowestStr;
-extern const char* kLowStr;
-extern const char* kManualStr;
-extern const char* kMaximumStr;
-extern const char* kMaxLeftStr;
-extern const char* kMaxRightStr;
-extern const char* kMaxStr;
-extern const char* kMediumStr;
-extern const char* kMedStr;
-extern const char* kMiddleStr;
-extern const char* kMidStr;
-extern const char* kMinimumStr;
-extern const char* kMinStr;
-extern const char* kMinutesStr;
-extern const char* kMinuteStr;
-extern const char* kModelStr;
-extern const char* kModeStr;
-extern const char* kMouldStr;
-extern const char* kMoveStr;
-extern const char* kNAStr;
-extern const char* kNightStr;
-extern const char* kNoStr;
-extern const char* kNowStr;
-extern const char* kOffStr;
-extern const char* kOffTimerStr;
-extern const char* kOnStr;
-extern const char* kOnTimerStr;
-extern const char* kOutsideQuietStr;
-extern const char* kOutsideStr;
-extern const char* kPowerButtonStr;
-extern const char* kPowerfulStr;
-extern const char* kPowerStr;
-extern const char* kPowerToggleStr;
-extern const char* kPreviousPowerStr;
-extern const char* kProtocolStr;
-extern const char* kPurifyStr;
-extern const char* kQuietStr;
-extern const char* kRecycleStr;
-extern const char* kRepeatStr;
-extern const char* kRightMaxStr;
-extern const char* kRightStr;
-extern const char* kRoomStr;
-extern const char* kSaveStr;
-extern const char* kSecondsStr;
-extern const char* kSecondStr;
-extern const char* kSensorStr;
-extern const char* kSensorTempStr;
-extern const char* kSetStr;
-extern const char* kSilentStr;
-extern const char* kSleepStr;
-extern const char* kSleepTimerStr;
-extern const char* kSlowStr;
-extern const char* kSpaceLBraceStr;
-extern const char* kSpecialStr;
-extern const char* kStartStr;
-extern const char* kStepStr;
-extern const char* kStopStr;
-extern const char* kSuperStr;
-extern const char* kSwingHStr;
-extern const char* kSwingStr;
-extern const char* kSwingVModeStr;
-extern const char* kSwingVStr;
-extern const char* kSwingVToggleStr;
-extern const char* kTempDownStr;
-extern const char* kTempStr;
-extern const char* kTempUpStr;
-extern const char* kThreeLetterDayOfWeekStr;
-extern const char* kTimerModeStr;
-extern const char* kTimerStr;
-extern const char* kToggleStr;
-extern const char* kTopStr;
-extern const char* kTrueStr;
-extern const char* kTurboStr;
-extern const char* kTurboToggleStr;
-extern const char* kTypeStr;
-extern const char* kUnknownStr;
-extern const char* kUpperStr;
-extern const char* kUpStr;
-extern const char* kVaneStr;
-extern const char* kWallStr;
-extern const char* kWeeklyTimerStr;
-extern const char* kWideStr;
-extern const char* kWifiStr;
-extern const char* kXFanStr;
-extern const char* kYesStr;
-extern const char* kZoneFollowStr;
+#ifdef ESP8266
+class __FlashStringHelper;
+#define IRTEXT_CONST_PTR_CAST(PTR)\
+    reinterpret_cast<const __FlashStringHelper*>(PTR)
+#define IRTEXT_CONST_PTR(NAME) const __FlashStringHelper* const NAME
+#else  // ESP8266
+#define IRTEXT_CONST_PTR_CAST(PTR) PTR
+#define IRTEXT_CONST_PTR(NAME) const char* const NAME
+#endif  // ESP8266
+
+extern const char kTimeSep;
+extern IRTEXT_CONST_PTR(k10CHeatStr);
+extern IRTEXT_CONST_PTR(k3DStr);
+extern IRTEXT_CONST_PTR(k6thSenseStr);
+extern IRTEXT_CONST_PTR(k8CHeatStr);
+extern IRTEXT_CONST_PTR(kAirFlowStr);
+extern IRTEXT_CONST_PTR(kAutomaticStr);
+extern IRTEXT_CONST_PTR(kAutoStr);
+extern IRTEXT_CONST_PTR(kBeepStr);
+extern IRTEXT_CONST_PTR(kBitsStr);
+extern IRTEXT_CONST_PTR(kBottomStr);
+extern IRTEXT_CONST_PTR(kBreezeStr);
+extern IRTEXT_CONST_PTR(kButtonStr);
+extern IRTEXT_CONST_PTR(kCancelStr);
+extern IRTEXT_CONST_PTR(kCeilingStr);
+extern IRTEXT_CONST_PTR(kCelsiusFahrenheitStr);
+extern IRTEXT_CONST_PTR(kCelsiusStr);
+extern IRTEXT_CONST_PTR(kCentreStr);
+extern IRTEXT_CONST_PTR(kChangeStr);
+extern IRTEXT_CONST_PTR(kCirculateStr);
+extern IRTEXT_CONST_PTR(kCleanStr);
+extern IRTEXT_CONST_PTR(kClockStr);
+extern IRTEXT_CONST_PTR(kCodeStr);
+extern IRTEXT_CONST_PTR(kColonSpaceStr);
+extern IRTEXT_CONST_PTR(kComfortStr);
+extern IRTEXT_CONST_PTR(kCommandStr);
+extern IRTEXT_CONST_PTR(kCommaSpaceStr);
+extern IRTEXT_CONST_PTR(kCoolStr);
+extern IRTEXT_CONST_PTR(kDaysStr);
+extern IRTEXT_CONST_PTR(kDayStr);
+extern IRTEXT_CONST_PTR(kDisplayTempStr);
+extern IRTEXT_CONST_PTR(kDownStr);
+extern IRTEXT_CONST_PTR(kDryStr);
+extern IRTEXT_CONST_PTR(kEconoStr);
+extern IRTEXT_CONST_PTR(kEconoToggleStr);
+extern IRTEXT_CONST_PTR(kEyeAutoStr);
+extern IRTEXT_CONST_PTR(kEyeStr);
+extern IRTEXT_CONST_PTR(kFalseStr);
+extern IRTEXT_CONST_PTR(kFanOnlyNoSpaceStr);
+extern IRTEXT_CONST_PTR(kFan_OnlyStr);
+extern IRTEXT_CONST_PTR(kFanOnlyStr);
+extern IRTEXT_CONST_PTR(kFanOnlyWithSpaceStr);
+extern IRTEXT_CONST_PTR(kFanStr);
+extern IRTEXT_CONST_PTR(kFastStr);
+extern IRTEXT_CONST_PTR(kFilterStr);
+extern IRTEXT_CONST_PTR(kFixedStr);
+extern IRTEXT_CONST_PTR(kFollowStr);
+extern IRTEXT_CONST_PTR(kFreshStr);
+extern IRTEXT_CONST_PTR(kHealthStr);
+extern IRTEXT_CONST_PTR(kHeatStr);
+extern IRTEXT_CONST_PTR(kHighestStr);
+extern IRTEXT_CONST_PTR(kHighStr);
+extern IRTEXT_CONST_PTR(kHiStr);
+extern IRTEXT_CONST_PTR(kHoldStr);
+extern IRTEXT_CONST_PTR(kHoursStr);
+extern IRTEXT_CONST_PTR(kHourStr);
+extern IRTEXT_CONST_PTR(kHumidStr);
+extern IRTEXT_CONST_PTR(kIdStr);
+extern IRTEXT_CONST_PTR(kIFeelStr);
+extern IRTEXT_CONST_PTR(kInsideStr);
+extern IRTEXT_CONST_PTR(kIonStr);
+extern IRTEXT_CONST_PTR(kLastStr);
+extern IRTEXT_CONST_PTR(kLeftMaxStr);
+extern IRTEXT_CONST_PTR(kLeftStr);
+extern IRTEXT_CONST_PTR(kLightStr);
+extern IRTEXT_CONST_PTR(kLightToggleStr);
+extern IRTEXT_CONST_PTR(kLoStr);
+extern IRTEXT_CONST_PTR(kLoudStr);
+extern IRTEXT_CONST_PTR(kLowerStr);
+extern IRTEXT_CONST_PTR(kLowestStr);
+extern IRTEXT_CONST_PTR(kLowStr);
+extern IRTEXT_CONST_PTR(kManualStr);
+extern IRTEXT_CONST_PTR(kMaximumStr);
+extern IRTEXT_CONST_PTR(kMaxLeftStr);
+extern IRTEXT_CONST_PTR(kMaxRightStr);
+extern IRTEXT_CONST_PTR(kMaxStr);
+extern IRTEXT_CONST_PTR(kMediumStr);
+extern IRTEXT_CONST_PTR(kMedStr);
+extern IRTEXT_CONST_PTR(kMiddleStr);
+extern IRTEXT_CONST_PTR(kMidStr);
+extern IRTEXT_CONST_PTR(kMinimumStr);
+extern IRTEXT_CONST_PTR(kMinStr);
+extern IRTEXT_CONST_PTR(kMinutesStr);
+extern IRTEXT_CONST_PTR(kMinuteStr);
+extern IRTEXT_CONST_PTR(kModelStr);
+extern IRTEXT_CONST_PTR(kModeStr);
+extern IRTEXT_CONST_PTR(kMouldStr);
+extern IRTEXT_CONST_PTR(kMoveStr);
+extern IRTEXT_CONST_PTR(kNAStr);
+extern IRTEXT_CONST_PTR(kNightStr);
+extern IRTEXT_CONST_PTR(kNoStr);
+extern IRTEXT_CONST_PTR(kNowStr);
+extern IRTEXT_CONST_PTR(kOffStr);
+extern IRTEXT_CONST_PTR(kOffTimerStr);
+extern IRTEXT_CONST_PTR(kOnStr);
+extern IRTEXT_CONST_PTR(kOnTimerStr);
+extern IRTEXT_CONST_PTR(kOutsideQuietStr);
+extern IRTEXT_CONST_PTR(kOutsideStr);
+extern IRTEXT_CONST_PTR(kPowerButtonStr);
+extern IRTEXT_CONST_PTR(kPowerfulStr);
+extern IRTEXT_CONST_PTR(kPowerStr);
+extern IRTEXT_CONST_PTR(kPowerToggleStr);
+extern IRTEXT_CONST_PTR(kPreviousPowerStr);
+extern IRTEXT_CONST_PTR(kProtocolStr);
+extern IRTEXT_CONST_PTR(kPurifyStr);
+extern IRTEXT_CONST_PTR(kQuietStr);
+extern IRTEXT_CONST_PTR(kRecycleStr);
+extern IRTEXT_CONST_PTR(kRepeatStr);
+extern IRTEXT_CONST_PTR(kRightMaxStr);
+extern IRTEXT_CONST_PTR(kRightStr);
+extern IRTEXT_CONST_PTR(kRoomStr);
+extern IRTEXT_CONST_PTR(kSaveStr);
+extern IRTEXT_CONST_PTR(kSecondsStr);
+extern IRTEXT_CONST_PTR(kSecondStr);
+extern IRTEXT_CONST_PTR(kSensorStr);
+extern IRTEXT_CONST_PTR(kSensorTempStr);
+extern IRTEXT_CONST_PTR(kSetStr);
+extern IRTEXT_CONST_PTR(kSilentStr);
+extern IRTEXT_CONST_PTR(kSleepStr);
+extern IRTEXT_CONST_PTR(kSleepTimerStr);
+extern IRTEXT_CONST_PTR(kSlowStr);
+extern IRTEXT_CONST_PTR(kSpaceLBraceStr);
+extern IRTEXT_CONST_PTR(kSpecialStr);
+extern IRTEXT_CONST_PTR(kStartStr);
+extern IRTEXT_CONST_PTR(kStepStr);
+extern IRTEXT_CONST_PTR(kStopStr);
+extern IRTEXT_CONST_PTR(kSuperStr);
+extern IRTEXT_CONST_PTR(kSwingHStr);
+extern IRTEXT_CONST_PTR(kSwingStr);
+extern IRTEXT_CONST_PTR(kSwingVModeStr);
+extern IRTEXT_CONST_PTR(kSwingVStr);
+extern IRTEXT_CONST_PTR(kSwingVToggleStr);
+extern IRTEXT_CONST_PTR(kTempDownStr);
+extern IRTEXT_CONST_PTR(kTempStr);
+extern IRTEXT_CONST_PTR(kTempUpStr);
+extern IRTEXT_CONST_PTR(kThreeLetterDayOfWeekStr);
+extern IRTEXT_CONST_PTR(kTimerModeStr);
+extern IRTEXT_CONST_PTR(kTimerStr);
+extern IRTEXT_CONST_PTR(kToggleStr);
+extern IRTEXT_CONST_PTR(kTopStr);
+extern IRTEXT_CONST_PTR(kTrueStr);
+extern IRTEXT_CONST_PTR(kTurboStr);
+extern IRTEXT_CONST_PTR(kTurboToggleStr);
+extern IRTEXT_CONST_PTR(kTypeStr);
+extern IRTEXT_CONST_PTR(kUnknownStr);
+extern IRTEXT_CONST_PTR(kUpperStr);
+extern IRTEXT_CONST_PTR(kUpStr);
+extern IRTEXT_CONST_PTR(kVaneStr);
+extern IRTEXT_CONST_PTR(kWallStr);
+extern IRTEXT_CONST_PTR(kWeeklyTimerStr);
+extern IRTEXT_CONST_PTR(kWideStr);
+extern IRTEXT_CONST_PTR(kWifiStr);
+extern IRTEXT_CONST_PTR(kXFanStr);
+extern IRTEXT_CONST_PTR(kYesStr);
+extern IRTEXT_CONST_PTR(kZoneFollowStr);
+extern IRTEXT_CONST_PTR(kAllProtocolNamesStr);
 
 #endif  // IRTEXT_H_
diff --git a/src/IRutils.cpp b/src/IRutils.cpp
index 905f0a04c..4154f7290 100644
--- a/src/IRutils.cpp
+++ b/src/IRutils.cpp
@@ -1,4 +1,4 @@
-// Copyright 2017 David Conran
+// Copyright 2017-2021 David Conran
 
 #include "IRutils.h"
 #ifndef UNIT_TEST
@@ -17,6 +17,27 @@
 #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<const char*>(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)
@@ -93,21 +114,20 @@ void serialPrintUint64(uint64_t input, uint8_t base) {
 /// @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) {
-  const char *ptr = kAllProtocolNamesStr;
-  uint16_t length = strlen(ptr);
+  auto *ptr = reinterpret_cast<const char*>(kAllProtocolNamesStr);
+  uint16_t length = STRLEN(ptr);
   for (uint16_t i = 0; length; i++) {
-    if (!strcasecmp(str, ptr)) return (decode_type_t)i;
+    if (!STRCASECMP(str, ptr)) return (decode_type_t)i;
     ptr += length + 1;
-    length = strlen(ptr);
+    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;
-  else
-    return decode_type_t::UNKNOWN;
+
+  return decode_type_t::UNKNOWN;
 }
 
 /// Convert a protocol type (enum etc) to a human readable string.
@@ -117,16 +137,20 @@ decode_type_t strToDecodeType(const char * const str) {
 String typeToString(const decode_type_t protocol, const bool isRepeat) {
   String result = "";
   result.reserve(30);  // Size of longest protocol name + " (Repeat)"
-  const char *ptr = kAllProtocolNamesStr;
   if (protocol > kLastDecodeType || protocol == decode_type_t::UNKNOWN) {
     result = kUnknownStr;
   } else {
-    for (uint16_t i = 0; i <= protocol && strlen(ptr); i++) {
-      if (i == protocol) {
-        result = ptr;
-        break;
+    auto *ptr = reinterpret_cast<const char*>(kAllProtocolNamesStr);
+    if (protocol > kLastDecodeType || protocol == decode_type_t::UNKNOWN) {
+      result = kUnknownStr;
+    } else {
+      for (uint16_t i = 0; i <= protocol && STRLEN(ptr); i++) {
+        if (i == protocol) {
+          result = FPSTR(ptr);
+          break;
+        }
+        ptr += STRLEN(ptr) + 1;
       }
-      ptr += strlen(ptr) + 1;
     }
   }
   if (isRepeat) {
diff --git a/src/ir_Electra.cpp b/src/ir_Electra.cpp
index fd70e4bd6..864ab82d6 100644
--- a/src/ir_Electra.cpp
+++ b/src/ir_Electra.cpp
@@ -351,7 +351,8 @@ String IRElectraAc::toString(void) const {
                            kElectraAcFanMed);
   result += addBoolToString(getSwingV(), kSwingVStr);
   result += addBoolToString(getSwingH(), kSwingHStr);
-  result += addLabeledString(getLightToggle() ? kToggleStr : "-", kLightStr);
+  result += addLabeledString(getLightToggle() ? String(kToggleStr)
+                                              : String("-"), kLightStr);
   result += addBoolToString(_.Clean, kCleanStr);
   result += addBoolToString(_.Turbo, kTurboStr);
   return result;
diff --git a/src/ir_Goodweather.cpp b/src/ir_Goodweather.cpp
index c86797ad5..2ddcb2aa2 100644
--- a/src/ir_Goodweather.cpp
+++ b/src/ir_Goodweather.cpp
@@ -346,9 +346,13 @@ String IRGoodweatherAc::toString(void) const {
   result += addFanToString(_.Fan, kGoodweatherFanHigh, kGoodweatherFanLow,
                            kGoodweatherFanAuto, kGoodweatherFanAuto,
                            kGoodweatherFanMed);
-  result += addLabeledString(_.Turbo ? kToggleStr : "-", kTurboStr);
-  result += addLabeledString(_.Light ? kToggleStr : "-", kLightStr);
-  result += addLabeledString(_.Sleep ? kToggleStr : "-", kSleepStr);
+
+  result += addLabeledString(_.Turbo ? String(kToggleStr)
+                                     : String("-"), kTurboStr);
+  result += addLabeledString(_.Light ? String(kToggleStr)
+                                     : String("-"), kLightStr);
+  result += addLabeledString(_.Sleep ? String(kToggleStr)
+                                     : String("-"), kSleepStr);
   result += addIntToString(_.Swing, kSwingStr);
   result += kSpaceLBraceStr;
   switch (_.Swing) {
diff --git a/src/ir_Sharp.cpp b/src/ir_Sharp.cpp
index 77d282d4a..29be0dee3 100644
--- a/src/ir_Sharp.cpp
+++ b/src/ir_Sharp.cpp
@@ -870,8 +870,9 @@ String IRSharpAc::toString(void) const {
   result.reserve(170);  // Reserve some heap for the string to reduce fragging.
   result += addModelToString(decode_type_t::SHARP_AC, getModel(), false);
 
-  result += addLabeledString(isPowerSpecial() ? "-"
-                                              : (getPower() ? kOnStr : kOffStr),
+  result += addLabeledString(isPowerSpecial() ? String("-")
+                                              : String(getPower() ? kOnStr
+                                                                  : kOffStr),
                              kPowerStr);
   const uint8_t mode = _.Mode;
   result += addModeToString(
@@ -919,11 +920,13 @@ String IRSharpAc::toString(void) const {
   switch (model) {
     case sharp_ac_remote_model_t::A705:
     case sharp_ac_remote_model_t::A903:
-      result += addLabeledString(getLightToggle() ? kToggleStr : "-",
+      result += addLabeledString(getLightToggle() ? String(kToggleStr)
+                                                  : String("-"),
                                  kLightStr);
       break;
     default:
-      result += addLabeledString(getEconoToggle() ? kToggleStr : "-",
+      result += addLabeledString(getEconoToggle() ? String(kToggleStr)
+                                                  : String("-"),
                                  kEconoStr);
   }
   result += addBoolToString(_.Clean, kCleanStr);
diff --git a/tools/generate_irtext_h.sh b/tools/generate_irtext_h.sh
index df2baee06..b0d9855c4 100755
--- a/tools/generate_irtext_h.sh
+++ b/tools/generate_irtext_h.sh
@@ -24,12 +24,26 @@ cat >${OUTPUT} << EOF
 // Constant text to be shared across all object files.
 // This means there is only one copy of the character/string/text etc.
 
+#ifdef ESP8266
+class __FlashStringHelper;
+#define IRTEXT_CONST_PTR_CAST(PTR)\\
+    reinterpret_cast<const __FlashStringHelper*>(PTR)
+#define IRTEXT_CONST_PTR(NAME) const __FlashStringHelper* const NAME
+#else  // ESP8266
+#define IRTEXT_CONST_PTR_CAST(PTR) PTR
+#define IRTEXT_CONST_PTR(NAME) const char* const NAME
+#endif  // ESP8266
+
 EOF
 
 # Parse and output contents of INPUT file.
 sed 's/ PROGMEM//' ${INPUT} | egrep "^(const )?char" | cut -f1 -d= |
     sed 's/ $/;/;s/^/extern /' | sort -u >> ${OUTPUT}
-
+egrep '^\s{,10}IRTEXT_CONST_STRING\(' ${INPUT} | cut -f2 -d\( | cut -f1 -d, |
+    sed 's/^/extern IRTEXT_CONST_PTR\(/;s/$/\);/' | sort -u >> ${OUTPUT}
+egrep '^\s{,10}IRTEXT_CONST_BLOB_DECL\(' ${INPUT} |
+    cut -f2 -d\( | cut -f1 -d\) |
+    sed 's/^/extern IRTEXT_CONST_PTR\(/;s/$/\);/' | sort -u >> ${OUTPUT}
 # Footer
 cat >> ${OUTPUT} << EOF
 
diff --git a/tools/mkkeywords b/tools/mkkeywords
index 84f2a46fd..e050e4964 100755
--- a/tools/mkkeywords
+++ b/tools/mkkeywords
@@ -31,7 +31,8 @@ cat << EndOfTextEndOfTextEndOfText
 EndOfTextEndOfTextEndOfText
 
 CLASSES=$(egrep -h "^ *((enum|class) |} [a-zA-Z0-9_]+_t;$)" src/*.h |
-          sed 's/^ *//;s/enum class//;s/\;$//' | cut -d' ' -f2 | sort -u)
+          sed 's/^ *//;s/enum class//;s/\;$//' | cut -d' ' -f2 | sort -u |
+          grep -v "^__")
 for i in ${CLASSES}; do
   echo -e "${i}\tKEYWORD1"
 done | sort -du
@@ -59,13 +60,15 @@ cat << EndOfTextEndOfTextEndOfText
 #######################################
 
 EndOfTextEndOfTextEndOfText
-LITERALS=$(grep "^#define [A-Z]" src/*.cpp src/*.h |
+LITERALS=$(grep -h "^#define [A-Z]" src/*.cpp src/*.h |
            while read ignore define ignore; do
              echo ${define};
            done | sort -u |
            grep -v [\(\)] | grep -v ^_ | grep -v _\$ | grep -v VIRTUAL)
-CONSTS=$(grep "^const " src/*.cpp src/*.h |
-         sed -E 's/\[.*\] =.*//;s/ =.*//;s/^.* \*?k/k/')
+CONSTS=$(grep -h "^const " src/*.cpp src/*.h |
+         sed -E 's/\[.*\] =.*//;s/ =.*//;s/^.* \*?k/k/';
+         grep -h "^IRTEXT_CONST_" src/*.cpp src/*.h |
+         sed -E 's/IRTEXT_CONST_\S+\(//;s/,.*//;s/\).*//')
 ENUMS=$(cat src/*.h | while read a b; do
           if [[ ${a} == "};" ]]; then
             ENUM=0;
@@ -76,7 +79,7 @@ ENUMS=$(cat src/*.h | while read a b; do
           if [[ ${a} == "enum" ]]; then
             ENUM=1;
           fi;
-        done)
+        done | grep -v "^//")
 for i in ${LITERALS} ${CONSTS} ${ENUMS}; do
   echo -e "${i}\tLITERAL1"
 done | sort -u