From bbb01c78e8e0177527396e51cfe274f8391dff1b Mon Sep 17 00:00:00 2001 From: openshwprojects Date: Thu, 23 Feb 2023 09:22:49 +0100 Subject: [PATCH 01/16] p1 --- src/driver/drv_ir.cpp | 260 +- src/libraries/IRremoteESP8266/.gitattributes | 3 + .../IRremoteESP8266/.github/CONTRIBUTING.md | 82 + .../IRremoteESP8266/.github/Contributors.md | 25 + .../.github/ISSUE_TEMPLATE/problem-report.md | 57 + .../.github/workflows/Build.yml | 74 + .../.github/workflows/Documentation.yml | 15 + .../.github/workflows/Lint.yml | 28 + .../.github/workflows/MetaChecks.yml | 54 + .../.github/workflows/UnitTests.yml | 30 + .../.github/workflows/codeql-analysis.yml | 72 + src/libraries/IRremoteESP8266/.gitignore | 55 + src/libraries/IRremoteESP8266/.style.yapf | 3 + src/libraries/IRremoteESP8266/LICENSE.txt | 458 ++ src/libraries/IRremoteESP8266/README.md | 89 + src/libraries/IRremoteESP8266/README_de.md | 82 + src/libraries/IRremoteESP8266/README_fr.md | 91 + src/libraries/IRremoteESP8266/README_nl.md | 89 + src/libraries/IRremoteESP8266/ReleaseNotes.md | 1234 ++++ .../IRremoteESP8266/SupportedProtocols.md | 271 + .../IRremoteESP8266/library.properties | 9 + src/libraries/IRremoteESP8266/pylintrc | 12 + src/libraries/IRremoteESP8266/src/CPPLINT.cfg | 1 + src/libraries/IRremoteESP8266/src/IRac.cpp | 5037 +++++++++++++++++ src/libraries/IRremoteESP8266/src/IRac.h | 580 ++ src/libraries/IRremoteESP8266/src/IRmacros.h | 53 + src/libraries/IRremoteESP8266/src/IRproto.cpp | 133 + src/libraries/IRremoteESP8266/src/IRproto.h | 7 + src/libraries/IRremoteESP8266/src/IRrecv.cpp | 2075 +++++++ src/libraries/IRremoteESP8266/src/IRrecv.h | 882 +++ .../IRremoteESP8266/src/IRremoteESP8266.h | 1512 +++++ src/libraries/IRremoteESP8266/src/IRsend.cpp | 1463 +++++ src/libraries/IRremoteESP8266/src/IRsend.h | 916 +++ src/libraries/IRremoteESP8266/src/IRtext.cpp | 559 ++ src/libraries/IRremoteESP8266/src/IRtext.h | 256 + src/libraries/IRremoteESP8266/src/IRtimer.cpp | 96 + src/libraries/IRremoteESP8266/src/IRtimer.h | 40 + src/libraries/IRremoteESP8266/src/IRutils.cpp | 1441 +++++ src/libraries/IRremoteESP8266/src/IRutils.h | 156 + src/libraries/IRremoteESP8266/src/String.cpp | 766 +++ src/libraries/IRremoteESP8266/src/String.h | 251 + src/libraries/IRremoteESP8266/src/i18n.h | 25 + .../IRremoteESP8266/src/ir_Airton.cpp | 363 ++ src/libraries/IRremoteESP8266/src/ir_Airton.h | 134 + .../IRremoteESP8266/src/ir_Airwell.cpp | 287 + .../IRremoteESP8266/src/ir_Airwell.h | 101 + src/libraries/IRremoteESP8266/src/ir_Aiwa.cpp | 105 + .../IRremoteESP8266/src/ir_Amcor.cpp | 355 ++ src/libraries/IRremoteESP8266/src/ir_Amcor.h | 141 + src/libraries/IRremoteESP8266/src/ir_Argo.cpp | 1823 ++++++ src/libraries/IRremoteESP8266/src/ir_Argo.h | 521 ++ .../IRremoteESP8266/src/ir_Arris.cpp | 123 + .../IRremoteESP8266/src/ir_Bosch.cpp | 332 ++ src/libraries/IRremoteESP8266/src/ir_Bosch.h | 189 + src/libraries/IRremoteESP8266/src/ir_Bose.cpp | 69 + .../IRremoteESP8266/src/ir_Carrier.cpp | 748 +++ .../IRremoteESP8266/src/ir_Carrier.h | 146 + .../IRremoteESP8266/src/ir_ClimaButler.cpp | 86 + .../IRremoteESP8266/src/ir_Coolix.cpp | 758 +++ src/libraries/IRremoteESP8266/src/ir_Coolix.h | 200 + .../IRremoteESP8266/src/ir_Corona.cpp | 576 ++ src/libraries/IRremoteESP8266/src/ir_Corona.h | 168 + .../IRremoteESP8266/src/ir_Daikin.cpp | 3927 +++++++++++++ src/libraries/IRremoteESP8266/src/ir_Daikin.h | 1263 +++++ .../IRremoteESP8266/src/ir_Delonghi.cpp | 470 ++ .../IRremoteESP8266/src/ir_Delonghi.h | 136 + .../IRremoteESP8266/src/ir_Denon.cpp | 122 + src/libraries/IRremoteESP8266/src/ir_Dish.cpp | 103 + .../IRremoteESP8266/src/ir_Doshisha.cpp | 124 + .../IRremoteESP8266/src/ir_Ecoclim.cpp | 425 ++ .../IRremoteESP8266/src/ir_Ecoclim.h | 142 + .../IRremoteESP8266/src/ir_Electra.cpp | 458 ++ .../IRremoteESP8266/src/ir_Electra.h | 178 + .../IRremoteESP8266/src/ir_EliteScreens.cpp | 89 + .../IRremoteESP8266/src/ir_Epson.cpp | 111 + .../IRremoteESP8266/src/ir_Fujitsu.cpp | 1101 ++++ .../IRremoteESP8266/src/ir_Fujitsu.h | 266 + .../IRremoteESP8266/src/ir_GICable.cpp | 95 + .../IRremoteESP8266/src/ir_GlobalCache.cpp | 64 + .../IRremoteESP8266/src/ir_Goodweather.cpp | 500 ++ .../IRremoteESP8266/src/ir_Goodweather.h | 154 + .../IRremoteESP8266/src/ir_Gorenje.cpp | 71 + src/libraries/IRremoteESP8266/src/ir_Gree.cpp | 751 +++ src/libraries/IRremoteESP8266/src/ir_Gree.h | 240 + .../IRremoteESP8266/src/ir_Haier.cpp | 2170 +++++++ src/libraries/IRremoteESP8266/src/ir_Haier.h | 653 +++ .../IRremoteESP8266/src/ir_Hitachi.cpp | 1997 +++++++ .../IRremoteESP8266/src/ir_Hitachi.h | 667 +++ src/libraries/IRremoteESP8266/src/ir_Inax.cpp | 73 + src/libraries/IRremoteESP8266/src/ir_JVC.cpp | 131 + .../IRremoteESP8266/src/ir_Kelon.cpp | 551 ++ src/libraries/IRremoteESP8266/src/ir_Kelon.h | 142 + .../IRremoteESP8266/src/ir_Kelvinator.cpp | 582 ++ .../IRremoteESP8266/src/ir_Kelvinator.h | 198 + src/libraries/IRremoteESP8266/src/ir_LG.cpp | 863 +++ src/libraries/IRremoteESP8266/src/ir_LG.h | 202 + .../IRremoteESP8266/src/ir_Lasertag.cpp | 116 + src/libraries/IRremoteESP8266/src/ir_Lego.cpp | 106 + .../IRremoteESP8266/src/ir_Lutron.cpp | 143 + src/libraries/IRremoteESP8266/src/ir_MWM.cpp | 197 + .../IRremoteESP8266/src/ir_Magiquest.cpp | 154 + .../IRremoteESP8266/src/ir_Magiquest.h | 43 + src/libraries/IRremoteESP8266/src/ir_Metz.cpp | 101 + .../IRremoteESP8266/src/ir_Midea.cpp | 889 +++ src/libraries/IRremoteESP8266/src/ir_Midea.h | 276 + .../IRremoteESP8266/src/ir_MilesTag2.cpp | 113 + .../IRremoteESP8266/src/ir_Mirage.cpp | 851 +++ src/libraries/IRremoteESP8266/src/ir_Mirage.h | 277 + .../IRremoteESP8266/src/ir_Mitsubishi.cpp | 1728 ++++++ .../IRremoteESP8266/src/ir_Mitsubishi.h | 456 ++ .../src/ir_MitsubishiHeavy.cpp | 1051 ++++ .../IRremoteESP8266/src/ir_MitsubishiHeavy.h | 346 ++ .../IRremoteESP8266/src/ir_Multibrackets.cpp | 115 + src/libraries/IRremoteESP8266/src/ir_NEC.cpp | 140 + src/libraries/IRremoteESP8266/src/ir_NEC.h | 80 + .../IRremoteESP8266/src/ir_Neoclima.cpp | 609 ++ .../IRremoteESP8266/src/ir_Neoclima.h | 198 + .../IRremoteESP8266/src/ir_Nikai.cpp | 74 + .../IRremoteESP8266/src/ir_Panasonic.cpp | 1345 +++++ .../IRremoteESP8266/src/ir_Panasonic.h | 271 + .../IRremoteESP8266/src/ir_Pioneer.cpp | 143 + .../IRremoteESP8266/src/ir_Pronto.cpp | 107 + .../IRremoteESP8266/src/ir_RC5_RC6.cpp | 454 ++ src/libraries/IRremoteESP8266/src/ir_RCMM.cpp | 165 + .../IRremoteESP8266/src/ir_Rhoss.cpp | 365 ++ src/libraries/IRremoteESP8266/src/ir_Rhoss.h | 145 + .../IRremoteESP8266/src/ir_Samsung.cpp | 993 ++++ .../IRremoteESP8266/src/ir_Samsung.h | 280 + .../IRremoteESP8266/src/ir_Sanyo.cpp | 1047 ++++ src/libraries/IRremoteESP8266/src/ir_Sanyo.h | 285 + .../IRremoteESP8266/src/ir_Sharp.cpp | 977 ++++ src/libraries/IRremoteESP8266/src/ir_Sharp.h | 241 + .../IRremoteESP8266/src/ir_Sherwood.cpp | 25 + src/libraries/IRremoteESP8266/src/ir_Sony.cpp | 192 + .../IRremoteESP8266/src/ir_Symphony.cpp | 96 + src/libraries/IRremoteESP8266/src/ir_Tcl.cpp | 618 ++ src/libraries/IRremoteESP8266/src/ir_Tcl.h | 201 + .../IRremoteESP8266/src/ir_Technibel.cpp | 409 ++ .../IRremoteESP8266/src/ir_Technibel.h | 135 + src/libraries/IRremoteESP8266/src/ir_Teco.cpp | 377 ++ src/libraries/IRremoteESP8266/src/ir_Teco.h | 131 + .../IRremoteESP8266/src/ir_Teknopoint.cpp | 81 + .../IRremoteESP8266/src/ir_Toshiba.cpp | 544 ++ .../IRremoteESP8266/src/ir_Toshiba.h | 195 + src/libraries/IRremoteESP8266/src/ir_Toto.cpp | 131 + .../IRremoteESP8266/src/ir_Transcold.cpp | 501 ++ .../IRremoteESP8266/src/ir_Transcold.h | 174 + .../IRremoteESP8266/src/ir_Trotec.cpp | 643 +++ src/libraries/IRremoteESP8266/src/ir_Trotec.h | 252 + .../IRremoteESP8266/src/ir_Truma.cpp | 341 ++ src/libraries/IRremoteESP8266/src/ir_Truma.h | 126 + .../IRremoteESP8266/src/ir_Vestel.cpp | 572 ++ src/libraries/IRremoteESP8266/src/ir_Vestel.h | 172 + .../IRremoteESP8266/src/ir_Voltas.cpp | 517 ++ src/libraries/IRremoteESP8266/src/ir_Voltas.h | 162 + .../IRremoteESP8266/src/ir_Whirlpool.cpp | 658 +++ .../IRremoteESP8266/src/ir_Whirlpool.h | 205 + .../IRremoteESP8266/src/ir_Whynter.cpp | 103 + .../IRremoteESP8266/src/ir_Wowwee.cpp | 91 + src/libraries/IRremoteESP8266/src/ir_Xmp.cpp | 227 + .../IRremoteESP8266/src/ir_Zepeal.cpp | 94 + src/libraries/IRremoteESP8266/src/itoa.cpp | 79 + src/libraries/IRremoteESP8266/src/itoa.h | 26 + .../IRremoteESP8266/src/locale/README.md | 97 + .../IRremoteESP8266/src/locale/de-CH.h | 158 + .../IRremoteESP8266/src/locale/de-DE.h | 135 + .../IRremoteESP8266/src/locale/defaults.h | 1145 ++++ .../IRremoteESP8266/src/locale/en-AU.h | 8 + .../IRremoteESP8266/src/locale/en-IE.h | 8 + .../IRremoteESP8266/src/locale/en-UK.h | 8 + .../IRremoteESP8266/src/locale/en-US.h | 13 + .../IRremoteESP8266/src/locale/es-ES.h | 136 + .../IRremoteESP8266/src/locale/fr-FR.h | 117 + .../IRremoteESP8266/src/locale/it-IT.h | 159 + .../IRremoteESP8266/src/locale/nl-NL.h | 136 + .../IRremoteESP8266/src/locale/pt-BR.h | 177 + .../IRremoteESP8266/src/locale/ru-RU.h | 152 + .../IRremoteESP8266/src/locale/sv-SE.h | 189 + .../IRremoteESP8266/src/locale/zh-CN.h | 465 ++ src/libraries/IRremoteESP8266/src/minmax.h | 41 + 180 files changed, 71577 insertions(+), 112 deletions(-) create mode 100644 src/libraries/IRremoteESP8266/.gitattributes create mode 100644 src/libraries/IRremoteESP8266/.github/CONTRIBUTING.md create mode 100644 src/libraries/IRremoteESP8266/.github/Contributors.md create mode 100644 src/libraries/IRremoteESP8266/.github/ISSUE_TEMPLATE/problem-report.md create mode 100644 src/libraries/IRremoteESP8266/.github/workflows/Build.yml create mode 100644 src/libraries/IRremoteESP8266/.github/workflows/Documentation.yml create mode 100644 src/libraries/IRremoteESP8266/.github/workflows/Lint.yml create mode 100644 src/libraries/IRremoteESP8266/.github/workflows/MetaChecks.yml create mode 100644 src/libraries/IRremoteESP8266/.github/workflows/UnitTests.yml create mode 100644 src/libraries/IRremoteESP8266/.github/workflows/codeql-analysis.yml create mode 100644 src/libraries/IRremoteESP8266/.gitignore create mode 100644 src/libraries/IRremoteESP8266/.style.yapf create mode 100644 src/libraries/IRremoteESP8266/LICENSE.txt create mode 100644 src/libraries/IRremoteESP8266/README.md create mode 100644 src/libraries/IRremoteESP8266/README_de.md create mode 100644 src/libraries/IRremoteESP8266/README_fr.md create mode 100644 src/libraries/IRremoteESP8266/README_nl.md create mode 100644 src/libraries/IRremoteESP8266/ReleaseNotes.md create mode 100644 src/libraries/IRremoteESP8266/SupportedProtocols.md create mode 100644 src/libraries/IRremoteESP8266/library.properties create mode 100644 src/libraries/IRremoteESP8266/pylintrc create mode 100644 src/libraries/IRremoteESP8266/src/CPPLINT.cfg create mode 100644 src/libraries/IRremoteESP8266/src/IRac.cpp create mode 100644 src/libraries/IRremoteESP8266/src/IRac.h create mode 100644 src/libraries/IRremoteESP8266/src/IRmacros.h create mode 100644 src/libraries/IRremoteESP8266/src/IRproto.cpp create mode 100644 src/libraries/IRremoteESP8266/src/IRproto.h create mode 100644 src/libraries/IRremoteESP8266/src/IRrecv.cpp create mode 100644 src/libraries/IRremoteESP8266/src/IRrecv.h create mode 100644 src/libraries/IRremoteESP8266/src/IRremoteESP8266.h create mode 100644 src/libraries/IRremoteESP8266/src/IRsend.cpp create mode 100644 src/libraries/IRremoteESP8266/src/IRsend.h create mode 100644 src/libraries/IRremoteESP8266/src/IRtext.cpp create mode 100644 src/libraries/IRremoteESP8266/src/IRtext.h create mode 100644 src/libraries/IRremoteESP8266/src/IRtimer.cpp create mode 100644 src/libraries/IRremoteESP8266/src/IRtimer.h create mode 100644 src/libraries/IRremoteESP8266/src/IRutils.cpp create mode 100644 src/libraries/IRremoteESP8266/src/IRutils.h create mode 100644 src/libraries/IRremoteESP8266/src/String.cpp create mode 100644 src/libraries/IRremoteESP8266/src/String.h create mode 100644 src/libraries/IRremoteESP8266/src/i18n.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Airton.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Airton.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Airwell.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Airwell.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Aiwa.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Amcor.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Amcor.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Argo.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Argo.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Arris.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Bosch.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Bosch.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Bose.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Carrier.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Carrier.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_ClimaButler.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Coolix.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Coolix.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Corona.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Corona.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Daikin.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Daikin.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Delonghi.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Delonghi.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Denon.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Dish.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Ecoclim.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Ecoclim.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Electra.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Electra.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_EliteScreens.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Epson.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Fujitsu.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Fujitsu.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_GICable.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_GlobalCache.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Goodweather.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Gorenje.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Gree.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Gree.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Haier.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Haier.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Hitachi.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Hitachi.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Inax.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_JVC.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Kelon.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Kelon.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Kelvinator.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Kelvinator.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_LG.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_LG.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Lasertag.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Lego.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Lutron.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_MWM.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Magiquest.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Metz.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Midea.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Midea.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Mirage.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Mirage.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Mitsubishi.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Mitsubishi.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Multibrackets.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_NEC.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_NEC.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Neoclima.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Neoclima.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Nikai.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Panasonic.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Panasonic.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Pioneer.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Pronto.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_RCMM.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Rhoss.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Rhoss.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Samsung.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Samsung.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Sanyo.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Sanyo.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Sharp.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Sharp.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Sherwood.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Sony.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Symphony.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Tcl.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Tcl.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Technibel.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Technibel.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Teco.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Teco.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Teknopoint.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Toshiba.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Toshiba.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Toto.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Transcold.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Transcold.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Trotec.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Trotec.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Truma.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Truma.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Vestel.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Vestel.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Voltas.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Voltas.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Whirlpool.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Whirlpool.h create mode 100644 src/libraries/IRremoteESP8266/src/ir_Whynter.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Wowwee.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Xmp.cpp create mode 100644 src/libraries/IRremoteESP8266/src/ir_Zepeal.cpp create mode 100644 src/libraries/IRremoteESP8266/src/itoa.cpp create mode 100644 src/libraries/IRremoteESP8266/src/itoa.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/README.md create mode 100644 src/libraries/IRremoteESP8266/src/locale/de-CH.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/de-DE.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/defaults.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/en-AU.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/en-IE.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/en-UK.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/en-US.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/es-ES.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/fr-FR.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/it-IT.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/nl-NL.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/pt-BR.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/ru-RU.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/sv-SE.h create mode 100644 src/libraries/IRremoteESP8266/src/locale/zh-CN.h create mode 100644 src/libraries/IRremoteESP8266/src/minmax.h diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index d2f2573b0..162421927 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -1,4 +1,3 @@ - #if PLATFORM_BEKEN extern "C" { @@ -28,7 +27,6 @@ extern "C" { #include "../../beken378/driver/i2c/i2c1.h" #include "../../beken378/driver/gpio/gpio.h" - #include unsigned long ir_counter = 0; uint8_t gEnableIRSendWhilstReceive = 0; @@ -39,15 +37,17 @@ extern "C" { extern int my_strnicmp(const char* a, const char* b, int len); } + #include "drv_ir.h" //#define USE_IRREMOTE_HPP_AS_PLAIN_INCLUDE 1 #undef read #undef write -#define PROGMEM +#undef send +//#define PROGMEM -#define NO_LED_FEEDBACK_CODE 1 +//#define NO_LED_FEEDBACK_CODE 1 //typedef unsigned char uint_fast8_t; typedef unsigned short uint16_t; @@ -122,10 +122,10 @@ void pinModeFast(unsigned char P, unsigned char V) { #define TIMER_RESET_INTR_PENDING -# if defined(ISR) -#undef ISR -# endif -#define ISR void IR_ISR +// # if defined(ISR) +// #undef ISR +// # endif +// #define ISR void IR_ISR extern "C" void DRV_IR_ISR(UINT8 t); static UINT32 ir_chan = BKTIMER0; @@ -204,39 +204,13 @@ class SpoofIrReceiver { SpoofIrReceiver IrReceiver; -#include "../libraries/Arduino-IRremote-mod/src/IRProtocol.h" - -// this is to replicate places where the library uses the static class. -// will need to update to call our dynamic class -class SpoofIrSender { - public: - void enableIROut(uint_fast8_t freq){ +#include "../libraries/IRremoteESP8266/src/IRremoteESP8266.h" +#include "../libraries/IRremoteESP8266/src/IRsend.h" +#include "../libraries/IRremoteESP8266/src/IRrecv.h" +#include "../libraries/IRremoteESP8266/src/IRutils.h" +#include "../libraries/IRremoteESP8266/src/IRac.h" +#include "../libraries/IRremoteESP8266/src/IRproto.h" - } - void mark(unsigned int aMarkMicros){ - - } - void space(unsigned int aMarkMicros){ - - } - void sendPulseDistanceWidthFromArray(uint_fast8_t aFrequencyKHz, unsigned int aHeaderMarkMicros, - unsigned int aHeaderSpaceMicros, unsigned int aOneMarkMicros, unsigned int aOneSpaceMicros, unsigned int aZeroMarkMicros, - unsigned int aZeroSpaceMicros, uint32_t *aDecodedRawDataArray, unsigned int aNumberOfBits, bool aMSBFirst, - bool aSendStopBit, unsigned int aRepeatPeriodMillis, int_fast8_t aNumberOfRepeats) { - - } - void sendPulseDistanceWidthFromArray(PulsePauseWidthProtocolConstants *aProtocolConstants, uint32_t *aDecodedRawDataArray, - unsigned int aNumberOfBits, int_fast8_t aNumberOfRepeats) { - - } - -}; - -SpoofIrSender IrSender; - -// this is the actual IR library include. -// it's all in .h and .hpp files, no .c or .cpp -#include "../libraries/Arduino-IRremote-mod/src/IRremote.hpp" extern "C" int PIN_GetPWMIndexForPinIndex(int pin) ; @@ -246,36 +220,28 @@ extern "C" int PIN_GetPWMIndexForPinIndex(int pin) ; // and then every 50us service the rolling buffer, changing the PWM from 0 duty to 50% duty // appropriately. #define SEND_MAXBITS 128 + class myIRsend : public IRsend { public: - myIRsend(uint_fast8_t aSendPin){ - //IRsend::IRsend(aSendPin); - has been called already? + myIRsend(uint_fast8_t aSendPin):IRsend(aSendPin){ our_us = 0; our_ms = 0; resetsendqueue(); } ~myIRsend() { } - void enableIROut(uint_fast8_t aFrequencyKHz){ - // just setup variables for use in ISR - pwmfrequency = ((uint32_t)aFrequencyKHz) * 1000; - pwmperiod = (26000000 / pwmfrequency); - pwmduty = pwmperiod/2; - } uint32_t millis(){ return our_ms; } + void delay(long int ms){ // add a pure delay to our queue ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Delay %dms", ms); space(ms*1000); } - - using IRsend::write; - - void mark(unsigned int aMarkMicros){ + uint16_t mark(uint16_t aMarkMicros){ // sends a high for aMarkMicros uint32_t newtimein = (timein + 1)%(SEND_MAXBITS * 2); if (newtimein != timeout){ @@ -287,8 +253,10 @@ class myIRsend : public IRsend { } else { overflows++; } + return 1; } - void space(unsigned int aMarkMicros){ + + void space(uint32_t aMarkMicros){ // sends a low for aMarkMicros uint32_t newtimein = (timein + 1)%(SEND_MAXBITS * 2); if (newtimein != timeout){ @@ -301,6 +269,17 @@ class myIRsend : public IRsend { } } + void enableIROut(uint32_t freq, uint8_t duty){ + //uint_fast8_t aFrequencyKHz + if (freq < 1000) // Were we given kHz? Supports the old call usage. + freq *= 1000; + + // just setup variables for use in ISR + //pwmfrequency = ((uint32_t)aFrequencyKHz) * 1000; + pwmperiod = (26000000 / freq); + pwmduty = pwmperiod/2; + } + void resetsendqueue(){ // sends a low for aMarkMicros timein = timeout = 0; @@ -322,7 +301,7 @@ class myIRsend : public IRsend { if (timein != timeout){ val = times[timeout]; timeout = (timeout + 1)%(SEND_MAXBITS * 2); - timecount--; + timecount --; } return val; } @@ -343,7 +322,7 @@ class myIRsend : public IRsend { // our send/receive instances myIRsend *pIRsend = NULL; -IRrecv *ourReceiver = NULL; +//IRrecv *ourReceiver = NULL; // this is our ISR. // it is called every 50us, so we need to work on making it as efficient as possible. @@ -352,7 +331,7 @@ extern "C" void DRV_IR_ISR(UINT8 t){ if (pIRsend && (pIRsend->pwmIndex >= 0)){ pIRsend->our_us += 50; if (pIRsend->our_us > 1000){ - pIRsend->our_ms++; + pIRsend->our_ms ++; pIRsend->our_us -= 1000; } @@ -412,18 +391,34 @@ extern "C" void DRV_IR_ISR(UINT8 t){ } // don't receive if we are currently sending - if (ourReceiver && !sending){ - IR_ISR(); - } + //if (ourReceiver && !sending){ + //IR_ISR(); + //TODO: implement reading from the recieve pin here + //} ir_counter++; } +decode_type_t find_protocol_by_name(const char* args,int ournamelen) +{ + decode_type_t protocol = decode_type_t::UNKNOWN; // UNKNOW? + + for (int i = 0; i < numProtocols; i++) { + const char *name = ProtocolNames[i]; + int namelen = strlen(name); + if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)){ + protocol = (decode_type_t)i; + break; + } + } + return protocol; +} + + extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { - int numProtocols = sizeof(ProtocolNames)/sizeof(*ProtocolNames); if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; - char args[20]; - strncpy(args, args_in, 19); - args[19] = 0; + char args[64]; + strncpy(args, args_in, sizeof(args)-1); + args[sizeof(args)-1] = 0; // split arg at hyphen; char *p = args; @@ -437,15 +432,7 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con } int ournamelen = (p - args); - int protocol = 0; - for (int i = 0; i < numProtocols; i++){ - const char *name = ProtocolNames[i]; - int namelen = strlen(name); - if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)){ - protocol = i; - break; - } - } + decode_type_t protocol = find_protocol_by_name(args,ournamelen); p++; int addr = strtol(p, &p, 16); @@ -456,8 +443,6 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con p++; int command = strtol(p, &p, 16); - IRData data; - memset(&data, 0, sizeof(data)); int repeats = 0; if ((*p == '-') || (*p == ' ')) { @@ -465,20 +450,18 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con repeats = strtol(p, &p, 16); } - data.protocol = (decode_type_t)protocol; - data.address = addr; - data.command = command; - data.flags = 0; - if (pIRsend){ - pIRsend->write(&data, (int_fast8_t) repeats); + bool success = true; // Assume success. + + success = pIRsend->send(protocol, addr, command, repeats); // add a 100ms delay after command // NOTE: this is NOT a delay here. it adds 100ms 'space' in the TX queue pIRsend->delay(100); - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR send %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)data.protocol, (int)data.address, (int)data.command, (int)repeats); + + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR send %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)protocol, (int)addr, (int)command, (int)repeats); return CMD_RES_OK; } else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR NOT send (no IRsend running) %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)data.protocol, (int)data.address, (int)data.command, (int)repeats); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR NOT send (no IRsend running) %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)protocol, (int)addr, (int)command, (int)repeats); } return CMD_RES_ERROR; } @@ -528,11 +511,12 @@ extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const p++; } - int numProtocols = sizeof(ProtocolNames)/sizeof(*ProtocolNames); + //int numProtocols = sizeof(ProtocolNames)/sizeof(*ProtocolNames); + int numProtocols=0; int ournamelen = (p - args); int protocol = -1; for (int i = 0; i < numProtocols; i++){ - const char *name = ProtocolNames[i]; + const char *name = "Unknown" ; //= ProtocolNames[i]; int namelen = strlen(name); if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)){ protocol = i; @@ -551,7 +535,7 @@ extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable invalid protocol %s", args); return CMD_RES_BAD_ARGUMENT; } else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable found protocol %s(%d), enable %d from %s, bitmask 0x%08X", ProtocolNames[protocol], protocol, enable, p, thisbit); + //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable found protocol %s(%d), enable %d from %s, bitmask 0x%08X", ProtocolNames[protocol], protocol, enable, p, thisbit); } if (enable) { gIRProtocolEnable = gIRProtocolEnable | thisbit; @@ -564,6 +548,32 @@ extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const } + +extern "C" commandResult_t IR_AC_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { + if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; + + char args[64]; + strncpy(args, args_in, sizeof(args)-1); + args[sizeof(args)-1] = 0; + + // split arg at hyphen; + char *p = args; + while (*p && (*p != '-') && (*p != ' ')){ + p++; + } + int ournamelen = (p - args); + if ((*p != '-') && (*p != ' ')) { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); + return CMD_RES_BAD_ARGUMENT; + } + + decode_type_t protocol = find_protocol_by_name(args,ournamelen); + + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRAC cmnd not implemented yet", args); + + return CMD_RES_OK; +} + // test routine to start IR RX and TX // currently fixed pins for testing. extern "C" void DRV_IR_Init(){ @@ -576,11 +586,11 @@ extern "C" void DRV_IR_Init(){ pin = PIN_FindPinIndexForRole(IOR_IRRecv,pin); txpin = PIN_FindPinIndexForRole(IOR_IRSend,txpin); - if (ourReceiver){ - IRrecv *temp = ourReceiver; - ourReceiver = NULL; - delete temp; - } + // if (ourReceiver){ + // IRrecv *temp = ourReceiver; + // ourReceiver = NULL; + // delete temp; + // } ADDLOG_INFO(LOG_FEATURE_IR, (char *)"DRV_IR_Init: recv pin %i",pin); if ((pin > 0) || (txpin > 0)){ } else { @@ -592,8 +602,8 @@ extern "C" void DRV_IR_Init(){ // setup IRrecv pin as input bk_gpio_config_input_pup((GPIO_INDEX)pin); - ourReceiver = new IRrecv(pin); - ourReceiver->start(); + // ourReceiver = new IRrecv(pin); + // ourReceiver->enableIRIn(); } if (pIRsend){ @@ -630,12 +640,13 @@ extern "C" void DRV_IR_Init(){ //cmddetail:"descr":"Sends IR commands in the form PROT-ADDR-CMD-REP, e.g. NEC-1-1A-0", //cmddetail:"fn":"IR_Send_Cmd","file":"driver/drv_ir.cpp","requires":"", //cmddetail:"examples":""} - CMD_RegisterCommand("IRSend",IR_Send_Cmd, NULL); + CMD_RegisterCommand("IRSend",NULL,IR_Send_Cmd, NULL, NULL); + CMD_RegisterCommand("IRAC",NULL,IR_AC_Cmd, NULL, NULL); //cmddetail:{"name":"IREnable","args":"[Str][1or0]", //cmddetail:"descr":"Enable/disable aspects of IR. IREnable RXTX 0/1 - enable Rx whilst Tx. IREnable [protocolname] 0/1 - enable/disable a specified protocol", //cmddetail:"fn":"IR_Enable","file":"driver/drv_ir.cpp","requires":"", //cmddetail:"examples":""} - CMD_RegisterCommand("IREnable", IR_Enable, NULL); + CMD_RegisterCommand("IREnable", "", IR_Enable, NULL, NULL); } } if ((pin > 0) || (txpin > 0)){ @@ -647,13 +658,15 @@ extern "C" void DRV_IR_Init(){ // log the received IR -void PrintIRData(IRData *aIRDataPtr){ - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %d", (int)aIRDataPtr->protocol); - if (aIRDataPtr->protocol == UNKNOWN) { +void PrintIRData(decode_results *aIRDataPtr){ + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %d", (int)aIRDataPtr->decode_type); + +#if 0 // TODO: disabled for now, using routines from IRremoteESP8266 + if (aIRDataPtr->decode_type == UNKNOWN) { #if defined(DECODE_HASH) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Hash=0x%X", (int)aIRDataPtr->decodedRawData); #endif - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"%d bits (incl. gap and start) received", (int)((aIRDataPtr->rawDataPtr->rawlen + 1) / 2)); + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"%d bits (incl. gap and start) received", (int)((aIRDataPtr->rawlen) / 2)); } else { #if defined(DECODE_DISTANCE) if(aIRDataPtr->protocol != PULSE_DISTANCE) { @@ -709,11 +722,17 @@ void PrintIRData(IRData *aIRDataPtr){ } } } + #endif //0 + // TODO: reimplement ? + //ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)resultToHumanReadableBasic(aIRDataPtr).c_str()); + String description = IRAcUtils::resultAcToString(aIRDataPtr); + if (description.length()) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)description.c_str()); + } //////////////////////////////////////////////////// -// this polls the IR receive to see off there was any IR received +// this polls the IR receive to see if there was any IR received extern "C" void DRV_IR_RunFrame(){ // Debug-only check to see if the timer interrupt is running if (ir_counter){ @@ -728,26 +747,31 @@ extern "C" void DRV_IR_RunFrame(){ } } + #if 0 if (ourReceiver){ - if (ourReceiver->decode()) { - const char *name = ProtocolNames[ourReceiver->decodedIRData.protocol]; - if (!(gIRProtocolEnable & (1 << (int)ourReceiver->decodedIRData.protocol))){ - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", name, (int)ourReceiver->decodedIRData.protocol, gIRProtocolEnable); + decode_results results; + if (ourReceiver->decode(&results)) { + //const char *name = ProtocolNames[ourReceiver->decodedIRData.protocol]; + const char *name ="TODO"; + if (!(gIRProtocolEnable & (1 << (int)results.decode_type))){ + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", name, (int)results.decode_type, gIRProtocolEnable); } // 'UNKNOWN' protocol is by default disabled in flags // This is because I am getting a lot of 'UNKNOWN' spam with no IR signals in room - if (((ourReceiver->decodedIRData.protocol != UNKNOWN) || - (ourReceiver->decodedIRData.protocol == UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) && + if (((results.decode_type != UNKNOWN) || + (results.decode_type == UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) && // only process if this protocol is enabled. all by default. - (gIRProtocolEnable & (1 << (int)ourReceiver->decodedIRData.protocol)) + (gIRProtocolEnable & (1 << (int)results.decode_type)) ) { + #if 0 //TODO char out[128]; - PrintIRData(&ourReceiver->decodedIRData); - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %s (%d)", name, (int)ourReceiver->decodedIRData.protocol); + PrintIRData(&results); + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %s (%d)", name, (int)results.decode_type); int repeat = 0; + if (ourReceiver->decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) { if (ourReceiver->decodedIRData.flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { repeat = 2; @@ -761,9 +785,15 @@ extern "C" void DRV_IR_RunFrame(){ } else { snprintf(out, sizeof(out), "IR_%s 0x%X 0x%X %d", name, ourReceiver->decodedIRData.address, ourReceiver->decodedIRData.command, repeat); } + #endif + + String lastIrReceived = String(capture.decode_type) + "," + resultToHexidecimal(&results); + if (!hasACState(capture.decode_type)) + lastIrReceived += "," + String(results.bits); + // if user wants us to publish every received IR data, do it now if(CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED)) { - + // another flag required? int publishrepeats = 1; @@ -771,24 +801,28 @@ extern "C" void DRV_IR_RunFrame(){ //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR MQTT publish %s", out); uint32_t counter_in = ir_counter; - MQTT_PublishMain_StringString("ir",out, 0); + MQTT_PublishMain_StringString("ir",lastIrReceived.c_str(), 0); uint32_t counter_dur = ((ir_counter - counter_in)*50)/1000; ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR MQTT publish %s took %dms", out, counter_dur); } else { ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", out); } } else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", out); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", lastIrReceived.c_str()); } if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED_IN_JSON)) { // {"IrReceived":{"Protocol":"RC_5","Bits":0x1,"Data":"0xC"}} - // + // + + #if 0 // TODO: snprintf(out, sizeof(out), "{\"IrReceived\":{\"Protocol\":\"%s\",\"Bits\":%i,\"Data\":\"0x%lX\"}}", name, (int)ourReceiver->decodedIRData.numberOfBits, (unsigned long)ourReceiver->decodedIRData.decodedRawData); MQTT_PublishMain_StringString("RESULT", out, OBK_PUBLISH_FLAG_FORCE_REMOVE_GET); + #endif //TODO } + #if 0 if(ourReceiver->decodedIRData.protocol != UNKNOWN) { snprintf(out, sizeof(out), "%X", ourReceiver->decodedIRData.command); int tgType = 0; @@ -823,6 +857,7 @@ extern "C" void DRV_IR_RunFrame(){ uint32_t counter_dur = ((ir_counter - counter_in)*50)/1000; ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR fire event took %dms", counter_dur); } + #endif //0 TODO } /* * !!!Important!!! Enable receiving of the next value, @@ -831,6 +866,7 @@ extern "C" void DRV_IR_RunFrame(){ ourReceiver->resume(); // Enable receiving of the next value } } + #endif // TODO } diff --git a/src/libraries/IRremoteESP8266/.gitattributes b/src/libraries/IRremoteESP8266/.gitattributes new file mode 100644 index 000000000..05945c4fb --- /dev/null +++ b/src/libraries/IRremoteESP8266/.gitattributes @@ -0,0 +1,3 @@ +# Directories & files to ignore from release archives +docs export-ignore +assets export-ignore diff --git a/src/libraries/IRremoteESP8266/.github/CONTRIBUTING.md b/src/libraries/IRremoteESP8266/.github/CONTRIBUTING.md new file mode 100644 index 000000000..87fd34fc1 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/CONTRIBUTING.md @@ -0,0 +1,82 @@ +# Contributing to the IRremoteESP8266 library + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +The following is a set of guidelines for contributing to the IRremoteESP8266 library, hosted on GitHub. These are guidelines, [not rules](http://imgur.com/mSHi8). Use your best judgment, and feel free to propose changes to this document in a pull request. + +#### Table Of Contents + +[Code of Conduct](#code-of-conduct) + +[How Can I Contribute?](#how-can-i-contribute) + * [Reporting Bugs](#reporting-bugs) + * [Pull Requests](#pull-requests) + +[Styleguides](#styleguides) + * [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) + * [Git Commit Messages](#git-commit-messages) + + +## Code of Conduct + +This project and everyone participating in it is governed by the principle of ["Be excellent to each other"](http://www.imdb.com/title/tt0096928/quotes). That's it. TL;DR: _Don't be a jerk._ + +## How Can I Contribute? + +### Reporting Bugs + +This section guides you through submitting a bug report for the library. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. + +Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as much detail as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](issue_template.md), the information it asks for helps us resolve issues faster. + +> **Note:** If you find a **Closed** issue that seems like it's the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +#### Before Submitting A Bug Report + +* **Check the [Troubleshooting Guide](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Troubleshooting-Guide).** You might be able to find the cause of the problem and fix it yourself. Most importantly, check if you can reproduce the problem in the latest version (a.k.a. 'master') of the library. +* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+repo%3Acrankyoldgit/IRremoteESP8266)** to see if the problem is already reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Bug Report? + +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue and provide the following information by filling in [the template](issue_template.md). + +Explain the problem and include any additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as much detail as possible. +* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected to see instead and why.** +* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. + +Provide more context by answering these questions: + +* **Can you reproduce the problem in one of the code examples?** +* **Did the problem start happening recently** (e.g. after updating to a new version of Arduino or the library) or was this always a problem? +* If the problem started happening recently, **can you reproduce the problem in an older version of the library?** What's the most recent version in which the problem doesn't happen? You can download older versions of the library from [the releases page](https://github.com/crankyoldgit/IRremoteESP8266/releases). +* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. + +Include details about your configuration, circuit and environment: + +* **Which version of the library are you using?** You can get the exact version by inspecting the `library.json` file in the root directory of the library. +* **What board are you running this on?** + +### Pull Requests + +* Do not include issue numbers in the PR title +* Include as much data and comments as practicle. +* Follow the [C++ style guide](https://google.github.io/styleguide/cppguide.html). +* Please write or ensure Unit Tests cover the change you are making, if you can. +* End all files with a newline +* Avoid platform-dependent code. +* Use c98 types where possible for better portablity. +* In almost all cases, code & documentation should be peer-reviewed by at least one other contributor. +* The code should pass all the existing testing infrastructure in GitHub Actions. e.g. Unit tests, cpplint, and basic compilation etc. +* State if you have tested this under real conditions if you have, and what other tests you may have carried out. + +### Git Commit Messages + +* Limit the first line to 72 characters or less +* Reference issues and pull requests liberally after the first line +* Humour is always acceptable. Be liberal with it. ;-) +* While not required, a comprehensive description of all the changes in the PR is best. diff --git a/src/libraries/IRremoteESP8266/.github/Contributors.md b/src/libraries/IRremoteESP8266/.github/Contributors.md new file mode 100644 index 000000000..afa0a51e9 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/Contributors.md @@ -0,0 +1,25 @@ +## Contributors of this project +### Main contributors & maintainers +- [Mark Szabo](https://github.com/markszabo/) : Initial IR sending on ESP8266 +- [Sébastien Warin](https://github.com/sebastienwarin/) (http://sebastien.warin.fr) : Initial IR receiving on ESP8266 +- [David Conran](https://github.com/crankyoldgit/) : ESP32 support and pretty much everything else. +- [Roi Dayan](https://github.com/roidayan/) +- [Marcos de Alcântara Marinho](https://github.com/marcosamarinho/) +- [Massimiliano Pinto](https://github.com/pintomax/) +- [Darsh Patel](https://github.com/darshkpatel/) +- [Jonny Graham](https://github.com/jonnygraham/) +- [Stu Fisher](https://github.com/stufisher/) +- [Jorge Cisneros](https://github.com/jorgecis/) +- [Denes Varga](https://github.com/denxhun/) +- [Brett T. Warden](https://github.com/bwarden/) +- [Fabien Valthier](https://github.com/hcoohb) +- [Ajay Pala](https://github.com/ajaypala/) +- [Motea Marius](https://github.com/mariusmotea) +- [Mark Kuchel](https://github.com/kuchel77) +- [Christian Nilsson](https://github.com/NiKiZe) +- [Zhongxian Li](https://github.com/siriuslzx) +- [Davide Depau](https://github.com/Depau) + +All contributors can be found on the [contributors site](https://github.com/crankyoldgit/IRremoteESP8266/graphs/contributors). + +### Contributors of the [original project](https://github.com/z3t0/Arduino-IRremote) can be found on the [original project's contributors page](https://github.com/z3t0/Arduino-IRremote/blob/master/Contributors.md) diff --git a/src/libraries/IRremoteESP8266/.github/ISSUE_TEMPLATE/problem-report.md b/src/libraries/IRremoteESP8266/.github/ISSUE_TEMPLATE/problem-report.md new file mode 100644 index 000000000..1f1556781 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/ISSUE_TEMPLATE/problem-report.md @@ -0,0 +1,57 @@ +--- +name: Problem report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +### Version/revision of the library used +_Typically located in the `library.json` & `src/IRremoteESP8266.h` files in the root directory of the library. +e.g. v2.0.0, or 'master' as at 1st of June, 2017. etc._ + +### Describe the bug +A clear and concise description of what the bug is. + +#### To Reproduce +_What steps did you do, and what did or didn't actually happen? How can we reproduce the issue?_ + +_e.g._ +_1. Initialise the IRsend class._ +_2. `IRsend.sendFoobar(0xdeadbeef);`_ +_3. Foobar BBQ went into Cow(er)-saving mode and fried me a couple of eggs instead._ + +#### Example code used +_Include all relevant code snippets or links to the actual code files. Tip: [How to quote your code so it is still readable in the report](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code)._ + +#### Expected behaviour +_What steps did you do and what should it have done? A clear and concise description of what you expected to happen._ + +_e.g._ +_1. Initialise the IRsend class._ +_2. `IRsend.sendFoobar(0xdeadbeef);`_ +_3. Foobar branded BBQ turns on and cooks me some ribs._ + +#### Output of raw data from [IRrecvDumpV2.ino](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/IRrecvDumpV2/IRrecvDumpV2.ino) or [V3](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/IRrecvDumpV3/) (if applicable) +_Include some serial text of the raw dumps of what the device saw._ + +_**Note**: Output from Tasmota is not acceptable. We can't easily use their raw format._ + + +### What brand/model IR demodulator are you using? +_Brand/Model code_ or _None_ + +_If VS1838b: That is likely your problem. Please buy a better one & try again. Reporting a capture or decode issue when you use one of these is effectively wasting our & your time. See the [FAQ](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Frequently-Asked-Questions#Help_Im_getting_very_inconsistent_results_when_capturing_an_IR_message_using_a_VS1838b_IR_demodulator)_ + +### Circuit diagram and hardware used (if applicable) +_Link to an image of the circuit diagram used. Part number of the IR receiver module etc. ESP8266 or ESP32 board type._ + +### I have followed the steps in the [Troubleshooting Guide](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Troubleshooting-Guide) & read the [FAQ](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Frequently-Asked-Questions) +_Yes/No._ + +### Has this library/code previously worked as expected for you? +_Yes/No. If "Yes", which version last worked for you?_ + +### Other useful information +_More information is always welcome. Be verbose._ diff --git a/src/libraries/IRremoteESP8266/.github/workflows/Build.yml b/src/libraries/IRremoteESP8266/.github/workflows/Build.yml new file mode 100644 index 000000000..dd4504361 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/workflows/Build.yml @@ -0,0 +1,74 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + Gen_Matrix: + outputs: + matrix: ${{ steps.files.outputs.matrix }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Generate Matrix of all examples + id: files + run: | + JSONI=$(find . -name platformio.ini -type f | sed 's,/platformio.ini$,,' | xargs -n 1 -I {} echo -e '"{}",') + + # Remove last "," and add closing brackets + if [[ $JSONI == *, ]]; then + JSONI="${JSONI%?}" + fi + JSONI=${JSONI//$'\n'} + echo $JSONI + # Set output + echo "::set-output name=matrix::[${JSONI}]" + Build_Example: + needs: Gen_Matrix + runs-on: ubuntu-latest + strategy: + matrix: + project: ${{fromJson(needs.Gen_Matrix.outputs.matrix)}} + + steps: + - uses: actions/checkout@v2 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Cache PlatformIO + uses: actions/cache@v2 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/platformio.ini') }} + - name: Cache PlatformIO build + uses: actions/cache@v2 + with: + path: .pio + key: pio-${{ runner.os }}-${{ matrix.project }} + restore-keys: | + pio-${{ runner.os }}- + pio- + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + - name: Build example + env: + PLATFORMIO_BUILD_CACHE_DIR: "../../.pio/buildcache" + run: pio run --jobs 2 --project-dir ${{ matrix.project }} + + Build_Examples: + needs: Build_Example + runs-on: ubuntu-latest + steps: + - name: done + run: echo ok diff --git a/src/libraries/IRremoteESP8266/.github/workflows/Documentation.yml b/src/libraries/IRremoteESP8266/.github/workflows/Documentation.yml new file mode 100644 index 000000000..e7520577c --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/workflows/Documentation.yml @@ -0,0 +1,15 @@ +# This is a basic workflow that is triggered on push/PRs to the master branch. + +name: Documentation + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: [push, pull_request] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + Doxygen: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: mattnotmitt/doxygen-action@v1 diff --git a/src/libraries/IRremoteESP8266/.github/workflows/Lint.yml b/src/libraries/IRremoteESP8266/.github/workflows/Lint.yml new file mode 100644 index 000000000..a0f7664d9 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/workflows/Lint.yml @@ -0,0 +1,28 @@ +name: Code Lint + +on: [push, pull_request] + +jobs: + Linters: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + pip install cpplint + - name: Analysing the code with pylint + run: | + shopt -s nullglob + pylint -d F0001 {src,test,tools}/*.py + - name: Analysing the code with cpplint + run: | + shopt -s nullglob + cpplint --extensions=c,cc,cpp,ino --headers=h,hpp {src,src/locale,test,tools}/*.{h,c,cc,cpp,hpp,ino} examples/*/*.{h,c,cc,cpp,hpp,ino} diff --git a/src/libraries/IRremoteESP8266/.github/workflows/MetaChecks.yml b/src/libraries/IRremoteESP8266/.github/workflows/MetaChecks.yml new file mode 100644 index 000000000..d8dcd0703 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/workflows/MetaChecks.yml @@ -0,0 +1,54 @@ +# This is a basic workflow that is triggered on push/PRs to the master branch. + +name: Library Linter + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + arduino-library-manager-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: arduino/arduino-lint-action@v1 + with: + library-manager: update + compliance: strict + # Detect case-insensitive file duplication in the same directory. + # This can cause a problem for the Arduino IDE on Windows & Macs. + # See: + # - https://github.com/arduino/Arduino/issues/11441 + # - https://github.com/crankyoldgit/IRremoteESP8266/issues/1451 + detect-duplicate-files: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Look for case-insensitive filename collisions. + run: DUPS=$(find . -path '*/.pio' -prune -o -print | sort | uniq -D -i); if [[ -n "${DUPS}" ]]; then echo -e "Duplicates found:\n${DUPS}"; false; fi + version-number-consitent: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check all the version numbers match. + run: | + LIB_VERSION=$(tools/extract_lib_version.sh) + test ${LIB_VERSION} == "$(jq -r .version library.json)" + grep -q "^version=${LIB_VERSION}$" library.properties + examples-have-platformio_ini: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check that every example directory has a platformio.ini file. + run: (status=0; for dir in examples/*; do if [[ ! -f "${dir}/platformio.ini" ]]; then echo "${dir} has no 'platform.ini' file!"; status=1; fi; done; exit ${status}) + supported-devices-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check that all files have supported sections. + run: (SUPPORTED_OUTPUT=$(python3 tools/scrape_supported_devices.py --noout --alert 2>&1); if [[ $? -ne 0 || -n "${SUPPORTED_OUTPUT}" ]]; then echo "${SUPPORTED_OUTPUT}"; exit 1; fi) diff --git a/src/libraries/IRremoteESP8266/.github/workflows/UnitTests.yml b/src/libraries/IRremoteESP8266/.github/workflows/UnitTests.yml new file mode 100644 index 000000000..a99e18c80 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/workflows/UnitTests.yml @@ -0,0 +1,30 @@ +name: Tests + +on: [push, pull_request] + +jobs: + Tools_Tests: + runs-on: ubuntu-latest + env: + MAKEFLAGS: "-j 2" + steps: + - uses: actions/checkout@v2 + - name: Build tools unit tests + run: (cd tools; make all) + - name: Run tools unit tests + run: (cd tools; make run_tests) + + Unit_Tests: + runs-on: ubuntu-latest + env: + MAKEFLAGS: "-j 2" + steps: + - uses: actions/checkout@v2 + - name: Install the Google test suite + run: (cd test; make install-googletest) + - name: Build base unit test + run: (cd test; make IRac_test) + - name: Build library unit tests + run: (cd test; make all) + - name: Run library unit tests + run: (cd test; make run) diff --git a/src/libraries/IRremoteESP8266/.github/workflows/codeql-analysis.yml b/src/libraries/IRremoteESP8266/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..48500339a --- /dev/null +++ b/src/libraries/IRremoteESP8266/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '38 1 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/src/libraries/IRremoteESP8266/.gitignore b/src/libraries/IRremoteESP8266/.gitignore new file mode 100644 index 000000000..b58efd41d --- /dev/null +++ b/src/libraries/IRremoteESP8266/.gitignore @@ -0,0 +1,55 @@ +#----------------------------------------# +# .gitingore for IRremoteESP8266 library # +#----------------------------------------# + +### Files to ignore. + +## Editors +# vi/vim +**/*.swp + +# vscode +.vscode + +## Build environments +# Platformio +**/.pio/ +**/.pioenvs/ +**/.piolibdeps/ +**/.clang_complete +**/.gcc-flags.json +examples/**/lib +examples/**/.travis.yml +examples/**/.gitignore +lib/readme.txt +lib/googletest/ + +# GCC pre-compiled headers. +**/*.gch + +# Python compiled files +**/*.pyc + +# Unit Test builds +test/*.o +test/*.a +test/*_test + +# Tools builds +tools/*.o +tools/*.a +tools/gc_decode +tools/mode2_decode +tools/code_to_raw + +.pioenvs +.piolibdeps +.clang_complete +.gcc-flags.json + +#Cygwin builds +*.exe + +# Mac extended attributes +.DS_Store +/.vs diff --git a/src/libraries/IRremoteESP8266/.style.yapf b/src/libraries/IRremoteESP8266/.style.yapf new file mode 100644 index 000000000..65fa0ee33 --- /dev/null +++ b/src/libraries/IRremoteESP8266/.style.yapf @@ -0,0 +1,3 @@ +[style] +based_on_style: google +indent_width: 2 diff --git a/src/libraries/IRremoteESP8266/LICENSE.txt b/src/libraries/IRremoteESP8266/LICENSE.txt new file mode 100644 index 000000000..77cec6dd1 --- /dev/null +++ b/src/libraries/IRremoteESP8266/LICENSE.txt @@ -0,0 +1,458 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + diff --git a/src/libraries/IRremoteESP8266/README.md b/src/libraries/IRremoteESP8266/README.md new file mode 100644 index 000000000..b15ef1abe --- /dev/null +++ b/src/libraries/IRremoteESP8266/README.md @@ -0,0 +1,89 @@ +![IRremoteESP8266 Library](./assets/images/banner.svg) + +[![Build Status](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Build.yml/badge.svg)](../../actions/workflows/Build.yml) +[![Code Lint](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Lint.yml/badge.svg)](../../actions/workflows/Lint.yml) +[![Tests](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/UnitTests.yml/badge.svg)](../../actions/workflows/UnitTests.yml) +[![Documentation](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Documentation.yml/badge.svg)](../../actions/workflows/Documentation.yml/badge.svg) +[![arduino-library-badge](https://www.ardu-badge.com/badge/IRremoteESP8266.svg?)](https://www.ardu-badge.com/IRremoteESP8266) +[![GitLicense](https://gitlicense.com/badge/crankyoldgit/IRremoteESP8266)](https://gitlicense.com/license/crankyoldgit/IRremoteESP8266) + +This library enables you to **send _and_ receive** infra-red signals on an [ESP8266](https://github.com/esp8266/Arduino) or an +[ESP32](https://github.com/espressif/arduino-esp32) using the [Arduino framework](https://www.arduino.cc/) using common 940nm IR LEDs and common IR receiver modules. e.g. TSOP{17,22,24,36,38,44,48}* demodulators etc. + +## v2.8.4 Now Available +Version 2.8.4 of the library is now [available](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). You can view the [Release Notes](ReleaseNotes.md) for all the significant changes. + +#### Upgrading from pre-v2.0 +Usage of the library has been slightly changed in v2.0. You will need to change your usage to work with v2.0 and beyond. You can read more about the changes required on our [Upgrade to v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0) page. + +#### Upgrading from pre-v2.5 +The library has changed from using constants declared as `#define` to +[const](https://google.github.io/styleguide/cppguide.html#Constant_Names) with +the appropriate naming per the +[C++ style guide](https://google.github.io/styleguide/cppguide.html). +This may potentially cause old programs to not compile. +The most likely externally used `#define`s have been _aliased_ for limited +backward compatibility for projects using the old style. Going forward, only the +new `kConstantName` style will be supported for new protocol additions. + +In the unlikely case, it does break your code, then you may have been referencing +something you likely should not have. You should be able to quickly determine +the new name from the old. e.g. `CONSTANT_NAME` to `kConstantName`. +Use common sense or examining the library's code if this does affect code. + +## Supported Protocols +You can find the details of which protocols & devices are supported +[here](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/SupportedProtocols.md). + +## Troubleshooting +Before reporting an issue or asking for help, please try to follow our [Troubleshooting Guide](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Troubleshooting-Guide) first. + +## Frequently Asked Questions +Some common answers to common questions and problems are on our [F.A.Q. wiki page](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Frequently-Asked-Questions). + +## Library API Documentation +This library uses [Doxygen](https://www.doxygen.nl/index.html) to [automatically document](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) the [library's](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) [API](https://en.wikipedia.org/wiki/Application_programming_interface). +You can find it [here](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/). + +## Installation +##### Official releases via the Arduino IDE v1.8+ (Windows & Linux) +1. Click the _"Sketch"_ -> _"Include Library"_ -> _"Manage Libraries..."_ Menu items. +1. Enter `IRremoteESP8266` into the _"Filter your search..."_ top right search box. +1. Click on the IRremoteESP8266 result of the search. +1. Select the version you wish to install and click _"Install"_. + +##### Manual Installation for Windows +1. Click on _"Clone or Download"_ button, then _"[Download ZIP](https://github.com/crankyoldgit/IRremoteESP8266/archive->master.zip)"_ on the page. +1. Extract the contents of the downloaded zip file. +1. Rename the extracted folder to _"IRremoteESP8266"_. +1. Move this folder to your libraries directory. (under windows: `C:\Users\YOURNAME\Documents\Arduino\libraries\`) +1. Restart your Arduino IDE. +1. Check out the examples. + +##### Using Git to install the library ( Linux ) +``` +cd ~/Arduino/libraries +git clone https://github.com/crankyoldgit/IRremoteESP8266.git +``` +###### To update to the latest version of the library +``` +cd ~/Arduino/libraries/IRremoteESP8266 && git pull +``` + +## Contributing +If you want to [contribute](.github/CONTRIBUTING.md#how-can-i-contribute) to this project, consider: +- [Reporting](.github/CONTRIBUTING.md#reporting-bugs) bugs and errors +- Ask for enhancements +- Improve our documentation +- [Creating issues](.github/CONTRIBUTING.md#reporting-bugs) and [pull requests](.github/CONTRIBUTING.md#pull-requests) +- Tell other people about this library + +## Contributors +Available [here](.github/Contributors.md) + +## Library History +This library was originally based on Ken Shirriff's work (https://github.com/shirriff/Arduino-IRremote/) + +[Mark Szabo](https://github.com/crankyoldgit/IRremoteESP8266) has updated the IRsend class to work on ESP8266 and [Sebastien Warin](https://github.com/sebastienwarin/IRremoteESP8266) the receiving & decoding part (IRrecv class). + +As of v2.0, the library was almost entirely re-written with the ESP8266's resources in mind. diff --git a/src/libraries/IRremoteESP8266/README_de.md b/src/libraries/IRremoteESP8266/README_de.md new file mode 100644 index 000000000..51c18c482 --- /dev/null +++ b/src/libraries/IRremoteESP8266/README_de.md @@ -0,0 +1,82 @@ +![IRremoteESP8266 Library](./assets/images/banner.svg) + +[![Build-Status](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Build.yml/badge.svg)](../../actions/workflows/Build.yml/badge.svg) +[![Code-Lint](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Lint.yml/badge.svg)](../../actions/workflows/Lint.yml) +[![Tests](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/UnitTests.yml/badge.svg)](../../ctions/workflows/UnitTests.yml) +[![Dokumentation](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Documentation.yml/badge.svg)](../../actions/workflows/Documentation.yml) +[![arduino-library-badge](https://www.ardu-badge.com/badge/IRremoteESP8266.svg?)](https://www.ardu-badge.com/IRremoteESP8266) +[![Arduino-Bibliothek-Abzeichen](https://www.ardu-badge.com/badge/IRremoteESP8266.svg?)](https://www.ardu-badge.com/IRremoteESP8266) +[![Git-Lizenz](https://gitlicense.com/badge/crankyoldgit/IRremoteESP8266)](https://gitlicense.com/license/crankyoldgit/IRremoteESP8266) + +Diese Programmbibliothek ermöglicht das **Senden _und_ Empfangen** von Infrarotsignalen mit [ESP8266](https://github.com/esp8266/Arduino)- und +[ESP32](https://github.com/espressif/arduino-esp32)-Mikrocontrollern mithilfe des [Arduino-Frameworks](https://www.arduino.cc/) und handelsüblichen 940nm Infrarot-LEDs undIR-Empfängermodulen, wie zum Beispiel TSOP{17,22,24,36,38,44,48}*-Demodulatoren. + +## v2.8.4 jetzt verfügbar +Version 2.8.4 der Bibliothek ist nun [verfügbar](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Die [Versionshinweise](ReleaseNotes.md) enthalten alle wichtigen Neuerungen. + +#### Hinweis für Nutzer von Versionen vor v2.0 +Die Benutzung der Bibliothek hat sich mit Version 2.0 leicht geändert. Einige Anpassungen im aufrufenden Code werden nötig sein, um mit Version ab 2.0 korrekt zu funktionieren. Mehr zu den Anpassungen finden sich auf unserer [Upgrade to v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0)-Seite. + +#### Hinweis für Nutzer von Versionen vor v2.5 +Die Bibliothek benutzt zur Konstantendefinition nun statt `#define` den [const](https://google.github.io/styleguide/cppguide.html#Constant_Names)-Ansatz mit der Benennung in Einklang mit dem [C++-Style-Guide](https://google.github.io/styleguide/cppguide.html). +Dies kann eventuell dazu führen, daß sich ältere Programme nicht mehr kompilieren lassen. +Die mutmaßlich am häufigsten extern verwendeten `#define`s wurden daher ge_aliased_, um eine gewisse Rückwärts-Kompatibilität zu gewährleisten. In künftigen Protokollimplementationen wird nur noch der neue `kConstantName`-Stil unterstützt werden. + +Falls die Änderungen dazu geführt haben, daß der aufrufende Code nicht mehr funktioniert, wurde vermutlich etwas referenziert, was nicht referenziert werden sollte. Den neuen Kontanten-Namen herauszufinden sollte nach der Form `CONSTANT_NAME` nach `kConstantName` leicht möglich sein. +Etwas gesunder Menschenverstand oder ein Blick in den Bibliotheksquellcode helfen, falls aufrufender Code betroffen ist. + +## Unterstützte Protokolle +Details zu den unterstützten Protokollen und Geräten befinden sich [hier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/SupportedProtocols.md). + +## Fehlersuche +Bitte erst den [Troubleshooting Guide](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Troubleshooting-Guide) lesen, bevor Probleme gemeldet werden oder um Hilfe gebeten wird. + +## FAQ - häufige Fragen +Einige Antworten zu häufig gestellten Fragen sind auf unserer [F.A.Q. Wiki-Seite](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Frequently-Asked-Questions) hinterlegt. + +## Library API-Dokumentation +Diese Bibliothek benutzt [Doxygen](https://www.doxygen.nl/index.html) zur [automatischen Dokumentation](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) der [API](https://en.wikipedia.org/wiki/Application_programming_interface) dieser [Bibliothek](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/). +Sie ist [hier](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) zu finden. + +## Installation +##### Installation von offiziellen Releases über die Arduino-IDE v1.8+ (Windows & Linux) +1. Das Untermenü _"Sketch"_ -> _"Bibliothek einbinden"_ -> _"Bibliotheken verwalten..."_ aufrufen. +1. In das Suchfeld oben rechts (_"Grenzen Sie Ihre Suche ein..."_) `IRremoteESP8266` eintragen. +1. Bei den Suchergebnissen IRremoteESP8266 auswählen. +1. Die Version markieren, die installiert werden soll, und dann _"Installieren"_ klicken. + +##### Manuelle Installation (Windows) +1. Auf der Website auf den grünen _"Code"_-Knopf klicken, dann _"[Download ZIP](https://github.com/crankyoldgit/IRremoteESP8266/archive->master.zip)"_ auswählen. +1. Die heruntergeladene Zip-Datei entpacken. +1. Den entpackten Dateiordner in _"IRremoteESP8266"_ umbenennen. +1. Diesen Ordner anschließend in den Bibliotheken-Pfad verschieben. (Unter Windows: `C:\Users\BENUTZER\Dokumente\Arduino\libraries\`) +1. Die Arduino-IDE neu starten. +1. Unter den Beispielen finden sich neue Einträge. + +##### Benutzung von Git für die Installation der Bibliothek (Linux) +``` +cd ~/Arduino/libraries +git clone https://github.com/crankyoldgit/IRremoteESP8266.git +``` +###### Um die neueste Version der Bibliothek zu beziehen +``` +cd ~/Arduino/libraries/IRremoteESP8266 && git pull +``` + +## Mithelfen +Anregungen für die [Mithilfe](.github/CONTRIBUTING.md#how-can-i-contribute) am Projekt: +- Das [Melden](.github/CONTRIBUTING.md#reporting-bugs) von Bugs und Fehlern +- Das Einreichen von Verbesserungs- und Erweiterungsvorschlägen +- Das Erstellen und Verbessern der Dokumentation +- Das [Melden von Problemen](.github/CONTRIBUTING.md#reporting-bugs) und Einreichen von [Pull-Requests](.github/CONTRIBUTING.md#pull-requests) +- Anderen Leuten von dieser Bibliothek erzählen + +## Beitragende +Die Beitragenden sind [hier](.github/Contributors.md) aufgelistet. + +## Historie der Bibliothek +Diese Bibliothek basiert auf Ken Shirriff's Vorarbeit (https://github.com/shirriff/Arduino-IRremote/). + +[Mark Szabo](https://github.com/crankyoldgit/IRremoteESP8266) programmierte die IRsend-Klassen auf ESP8266 und [Sebastien Warin](https://github.com/sebastienwarin/IRremoteESP8266) war verantwortlich für die Empfangs- und Dekodier-Teile (IRrecv-Klassen). + +Die Bibliothek wurde ab Version v2.0 fast komplett neu geschrieben, um besser auf die ESP8266-Ressourcen Rücksicht zu nehmen. diff --git a/src/libraries/IRremoteESP8266/README_fr.md b/src/libraries/IRremoteESP8266/README_fr.md new file mode 100644 index 000000000..71e436fc3 --- /dev/null +++ b/src/libraries/IRremoteESP8266/README_fr.md @@ -0,0 +1,91 @@ +![IRremoteESP8266 Library](./assets/images/banner.svg) + +[![Construire](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Build.yml/badge.svg)](../../actions/workflows/Build.yml) +[![Charbon de code](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Lint.yml/badge.svg)](../../actions/workflows/Lint.yml) +[![Essais](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/UnitTests.yml/badge.svg)](../../actions/workflows/UnitTests.yml) +[![Documentation](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Documentation.yml/badge.svg)](../../actions/workflows/Documentation.yml) +[![arduino-library-badge](https://www.ardu-badge.com/badge/IRremoteESP8266.svg?)](https://www.ardu-badge.com/IRremoteESP8266) +[![LicenseGit](https://gitlicense.com/badge/crankyoldgit/IRremoteESP8266)](https://gitlicense.com/license/crankyoldgit/IRremoteESP8266) + +Cette librairie vous permetra de **recevoir et d'envoyer des signaux** infrarouge sur le protocole [ESP8266](https://github.com/esp8266/Arduino) ou sur le protocole +[ESP32](https://github.com/espressif/arduino-esp32) en utilisant le [Arduino framework](https://www.arduino.cc/) qui utilise la norme 940nm IR LEDs et le module basique de reception d'onde IR. Exemple : TSOP{17,22,24,36,38,44,48}* modules etc. + +## v2.8.4 disponible +Version 2.8.4 de la libraire est maintenant [disponible](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Vous pouvez voir le [Release Notes](ReleaseNotes.md) pour tous les changements importants. + +#### mise à jour depuis pre-v2.0 +L'utilisation de la librairie à un peu changer depuis la version in v2.0. Si vous voulez l'utiliser vous devrez changer votre utilisation aussi. Vous pouvez vous renseigner sur les précondition d'utilisation ici : [Upgrade to v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0) page. + +#### Mise à jour depuis pre-v2.5 +La librairie à changer, elle n'utilise plus les constantes déclarées comme `#define` mais comme : +[const](https://google.github.io/styleguide/cppguide.html#Constant_Names) avec le nom approprié par le langage +[C++ style guide](https://google.github.io/styleguide/cppguide.html). +Il se peut que d'ancien programme ne compile pas. +Le cas le plus utilisé de `#define`s à été remplacé par _aliased_ pour limiter +la compatibilité de revenir en arrière pour les vieux projet. En revenant en arrière seulement la +nouvelle `kConstantName` style est supporté. + +Dans le cas peu probable, votre code serait cassé, alors vous avez peut-être fait référence à +quelque chose que vous ne devriez probablement pas avoir.Vous devez être capable de determiner le nouveau nom +qui remplacera l'ancien. exemple : `CONSTANT_NAME` par `kConstantName`. +Si vous avez un problème examinez le code pour trouver le problème. + +## Protocoles supportés +Vous pouvez trouver le détails des protocoles et machines supportés +[here](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/SupportedProtocols.md). + +## Dépannage +Avant de reporter un probème ou de demander de l'aide, essayez de suivre notre [guide de dépannage](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Troubleshooting-Guide) first. + +## Questions fréquentes +Les questions les plus fréquentes sont ici, avec des réponses [F.A.Q. wiki page](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Frequently-Asked-Questions). + +## Documentation API de la bibliothèque +Cette bibliothèque utilise [Doxygen](https://www.doxygen.nl/index.html) pour [documenter automatiquement](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) [l'API](https://en.wikipedia.org/wiki/Application_programming_interface) de la [bibliothèque](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/). Vous pouvez le trouver [ici](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/). + +## Installation +##### Officiel releases avec l'Arduino IDE v1.8+ (Windows & Linux) +1. Cliquez sur _"Sketch"_ -> _"Include Library"_ -> _"Manage Libraries..."_ Menu items. +1. Entrez `IRremoteESP8266` dans le _"Filter your search..."_ barre de recherche en haut à droite. +1. Cliquez sur le IRremoteESP8266 pour avoir les résultats de la recherche. +1. Selectionnez la version que vous voulez installer et cliquez sur _"Install"_. + +## Library API Documentation +This library uses [Doxygen](https://www.doxygen.nl/index.html) to [automatically document](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) the [library's](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) [API](https://en.wikipedia.org/wiki/Application_programming_interface). +You can find it [here](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/). + +##### Installation manuelle pour Windows +1. cliquez le boutton sur _"Clone or Download"_ , et _"[Download ZIP](https://github.com/crankyoldgit/IRremoteESP8266/archive->master.zip)"_ on the page. +1. Extraire l'archive. +1. renommez le fichier par _"IRremoteESP8266"_. +1. déplacer le fichier dans votre fichier de bibliothèques. (Pour windows : `C:\Users\VOTRE_NOM\Documents\Arduino\libraries\`) +1. Redemarrez arduino IDE. +1. Regardez les exemples. + +##### En utilisant GIT ( Linux ) +``` +cd ~/Arduino/libraries +git clone https://github.com/crankyoldgit/IRremoteESP8266.git +``` +###### Pour se mettre à jour +``` +cd ~/Arduino/libraries/IRremoteESP8266 && git pull +``` + +## Contribution +Si vous voulez [contribuer](.github/CONTRIBUTING.md#how-can-i-contribute) au projet, pour les erreurs: +- [Reporting](.github/CONTRIBUTING.md#reporting-bugs) bug et erreurs +- Demander des améliorations +- Améliorer notre documentation +- [Création d'issues](.github/CONTRIBUTING.md#reporting-bugs) et [pull requests](.github/CONTRIBUTING.md#pull-requests) +- Parlez de cettre librairie à d'autres personnes + +## Contributeurs +disponible [ici](.github/Contributors.md) + +## Historique de la bibliothèque +Elle est basée sur le travail de Shirriff (https://github.com/shirriff/Arduino-IRremote/) + +[Mark Szabo](https://github.com/crankyoldgit/IRremoteESP8266) à mis a jour la IRsend class pour qu'elle soit fonctionnelle sur ESP8266 et [Sebastien Warin](https://github.com/sebastienwarin/IRremoteESP8266) s'est occupé de la partie réception et décodage (IRrecv class). + +Comme pour la version 2.0, la bibliothèque à été completement réécrite avec les ressources sur ESP8266. diff --git a/src/libraries/IRremoteESP8266/README_nl.md b/src/libraries/IRremoteESP8266/README_nl.md new file mode 100644 index 000000000..d85c3e9b1 --- /dev/null +++ b/src/libraries/IRremoteESP8266/README_nl.md @@ -0,0 +1,89 @@ +![IRremoteESP8266 Library](./assets/images/banner.svg) + +[![Build-Status](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Build.yml/badge.svg)](../../actions/workflows/Build.yml/badge.svg) +[![Code-Lint](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Lint.yml/badge.svg)](../../actions/workflows/Lint.yml) +[![Tests](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/UnitTests.yml/badge.svg)](../../ctions/workflows/UnitTests.yml) +[![Dokumentation](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Documentation.yml/badge.svg)](../../actions/workflows/Documentation.yml) +[![arduino-library-badge](https://www.ardu-badge.com/badge/IRremoteESP8266.svg?)](https://www.ardu-badge.com/IRremoteESP8266) +[![Arduino-Bibliothek-Abzeichen](https://www.ardu-badge.com/badge/IRremoteESP8266.svg?)](https://www.ardu-badge.com/IRremoteESP8266) +[![Git-Lizenz](https://gitlicense.com/badge/crankyoldgit/IRremoteESP8266)](https://gitlicense.com/license/crankyoldgit/IRremoteESP8266) + +Deze library maakt het mogelijk om Infraroodsignalen **te versturen en ontvangen** via het [Arduino framework](https://www.arduino.cc/) met veelgebruikte 940nm IR LEDs en IR ontvang modules. b.v. TSOP{17,22,24,36,38,44,48}* demodulatoren enz. + +## v2.8.4 nu beschikbaar +Versie 2.8.4 van de bibliotheek is nu [beschikbaar](https://github.com/crankyoldgit/IRremoteESP8266/releases/latest). Bekijk de [Release Notes](ReleaseNotes.md) voor alle belangrijke veranderingen. + +#### Upgraden vanaf pre-v2.0 +Het gebruik van de bibliotheek is enigszins gewijzigd in v2.0. Je zult het gebruik moeten aanpassen om te kunnen werken met v2.0 en hoger. Je kunt meer lezen over de vereiste aanpassingen op onze [Upgraden naar v2.0](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Upgrading-to-v2.0) pagina. + +#### Upgraden vanaf pre-v2.5 +De bibliotheek defineert constanten nu niet meer als `#define`, maar gebruikt +[const](https://google.github.io/styleguide/cppguide.html#Constant_Names) met +de juiste naamgeving volgens de +[C++ style guide](https://google.github.io/styleguide/cppguide.html). +Dit kan ertoe leiden dat oude programma's niet compileren. +De meest extern gebruikte `#define`s zijn _gealiased_ voor beperkte +compatibiliteit voor projecten die de oude stijl gebruiken. In de toekomst zal alleen de +nieuwe `kConstantName` stijl worden ondersteund voor nieuwe protocoltoevoegingen. + +In het onwaarschijnlijke geval dat het je code breekt, dan heb je misschien verwezen naar +iets wat je waarschijnlijk niet had moeten doen. Gelukkig is het redelijk simpel om de nieuwe naam +te bepalen vanaf de oude, b.v. `CONSTANT_NAME` naar `kConstantName`. +Gebruik gezond verstand of onderzoek de code van de bibliotheek als dit van toepassing is op jouw code. + +## Ondersteunde Protocollen +De details van de ondersteunde protocollen en apparaten staan +[hier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/SupportedProtocols.md) vermeld. + +## Probleemoplossing +Voordat je een probleem meldt of om hulp vraagt, graag eerst onze [Probleemoplossingsgids](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Troubleshooting-Guide) volgen. + +## Veelgestelde Vragen +Enkele antwoorden op veel veelgestelde vragen en problemen staan op onze [F.A.Q. wiki pagina](https://github.com/crankyoldgit/IRremoteESP8266/wiki/Frequently-Asked-Questions). + +## Bibliotheek API Documentatie +De bibliotheek gebruikt [Doxygen](https://www.doxygen.nl/index.html) om [automatisch documentatie](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) toe te voegen aan de [API](https://en.wikipedia.org/wiki/Application_programming_interface) van de [bibliotheek](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/). +Je kunt de documentatie [hier](https://crankyoldgit.github.io/IRremoteESP8266/doxygen/html/) vinden. + +## Installatie +##### Officiële versies via de Arduino IDE v1.8+ (Windows & Linux) +1. Klik op de _"Schets"_ -> _"Bibliotheek gebruiken"_ -> _"Bibliotheken beheren..."_ menuknoppen. +1. Vul `IRremoteESP8266` in bij _"Filter je zoekresultaten..."_ rechtsboven de pop-up. +1. Klik op het IRremoteESP8266 resultaat van de zoekopdracht. +1. Selecteer de versie die je wilt installeren en klik op _"Installeren"_. + +##### Handmatige installatie voor Windows +1. Klik op de _"Clone or Download"_ knop, en kies dan _"[Download ZIP](https://github.com/crankyoldgit/IRremoteESP8266/archive->master.zip)"_. +1. Pak de inhoud van de gedownloade zip uit. +1. Hernoem de uitgepakte map naar _"IRremoteESP8266"_. +1. Verplaats de map naar de bibliotheken map. (voor Windows: `C:\Gebruikers\GEBRUIKERSNAAM\Documenten\Arduino\libraries\`) +1. Herstart de Arduino IDE. +1. Bekijk de voorbeelden. + +##### Git gebruiken om de bibliotheken te installeren ( Linux ) +``` +cd ~/Arduino/libraries +git clone https://github.com/crankyoldgit/IRremoteESP8266.git +``` +###### Om de bibliotheken te updaten naar de laatste versie +``` +cd ~/Arduino/libraries/IRremoteESP8266 && git pull +``` + +## Bijdragen +Als je wilt [bijdragen](.github/CONTRIBUTING.md#how-can-i-contribute) aan dit project, hulp is altijd welkom bij: +- Het [melden](.github/CONTRIBUTING.md#reporting-bugs) van problemen en foutmeldingen +- Ideeën voor verbeteringen +- Verbeteringen van de documentatie +- [Het aanmaken van issues](.github/CONTRIBUTING.md#reporting-bugs) en [pull requests](.github/CONTRIBUTING.md#pull-requests) +- Het delen van deze bibliotheek + +## Bijdragers +Bekijk alle bijdragers [hier](.github/Contributors.md) + +## Bibliotheek Geschiedenis +Deze bibliotheek was oorspronkelijk gebaseerd op het werk van Ken Shirriff (https://github.com/shirriff/Arduino-IRremote/) + +[Mark Szabo](https://github.com/crankyoldgit/IRremoteESP8266) heeft de IRsend class bijgewerkt om te werken op een ESP8266 en [Sebastien Warin](https://github.com/sebastienwarin/IRremoteESP8266) de ontvang & decodeer class (IRrecv). + +Voor v2.0 is de bibliotheek bijna volledig herschreven met de mogelijkheden van de ESP8266 in het achterhoofd. diff --git a/src/libraries/IRremoteESP8266/ReleaseNotes.md b/src/libraries/IRremoteESP8266/ReleaseNotes.md new file mode 100644 index 000000000..0370c91a6 --- /dev/null +++ b/src/libraries/IRremoteESP8266/ReleaseNotes.md @@ -0,0 +1,1234 @@ +# Release Notes + +## _v2.8.4 (20220918)_ + +**[Bug Fixes]** + - [Bugfix] Handle gcc unsupported __VA_OPT___ macro (#1880 #1881) + + +## _v2.8.3 (20220915)_ + +**[Bug Fixes]** +- Fix `#if` for DECODE_COOLIX48 (#1796) +- Add missing `prev`s to `decodeToState()` (#1783) + +**[Features]** +- Add `pause()` function to ESP32 when receiving. (#1871) +- ARGO: Argo add `sendSensorTemp()` (#1858 #1859) +- HAIER_AC160: Experimental detail support. (#1852 #1804) +- BOSCH144: Add IRac class support (#1841) +- Mitsubishi_AC: update left vane in `IRac` class (#1837) +- Basic support for Daikin 312bit/39byte A/C protocol. (#1836 #1829) +- Experimental basic support for Sanyo AC 152 bit protocol. (#1828 #1826) +- GREE: Add model support for `YX1FSF`/Soleus Air Windown A/C (#1823 #1821) +- Experimental basic support for Bosch 144bit protocol. (#1822 #1787) +- Experimental basic support for TCL AC 96 bit protocol. (#1820 #1810) +- Add basic support for clima-butler (52bit) RCS-SD43UWI (#1815 #1812) +- TOTO: An experimental _(s)wipe_ at support for Toto Toilets. (#1811 #1806) +- CARRIER_AC128: Experimental Basic support for Carrier AC 128bit protocol. (#1798 #1797) +- HAIER_AC160: Add basic support for Haier 160bit protocol. (#1805 #1804) +- DAIKIN: Add basic support for 200-bit Daikin protocol. (#1803 #1802) +- FUJITSU: Improve handling of 10C Heat mode. (#1788 #1780) +- FUJITSU: Improve handling of short (command only) messages. (#1784 #1780) + +**[Misc]** +- Improve the `_IRREMOTEESP8266_VERSION_VAL` macro (#1875 #1870) +- SONY: Update supported devices. (#1872) +- SAMSUNG: Update supported devices (#1873) +- NEC: Update supported devices (#1874) +- Give IRmacros.h smaller scope to avoid impacting projects using IRremoteESP8266 (#1857 #1853 #1851) +- Inhibit protocol names for not-included protocols (#1853 #1851) +- Test out codeql static analysis (#1842) +- Remove pylint disable=no-self-use (#1817) +- Fujitsu General: update supported devices (#1813) +- DAIKIN: Update supported devices (#1808 #1807) +- Fujitsu: Update supported remote info. (#1801 #1794) +- DAIKIN128: Update supported devices (#1754) +- Voltas: Add link to manual for 122LZF A/C. (#1800 #1799 #1238) +- Daikin128: Additional unit test. (#1795 #1754) +- MIDEA: Update supported devices (#1791 #1790) + + +## _v2.8.2 (20220314)_ + +**[Bug Fixes]** +- ESP32-C3: Fix reboot/crashes on ESP32-C3s when receiving. (#1768 #1751) + +**[Features]** +- HITACHI_AC296: Add `IRac` class support & tests. (#1776 #1758 #1757) +- Support for Hitachi RAS-70YHA3 (remote RAR-3U3) (#1758 #1757) +- LG: Add Swing Toggle support for Model `LG6711A20083V` (#1771 #1770) +- IRMQTTServer: add `MQTT_SERVER_AUTODETECT_ENABLE` via mqtt mDNS (#1769) +- Experimental basic support for Kelon 168 bit / 21 byte protocol. (#1747 #1745 #1744) +- MitsubishiAC: Tweak repeat gap timing. (#1760 #1759) +- Gree YAP0F8 (Detected as Kelvinator) vertical position set support (#1756) +- Make KELON (48 bit) protocol decoding stricter. (#1746 #1744) +- IRMQTTServer V1.6.1 (#1740 #1739 #1729) +- HITACHI_AC264: Add minimal detailed support. (#1735 #1729) +- LG2: Improve Light toggle msg handling. (#1738 #1737) +- MIDEA: Add support for Quiet, Clean & Freeze Protect controls. (#1734 #1733) +- Add basic support for HITACHI_AC264 264bit protocol. (#1730 #1729) +- ESP32-C3: Work around for some C3 specific compiler issues again. (#1732 #1695) + +**[Misc]** +- MIDEA: Update supported devices (#1774 #1773 #1716) +- Update devices supported by ELECTRA_AC (#1766 #1765) +- Improve documentation for `encodePioneer()` (#1761 #1749) +- Update (un)supported DAIKIN128 devices. (#1752) +- Refactor `decodeCOOLIX()` code & add another test case. (#1750 #1748) +- Simplify code based on state_t being initialised by default. (#1736 #1699) +- Add comments to help Teknopoint users. (#1731 #1728) +- Fix library version string calculation. (#1727 #1725) +- Confirm we can reproduce `TurnOnFujitsuAC.ino` via IRac/IRMQTTServer. (#1726 #1701) + + +## _v2.8.1 (20220101)_ + +**[Bug Fixes]** +- Arduino ESP32 Core v2.0.2+ crashes due to our timer hack. (#1715 #1713) +- SONY: Fix old Sony CD-Player Remote (12 Bit) (#1714) + +**[Features]** +- Add tool to convert protocol & code to raw timing info. (#1708 #1707 #1703) +- Add basic support for COOLIX48 protocol. (#1697 #1694) +- MITSUBISHI_AC: Added support for i-SAVE mode. (#1666) +- TOSHIBA_AC: Add Filter setting support. aka. Pure. (#1693 #1692) +- Airton: Add detailed A/C support. (#1688 #1670) + +**[Misc]** +- Add a structured library version number. (#1717) +- Workflows Split UnitTests (#1712) +- Reduce time for workflow/Build (#1709) +- Fix some compiler & linter warnings (#1699 #1700) +- Fujitsu: Update supported A/C models (#1690 #1689 #1702 #1701) +- Remove extra `const` qualifier for char pointer (#1704) +- TCL: Update supported devices. (#1698) +- ESP32-C3: Work around for some C3 specific compiler issues. (#1696 #1695) + + +## _v2.8.0 (20211119)_ + +**[Bug Fixes]** +- Fix compilation issue when using old 8266 Arduino Frameworks. (#1639 #1640) +- Fix potential security issue with `scrape_supported_devices.py` (#1616 #1619) + +**[Features]** +- SAMSUNG_AC + - Change `clean` setting to a toggle. (#1676 #1677) + - Highest fan speed is available without Powerful setting. (#1675 #1678) + - Change `beep` setting to a toggle. (#1669 #1671) + - Fix Beep for AR12TXEAAWKNEU (#1668 #1669) + - Add support for Horizontal Swing & Econo (#1277 #1667) + - Add support for On, Off, & Sleep Timers (#1277 #1662) + - Fix power control. Clean-up code & bitmaps from Checksum changes. (#1277 #1648 #1650) +- HAIER_AC176/HAIER_AC_YRW02 + - Add support A/B unit setting (#1672) + - Add support degree Fahrenheit (#1659) + - Add support `Lock` function (#1652) + - Implement horizontal swing feature (#1641) + - Implement Quiet setting. (#1634 #1635) +- Basic support for Airton Protocol (#1670 #1681) +- HAIER_AC176: Add Turbo and Quiet settings (#1634) +- Gree: Add `SwingH` & `Econo` control. (#1587 #1653) +- MIRAGE + - Add experimental detailed support. (#1573 #1615) + - Experimental detailed support for KKG29A-C1 remote. (#1573 #1660) +- ELECTRA_AC: Add support for "IFeel" & Sensor settings. (#1644 #1645) +- Add Russian translation (#1649) +- Add Swedish translation (#1627) +- Reduce flash space used. (#1633) +- Strings finally in Flash! (#1493 #1614 #1623) +- Add support for Rhoss Idrowall MPCV 20-30-35-40 A/C protocol (#1630) +- Make `IRAc::opmodeToString()` output nicer for humans. (#1613) +- TCL112AC/TEKNOPOINT: Add support for `GZ055BE1` model (#1486 #1602) +- Support for Arris protocol. (#1598) +- SharpAc: Allow position control of SwingV (#1590 #1594) + +**[Misc]** +- HAIER_AC176/HAIER_AC_YRW02 + - Replace some magic numbers with constants (#1679) + - Small fix `Quiet` and `Turbo` test (#1674) + - Fix `IRHaierAC176::getTemp()` return value description (#1663) +- Security Policy creation and changes. (#1616 #1617 #1618 #1621 #1680) +- IRrecvDumpV2/3: Update PlatformIO envs for missing languages (#1661) +- IRMQTTServer + - Use the correct string for Fan mode in Home Assistant. (#1610 #1657) + - Move a lot of the strings/text to flash. (#1638) +- Minor code style improvements. (#1656) +- Update Supported Devices + - HAIER_AC176 (#1673) + - LG A/C (#1651 #1655) + - Symphony (#1603 #1605) + - Epson (#1574 #1601) + - GREE (#1587 #1588) + - SharpAc (#1590 #1591) +- Add extra tests for LG2 protocol (#1654) +- Fix parameter expansion in several macros. +- Move some strings to `IRtext.cpp` & `locale/default.h` (#1637) +- RHOSS: Move include and defines to their correct places (#1636) +- Make makefile only build required files when running `run-%` target (#1632) +- Update Portuguese translation (#1628) +- Add possibility to run specific test case (#1625) +- Change `googletest` library ignore (#1626) +- Re-work "Fan Only" strings & matching. (#1610) +- Address `C0209` pylint warnings. (#1608) + + +## _v2.7.20 (20210828)_ + +**[Bug Fixes]** +- Make `strToSwingH()` match "Right Max" (#1550 #1551) + +**[Features]** +- Experimental Bose remote support (#1579) +- Added MitsubishiAC VaneLeft (#1572 #1576) +- HAIER_AC176: Add experimental detailed support (#1480 #1571) +- Detailed support for Tornado/Sanyo 88-bit A/C protocol (#1503 #1568) +- Add support for new `TROTEC_3550` A/C protocol (#1563 #1566 #1507) +- SamsungAc: Use `sendExtended()` going forward. (#1484 #1562) +- SamsungAc: Redo/fix checksum calculations. (#1538 #1554) +- LG: Add support for `AKB73757604` model (#1531 #1545) +- Daikin176: Add support for Unit Id. (#1543 #1544) +- Daikin2: Add support for Humidity setting/operation. (#1535 #1540) +- TCL112AC: Add support for quiet/mute setting. (#1528 #1529) +- LG2: Add Fan speed, Swing, & Light support for new `AKB74955603` model (#1513 #1530) +- Add Mitsubishi AC "fan only" mode (#1527) + +**[Misc]** +- Change when some github workflows run (#1583) +- Add/update supported device info (#1580 #1581 #1585) +- Fix pylint issues due to pylint update. (#1569 #1570) +- DAIKIN216: Update supported models. (#1552 #1567) +- IRMQTTServer: Build a minimal OTA image via PlatformIO. (#1513 #1541) +- Reduce memory fragmentation cause by String usage. (#1493 #1536) +- Refactor `decodeMitsubishiAC()` (#1523 #1532) +- Fix incorrect comment. +- Migrate from Travis to GitHub Actions (#1522 #1526) +- Documentation update with additional supported Panasonic AC models (#1525) + + +## _v2.7.19 (20210706)_ + +**[Bug Fixes]** +- Illegal Heap write in rawbuf when the capture has overflowed. (#1516 #1517) +- PANASONIC_AC: Fix Low and High fan speeds (#1515) +- Fix MDNS in IRServer and IRMQTTServer example code (#1498 #1499) +- IRac: Fix off-by-one error in Coolix's sleep setting. (#1500) +- Fix undefined constant (#1490) + +**[Features]** +- Add detailed support for Kelon ACs (#1494) +- Experimental basic support for Teknopoint A/C protocol (#1486 #1504) +- Daikin64: Add support for Heat mode (#1492) +- Basic support for `HAIER_AC176` 176 bit protocol. (#1480 #1481) + +**[Misc]** +- GREE: Update inter-message gap timing (#1508 #1509) +- IRac: Change Coolix to send special messages after a normal message. (#1501 #1502) +- Fix compiler warnings causing Travis failures. (#1491) +- Update supported model info (#1477 #1485 #1488 #1489) +- Add HTML viewport meta tag to IRServer and IRMQTTServer examples (#1467 #1469) + + +## _v2.7.18 (20210420)_ + +**[Misc]** +- Attempt to fix issues with installing the library under the Arduino IDE on Win10 & OSX (#1451 #1464) +- Reduce the library's github zip download size. (#1451 #1463) +- An experiment in using Github Actions to do some of the CI work. (#1462) + + +## _v2.7.17 (20210418)_ + +**[News]** +- The library now supports 100 IR protocols! \o/ + +**[Bug Fixes]** +- Fix `IRAcUtils::decodeToState()` for different length Samsung msgs (#1447 #1448) + +**[Features]** +- Fujitsu: Add support for `ARREW4E` model. (#1455 #1456) +- Experimental detailed support for Truma A/Cs. (#1440 #1449) + +**[Misc]** +- Fix Arduino library linter issues. (#1451 #1452 #1453 #1460) +- Reduce the library's zip download size. (#1451 #1463) +- An experiment in using Github Actions to do some of the CI work. (#1462) + + +## _v2.7.16 (20210324)_ + +**[Features]** +- ToshibaAC: Swing handling and `setRaw()` improvements. (#1423 #1424 #1425) +- Support for XMP (Xfinity) protocol. (#1414 #1422) +- ToshibaAC: Adjust inter-message gap timing to improve matching. (#1420 #1421) +- Ecoclim: Add detailed A/C support (#1397 #1415) + +**[Misc]** +- [ESP32] Fix `addApbChangeCallback(): duplicate func` kernel msgs (#1434 #1435) +- refactor ir_Fujitsu (#1419) +- refactor ir_Whirlpool (#1416) +- refactor ir_Vestel (#1413) +- refactor ir_Trotec (#1412) + + +## _v2.7.15 (20210213)_ + +**[BREAKING CHANGES]** +- Some Daikin2 constants have been changed. (#1393) + +**[Features]** +- Experimental basic support for EcoClim 56 & 15 bit protocols. (#1397 #1410) +- MITSUBISHI_AC: Add support for enabling Weekly Timer. (#1403 #1404) +- Mitsubishi ACs: Improve handling swing/vane settings. (#1399 #1401) +- MITSUBISHI_AC: Add support for half degrees. (#1398 #1400) +- Add `irutils::addSwing[V|H]ToString()` and adjust some constants (#1365 #1393) +- SharpAc: Add support for model A903, and improve `IRac` fan & power control. (#1387 #1390) +- Experimental support for Milestag2 (#1360 #1380) + +**[Misc]** +- Improve `IRac::sendAc()` documentation. (#1408 #1409) +- refactor ir_Transcold (#1407) +- refactor ir_Toshiba (#1395) +- Fix Travis-CI build issues. (#1396) +- refactor ir_Teco (#1392) +- Fujitsu A/C: Add warning/suggestions for AR-RAH1U devices (#1376 #1386) +- refactor ir_Technibel (#1385) +- Add the new logo and banner :tada: (#1371 #1372) +- Update references to sbprojects website. (#1381 #1383) +- refactor ir_Tcl (#1379) + + +## _v2.7.14 (20210103)_ + +**[Bug Fixes]** +- SanyoAc: Fix Sensor Location error (#1359) +- IRMQTTServer: Compiler error under PlatformIO on Windows. (#1353 #1354) +- Workaround for ESP32 hw timer library calls not in IRAM. (#1350 #1351) + +**[Features]** +- PANASONIC_AC32: Add limited detailed support. (#1364 #1366) +- Move global vars in IRrecv into a namespace. (#1350 #1352) +- Fujitsu: Handle toggles of Econo & Turbo when `IRac` interface is used. (#1334 #1345) + +**[Misc]** +- Elitescreens: Update supported brands/models (#1375) +- refactor ir_Sharp (#1374) +- refactor ir_Sanyo (#1359) +- Gree: List Amana as supported. (#1361 #1363) +- Lasertag: Increase matching tolerance. (#1360 #1362) +- refactor ir_Samsung (#1358) +- refactor ir_Neoclima (#1349) +- Update issue templates (#1348 #1355) +- Midea: Update supported devices & add notes for an odd Pioneer System. (#1342 #1344) +- Kelvinator: Update supported models. (#1335 #1346) + + +## _v2.7.13 (20201125)_ + +**[Bug Fixes]** +- Fix crash when IRac::sendAc(state_t, *state_t) called with SAMSUNG_AC & NULL (#1341 #1339) +- Mitsubishi112 & 136: `setSwingV()` incorrectly sets mode. (#1337) +- Typo preventing RC6 from compiling when other protocols disabled. (#1332 #1331) + +**[Features]** +- Coolix: Improve Sensor(ZoneFollow) and add Vane Step support. (#1340 #1318) + +**[Misc]** +- refactor ir_Coolix (#1340) +- refactor ir_Mitsubishi (#1336) +- refactor ir_MitsubishiHeavy (#1333) + + +## _v2.7.12 (20201113)_ + +**[Bug Fixes]** +- `defaultBits()` returned incorrect result for `PANASONIC_AC` (#1307 #1314) +- Fix LG2 timings and refactor `decodeLG()` (#1298 #1304) + +**[Features]** +- Midea: Add support for "Follow Me"/Sensor, Turbo, Light, & Timers (#1318 #1327) +- SharpAc: Add model support for A705 (#1309 #1313) +- Add basic support for Panasonic A/C 32bit/16bit protocol. (#1307 #1316) +- Add support for Elite Screens protocol. (#1306 #1310) +- IRrecvDumpV2+: Add tolerance setting. (#1292) +- Add basic support for the Mirage Protocol. (#1289 #1291) +- Internationalisation Support + - pt-BR: Add Portuguese/Brazilian support. (#1303) + - de-DE: Backfill missing strings (#1294) + - de-DE: update for recent addition of 'tolerance' (#1293) + - de-DE: Translate root README.md into German (#1297) + +**[Misc]** +- refactor ir_LG (#1325) +- refactor ir_Kelvinator (#1317) +- refactor ir_Hitachi (#1308) +- refactor ir_Goodweather (#1295) +- refactor ir_Electra (#1290) +- refactor ir_Daikin (#1288) +- Update Kaysun supported models. (#1322) +- fix typos/spelling mistakes (#1301) +- Add some missing Doxygen class/data-type descriptions. (#1287) + + +## _v2.7.11 (20201002)_ + +**[Features]** +- Transcold: Add detailed support. (#1256 #1278) +- Airwell/Whirlpool: Add handling of previous state to `.toCommon()` (#1275 #1276) +- IRMQTTServer: Change how MQTT packet/buffer size is set. (#1271) +- Fujitsu: Add support for timers. (#1255 #1261 #1262) +- Neoclima: Add Economy & Fahrenheit support (#1260 #1265) +- Technibel: Cleanup and code fixes/improvements. (#1259 #1266) +- Technibel: Add detailed A/C support (#1259) +- Transcold: Add basic support. (#1256 #1258) + +**[Misc]** +- refactor ir_Delonghi (#1285) +- Whirlpool: Change default mode in `convertMode()` (#1283 #1284) +- SamsungAC: Unit tests to help debug poor signal (#1277 #1280) +- Add question & note about VS1838b use to issue template. (#1281) +- rewrite ir_Corona (#1274) +- tools/mkkeywords: Fix minor parsing issue. (#1272) +- Add Zhongxian Li to Contributers.md (#1270) +- rewrite Carrier (#1269) +- rewrite ir_Argo by using bit field (#1264) +- rewrite ir_Amcor by using bit field (#1263) +- Update Fujitsu supported model info. +- Clarify the scope of the LittleFS breaking change. + + +## _v2.7.10 (20200831)_ + +**[BREAKING CHANGES]** +- IRMQTTServer & Web-AC-Control: move SPIFFS to LittleFS for ESP8266 (#1182 #1226) +- Daikin176: Change & increase operating mode values. (#1233 #1235) + +**[Bug Fixes]** +- TOSHIBA_AC: not turning off when using `IRac` class. (#1250 #1251) +- Haier: change position of Fan speed bits. (#1246 #1247) + +**[Features]** +- Voltas: Add detailed support for Voltas A/Cs (#1238 #1248) +- Add support for Metz protocol. (#1241 #1242) +- Basic support for Voltas A/C protocol (#1238 #1243) +- Add low level bit formatting sanity checks. (#1232) + +**[Misc]** +- Rewrite Airwell by using bit fields (#1254) +- Rewrite Haier YRW02 using bit fields (#1253) +- rewrite Haier HSU07-HEA03 (#1246 #1247) +- rewrite ir_Gree & ir_Midea by using bit field (#1240) +- Incorrect usage of `assert()` (#1244 #1245 #1232) +- rewrite Gree (#1210) + + +## _v2.7.9 (20200730)_ + +**[Bug Fixes]** +- Fix mistake in `IRLGAc::convertFan()`. (#1214 #1215) + +**[Features]** +- Add Sanyo A/C (72 bit) protocol with detailed support. (#1211 #1218) +- Added modification to Midea unit to support Danby DAC AC units. (#1213) +- ToshibaAc: Rework to support Carrier models and add more settings. (#1205 #1212) +- Add detailed support for Airwell A/C protocol. (#1202 #1204) + +**[Misc]** +- Pioneer: Update timings based on user collected data. (#1220 #1222) +- Samsung36: Adjust timings & update unit tests. (#1220 #1221) +- Consolidate common code: Inverted byte pairs (#1219) +- Remove duplicate code from `IRToshibaAC::calcChecksum()` (#1207) +- Update missing/incorrect doxygen comments (#1203) + + +## _v2.7.8 (20200622)_ + +**[BREAKING CHANGES]** +- Fix Manchester code handling; Increase Airwell to `34` bits. (#1200) + +**[Bug Fixes]** +- Carrier40: Use correct gap value. (#1193) + +**[Features]** +- CarrierAc64: Add detailed support. (#1133) +- Add experimental support for Hitachi A/C 344 bit protocol (#1139) +- Automatic & full library code/API documentation via Doxygen (#1150 #1154 #1155 #1156 #1158 #1165 #1167 #1169 #1180 #1184 #1189 #1191 #1194 #1195 #1197 #1198) +- Hitachi344: Add detailed support and change bit ordering. (#1147) +- Add Corona AC Protocol (#1152) +- Hitachi344: Add Swing(H) and improve Swing(V) (#1148) +- Update auto_analyse_raw_data.py with better code comment sections (#1164) +- Add support for Midea24 protocol. (#1171) +- Add basic Zepeal protocol support (#1178) + +**[Misc]** +- scrape_supported_devices.py: avoid changes to SupportedProtocols.md (#1140) +- auto_analyze nice exit on empty rawdata input (#1141) +- Comments update + cleanup (#1143) +- Update D_STR_IRRECVDUMP_STARTUP text and comments. (#1144) +- Minor code cleanups (#1149) +- Update `README.md`'s to point to new API docs. (#1151) +- Update "Supports" sections (#1160) +- Add a `doxygen` check to CI/Travis. (#1161) +- scrape_supported_devices: warn about misplaced or legacy supports sections (#1159) +- Add Supports sections to some files (#1163 #1166) +- Fix compile error when `DEBUG` is enabled. +- Add no-output option and return code on error to scrape_supported_devices +- Travis: Add scrape_supported_devices error check +- Update auto_analyse_raw_data.py to have a default Supports: section +- Treat compiler warnings as errors. (#1174) +- Remove `calcLGChecksum()` and use new generic `sumNibbles()` (#1175) +- Suppress more potential compiler warnings. (#1179) +- Load balance travis tasks to reduce wall clock time. (#1183) +- Set PlatformIO's default baudrate to 115200 (#1188) +- Some fixes to Doshisha protocol handler +- Minor cleanups of Corona and Zepeal +- Enable Doxygen warning when the parameters for a function/method/procedure are wrong/missing. (#1196) + + +## _v2.7.7 (20200519)_ + +**[BREAKING CHANGES]** +- Fix Symphony protocol. (#1107, #1105) + * Now 12 bits and bits are inverted. All previous codes will no longer work. +- IRMQTTServer: Better handle power & mode operations for Home Assistant. (#1099, #1092) + * When `MQTT_CLIMATE_HA_MODE` is enabled (default) this will break previous operation mode resumption when power is changed. + +**[Bug Fixes]** +- Set correct return type for `.calibrate()` (#1095, #1093) + +**[Features]** +- Add basic support for Carrier 40 & 64 bit protocols. (#1125, #1112, #1127) +- Gree: Enable native support for Fahrenheit (#1124, #1121) +- Gree: Add option to control display temp source. (#1120, #1118) +- Add support for Multibrackets protocol. (#1106, #1103) +- Add RawToPronto.py tool & improve `sendPronto()` precision (#1104, #1103) +- Add support for `Doshisha` LED light protocol (#1115) +- Introduce IRrecvDumpV3 with basic OTA update support (#1111) +- Add detailed support for Delonghi A/C (#1098, #1096) +- Improved support for SharpAc. (#1094, #1091) +- Update auto_analyse to use new decode call structure. (#1102, #1097) +- Added Blynk app example (#1090) + +**[Misc]** +- update auto_analyse script to use new param documentation (#1126) +- Improve `raw_to_pronto_code.py` (#1122, #1103) +- Use pattern rules in Makefiles to reduce specific rule (#1110) +- Update list of supported Daikin models. (#1101) + + +## _v2.7.6 (20200425)_ + +**[Features]** +- IRMQTTServer: Use more i18n text. (#1086) +- Convert Protocol names to shared text. Saves ~3k of flash. (#1078) +- Add Chinese translation (zh-CN) & add utf-8 support. (#1080, #1085) + +**[Misc]** +- IRMQTTServer: Ensure MQTT_MAX_PACKET_SIZE is correctly set. (#1084) +- Add Italian locale to IRrecvDumpV2 platformio file. + + +## _v2.7.5 (20200409)_ + +**[Features]** +- Detailed support for `HITACHI_AC1` protocol. (#1056, #1061, #1072) +- update sharp to match Sharp AH-A5SAY (#1074) +- Experimental support for AIRWELL protocol. (#1069, #1070) +- SamsungAC: Add Breeze (Aka WindFree) control (#1062, #1071) +- Support for Daikin FFN-C A/C (#1064, #1065) +- Add basic support for HITACHI_AC3 protocol. (#1060, #1063) +- Add support for `SYMPHONY` 11 bit protocol. (#1057, #1058) +- IRMQTTServer: Improve Home-Assistant discovery by sending a 'device' with the discovery packet (#1055) + +**[Misc]** +- Clean up support status of various protocols. +- Add `decodeToState()` unit tests to all supported protocols (#1067, #1068) +- Add Gree AC example code. (#1066) + + +## _v2.7.4 (20200226)_ + +**[Bug Fixes]** +- IRMQTTServer: Fix bug when receiving an IR A/C message and not re-transmitting it. (#1035, #1038) +- Coolix: `setRaw()` doesn't update power state. (#1040, #1041) + +**[Features]** +- Electra: Add improved feature support. (#1033, #1051) +- Add support for Epson protocol. (#1034, #1050) +- Add options to `decode()` to aid detection. Improve NEC detection. (#1042, #1046) +- SamsungAc: Add support for Light & Ion (VirusDoctor). (#1045, #1048, #1049) +- Add Italian (it-IT) locale/language support. (#1047) (kudos @egueli) +- gc_decode: Add repeat support for pronto codes. (#1034, #1043) + +**[Misc]** +- Update supported SamsungAc devices (#1045) +- Coolix: Subtle protocol timing adjustments (#1036, #1037) +- Add supported Electra device model info (#1033) + + +## _v2.7.3 (20200130)_ + +**[Features]** +- Allow protocols to be enabled or disabled with compiler flags. (#1013, #1012) +- Panasonic AC: Add Ion Filter support for DKE models. (#1025, #1024) +- Add support for sending Sony at 38Khz (#1029, #1018, #1019) +- auto_analyse_raw_data.py: Handle analysing messages with no headers. (#1017) + +**[Misc]** +- Fix Coolix unit test errors when using Apple c++ compiler. (#1030, #1028) +- Fix Apple clang c++ compiler error in unit tests. (#1027, #1026) +- Improve/fix scraping of supported devices (#1022) +- Panasonic PKR series A/C uses DKE protocol. (#1020, #1021) +- Update NEC supported devices. (#1018) +- Add note to avoid GPIO16 on the ESP8266 for receiving. (#1016, #1015) + + +## _v2.7.2 (20200106)_ + +**[Bug Fixes]** +- Common AC api: Better handle protocols with power toggles. (#1002) + +**[Features]** +- Experimental detailed support for LG a/c. (#1008 #1009) + +**[Misc]** +- Add remote codes for Aloka LED lamp. (#1005) +- Improve Supported Devices scraping. (#1006) + + +## _v2.7.1 (20191125)_ + +**[Bug Fixes]** +- Hitachi424Ac: Fix Incorrect Power Byte Values (#987) +- Coolix: Fix setPower(false) issue. (#990) + +**[Features]** +- Use `char*` instead of `String` for common text. Saves ~1-3k. (#992, #989) +- Hitachi424Ac: Add Vertical Swing ability (#986) + +**[Misc]** +- IRMQTTServer: Update HA example/discovery message. (#995) +- Move newly added common text to a better location. (#993) + + +## _v2.7.0 (20191030)_ + +**[Bug Fixes]** +- auto_analyse: Fix > 64 bit send code generation. (#976) +- auto_analyse: Fix missing arguments in generated code for send64+ (#972) +- IRsendProntoDemo: Fix compile issue on ESP32 platform. (#938) +- IRMQTTServer: Fix compile error when `MQTT_ENABLE` is false. (#933) + +**[Features]** +- Add Hitachi 424 bit A/C support. (#975, #980, #981) +- Experimental detailed support for `DAIKIN152` (#971) +- Mitsubishi 112bit A/C support (#947, #968) +- gc_decode: Adding Support for Decoding codes in raw code format (#963) +- Refactor to use common routines/macros to handle bit manipulation. (#934) +- Use centralised common strings. Saves ~1.5k of program space. (#946) +- Add Internationalisation (i18n) / Locale support. (#946, #955, #966) + - `de-CH`: Swiss German. (#949, #954) + - `de-DE`: German. (#946, #950, #952) + - `en-AU`: English/Australia (Default locale) (#946) + - `en-IE`: English/Ireland (#946) + - `en-UK`: English/United Kingdom (#946) + - `en-US`: English/United States (#946) + - `es-ES`: Spanish. (#953) + - `fr-FR`: French. (#962) +- Port CI pipeline to PlatformIO (#936) + +**[Misc]** +- Add DAIKIN128 & DAIKIN152 to `decodeToState()` (#982) +- auto_analyse: Produce better code when leader is detected. (#977) +- Coolix A/C improvements (#944) +- A/C setRaw/getRaw/stateReset() cleanup. (#967) +- Add documentation on how to use & support the i18n aspects of the library. +- Make travis checks faster. (#957) +- Translate README.md to french (#959) +- Fixed Coolix kCoolixDefaultState (#941) +- Improve generation of list of pio projects. (#940) + + +## _v2.6.6 (20190923)_ + +**[Bug Fixes]** +- Ensure `begin()` is called for every supported common a/c. (#905, #899) +- IRMQTTServer: Fix JSON state parsing. (#896) +- IRMQTTServer: Fix compilation error when `MQTT_CLIMATE_JSON` is `true`. (#893) + +**[Features]** +- Mitsubishi136: Full A/C support. (#898, #890) +- Fujitsu: Add support for ARRY4 remote. (#895) +- Web-AC-control: Add new WebUI example sketch. (#880, #886) +- Improve Common A/C API (#913) +- IRMQTTServer: Support for multiple climates. (#903) +- IRMQTTServer: Add TX channel support for HTTP interface. (#929) +- IRMQTTServer: Add option to clear retained settings. (#917) +- auto_analyse_raw_data.py: Add decode code generation. (#909) +- auto_analyse_raw_data.py: General improvements (#906) + +**[Misc]** +- IRMQTTServer: Use latest API for common A/C. (#928) +- IRMQTTServer: Add flag & documentation for Home Assistant mode. (#919) +- IRMQTTServer: Move from ArduinoJson v5 to v6. (#878) +- IRMQTTServer: Use retain for discovery message. (#881) +- Goodweather: Adjust timings & minor fixes. (#924) +- PanasonicAc: Add better SwingV support for common a/c framework. (#923) +- Daikin2: Corrections for common A/C interface. (#910) +- MitsubishiAC: Improve decoding. (#914) +- Fujitsu: Disable horiz swing for ARRY4. (#907) +- SamsungAc: Only send power on/off code if it's needed. (#884) +- Teco: Add timer support. (#883) +- More consistent A/C `::toString()` output. (#920) + + +## _v2.6.5 (20190828)_ + +**[Bug Fixes]** +- IRMQTTServer: Remove duplicate MQTT_CLIMATE from HA discovery (#869) +- Fujitsu: Ensure `on()` is called in common a/c framework. (#862) +- Update `strToModel()` (#861) +- IRMQTTServer: Add missing header file. (#858) +- IRMQTTServer: Fix a compile error when HTML_PASSWORD_ENABLE is enabled. (#856) + +**[Features]** +- IRrecv: Allow tolerance percentage to be set at run-time. (#865) +- Basic support for Daikin152 A/C protocol. (#874) +- Teco: Add light, humid, & save support. (#871) +- Detailed support for Amcor A/C protocol. (#836, #854) +- IRMQTTServer: Add ability to report Vcc at the ESP chip. (#845) +- Gree: Add timer support. (#849) +- IRac/Mitsubishi A/C: Support wide `swingh_t` mode (#844) +- IRMQTTServer: Generate protocol and bit size html selects (#838) + +**[Misc]** +- New example code to show how to use the `IRac` class to control A/Cs (#839) +- Improve/fix `swingh_t::kWide` support (#846) +- Kelvinator: Optimise code a little to save space. (#843) + + +## _v2.6.4 (20190726)_ + +**[Bug Fixes]** +- Fix some swing problems with the Mitsubishi HAVC protocol (#831) +- Fix parameter ordering for Gree in common a/c code. (#815) +- Fix parameters for Coolix in IRac::sendAc() (#829) +- IRMQTTServer: Fix sending >64 bit codes. (#811) + +**[Features]** +- Daikin128: Full detailed support & common a/c support. (#832) +- Midea: Support native temp units of Celsius & SwingV. (#823) +- Gree: Support `YBOFB` models and bug fix. (#815) +- Pioneer: Fix sendPioneer with Pioneer specific timings (#830) +- Daikin128: Initial support for Daikin 17 Series/BRC52B63 (#828) +- Coolix: Better `toCommon()` support. (#825) +- Experimental detailed support for Daikin 176 bits (#816) +- Add setting of output options to A/C classes. (#808) +- Add invert flag support to Samsung AC (#807) + +**[Misc]** +- Daikin176: making some change on Daikin176 to work with IRMQTTServer (#826) +- Reduce duplicate code to save (3K+) space. (#813) +- Daikin176: Experiment Daikin176bits with IRMQTTServer (#824) +- Update platformio.ini files for PlatformIO v4.0.0 (#812) +- Change repo URLs to new location. (#806) +- Move `htmlEscape()` to the IRutils namespace (#801) + + +## _v2.6.3 (20190704)_ + +**[Bug Fixes]** +- IRMQTTServer: REPLAY_DECODED_AC_MESSAGE not working. (#784, #797) +- ESP32: Ensure `IRrecv`'s GPIO is set to input mode. (#774) + +**[Features]** +- IRMQTTServer: Show available sketch space for OTA uploads. (#795) +- Experimental detailed support for Electra/AUX protocol (#788) +- IRMQTTServer: Ability to resend existing climate state via MQTT & HTTP (#784) +- Daikin160: Add detailed & common a/c support. (#777) +- Experimental detailed support for Neoclima protocol. (#767) +- Gree: add WiFi and IFeel bits (#770) +- Handle A/Cs with toggles better. (#758) +- IRMQTTServer: Allow sending/receiving climate via JSON over MQTT. (#763) + +**[Misc]** +- Move converting of IR A/C messages out of example code. (#798) +- Reduce example code size and complexity (#790) +- Change `ControlSamsungAC` example to not use `sendExtended()` (#792) +- IRMQTTServer: Add MQTT_CLIMATE_IR_SEND_ON_RESTART compile-time flag. (#784) +- Refactor A/C's toString()'s to reduce code size. Saves ~3.5k (#782) +- Add sanity tests for unexpected conditions in IRrecv. (#773) +- IRMQTTServer: Fixed the HA config documentation (missing '-') (#776) +- Improve `mkkeywords` tool. (#766) +- Refactor with generic decode routines in `IRrecv` class. Saves ~7k. (#765) + + +## _v2.6.2 (20190616)_ + +**[Features]** +- Initial support for the ESP32 architecture & boards. (#742) +- Add changable GPIO settings to IRMQTTServer. (#730) +- IRMQTTServer: Enforce a repeat for all Coolix calls (#752) +- Basic DAIKIN 160bit send and decode. (#754) +- Add example code for a Smart(er) IR Repeater. (#740) +- Enforce Samsung A/C Quiet & Powerful mutual exclusivity. + +**[Misc]** +- IRMQTTServer: Add some memory alloc safety checks. (#749) +- Move some ToString() functions to IRac.cpp (#748) +- Increase tolerance value for TCL112AC protocol. (#745) +- Fix compiler warning in IRutils_test.cpp (#756) +- Scrape Supported Protocols and generate SupportedProtocols.md (#755) +- Make supported device info more organised. (#753) + + +## _v2.6.1 (20190609)_ + +**[Breaking Changes]** +- Major rework/breaking changes to Argo A/C support. (#705) + +**[Bug Fixes]** +- Correct `set/getQuiet` for Samsung A/C (#736) +- Add missing `on/off()` to IRCoolixAC class. (#725) +- Daikin `set/getEye()` uses wrong bit. (#711) +- IRMQTTServer: Continue to use same Temperature units. (#710) +- Fixed a bug with `setMode()`/`getMode()` for HAIER_AC. (#705) + +**[Features]** +- Add set/getPowerful for Samsung A/C (#736) +- Add `calibrate()` to all the A/C classes. (#735) +- IRMQTTServer: Add sequencing for sending MQTT IR commands. (#723) +- Add support for Fujitsu AR-REB1E & AR-JW2 remotes. (#718) +- Add Beta `decodeTrotec()` support. (#719) +- Add experimental `decodeArgo()` support. (#717) +- Support for Goodweather A/Cs. (#715) +- Add `DISABLE_CAPTURE_WHILE_TRANSMITTING` feature to IRMQTTServer. (#713) +- Support for Lixil Inax Toilet protocol. (#712) +- Add `set/getWeeklyTimerEnable()` to Daikin (#711) +- IRMQTTServer: Update Common A/C settings based on received IR messages. (#705) +- Add day of week to DAIKIN protocol (#699) +- Add limited support for Sharp A/C (#696) +- SAMSUNG_AC: Make sure special power mode messages are sent. (#695) +- Add `set/getPowerful()` (turbo) to DAIKIN216 (#693) + +**[Misc]** +- Add kPeriodOffset for CPU Freq of 160MHz. (#729) +- Example code for a Dumb IR repeater. (#737) +- Update swing handling for Fujitsu A/Cs. (#724) +- Add function to convert `decode_results` to `sendRaw()` array. (#721) +- Attempt to reduce heap fragmentation from strings. (#707) +- Update Fujitsu A/C example code to safer settings (#716) +- Enforce better `const` usage in IRUtils. (#708) +- Attempt to reduce heap fragmentation by A/C `toString()`s. (#694) +- Minor changes to DAIKIN216 timings and features. (#693) + + +## _v2.6.0 (20190430)_ + +**[Bug Fixes]** +- Fixed problem where LG protocol used wrong duty cycle for repeat. (#687) +- Fix checksum calculation for Daikin protocols. (#678) +- Fix the byte array version of sendGree() (#684, #685) +- Fix artificial vs. real state creation on HaierAC. (#668, #671) +- Fix issues caused by having `MQTT_ENABLE` set to false. (#677) +- Fix compile problem when DEBUG is defined. (#673, #674) +- Fix Minor bug with MQTT_ENABLE False condition (#654) + +**[Features]** +- Experimental support for DAIKIN216 (ARC433B69) (#690) +- Experimental support for Mitsubishi Heavy Industries A/Cs. (#660, #665, #667) +- Support more features of TCL A/C (#656) +- Add LEGO(TM) Power Functions IR protocol. (#655) +- Add Panasonic AC RKR model & Example (#649) +- DAIKIN/IRDaikinESP overhaul and add Comfort mode support. (#678) + **WARNING**: Previous `sendDaikin()` calls may not work. + Please recapture codes or use `kDaikinStateLengthShort` for + `nbytes` in those calls. +- IRMQTTServer: Move MQTT server and other parameters to WifiManager. (#680) + **WARNING**: Previous users may need to fully wipe/reset the + SPIFFS/WifiManager settings by visiting + `http:///reset` prior to or + after update. +- Add Wifi filtering options to IRMQTTServer. (#679) +- Add advanced aircon/climate functionality to IRMQTTServer (#677) +- Initial prototype of a common interface for all A/Cs. (#664) +- Improve MQTT topic usage for feedback messages. (#663) +- Add multiple independent GPIO sending support via MQTT. (#661) + +**[Misc]** +- Adjust kGreeHdrSpace to 4500 (#684, #686) +- Add Home Assistant mqtt climate instructions. (#682) +- Implement htmlEscape() to prevent XSS etc. (#681) +- Add F() Macros (#670) +- Update Daikin2's Cool mode min temp to 18C (#658) +- Change per byte bit-order in Electra protocol. (#648) +- Improve Daikin2 power on/off. (#647) + + +## _v2.5.6 (20190316)_ + +**[Bug Fixes]** +- Fix Coolix A/C Class to handle special states better. (#633, #624) + +**[Features]** +- Fix case style for recent A/C protocols. (#631) +- Update `IRsend::send()` to include all simple protocols. (#629, #628) +- Experimental basic support for 112 bit TCL AC messages (#627, #619) +- Add support for TECO AC (#622) +- Experimental support for Samsung 36 bit protocol (#625, #621) + +**[Misc]** +- Set Coolix to default to 1 repeat. (#637, #636, #624, #439) +- Set Daikin2 modulation to 36.7kHz. (#635) +- Refactor IRVestelAC class to use portable code. (#617) +- Adjust Daikin2 timings and tolerance. (#616, #582) + + +## _v2.5.5 (20190207)_ + +**[Bug Fixes]** +- Fix decoding of Samsung A/C Extended messages. (#610) +- Fix IRMQTTServer example to work with GPIO0 as IR_RX (#608) +- Fix incorrect #define usage. (#597, #596) + +**[Features]** +- Add deep decoding/construction of Daikin2 messages (#600) +- Added Old Vestel A/C support (56 Bits) with full functions. (#607) + +**[Misc]** +- Add ControlSamsungAC example code. (#599) +- Add how to send a state/air-con to IRsendDemo.ino (#594) + + +## _v2.5.4 (20190102)_ + +**[Features]** +- Experimental basic support for 39 Byte Daikin A/C (#583) +- Handle send() repeats in A/C classes. Improve Coolix support. (#580) +- Add optional RX pin pullup and dump raw messages in IRMQTTServer.ino (#589) + +**[Misc]** +- Make auto_analyse_raw_data.py work with Python3 (#581) +- Update CI/travis config due to esp8266 core 2.5.0 changes (#591) + + +## _v2.5.3 (20181123)_ + +**[Features]** +- Add deep support for the Hitachi 28-Byte A/C Protocol (#563) +- Deep decoding for Whirlpool A/C (#572) +- Improve security options for IRMQTTServer example. (#575) +- Require a changed firmware password before upload. (#576) + +**[Misc]** +- Add missing '}' in output of Auto analyse. (#562) +- Make A/C example code a bit more simple. (#571) + + +## _v2.5.2 (20181021)_ + +**[Bug Fixes]** +- Add missing send() method to IRPanasonicAC class. (#545) +- Add missing sendWhirlpoolAC() to IRMQTTServer.ino (#558) + +**[Features]** +- Add IR receiving support to IRMQTTServer. (#543) +- Pioneer support (#547) +- Add support for a second LG protocol variant. (#552) +- Support for short Panasonic A/C messages. (#553) +- Add support for Panasonic CKP series A/Cs. (#554) +- Experimental timer/clock support for Panasonic A/Cs. (#546) +- Add Made With Magic (MWM) support (#557) + +**[Misc]** +- Grammar and typo fixes (#541, #549) +- Increase Panasonic A/C message tolerances. (#542) +- Added command mode2_decode in tools/ (#557) +- General code style cleanup (#560) + + +## _v2.5.1 (20181002)_ + +**[Bug Fixes]** +- Correct the byte used for Samsung AC Swing. (#529) +- Fix not sending Samsung A/C messages in IRMQTTServer. (#529) + +**[Features]** +- Experimental support for Electra A/C messages. (#528) +- Experimental support for Panasonic A/C messages. (#535) +- Samsung A/C fixes & improvements (#529) +- IRMQTTServer v0.6.0 (#530) + +**[Misc]** +- Change required WifiManager lib version to v0.14 +- Add alias for RAWTICK to kRawTick. (#535) +- Update sendLutron() status. (#515) +- Remove leftover debug message in IRrecvDumpV2 (#526) + + +## _v2.5.0 (20180919)_ + +**[Bug Fixes]** +- Fix HTML menu error for GICABLE in IRMQTTServer. (#516) +- Fix Mitsubishi A/C mode setting. (#514) +- Add missing ',' in auto analyse tool generated code. (#513) +- Fix Fujitsu checksum validation. (#501) +- Remove errant Repeat debug statement in IRMQTTServer. (#507) + +**[Features]** +- Mitsubishi A/C decode improvements. (#514) +- Basic support for Whirlpool A/C messages. (#511) +- Basic support for Samsung A/C messages. (#512) +- Experimental support for detailed Samsung A/C messages. (#521) +- Experimental support for detailed Coolix A/C messages. (#518) +- Experimental support for Lutron protocol. (#516) +- Calculate and use average values for timings in analysing tool. (#513) + +**[Misc]** +- Style change from using #define's for constants to `const kConstantName`. +- Improve the JVC example code. + + +## _v2.4.3 (20180727)_ + +**[Bug Fixes]** +- Handle Space Gaps better in auto analyse tool. (#482) +- Correct min repeat for GICABLE in IRMQTTServer. (#494) + +**[Features]** +- Add static IP config option to IRMQTTServer (#480) +- Full decoding/encoding support for the Haier YRW02 A/C. (#485 #486 #487) + +**[Misc]** +- Update LG (28-bit) HDR mark and space timings. (#492) +- Spelling and grammar fixes (#491) + + +## _v2.4.2 (20180601)_ + +**[Bug Fixes]** +- Timing Fix: Update the period offset compensation. + +**[Features]** +- Improvements for IRMQTTServer example (#466) + + +## _v2.4.1 (20180520)_ + +**[Bug Fixes]** +- Fix crash in IRMQTTServer when compiled under Arduino IDE. (#455) +- Default bit length not set for RCMM in IRMQTTServer example. (#456) +- Bad acknowledgements for some A/C protocols in IRMQTTServer example. (#460) + +**[Features]** +- Allow disabling the use of delay() calls. (#450) +- Initial support for G.I. Cable protocol. (#458) +- Support of Hitachi A/C 13 & 53 byte messages. (#461) + +**[Misc]** +- Auto Analyse Raw Data script converted to Python. (#454) + +## _v2.4.0 (20180407)_ + +**[Bug Fixes]** +- Add missing WiFi.begin() call to IRGCTCPServer example. (#433) +- Add missing sendHaierAC() to IRMQTTServer example. (#434 & #444) +- Make mqtt clientid unique in IRMQTTServer example. (#444) + +**[Features]** + +- Initial Mitsubishi projector protocol support. (#442) +- Experimental support of Hitachi A/C messages. (#445) +- Improve transmission pulse modulation support. + Allow disabling of transmission frequency modulation.(#439) + +**[Misc]** +- IRMQTTServer example improvements. (#444) + + +## _v2.3.3 (20180302)_ + +**[Bug Fixes]** +- Ensure the IR LED is off before we start. (#405) + +**[Features]** +- Experimental decode support for Gree HVAC units (#397) +- Initial support for Haier A/Cs. (#409) +- Improve timing accuracy of unit tests. (#403) +- Rework matchData() to handle equal total data bit time protocols. (#408) + +**[Misc]** +- Add startup text to IRrecvDumpV2 and IRrecvDemo (#412) +- Tweak timings on Fujitsu A/C header (#418) +- AutoAnalyseRawData.sh: Add some support for handling larger than 64 bit codes. (#419) +- Use better comments for send GPIO in examples. (#425) + + +## _v2.3.2 (20180126)_ + +**[Bug Fixes]** +- Integer underflow caused device not to respond in `sendJVC()` (#401) + +**[Features]** +- Initial support for sending & receiving Carrier HVAC codes. (#387) +- Add Pronto HEX code support to _gc_decode_ tool. (#388) + +**[Misc]** +- Make mDNS independent of MQTT in IRMQTTServer example code. (#390 #391) + + +## _v2.3.1 (20171229)_ + +**[Bug Fixes]** +- Setting `#define SEND_FUJITSU_AC false` caused a compilation error (#375) +- Integer underflow caused huge `space()` in `sendGeneric()` (#381) + +**[Features]** +- Support sending & receiving Lasertag codes. (#374) +- Reduce the library footprint by using a new `sendGeneric()` routine. (#373) + +**[Misc]** +- Lots of grammar & typo fixes. (#378) +- Update keywords.txt for Arduino IDE users (#371) +- Update pins in examples so they are compatible with Adafruit boards. (#383) + + +## _v2.3.0 (20171208)_ + +**[Bug Fixes]** +- Panasonic-based protocols had incorrect message gap. (#358) +- Formatting error for large rawData values in example code. (#355) +- Off-by-one error in payload_copy malloc. (#337) +- Off-by-one error in unit test helper routines (#363) + +**[Features]** +- Support sending and receiving Midea A/C codes. +- Support for receiving Kelvinator A/C codes. (#332) +- Support more operation features for Daikin A/Cs. +- Support for decoding Daikin A/Cs. +- Support sending and receiving Toshiba A/Cs. (#333) +- Support sending and receiving AR-DB1 Fujitsu A/C codes. (#367) +- Add new AutoAnalyseRawData.sh & RawToGlobalCache.sh tools (#345) (#343) +- Support for MagiQuest wands. (#365) + +**[Misc]** +- Add checksum verification to Kelvinator A/C decodes. (#348) +- Changes to the threshold reporting of UNKNOWN messages (#347) +- Major re-work of Daikin A/C support. +- Sending for all A/Cs added to MQTT example code. +- MQTT example code improvements. (#334) +- IRrecvDumpV2 significant output improvements. (#363) +- Improved unit test coverage for the library. + + +## _v2.2.1 (20171025)_ + +**[Features]** +- Support for sending and decoding Nikai TV messages. (#311, #313) +- gc_decode: External utility to decode Global Cache codes. (#308, #312) +- IRMQTTServer: Example code to send IR messages via HTTP & MQTT. (#316, #323) +- Improve converting 64bit values to hexadecimal. (#318) + +**[Misc]** +- IRrecvDump.ino code is now deprecated. Use IRrecvDumpV2.ino instead. (#314) + + +## _v2.2.0 (20170922)_ + +**[Bug Fixes]** +- Add printing output of RC-MM and RC-5X protocols in example code. (#284) +- LG timing improvements based on observations (#291) + +**[Features]** +- Automatic capture timing calibration for some protocols. (#268) +- Support for creating & sending Trotec AC codes. (#279) +- Support for creating & sending Argo Ulisse 13 DCI codes. (#280 #300) +- Move to 2 microsecond timing resolution for capture of codes. (#287) +- Capture buffer changes: +- Size at runtime. (#276) +- Message timeout at runtime. (#294) +- Simplify creating & using a second buffer (#303) +- New example code: + - Trotec A/C (#279) + - LG A/C units (#289) + - Argo Ulisse 13 DCI codes. (#300) + + +## _v2.1.1 (20170711)_ + +**[Bug Fixes]** +- GlobalCache incorrectly using hardware offset for period calc. (#267) + +**[Features]** +- Support reporting of 'NEC'-like 32-bit protocols. e.g. Apple TV remote (#265) +- Add an example of sendRaw() to IRsendDemo.ino (#270) + + +## _v2.1.0 (20170704)_ + +**[Features]** +- Support for sending Pronto IR codes. (#248) +- Support for sending Fujitsu A/C codes. (#88) +- Minor improvements to examples. + + +## _v2.0.3 (20170618)_ + +**[Bug fixes]** +- Capture buffer could become corrupt after large message, breaking subsequent decodes. (#253) + + +## _v2.0.2 (20170615)_ + +**[Bug fixes]** +- Correct decode issue introduced in v2.0.1 affecting multiple protocol decoders (#243) +- Correct post-message gap for the Panasonic protocol(s) (#245) +- Incorrect display of the decoded uint64_t value in the example code. (#245) + + +## _v2.0.1 (20170614)_ + +**[Bug fixes]** +- Decoding protocols when it doesn't detect a post-command gap, and there is no more data. (#243) +- Incorrect minimum size calculation when there is no post-command gap. (#243) + + +## _v2.0.0 - 64 bit support and major improvements (20170612)_ + +**[Misc]** +- This is almost a complete re-write of the library. + +**[Features]** +- All suitable protocols now handle 64-bit data messages and are repeatable via an optional argument. +- Unit tests for all protocols. +- Far better and stricter decoding for most protocols. +- Address & command decoding for protocols where that information is available. +- Much more precise timing for generation of signals sent. +- Lower duty-cycles for some protocols. +- Several new protocols added, and some new sending and decoding routines for existing ones. +- Ability to optionally chose which protocols are included, enabling faster decoding and smaller code footprints if desired. +- Support for far larger capture buffers. (e.g. RAWLEN > 256) + +**[Bug fixes]** +- Numerous bug fixes. + + +## _v1.2.0 (20170429)_ + +**[Features]** +- Add ability to copy IR capture buffer, and continue capturing. Means faster and better IR command decoding. +- Reduce IRAM usage by 28 bytes. +- Improve capture of RC-MM & Panasonic protocols. +- Upgrade IRrecvDumpV2 to new IR capture buffer. Much fewer corrupted/truncated IR messages. + + +## _v1.1.1 (20170413)_ + +**[Bug fixes]** +- Fix a reported problem when sending the LG protocol. Preemptive fix for possible similar cases. +- Fix minor issues in examples. + +**[Features]** +- Add documentation to some examples to aid new people. +- Add ALPHA support for RC-MM protocol. (Known to be currently not 100% working.) diff --git a/src/libraries/IRremoteESP8266/SupportedProtocols.md b/src/libraries/IRremoteESP8266/SupportedProtocols.md new file mode 100644 index 000000000..422a5bf2d --- /dev/null +++ b/src/libraries/IRremoteESP8266/SupportedProtocols.md @@ -0,0 +1,271 @@ + +# IR Protocols supported by this library + +| Protocol | Brand | Model | A/C Model | Detailed A/C Support | +| --- | --- | --- | --- | --- | +| [Airton](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Airton.cpp) | **[Airton](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Airton.h)** | RD1A1 remote
SMVH09B-2A2A3NH ref. 409730 A/C | | Yes | +| [Airwell](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Airwell.cpp) | **[Airwell](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Airwell.h)** | DLS 21 DCI R410 AW A/C
RC04 remote
RC08W remote | | Yes | +| [Aiwa](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Aiwa.cpp) | **Aiwa** | RC-T501 RCU | | - | +| [Amcor](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Amcor.cpp) | **[Amcor](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Amcor.h)** | ADR-853H A/C
TAC-444 remote
TAC-495 remote | | Yes | +| [Argo](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Argo.cpp) | **[Argo](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Argo.h)** | Ulisse 13 DCI Mobile Split A/C | | Yes | +| [Arris](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Arris.cpp) | **Arris** | 120A V1.0 A18 remote
VIP1113M Set-top box | | - | +| [Bosch](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Bosch.cpp) | **[Bosch](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Bosch.h)** | CL3000i-Set 26 E A/C
RG10A(G2S)BGEF remote | | Yes | +| [Bose](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Bose.cpp) | **Bose** | Bose TV Speaker | | - | +| [Carrier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Carrier.cpp) | **[Carrier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Carrier.h)** | 40GKX0E2006 remote (CARRIER_AC128) | | Yes | +| [Carrier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Carrier.cpp) | **[Carrier/Surrey](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Carrier.h)** | 42QG5A55970 remote
53NGK009/012 Inverter
619EGX0090E0 A/C
619EGX0120E0 A/C
619EGX0180E0 A/C
619EGX0220E0 A/C | | Yes | +| [ClimaButler](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_ClimaButler.cpp) | **Clima-Butler** | AR-715 remote
RCS-SD43UWI A/C | | - | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Airwell](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | RC08B remote | | Yes | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Beko](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | BINR 070/071 split-type A/C
RG57K7(B)/BGEF Remote | | Yes | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Bosch](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | B1ZAI2441W/B1ZAO2441W A/C
RG36B4/BGE remote | | Yes | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Kastron](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | RG57A7/BGEF Inverter remote | | Yes | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Kaysun](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | Casual CF A/C | | Yes | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | MS12FU-10HRDN1-QRD0GW(B) A/C
MSABAU-07HRFN1-QRD0GW A/C (circa 2016)
RG52D/BGE Remote | | Yes | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Tokio](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | AATOEMF17-12CHR1SW split-type RG51\|50/BGE Remote | | Yes | +| [Coolix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.cpp) | **[Toshiba](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Coolix.h)** | RAS-4M27YAV-E A/C
RAS-M10YKV-E A/C
RAS-M13YKV-E A/C
WH-E1YE remote | | Yes | +| [Corona](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Corona.cpp) | **[Corona](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Corona.h)** | AR-01 remote
CSH-N2211 A/C
CSH-N2511 A/C
CSH-N2811 A/C
CSH-N4011 A/C | | Yes | +| [Daikin](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Daikin.cpp) | **[Daikin](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Daikin.h)** | 17 Series FTXB09AXVJU A/C (DAIKIN128)
17 Series FTXB12AXVJU A/C (DAIKIN128)
17 Series FTXB24AXVJU A/C (DAIKIN128)
ARC423A5 remote (DAIKIN160)
ARC433** remote (DAIKIN)
ARC433B69 remote (DAIKIN216)
ARC466A12 remote (DAIKIN)
ARC466A33 remote (DAIKIN)
ARC466A67 remote (DAIKIN312)
ARC477A1 remote (DAIKIN2)
ARC480A5 remote (DAIKIN152)
ARC484A4 remote (DAIKIN216)
BRC4C151 remote (DAIKIN176)
BRC4C153 remote (DAIKIN176)
BRC4M150W16 remote (DAIKIN200)
BRC52B63 remote (DAIKIN128)
DGS01 remote (DAIKIN64)
FFN-C/FCN-F Series A/C (DAIKIN64)
FFQ35B8V1B A/C (DAIKIN176)
FTE12HV2S A/C
FTQ60TV16U2 A/C (DAIKIN216)
FTWX35AXV1 A/C (DAIKIN64)
FTXM-M A/C (DAIKIN)
FTXM20R5V1B A/C (DAIKIN312)
FTXZ25NV1B A/C (DAIKIN2)
FTXZ35NV1B A/C (DAIKIN2)
FTXZ50NV1B A/C (DAIKIN2)
M Series A/C (DAIKIN) | | Yes | +| [Delonghi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Delonghi.cpp) | **[Delonghi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Delonghi.h)** | PAC A95 | | Yes | +| [Denon](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Denon.cpp) | **Denon** | AVR-3801 A/V Receiver (probably) | | - | +| [Dish](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Dish.cpp) | **DISH NETWORK** | echostar 301 | | - | +| [Doshisha](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Doshisha.cpp) | **Doshisha** | CZ-S32D LED Light
CZ-S38D LED Light
CZ-S50D LED Light
RCZ01 remote | | - | +| [Ecoclim](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Ecoclim.cpp) | **[EcoClim](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Ecoclim.h)** | HYSFR-P348 remote
ZC200DPO A/C | | Yes | +| [Electra](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.cpp) | **[AEG](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.h)** | Chillflex Pro AXP26U338CW A/C | | Yes | +| [Electra](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.cpp) | **[AUX](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.h)** | KFR-35GW/BpNFW=3 A/C
YKR-T/011 remote | | Yes | +| [Electra](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.cpp) | **[Centek](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.h)** | SCT-65Q09 A/C
YKR-P/002E remote | | Yes | +| [Electra](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.cpp) | **[Electra](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.h)** | Classic INV 17 / AXW12DCS A/C
YKR-M/003E remote | | Yes | +| [Electra](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.cpp) | **[Frigidaire](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.h)** | FGPC102AB1 A/C | | Yes | +| [Electra](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.cpp) | **[Subtropic](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Electra.h)** | SUB-07HN1_18Y A/C
YKR-H/102E remote | | Yes | +| [EliteScreens](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_EliteScreens.cpp) | **Elite Screens** | CineTension2 / CineTension3 series
Home2 / Home3 series
Spectrum series
VMAX Plus4 series
VMAX2 / VMAX2 Plus series
ZSP-IR-B / ZSP-IR-W remote | | - | +| [EliteScreens](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_EliteScreens.cpp) | **Lumene Screens** | Embassy | | - | +| [Epson](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Epson.cpp) | **Epson** | EN-TW9100W Projector
EX3220 Projector
EX5220 Projector
EX5230 Projector
EX6220 Projector
EX7220 Projector
VS230 Projector
VS330 Projector | | - | +| [Fujitsu](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Fujitsu.cpp) | **[Fujitsu](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Fujitsu.h)** | AGTV14LAC A/C (ARRAH2E)
AR-DB1 remote (ARDB1)
AR-DL10 remote (ARDB1)
AR-RAC1E remote (ARRAH2E)
AR-RAE1E remote (ARRAH2E)
AR-RAH1U remote (ARREB1E)
AR-RAH2E remote (ARRAH2E)
AR-RAH2U remote (ARRAH2E)
AR-REB1E remote (ARREB1E)
AR-REB4E remote (ARREB1E)
AR-REG1U remote (ARRAH2E)
AR-REW1E remote (ARREW4E)
AR-REW4E remote (ARREW4E)
AR-RY4 remote (ARRY4)
AST9RSGCW A/C (ARDB1)
ASTB09LBC A/C (ARRY4)
ASTG09K A/C (ARREW4E)
ASTG18K A/C (ARREW4E)
ASU12RLF A/C (ARREB1E)
ASU30C1 A/C (ARDB1)
ASYG09KETA-B A/C (ARREW4E)
ASYG30LFCA A/C (ARRAH2E)
ASYG7LMCA A/C (ARREB1E) | ARDB1
ARJW2
ARRAH2E
ARREB1E
ARREW4E
ARRY4 | Yes | +| [Fujitsu](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Fujitsu.cpp) | **[Fujitsu General](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Fujitsu.h)** | AOHG09LLC A/C (ARRAH2E)
AR-JW17 remote (ARDB1)
AR-JW2 remote (ARJW2)
AR-RCE1E remote (ARRAH2E)
ASHG09LLCA A/C (ARRAH2E) | ARDB1
ARJW2
ARRAH2E
ARREB1E
ARREW4E
ARRY4 | Yes | +| [Fujitsu](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Fujitsu.cpp) | **[OGeneral](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Fujitsu.h)** | AR-RCL1E remote (ARRAH2E) | ARDB1
ARJW2
ARRAH2E
ARREB1E
ARREW4E
ARRY4 | Yes | +| [GICable](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_GICable.cpp) | **G.I. Cable** | XRC-200 remote | | - | +| [GlobalCache](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_GlobalCache.cpp) | **Global Cache** | Control Tower IR DB | | - | +| [Goodweather](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Goodweather.cpp) | **[Goodweather](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Goodweather.h)** | ZH/JT-03 remote | | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Amana](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | PBC093G00CC A/C
YX1FF remote | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Cooper & Hunter](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | CH-S09FTXG A/C
YB1F2 remote | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[EKOKAI](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | A/C | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | VIR09HP115V1AH A/C
VIR12HP230V1AH A/C
YAA1FBF remote
YAN1F1 remote
YB1F2F remote
YX1F2F remote (YX1FSF) | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Green](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | YBOFB remote
YBOFB2 remote | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[RusClimate](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | EACS/I-09HAR_X/N3 A/C
YAW1F remote | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Soleus Air](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | window A/C (YX1FSF) | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Ultimate](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | Heat Pump | YAW1F
YBOFB
YX1FSF | Yes | +| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Vailland](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | VAI5-035WNI A/C
YACIFB remote | YAW1F
YBOFB
YX1FSF | Yes | +| [Haier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.cpp) | **[Daichi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.h)** | D-H A/C (HAIER_AC176) | | Yes | +| [Haier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.cpp) | **[Haier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.h)** | HSU-09HMC203 A/C (HAIER_AC_YRW02)
HSU07-HEA03 remote (HAIER_AC)
KFR-26GW/83@UI-Ge A/C (HAIER_AC160)
V9014557 M47 8D remote (HAIER_AC176)
YR-W02 remote (HAIER_AC_YRW02) | | Yes | +| [Haier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.cpp) | **[Mabe](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.h)** | MMI18HDBWCA6MI8 A/C (HAIER_AC176)
V12843 HJ200223 remote (HAIER_AC176) | | Yes | +| [Hitachi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Hitachi.cpp) | **[Hitachi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Hitachi.h)** | KAZE-312KSDP A/C (HITACHI_AC1)
LT0541-HTA remote (HITACHI_AC1)
PC-LH3B (HITACHI_AC3)
R-LT0541-HTA/Y.K.1.1-1 V2.3 remote (HITACHI_AC1)
RAK-25NH5 A/C (HITACHI_AC264)
RAR-2P2 remote (HITACHI_AC264)
RAR-3U3 remote (HITACHI_AC296)
RAR-8P2 remote (HITACHI_AC424)
RAS-22NK A/C (HITACHI_AC344)
RAS-35THA6 remote
RAS-70YHA3 A/C (HITACHI_AC296)
RAS-AJ25H A/C (HITACHI_AC424)
RF11T1 remote (HITACHI_AC344)
Series VI A/C (Circa 2007) (HITACHI_AC1) | | Yes | +| [Inax](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Inax.cpp) | **Lixil** | Inax DT-BA283 Toilet | | - | +| [JVC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_JVC.cpp) | **JVC** | PTU94023B remote | | - | +| [Kelon](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.cpp) | **[Hisense](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.h)** | AST-09UW4RVETG00A A/C (KELON168) | | Yes | +| [Kelon](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.cpp) | **[Kelon](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelon.h)** | AST-09UW4RVETG00A A/C (KELON168)
DG11R2-01 remote (KELON168)
ON/OFF 9000-12000 (KELON) | | Yes | +| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | YAP0F8 remote | | Yes | +| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Green](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | YAPOF3 remote | | Yes | +| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | KSV26CRC A/C
KSV26HRC A/C
KSV35CRC A/C
KSV35HRC A/C
KSV53HRC A/C
KSV62HRC A/C
KSV70CRC A/C
KSV70HRC A/C
KSV80HRC A/C
YALIF Remote | | Yes | +| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Sharp](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | A5VEY A/C
YB1FA remote | | Yes | +| [LG](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_LG.cpp) | **[General Electric](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_LG.h)** | 6711AR2853M Remote (LG - GE6711AR2853M)
AG1BH09AW101 A/C (LG - GE6711AR2853M) | | Yes | +| [LG](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_LG.cpp) | **[LG](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_LG.h)** | 6711A20083V remote (LG - LG6711A20083V)
A4UW30GFA2 A/C (LG2 - AKB74955603 & AKB73757604)
AKB73315611 remote (LG2 - AKB74955603)
AKB73757604 remote (LG2 - AKB73757604)
AKB74395308 remote (LG2)
AKB74955603 remote (LG2 - AKB74955603)
AKB75215403 remote (LG2)
AMNW09GSJA0 A/C (LG2 - AKB74955603)
AMNW24GTPA1 A/C (LG2 - AKB73757604)
MS05SQ NW0 A/C (LG2 - AKB74955603)
S4-W12JA3AA A/C (LG2)
TS-H122ERM1 remote (LG - LG6711A20083V) | | Yes | +| [Lasertag](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Lasertag.cpp) | **Lasertag** | Phaser emitters | | - | +| [Lego](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Lego.cpp) | **LEGO Power Functions** | IR Receiver | | - | +| [Lutron](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Lutron.cpp) | **Lutron** | MIR-ITFS remote
MIR-ITFS-F remote
MIR-ITFS-LF remote
SP-HT remote | | - | +| [MWM](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_MWM.cpp) | **Disney** | Made With Magic (Glow With The Show) wand | | - | +| [Magiquest](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Magiquest.cpp) | **[MagiQuest](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Magiquest.h)** | Wand | | - | +| [Metz](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Metz.cpp) | **Metz** | CH610 TV
RM16 remote
RM17 remote
RM19 remote | | - | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Comfee](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | MPD1-12CRN7 A/C (MIDEA) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Danby](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | DAC080BGUWDB (MIDEA)
DAC100BGUWDB (MIDEA)
DAC120BGUWDB (MIDEA)
R09C/BCGE remote (MIDEA) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Kaysun](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | Casual CF A/C (MIDEA) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Keystone](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | RG57H4(B)BGEF remote (MIDEA) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Lennox](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | M22A indoor split A/C (MIDEA)
M33A indoor split A/C (MIDEA)
M33B indoor split A/C (MIDEA)
MCFA indoor split A/C (MIDEA)
MCFB indoor split A/C (MIDEA)
MMDA indoor split A/C (MIDEA)
MMDB indoor split A/C (MIDEA)
MWMA indoor split A/C (MIDEA)
MWMA009S4-3P A/C (MIDEA)
MWMA012S4-3P A/C (MIDEA)
MWMB indoor split A/C (MIDEA)
RG57A6/BGEFU1 remote (MIDEA) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | FS40-7AR Stand Fan (MIDEA24) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[MrCool](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | RG57A6/BGEFU1 remote (MIDEA) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Pioneer System](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | RG66B6(B)/BGEFU1 remote (MIDEA)
RUBO18GMFILCAD A/C (18K BTU) (MIDEA)
RYBO12GMFILCAD A/C (12K BTU) (MIDEA)
UB018GMFILCFHD A/C (12K BTU) (MIDEA)
WS012GMFI22HLD A/C (12K BTU) (MIDEA)
WS018GMFI22HLD A/C (12K BTU) (MIDEA) | | Yes | +| [Midea](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.cpp) | **[Trotec](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Midea.h)** | RG57H(B)/BGE remote (MIDEA)
RG57H3(B)/BGCEF-M remote (MIDEA)
TROTEC PAC 2100 X (MIDEA)
TROTEC PAC 3900 X (MIDEA) | | Yes | +| [MilesTag2](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_MilesTag2.cpp) | **Milestag2** | Various | | - | +| [Mirage](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mirage.cpp) | **[Maxell](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mirage.h)** | KKG9A-C1 remote
MX-CH18CF A/C | KKG29AC1
KKG9AC1 | Yes | +| [Mirage](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mirage.cpp) | **[Mirage](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mirage.h)** | VLU series A/C | KKG29AC1
KKG9AC1 | Yes | +| [Mirage](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mirage.cpp) | **[Tronitechnik](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mirage.h)** | KKG29A-C1 remote
Reykir 9000 A/C | KKG29AC1
KKG9AC1 | Yes | +| [Mitsubishi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mitsubishi.cpp) | **[Mitsubishi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mitsubishi.h)** | HC3000 Projector (MITSUBISHI2)
KM14A 0179213 remote
MS-GK24VA A/C
TV (MITSUBISHI) | | Yes | +| [Mitsubishi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mitsubishi.cpp) | **[Mitsubishi Electric](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Mitsubishi.h)** | 001CP T7WE10714 remote (MITSUBISHI136)
KPOA remote (MITSUBISHI112)
MLZ-RX5017AS A/C (MITSUBISHI_AC)
MSH-A24WV A/C (MITSUBISHI112)
MSZ-FHnnVE A/C (MITSUBISHI_AC)
MSZ-GV2519 A/C (MITSUBISHI_AC)
MSZ-SF25VE3 A/C (MITSUBISHI_AC)
MSZ-ZW4017S A/C (MITSUBISHI_AC)
MUH-A24WV A/C (MITSUBISHI112)
PEAD-RP71JAA Ducted A/C (MITSUBISHI136)
RH151 remote (MITSUBISHI_AC)
RH151/M21ED6426 remote (MITSUBISHI_AC)
SG153/M21EDF426 remote (MITSUBISHI_AC)
SG15D remote (MITSUBISHI_AC) | | Yes | +| [MitsubishiHeavy](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_MitsubishiHeavy.cpp) | **[Mitsubishi Heavy Industries](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_MitsubishiHeavy.h)** | RKX502A001C remote (88 bit)
RLA502A700B remote (152 bit)
SRKxxZJ-S A/C (88 bit)
SRKxxZM-S A/C (152 bit)
SRKxxZMXA-S A/C (152 bit) | | Yes | +| [Multibrackets](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Multibrackets.cpp) | **Multibrackets** | Motorized Swing mount large - 4500 | | - | +| [NEC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.cpp) | **[Aloka](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.h)** | SleepyLights LED Lamp | | - | +| [NEC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.cpp) | **[BBK](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.h)** | SP550S 5.1 sound system | | - | +| [NEC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.cpp) | **[Duux](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.h)** | Blizzard Smart 10K / DXMA04 A/C
YJ-A081 TR Remote | | - | +| [NEC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.cpp) | **[Silan Microelectronics](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.h)** | SC6121-001 IC | | - | +| [NEC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.cpp) | **[Tanix](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.h)** | TX3 mini Android TV Box | | - | +| [NEC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.cpp) | **[Toshiba](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.h)** | 42TL838 LCD TV | | - | +| [NEC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.cpp) | **[Yamaha](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_NEC.h)** | RAV561 remote
RXV585B A/V Receiver | | - | +| [Neoclima](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Neoclima.cpp) | **[Neoclima](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Neoclima.h)** | NS-09AHTI A/C
ZH/TY-01 remote | | Yes | +| [Neoclima](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Neoclima.cpp) | **[Soleus Air](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Neoclima.h)** | TTWM1-10-01 A/C
ZCF/TL-05 remote | | Yes | +| [Nikai](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Nikai.cpp) | **Nikai** | Unknown LCD TV | | - | +| [Panasonic](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Panasonic.cpp) | **[Panasonic](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Panasonic.h)** | A75C2295 remote (PANASONIC_AC32)
A75C2311 remote (PANASONIC_AC CKP/5)
A75C2616-1 remote (PANASONIC_AC DKE/3)
A75C3704 remote (PANASONIC_AC DKE/3)
A75C3747 remote (PANASONIC_AC JKE/4)
A75C4762 remote (PANASONIC_AC RKR/6)
CKP series A/C (PANASONIC_AC CKP/5)
CS-E7PKR A/C (PANASONIC_AC DKE/2)
CS-E9CKP series A/C (PANASONIC_AC32)
CS-ME10CKPG A/C (PANASONIC_AC CKP/5)
CS-ME12CKPG A/C (PANASONIC_AC CKP/5)
CS-ME14CKPG A/C (PANASONIC_AC CKP/5)
CS-YW9MKD A/C (PANASONIC_AC JKE/4)
CS-Z24RKR A/C (PANASONIC_AC RKR/6)
CS-Z9RKR A/C (PANASONIC_AC RKR/6)
DKE series A/C (PANASONIC_AC DKE/3)
DKW series A/C (PANASONIC_AC DKE/3)
JKE series A/C (PANASONIC_AC JKE/4)
NKE series A/C (PANASONIC_AC NKE/2)
PKR series A/C (PANASONIC_AC DKE/3)
RKR series A/C (PANASONIC_AC RKR/6)
TV (PANASONIC) | CKP
DKE
JKE
LKE
NKE
RKR | Yes | +| [Pioneer](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Pioneer.cpp) | **Pioneer** | AV Receivers
AXD7690 Remote
VSX-324 AV Receiver | | - | +| [Pronto](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Pronto.cpp) | **Pronto** | Pronto Hex | | - | +| [RC5_RC6](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_RC5_RC6.cpp) | **Philips** | RC-5X (RC5X)
Standard RC-5 (RC5)
Standard RC-6 (RC6) | | - | +| [RCMM](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_RCMM.cpp) | **Microsoft** | XBOX 360 | | - | +| [Rhoss](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Rhoss.cpp) | **[Rhoss](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Rhoss.h)** | Idrowall MPCV 20-30-35-40 | | Yes | +| [Samsung](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Samsung.cpp) | **[Samsung](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Samsung.h)** | AH59-02692E Soundbar remote (SAMSUNG36)
AK59-00167A Bluray remote (SAMSUNG36)
AR09FSSDAWKNFA A/C (SAMSUNG_AC)
AR09HSFSBWKN A/C (SAMSUNG_AC)
AR12HSSDBWKNEU A/C (SAMSUNG_AC)
AR12KSFPEWQNET A/C (SAMSUNG_AC)
AR12NXCXAWKXEU A/C (SAMSUNG_AC)
AR12TXEAAWKNEU A/C (SAMSUNG_AC)
BN59-01178B TV remote (SAMSUNG)
DB63-03556X003 remote
DB93-14195A remote (SAMSUNG_AC)
DB93-16761C remote
DB96-24901C remote (SAMSUNG_AC)
HW-J551 Soundbar (SAMSUNG36)
IEC-R03 remote
UA55H6300 TV (SAMSUNG)
UE40K5510AUXRU TV (SAMSUNG) | | Yes | +| [Sanyo](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sanyo.cpp) | **[Sanyo](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sanyo.h)** | LC7461 transmitter IC (SANYO_LC7461)
RCS-2HS4E remote (SANYO_AC)
RCS-2S4E remote (SANYO_AC)
RCS-4MHVPIS4EE remote (SANYO_AC152)
SA 8650B - disabled
SAP-K121AHA A/C (SANYO_AC)
SAP-K242AH A/C (SANYO_AC)
SAP-KMRV124EHE A/C (SANYO_AC152) | | Yes | +| [Sharp](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sharp.cpp) | **[Sharp](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sharp.h)** | AH-A12REVP-1 A/C (A903)
AH-AxSAY A/C (A907)
AH-PR13-GL A/C (A903)
AH-XP10NRY A/C (A903)
AY-ZP40KR A/C (A907)
CRMC-820 JBEZ remote (A903)
CRMC-A705 JBEZ remote (A705)
CRMC-A863 JBEZ remote (A903)
CRMC-A903JBEZ remote (A903)
CRMC-A907 JBEZ remote (A907)
CRMC-A950 JBEZ (A907)
LC-52D62U TV | A705
A903
A907 | Yes | +| [Sherwood](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sherwood.cpp) | **Sherwood** | RC-138 remote
RD6505(B) Receiver | | - | +| [Sony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sony.cpp) | **Sony** | HT-CT380 Soundbar (Uses 38kHz & 3 repeats)
HT-SF150 Soundbar (Uses 38kHz & 3 repeats) | | - | +| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Blyss** | Owen-SW-5 3 Fan
WP-YK8 090218 remote | | - | +| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **SamHop** | SM3015 Fan Remote Control
SM5021 Encoder chip
SM5032 Decoder chip | | - | +| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Satellite Electronic** | ID6 Remote
JY199I Fan driver
JY199I-L Fan driver | | - | +| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **SilverCrest** | SSVS 85 A1 Fan | | - | +| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Symphony** | Air Cooler 3Di | | - | +| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Westinghouse** | 78095 Remote
Ceiling fan | | - | +| [Tcl](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.cpp) | **[Daewoo](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.h)** | DSB-F0934ELH-V A/C
GYKQ-52E remote | GZ055BE1
TAC09CHSD | Yes | +| [Tcl](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.cpp) | **[Leberg](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.h)** | LBS-TOR07 A/C (TAC09CHSD) | GZ055BE1
TAC09CHSD | Yes | +| [Tcl](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.cpp) | **[TCL](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.h)** | GYKQ-58(XM) remote (TCL96AC)
TAC-09CHSD/XA31I A/C (TAC09CHSD) | GZ055BE1
TAC09CHSD | Yes | +| [Tcl](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.cpp) | **[Teknopoint](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.h)** | Allegro SSA-09H A/C (GZ055BE1)
GZ-055B-E1 remote (GZ055BE1) | GZ055BE1
TAC09CHSD | Yes | +| [Technibel](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Technibel.cpp) | **[Technibel](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Technibel.h)** | IRO PLUS | | Yes | +| [Teco](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Teco.cpp) | **[Alaska](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Teco.h)** | SAC9010QC A/C
SAC9010QC remote | | Yes | +| [Teknopoint](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Teknopoint.cpp) | **Teknopoint** | Allegro SSA-09H A/C
GZ-055B-E1 remote
GZ01-BEJ0-000 remote | | - | +| [Toshiba](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Toshiba.cpp) | **[Carrier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Toshiba.h)** | 42NQV025M2 / 38NYV025M2 A/C
42NQV035M2 / 38NYV035M2 A/C
42NQV050M2 / 38NYV050M2 A/C
42NQV060M2 / 38NYV060M2 A/C | | Yes | +| [Toshiba](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Toshiba.cpp) | **[Toshiba](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Toshiba.h)** | Akita EVO II
RAS 18SKP-ES
RAS-2558V A/C
RAS-25SKVP2-ND A/C
RAS-B13N3KV2
RAS-B13N3KVP-E
WC-L03SE
WH-TA01JE remote
WH-TA04NE
WH-UB03NJ remote | | Yes | +| [Toto](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Toto.cpp) | **Toto** | Washlet Toilet NJ | | - | +| [Transcold](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Transcold.cpp) | **[Transcold](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Transcold.h)** | M1-F-NO-6 A/C | | Yes | +| [Trotec](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Trotec.cpp) | **[Duux](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Trotec.h)** | Blizzard Smart 10K / DXMA04 A/C (TROTEC) | | Yes | +| [Trotec](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Trotec.cpp) | **[Trotec](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Trotec.h)** | PAC 3200 A/C (TROTEC)
PAC 3550 Pro A/C (TROTEC_3550) | | Yes | +| [Truma](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Truma.cpp) | **[Truma](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Truma.h)** | 40091-86700 remote
Aventa A/C | | Yes | +| [Vestel](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Vestel.cpp) | **[Vestel](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Vestel.h)** | BIOX CXP-9 A/C (9K BTU) | | Yes | +| [Voltas](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Voltas.cpp) | **[Voltas](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Voltas.h)** | 122LZF 4011252 Window A/C | 122LZF | Yes | +| [Whirlpool](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Whirlpool.cpp) | **[Whirlpool](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Whirlpool.h)** | DG11J1-04 remote
DG11J1-3A remote
DG11J1-91 remote
SPIS409L A/C
SPIS412L A/C
SPIW409L A/C
SPIW412L A/C
SPIW418L A/C | DG11J13A
DG11J191 | Yes | +| [Whynter](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Whynter.cpp) | **Whynter** | ARC-110WD A/C | | - | +| [Xmp](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Xmp.cpp) | **Xfinity** | XR11 remote
XR2 remote | | - | +| [Zepeal](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Zepeal.cpp) | **Zepeal** | DRT-A3311(BG) 5 button remote
DRT-A3311(BG) floor fan | | - | + + +## Send only protocols: + +- GLOBALCACHE +- PRONTO +- RAW +- SHERWOOD +- SONY_38K + + +## Send & decodable protocols: + +- AIRTON +- AIRWELL +- AIWA_RC_T501 +- AMCOR +- ARGO +- ARRIS +- BOSCH144 +- BOSE +- CARRIER_AC +- CARRIER_AC128 +- CARRIER_AC40 +- CARRIER_AC64 +- CLIMABUTLER +- COOLIX +- COOLIX48 +- CORONA_AC +- DAIKIN +- DAIKIN128 +- DAIKIN152 +- DAIKIN160 +- DAIKIN176 +- DAIKIN2 +- DAIKIN200 +- DAIKIN216 +- DAIKIN312 +- DAIKIN64 +- DELONGHI_AC +- DENON +- DISH +- DOSHISHA +- ECOCLIM +- ELECTRA_AC +- ELITESCREENS +- EPSON +- FUJITSU_AC +- GICABLE +- GOODWEATHER +- GREE +- HAIER_AC +- HAIER_AC160 +- HAIER_AC176 +- HAIER_AC_YRW02 +- HITACHI_AC +- HITACHI_AC1 +- HITACHI_AC2 +- HITACHI_AC264 +- HITACHI_AC296 +- HITACHI_AC3 +- HITACHI_AC344 +- HITACHI_AC424 +- INAX +- JVC +- KELON +- KELON168 +- KELVINATOR +- LASERTAG +- LEGOPF +- LG +- LG2 +- LUTRON +- MAGIQUEST +- METZ +- MIDEA +- MIDEA24 +- MILESTAG2 +- MIRAGE +- MITSUBISHI +- MITSUBISHI112 +- MITSUBISHI136 +- MITSUBISHI2 +- MITSUBISHI_AC +- MITSUBISHI_HEAVY_152 +- MITSUBISHI_HEAVY_88 +- MULTIBRACKETS +- MWM +- NEC +- NEC_LIKE +- NEOCLIMA +- NIKAI +- PANASONIC +- PANASONIC_AC +- PANASONIC_AC32 +- PIONEER +- RC5 +- RC5X +- RC6 +- RCMM +- RHOSS +- SAMSUNG +- SAMSUNG36 +- SAMSUNG_AC +- SANYO +- SANYO_AC +- SANYO_AC152 +- SANYO_AC88 +- SANYO_LC7461 +- SHARP +- SHARP_AC +- SONY +- SYMPHONY +- TCL112AC +- TCL96AC +- TECHNIBEL_AC +- TECO +- TEKNOPOINT +- TOSHIBA_AC +- TOTO +- TRANSCOLD +- TROTEC +- TROTEC_3550 +- TRUMA +- VESTEL_AC +- VOLTAS +- WHIRLPOOL_AC +- WHYNTER +- XMP +- ZEPEAL diff --git a/src/libraries/IRremoteESP8266/library.properties b/src/libraries/IRremoteESP8266/library.properties new file mode 100644 index 000000000..801fcab6e --- /dev/null +++ b/src/libraries/IRremoteESP8266/library.properties @@ -0,0 +1,9 @@ +name=IRremoteESP8266 +version=2.8.4 +author=David Conran, Sebastien Warin, Mark Szabo, Ken Shirriff +maintainer=David Conran, Mark Szabo, Sebastien Warin, Roi Dayan, Massimiliano Pinto, Christian Nilsson +sentence=Send and receive infrared signals with multiple protocols (ESP8266/ESP32) +paragraph=This library enables you to send and receive infra-red signals on an ESP8266 or an ESP32. +category=Device Control +url=https://github.com/crankyoldgit/IRremoteESP8266 +architectures=esp8266,esp32 diff --git a/src/libraries/IRremoteESP8266/pylintrc b/src/libraries/IRremoteESP8266/pylintrc new file mode 100644 index 000000000..987c6abf9 --- /dev/null +++ b/src/libraries/IRremoteESP8266/pylintrc @@ -0,0 +1,12 @@ +[REPORTS] + +# Tells whether to display a full report or only the messages +reports=no + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# String used as indentation unit. +indent-string=' ' diff --git a/src/libraries/IRremoteESP8266/src/CPPLINT.cfg b/src/libraries/IRremoteESP8266/src/CPPLINT.cfg new file mode 100644 index 000000000..fc30d70f8 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/CPPLINT.cfg @@ -0,0 +1 @@ +filter=-build/include,+build/include_alpha,+build/include_order,+build/include_what_you_use diff --git a/src/libraries/IRremoteESP8266/src/IRac.cpp b/src/libraries/IRremoteESP8266/src/IRac.cpp new file mode 100644 index 000000000..6c8359bc3 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRac.cpp @@ -0,0 +1,5037 @@ +// Copyright 2019 David Conran + +// Provide a universal/standard interface for sending A/C nessages. +// It does not provide complete and maximum granular control but tries +// to offer most common functionality across all supported devices. + +#include "IRac.h" +//#ifndef UNIT_TEST +#include "String.h" +//#endif +//#include +#ifndef ARDUINO +//#include +#endif +#include +#include "IRsend.h" +#include "IRremoteESP8266.h" +#include "IRtext.h" +#include "IRutils.h" +#include "ir_Airton.h" +#include "ir_Airwell.h" +#include "ir_Amcor.h" +#include "ir_Argo.h" +#include "ir_Bosch.h" +#include "ir_Carrier.h" +#include "ir_Coolix.h" +#include "ir_Corona.h" +#include "ir_Daikin.h" +#include "ir_Ecoclim.h" +#include "ir_Electra.h" +#include "ir_Fujitsu.h" +#include "ir_Haier.h" +#include "ir_Hitachi.h" +#include "ir_Kelon.h" +#include "ir_Kelvinator.h" +#include "ir_LG.h" +#include "ir_Midea.h" +#include "ir_Mitsubishi.h" +#include "ir_MitsubishiHeavy.h" +#include "ir_Neoclima.h" +#include "ir_Panasonic.h" +#include "ir_Rhoss.h" +#include "ir_Samsung.h" +#include "ir_Sanyo.h" +#include "ir_Sharp.h" +#include "ir_Tcl.h" +#include "ir_Technibel.h" +#include "ir_Teco.h" +#include "ir_Toshiba.h" +#include "ir_Transcold.h" +#include "ir_Trotec.h" +#include "ir_Truma.h" +#include "ir_Vestel.h" +#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(RHS)) +#else // ESP8266 +#define STRCASECMP(LHS, RHS) strcasecmp(LHS, RHS) +#endif // ESP8266 +#endif // STRCASECMP + +#ifndef UNIT_TEST +#define OUTPUT_DECODE_RESULTS_FOR_UT(ac) +#else +/* NOTE: THIS IS NOT A DOXYGEN COMMENT (would require ENABLE_PREPROCESSING-YES) +/// If compiling for UT *and* a test receiver @c IRrecv is provided via the +/// @c _utReceived param, this injects an "output" gadget @c _lastDecodeResults +/// into the @c IRAc::sendAc method, so that the UT code may parse the "sent" +/// value and drive further assertions +/// +/// @note The @c decode_results "returned" is a shallow copy (empty rawbuf), +/// mostly b/c the class does not have a custom/deep copy c-tor +/// and defining it would be an overkill for this purpose +/// @note For future maintainers: If @c IRAc class is ever refactored to use +/// polymorphism (static or dynamic)... this macro should be removed +/// and replaced with proper GMock injection. +*/ +#define OUTPUT_DECODE_RESULTS_FOR_UT(ac) \ + { \ + if (_utReceiver) { \ + _lastDecodeResults = nullptr; \ + (ac)._irsend.makeDecodeResult(); \ + if (_utReceiver->decode(&(ac)._irsend.capture)) { \ + _lastDecodeResults = std::unique_ptr( \ + new decode_results((ac)._irsend.capture)); \ + _lastDecodeResults->rawbuf = nullptr; \ + } \ + } \ + } +#endif // UNIT_TEST + +/// 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. +/// @param[in] use_modulation true means use frequency modulation. false, don't. +IRac::IRac(const uint16_t pin, const bool inverted, const bool use_modulation) { + _pin = pin; + _inverted = inverted; + _modulation = use_modulation; + this->markAsSent(); +} + +/// Initialise the given state with the supplied settings. +/// @param[out] state A Ptr to where the settings will be stored. +/// @param[in] vendor The vendor/protocol type. +/// @param[in] model The A/C model if applicable. +/// @param[in] power The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] beep Enable/Disable beeps when receiving IR messages. +/// @param[in] sleep Nr. of minutes for sleep mode. +/// -1 is Off, >= 0 is on. Some devices it is the nr. of mins to run for. +/// Others it may be the time to enter/exit sleep mode. +/// i.e. Time in Nr. of mins since midnight. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::initState(stdAc::state_t *state, + const decode_type_t vendor, const int16_t model, + const bool power, const stdAc::opmode_t mode, + const float degrees, const bool celsius, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool light, const bool filter, const bool clean, + const bool beep, const int16_t sleep, + const int16_t clock) { + state->protocol = vendor; + state->model = model; + state->power = power; + state->mode = mode; + state->degrees = degrees; + state->celsius = celsius; + state->fanspeed = fan; + state->swingv = swingv; + state->swingh = swingh; + state->quiet = quiet; + state->turbo = turbo; + state->econo = econo; + state->light = light; + state->filter = filter; + state->clean = clean; + state->beep = beep; + state->sleep = sleep; + state->clock = clock; +} + +/// Initialise the given state with the supplied settings. +/// @param[out] state A Ptr to where the settings will be stored. +/// @note Sets all the parameters to reasonable base/automatic defaults. +void IRac::initState(stdAc::state_t *state) { + stdAc::state_t def; + *state = def; +} + +/// Get the current internal A/C climate state. +/// @return A Ptr to a state containing the current (to be sent) settings. +stdAc::state_t IRac::getState(void) { return next; } + +/// Get the previous internal A/C climate state that should have already been +/// sent to the device. i.e. What the A/C unit should already be set to. +/// @return A Ptr to a state containing the previously sent settings. +stdAc::state_t IRac::getStatePrev(void) { return _prev; } + +/// Is the given protocol supported by the IRac class? +/// @param[in] protocol The vendor/protocol type. +/// @return true if the protocol is supported by this class, otherwise false. +bool IRac::isProtocolSupported(const decode_type_t protocol) { + switch (protocol) { +#if SEND_AIRTON + case decode_type_t::AIRTON: +#endif // SEND_AIRTON +#if SEND_AIRWELL + case decode_type_t::AIRWELL: +#endif // SEND_AIRWELL +#if SEND_AMCOR + case decode_type_t::AMCOR: +#endif +#if SEND_ARGO + case decode_type_t::ARGO: +#endif +#if SEND_BOSCH144 + case decode_type_t::BOSCH144: +#endif +#if SEND_CARRIER_AC64 + case decode_type_t::CARRIER_AC64: +#endif // SEND_CARRIER_AC64 +#if SEND_COOLIX + case decode_type_t::COOLIX: +#endif +#if SEND_CORONA_AC + case decode_type_t::CORONA_AC: +#endif +#if SEND_DAIKIN + case decode_type_t::DAIKIN: +#endif +#if SEND_DAIKIN128 + case decode_type_t::DAIKIN128: +#endif +#if SEND_DAIKIN152 + case decode_type_t::DAIKIN152: +#endif +#if SEND_DAIKIN160 + case decode_type_t::DAIKIN160: +#endif +#if SEND_DAIKIN176 + case decode_type_t::DAIKIN176: +#endif +#if SEND_DAIKIN2 + case decode_type_t::DAIKIN2: +#endif +#if SEND_DAIKIN216 + case decode_type_t::DAIKIN216: +#endif +#if SEND_DAIKIN64 + case decode_type_t::DAIKIN64: +#endif +#if SEND_DELONGHI_AC + case decode_type_t::DELONGHI_AC: +#endif +#if SEND_ECOCLIM + case decode_type_t::ECOCLIM: +#endif +#if SEND_ELECTRA_AC + case decode_type_t::ELECTRA_AC: +#endif +#if SEND_FUJITSU_AC + case decode_type_t::FUJITSU_AC: +#endif +#if SEND_GOODWEATHER + case decode_type_t::GOODWEATHER: +#endif +#if SEND_GREE + case decode_type_t::GREE: +#endif +#if SEND_HAIER_AC + case decode_type_t::HAIER_AC: +#endif +#if SEND_HAIER_AC160 + case decode_type_t::HAIER_AC160: +#endif // SEND_HAIER_AC160 +#if SEND_HAIER_AC176 + case decode_type_t::HAIER_AC176: +#endif // SEND_HAIER_AC176 +#if SEND_HAIER_AC_YRW02 + case decode_type_t::HAIER_AC_YRW02: +#endif +#if SEND_HITACHI_AC + case decode_type_t::HITACHI_AC: +#endif +#if SEND_HITACHI_AC1 + case decode_type_t::HITACHI_AC1: +#endif +#if SEND_HITACHI_AC264 + case decode_type_t::HITACHI_AC264: +#endif +#if SEND_HITACHI_AC296 + case decode_type_t::HITACHI_AC296: +#endif +#if SEND_HITACHI_AC344 + case decode_type_t::HITACHI_AC344: +#endif +#if SEND_HITACHI_AC424 + case decode_type_t::HITACHI_AC424: +#endif +#if SEND_KELON + case decode_type_t::KELON: +#endif +#if SEND_KELVINATOR + case decode_type_t::KELVINATOR: +#endif +#if SEND_LG + case decode_type_t::LG: + case decode_type_t::LG2: +#endif +#if SEND_MIDEA + case decode_type_t::MIDEA: +#endif // SEND_MIDEA +#if SEND_MIRAGE + case decode_type_t::MIRAGE: +#endif // SEND_MIRAGE +#if SEND_MITSUBISHI_AC + case decode_type_t::MITSUBISHI_AC: +#endif +#if SEND_MITSUBISHI112 + case decode_type_t::MITSUBISHI112: +#endif +#if SEND_MITSUBISHI136 + case decode_type_t::MITSUBISHI136: +#endif +#if SEND_MITSUBISHIHEAVY + case decode_type_t::MITSUBISHI_HEAVY_88: + case decode_type_t::MITSUBISHI_HEAVY_152: +#endif +#if SEND_NEOCLIMA + case decode_type_t::NEOCLIMA: +#endif +#if SEND_PANASONIC_AC + case decode_type_t::PANASONIC_AC: +#endif +#if SEND_PANASONIC_AC32 + case decode_type_t::PANASONIC_AC32: +#endif +#if SEND_RHOSS + case decode_type_t::RHOSS: +#endif +#if SEND_SAMSUNG_AC + case decode_type_t::SAMSUNG_AC: +#endif +#if SEND_SANYO_AC + case decode_type_t::SANYO_AC: +#endif +#if SEND_SANYO_AC88 + case decode_type_t::SANYO_AC88: +#endif +#if SEND_SHARP_AC + case decode_type_t::SHARP_AC: +#endif +#if SEND_TCL112AC + case decode_type_t::TCL112AC: +#endif +#if SEND_TECHNIBEL_AC + case decode_type_t::TECHNIBEL_AC: +#endif +#if SEND_TECO + case decode_type_t::TECO: +#endif +#if SEND_TEKNOPOINT + case decode_type_t::TEKNOPOINT: +#endif // SEND_TEKNOPOINT +#if SEND_TOSHIBA_AC + case decode_type_t::TOSHIBA_AC: +#endif +#if SEND_TRANSCOLD + case decode_type_t::TRANSCOLD: +#endif +#if SEND_TROTEC + case decode_type_t::TROTEC: +#endif +#if SEND_TROTEC_3550 + case decode_type_t::TROTEC_3550: +#endif // SEND_TROTEC_3550 +#if SEND_TRUMA + case decode_type_t::TRUMA: +#endif // SEND_TRUMA +#if SEND_VESTEL_AC + case decode_type_t::VESTEL_AC: +#endif +#if SEND_VOLTAS + case decode_type_t::VOLTAS: +#endif + case decode_type_t::WHIRLPOOL_AC: + return true; + default: + return false; + } +} + +#if SEND_AIRTON +/// Send an Airton 56-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRAirtonAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (ion/pollen/health/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. +/// @note -1 is Off, >= 0 is on. +void IRac::airton(IRAirtonAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool turbo, + const bool light, const bool econo, const bool filter, + const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + // No Quiet setting available. + ac->setLight(light); + ac->setHealth(filter); + ac->setTurbo(turbo); + ac->setEcono(econo); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Convert to a boolean. + ac->send(); +} +#endif // SEND_AIRTON + +#if SEND_AIRWELL +/// Send an Airwell A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRAirwellAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +void IRac::airwell(IRAirwellAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan) { + ac->begin(); + ac->setPowerToggle(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // No Swing setting available. + // No Quiet setting available. + // No Light setting available. + // No Filter setting available. + // No Turbo setting available. + // No Economy setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + ac->send(); +} +#endif // SEND_AIRWELL + +#if SEND_AMCOR +/// Send an Amcor A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRAmcorAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +void IRac::amcor(IRAmcorAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // No Swing setting available. + // No Quiet setting available. + // No Light setting available. + // No Filter setting available. + // No Turbo setting available. + // No Economy setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + ac->send(); +} +#endif // SEND_AMCOR + +#if SEND_ARGO +/// Send an Argo A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRArgoAC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] sensorTemp The room (iFeel) temperature sensor reading in degrees +/// Celsius. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] iFeel Whether to enable iFeel (remote temp) mode on the A/C unit. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] sleep Nr. of minutes for sleep mode. +/// @note -1 is Off, >= 0 is on. +void IRac::argo(IRArgoAC *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const float sensorTemp, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool iFeel, + const bool turbo, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(static_cast(round(degrees))); + if (sensorTemp != kNoTempValue) { + ac->setSensorTemp(static_cast(round(sensorTemp))); + } + ac->setiFeel(iFeel); + ac->setFan(ac->convertFan(fan)); + ac->setFlap(ac->convertSwingV(swingv)); + // No Quiet setting available. + // No Light setting available. + // No Filter setting available. + ac->setMax(turbo); + // No Economy setting available. + // No Clean setting available. + // No Beep setting available. + ac->setNight(sleep >= 0); // Convert to a boolean. + ac->send(); +} + +/// Send an Argo A/C WREM-3 AC **control** message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The set temperature setting in degrees Celsius. +/// @param[in] sensorTemp The room (iFeel) temperature sensor reading in degrees +/// Celsius. +/// @warning The @c sensorTemp param is assumed to be in 0..255 range (uint8_t) +/// The overflow is *not* checked, though. +/// @note The value is rounded to nearest integer, rounding halfway cases +/// away from zero. E.g. 1.5 [C] becomes 2 [C]. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] iFeel Whether to enable iFeel (remote temp) mode on the A/C unit. +/// @param[in] night Enable night mode (raises temp by +1*C after 1h). +/// @param[in] econo Enable eco mode (limits power consumed). +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] filter Enable filter mode +/// @param[in] light Enable device display/LEDs +void IRac::argoWrem3_ACCommand(IRArgoAC_WREM3 *ac, const bool on, + const stdAc::opmode_t mode, const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const bool iFeel, + const bool night, const bool econo, const bool turbo, const bool filter, + const bool light) { + ac->begin(); + ac->setMessageType(argoIrMessageType_t::AC_CONTROL); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + if (sensorTemp != kNoTempValue) { + ac->setSensorTemp(static_cast(round(sensorTemp))); + } + ac->setiFeel(iFeel); + ac->setFan(ac->convertFan(fan)); + ac->setFlap(ac->convertSwingV(swingv)); + ac->setNight(night); + ac->setEco(econo); + ac->setMax(turbo); + ac->setFilter(filter); + ac->setLight(light); + // No Clean setting available. + // No Beep setting available - always beeps in this mode :) + ac->send(); +} + +/// Send an Argo A/C WREM-3 iFeel (room temp) silent (no beep) report. +/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use. +/// @param[in] sensorTemp The room (iFeel) temperature setting +/// in degrees Celsius. +/// @warning The @c sensorTemp param is assumed to be in 0..255 range (uint8_t) +/// The overflow is *not* checked, though. +/// @note The value is rounded to nearest integer, rounding halfway cases +/// away from zero. E.g. 1.5 [C] becomes 2 [C]. +void IRac::argoWrem3_iFeelReport(IRArgoAC_WREM3 *ac, const float sensorTemp) { + ac->begin(); + ac->setMessageType(argoIrMessageType_t::IFEEL_TEMP_REPORT); + ac->setSensorTemp(static_cast(round(sensorTemp))); + ac->send(); +} + +/// Send an Argo A/C WREM-3 Config command. +/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use. +/// @param[in] param The parameter ID. +/// @param[in] value The parameter value. +/// @param[in] safe If true, will only allow setting the below parameters +/// in order to avoid accidentally setting a restricted +/// vendor-specific param and breaking the A/C device +/// @note Known parameters (P, where xx is the @c param) +/// P05 - Temperature Scale (0-Celsius, 1-Fahrenheit) +/// P06 - Transmission channel (0..3) +/// P12 - ECO mode power input limit (30..99, default: 75) +void IRac::argoWrem3_ConfigSet(IRArgoAC_WREM3 *ac, const uint8_t param, + const uint8_t value, bool safe /*= true*/) { + if (safe) { + switch (param) { + case 5: // temp. scale (note this is likely excess as not transmitted) + if (value > 1) { return; /* invalid */ } + break; + case 6: // channel (note this is likely excess as not transmitted) + if (value > 3) { return; /* invalid */ } + break; + case 12: // eco power limit + if (value < 30 || value > 99) { return; /* invalid */ } + break; + default: + return; /* invalid */ + } + } + ac->begin(); + ac->setMessageType(argoIrMessageType_t::CONFIG_PARAM_SET); + ac->setConfigEntry(param, value); + ac->send(); +} + +/// Send an Argo A/C WREM-3 Delay timer command. +/// @param[in, out] ac A Ptr to an IRArgoAC_WREM3 object to use. +/// @param[in] on Whether the unit is currently on. The timer, upon elapse +/// will toggle this state +/// @param[in] currentTime currentTime in minutes, starting from 00:00 +/// @note For timer mode, this value is not really used much so can be zero. +/// @param[in] delayMinutes Number of minutes after which the @c on state should +/// be toggled +/// @note Schedule timers are not exposed via this interface +void IRac::argoWrem3_SetTimer(IRArgoAC_WREM3 *ac, bool on, + const uint16_t currentTime, const uint16_t delayMinutes) { + ac->begin(); + ac->setMessageType(argoIrMessageType_t::TIMER_COMMAND); + ac->setPower(on); + ac->setTimerType(argoTimerType_t::DELAY_TIMER); + ac->setCurrentTimeMinutes(currentTime); + // Note: Day of week is not set (no need) + ac->setDelayTimerMinutes(delayMinutes); + ac->send(); +} +#endif // SEND_ARGO + +#if SEND_BOSCH144 +/// Send a Bosch144 A/C message with the supplied settings. +/// @note May result in multiple messages being sent. +/// @param[in, out] ac A Ptr to an IRBosch144AC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @note -1 is Off, >= 0 is on. +void IRac::bosch144(IRBosch144AC *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const bool quiet) { + ac->begin(); + ac->setPower(on); + if (!on) { + // after turn off AC no more commands should + // be accepted + ac->send(); + return; + } + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setMode(ac->convertMode(mode)); + ac->setQuiet(quiet); + ac->send(); // Send the state, which will also power on the unit. + // The following are all options/settings that create their own special + // messages. Often they only make sense to be sent after the unit is turned + // on. For instance, assuming a person wants to have the a/c on and in turbo + // mode. If we send the turbo message, it is ignored if the unit is off. + // Hence we send the special mode/setting messages after a normal message + // which will turn on the device. + // No Filter setting available. + // No Beep setting available. + // No Clock setting available. + // No Econo setting available. + // No Sleep setting available. +} +#endif // SEND_BOSCH144 + +#if SEND_CARRIER_AC64 +/// Send a Carrier 64-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRCarrierAc64 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] sleep Nr. of minutes for sleep mode. +/// @note -1 is Off, >= 0 is on. +void IRac::carrier64(IRCarrierAc64 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV((int8_t)swingv >= 0); + // No Quiet setting available. + // No Light setting available. + // No Filter setting available. + // No Turbo setting available. + // No Economy setting available. + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Convert to a boolean. + ac->send(); +} +#endif // SEND_CARRIER_AC64 + +#if SEND_COOLIX +/// Send a Coolix A/C message with the supplied settings. +/// @note May result in multiple messages being sent. +/// @param[in, out] ac A Ptr to an IRCoolixAC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] sensorTemp The room (iFeel) temperature sensor reading in degrees +/// Celsius. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] iFeel Whether to enable iFeel (remote temp) mode on the A/C unit. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] sleep Nr. of minutes for sleep mode. +/// @note -1 is Off, >= 0 is on. +void IRac::coolix(IRCoolixAC *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool iFeel, const bool turbo, const bool light, + const bool clean, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + if (!on) { + // after turn off AC no more commands should + // be accepted + ac->send(); + return; + } + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // No Filter setting available. + // No Beep setting available. + // No Clock setting available. + // No Econo setting available. + // No Quiet setting available. + if (sensorTemp != kNoTempValue) { + ac->setSensorTemp(static_cast(round(sensorTemp))); + } else { + ac->clearSensorTemp(); + } + ac->setZoneFollow(iFeel); + ac->send(); // Send the state, which will also power on the unit. + // The following are all options/settings that create their own special + // messages. Often they only make sense to be sent after the unit is turned + // on. For instance, assuming a person wants to have the a/c on and in turbo + // mode. If we send the turbo message, it is ignored if the unit is off. + // Hence we send the special mode/setting messages after a normal message + // which will turn on the device. + if (swingv != stdAc::swingv_t::kOff || swingh != stdAc::swingh_t::kOff) { + // Swing has a special command that needs to be sent independently. + ac->setSwing(); + ac->send(); + } + if (turbo) { + // Turbo has a special command that needs to be sent independently. + ac->setTurbo(); + ac->send(); + } + if (sleep >= 0) { + // Sleep has a special command that needs to be sent independently. + ac->setSleep(); + ac->send(); + } + if (light) { + // Light has a special command that needs to be sent independently. + ac->setLed(); + ac->send(); + } + if (clean) { + // Clean has a special command that needs to be sent independently. + ac->setClean(); + ac->send(); + } +} +#endif // SEND_COOLIX + +#if SEND_CORONA_AC +/// Send a Corona A/C message with the supplied settings. +/// @note May result in multiple messages being sent. +/// @param[in, out] ac A Ptr to an IRCoronaAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] econo Run the device in economical mode. +void IRac::corona(IRCoronaAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool econo) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVToggle(swingv != stdAc::swingv_t::kOff); + // No Quiet setting available. + // No Light setting available. + // No Filter setting available. + // No Turbo setting available. + ac->setEcono(econo); + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + ac->send(); +} +#endif // SEND_CARRIER_AC64 + +#if SEND_DAIKIN +/// Send a Daikin A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikinESP object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +void IRac::daikin(IRDaikinESP *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool clean) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical((int8_t)swingv >= 0); + ac->setSwingHorizontal((int8_t)swingh >= 0); + ac->setQuiet(quiet); + // No Light setting available. + // No Filter setting available. + ac->setPowerful(turbo); + ac->setEcono(econo); + ac->setMold(clean); + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_DAIKIN + +#if SEND_DAIKIN128 +/// Send a Daikin 128-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikin128 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::daikin128(IRDaikin128 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, const bool light, + const bool econo, const int16_t sleep, const int16_t clock) { + ac->begin(); + ac->setPowerToggle(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical((int8_t)swingv >= 0); + // No Horizontal Swing setting avaliable. + ac->setQuiet(quiet); + ac->setLightToggle(light ? kDaikin128BitWall : 0); + // No Filter setting available. + ac->setPowerful(turbo); + ac->setEcono(econo); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep > 0); + if (clock >= 0) ac->setClock(clock); + ac->send(); +} +#endif // SEND_DAIKIN128 + +#if SEND_DAIKIN152 +/// Send a Daikin 152-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikin152 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +void IRac::daikin152(IRDaikin152 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, const bool econo) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV((int8_t)swingv >= 0); + // No Horizontal Swing setting avaliable. + ac->setQuiet(quiet); + // No Light setting available. + // No Filter setting available. + ac->setPowerful(turbo); + ac->setEcono(econo); + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_DAIKIN152 + +#if SEND_DAIKIN160 +/// Send a Daikin 160-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikin160 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +void IRac::daikin160(IRDaikin160 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(ac->convertSwingV(swingv)); + ac->send(); +} +#endif // SEND_DAIKIN160 + +#if SEND_DAIKIN176 +/// Send a Daikin 176-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikin176 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingh The horizontal swing setting. +void IRac::daikin176(IRDaikin176 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingh_t swingh) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingHorizontal(ac->convertSwingH(swingh)); + ac->send(); +} +#endif // SEND_DAIKIN176 + +#if SEND_DAIKIN2 +/// Send a Daikin2 A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikin2 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] beep Enable/Disable beeps when receiving IR messages. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::daikin2(IRDaikin2 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool light, + const bool econo, const bool filter, const bool clean, + const bool beep, const int16_t sleep, const int16_t clock) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(ac->convertSwingV(swingv)); + ac->setSwingHorizontal(ac->convertSwingH(swingh)); + ac->setQuiet(quiet); + ac->setLight(light ? 1 : 3); // On/High is 1, Off is 3. + ac->setPowerful(turbo); + ac->setEcono(econo); + ac->setPurify(filter); + ac->setMold(clean); + ac->setClean(true); // Hardwire auto clean to be on per request (@sheppy99) + ac->setBeep(beep ? 2 : 3); // On/Loud is 2, Off is 3. + if (sleep > 0) ac->enableSleepTimer(sleep); + if (clock >= 0) ac->setCurrentTime(clock); + ac->send(); +} +#endif // SEND_DAIKIN2 + +#if SEND_DAIKIN216 +/// Send a Daikin 216-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikin216 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +void IRac::daikin216(IRDaikin216 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical((int8_t)swingv >= 0); + ac->setSwingHorizontal((int8_t)swingh >= 0); + ac->setQuiet(quiet); + ac->setPowerful(turbo); + ac->send(); +} +#endif // SEND_DAIKIN216 + +#if SEND_DAIKIN64 +/// Send a Daikin 64-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDaikin64 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::daikin64(IRDaikin64 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, + const int16_t sleep, const int16_t clock) { + ac->begin(); + ac->setPowerToggle(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical((int8_t)swingv >= 0); + ac->setTurbo(turbo); + ac->setQuiet(quiet); + ac->setSleep(sleep >= 0); + if (clock >= 0) ac->setClock(clock); + ac->send(); +} +#endif // SEND_DAIKIN64 + +#if SEND_DELONGHI_AC +/// Send a Delonghi A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDelonghiAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::delonghiac(IRDelonghiAc *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const bool turbo, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees, !celsius); + ac->setFan(ac->convertFan(fan)); + ac->setBoost(turbo); + ac->setSleep(sleep >= 0); + ac->send(); +} +#endif // SEND_DELONGHI_AC + +#if SEND_ECOCLIM +/// Send an EcoClim A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IREcoclimAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] sensorTemp The room (iFeel) temperature sensor reading in degrees +/// Celsius. +/// @param[in] fan The speed setting for the fan. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::ecoclim(IREcoclimAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const int16_t sleep, + const int16_t clock) { + ac->begin(); + ac->setPower(on); + uint8_t new_mode; + if (sleep >= 0) // EcoClim has a descrete Sleep operation mode, not a setting + new_mode = kEcoclimSleep; // Override the requested operating mode. + else + new_mode = ac->convertMode(mode); // Not Sleep, so use the supplied mode. + ac->setMode(new_mode); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + if (sensorTemp != kNoTempValue) { + ac->setSensorTemp(static_cast(round(sensorTemp))); + } else { + ac->setSensorTemp(degrees); //< Set to the desired temp + // until we can disable. + } + // No SwingV setting available + // No SwingH setting available + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Econo setting available. + // No Filter setting available. + // No Clean setting available + // No Beep setting available. + // No Sleep setting available. + if (clock >= 0) ac->setClock(clock); + ac->send(); +} +#endif // SEND_ECOCLIM + +#if SEND_ELECTRA_AC +/// Send an Electra A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRElectraAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] sensorTemp The room (iFeel) temperature sensor reading in degrees +/// Celsius. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] iFeel Whether to enable iFeel (remote temp) mode on the A/C unit. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] lighttoggle Should we toggle the LED/Display? +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +void IRac::electra(IRElectraAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, const bool iFeel, + const bool turbo, const bool lighttoggle, const bool clean) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + if (sensorTemp != kNoTempValue) { + ac->setSensorTemp(static_cast(round(sensorTemp))); + } + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); + // No Quiet setting available. + ac->setTurbo(turbo); + ac->setLightToggle(lighttoggle); + // No Econo setting available. + // No Filter setting available. + ac->setClean(clean); + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->setIFeel(iFeel); + ac->send(); +} +#endif // SEND_ELECTRA_AC + +#if SEND_FUJITSU_AC +/// Send a Fujitsu A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRFujitsuAC object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] sleep Nr. of minutes for sleep mode. <= 0 is Off, > 0 is on. +void IRac::fujitsu(IRFujitsuAC *ac, const fujitsu_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool filter, const bool clean, const int16_t sleep) { + ac->begin(); + ac->setModel(model); + if (on) { + // Do all special messages (except "Off") first, + // These need to be sent separately. + switch (ac->getModel()) { + // Some functions are only available on some models. + case fujitsu_ac_remote_model_t::ARREB1E: + if (turbo) { + ac->setCmd(kFujitsuAcCmdPowerful); + // Powerful is a separate command. + ac->send(); + } + if (econo) { + ac->setCmd(kFujitsuAcCmdEcono); + // Econo is a separate command. + ac->send(); + } + break; + default: + {}; + } + // Normal operation. + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees, celsius); + ac->setFanSpeed(ac->convertFan(fan)); + uint8_t swing = kFujitsuAcSwingOff; + if (swingv > stdAc::swingv_t::kOff) swing |= kFujitsuAcSwingVert; + if (swingh > stdAc::swingh_t::kOff) swing |= kFujitsuAcSwingHoriz; + ac->setSwing(swing); + if (quiet) ac->setFanSpeed(kFujitsuAcFanQuiet); + // No Light setting available. + ac->setFilter(filter); + ac->setClean(clean); + // No Beep setting available. + ac->setSleepTimer(sleep > 0 ? sleep : 0); + // No Sleep setting available. + // No Clock setting available. + ac->on(); // Ref: Issue #860 + } else { + // Off is special case/message. We don't need to send other messages. + ac->off(); + } + ac->send(); +} +#endif // SEND_FUJITSU_AC + +#if SEND_GOODWEATHER +/// Send a Goodweather A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRGoodweatherAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::goodweather(IRGoodweatherAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool light, + const int16_t sleep) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(swingv == stdAc::swingv_t::kOff ? kGoodweatherSwingOff + : kGoodweatherSwingSlow); + ac->setTurbo(turbo); + ac->setLight(light); + // No Clean setting available. + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + // No Horizontal Swing setting available. + // No Econo setting available. + // No Filter setting available. + // No Beep setting available. + // No Quiet setting available. + // No Clock setting available. + ac->setPower(on); + ac->send(); +} +#endif // SEND_GOODWEATHER + +#if SEND_GREE +/// Send a Gree A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRGreeAC object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] iFeel Whether to enable iFeel (remote temp) mode on the A/C unit. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Toggle the device's economical mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::gree(IRGreeAC *ac, const gree_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool iFeel, const bool turbo, const bool econo, + const bool light, const bool clean, const int16_t sleep) { + ac->begin(); + ac->setModel(model); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees, !celsius); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(swingv == stdAc::swingv_t::kAuto, // Set auto flag. + ac->convertSwingV(swingv)); + ac->setSwingHorizontal(ac->convertSwingH(swingh)); + ac->setIFeel(iFeel); + ac->setLight(light); + ac->setTurbo(turbo); + ac->setEcono(econo); + ac->setXFan(clean); + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + // No Econo setting available. + // No Filter setting available. + // No Beep setting available. + // No Quiet setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_GREE + +#if SEND_HAIER_AC +/// Send a Haier A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRGreeAC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::haier(IRHaierAC *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool filter, const int16_t sleep, const int16_t clock) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv)); + // No Horizontal Swing setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + ac->setHealth(filter); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + if (clock >= 0) ac->setCurrTime(clock); + if (on) + ac->setCommand(kHaierAcCmdOn); + else + ac->setCommand(kHaierAcCmdOff); + ac->send(); +} +#endif // SEND_HAIER_AC + +#if SEND_HAIER_AC160 +/// Send a Haier 160 bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHaierAC160 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] quiet Run the device in quiet mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the clean mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] prevlight Previous LED/Display mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::haier160(IRHaierAC160 *ac, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool quiet, const bool filter, + const bool clean, const bool light, const bool prevlight, + const int16_t sleep) { + ac->begin(); + // No Model setting available. + ac->setMode(ac->convertMode(mode)); + ac->setUseFahrenheit(!celsius); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv)); + // No Horizontal Swing setting available. + ac->setQuiet(quiet); + ac->setTurbo(turbo); + ac->setHealth(filter); + ac->setClean(clean); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + ac->setPower(on); + // Light needs to be sent last as the "button" value seems to control it. + ac->setLightToggle(light ^ prevlight); + ac->send(); +} +#endif // SEND_HAIER_AC160 + +#if SEND_HAIER_AC176 +/// Send a Haier 176 bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHaierAC176 object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] quiet Run the device in quiet mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::haier176(IRHaierAC176 *ac, const haier_ac176_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool turbo, const bool quiet, const bool filter, + const int16_t sleep) { + ac->begin(); + ac->setModel(model); + ac->setMode(ac->convertMode(mode)); + ac->setUseFahrenheit(!celsius); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv)); + ac->setSwingH(ac->convertSwingH(swingh)); + ac->setQuiet(quiet); + ac->setTurbo(turbo); + // No Light setting available. + ac->setHealth(filter); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + ac->setPower(on); + ac->send(); +} +#endif // SEND_HAIER_AC176 + +#if SEND_HAIER_AC_YRW02 +/// Send a Haier YRWO2 A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHaierACYRW02 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] quiet Run the device in quiet mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::haierYrwo2(IRHaierACYRW02 *ac, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool turbo, const bool quiet, const bool filter, + const int16_t sleep) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setUseFahrenheit(!celsius); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv)); + ac->setSwingH(ac->convertSwingH(swingh)); + ac->setQuiet(quiet); + ac->setTurbo(turbo); + // No Light setting available. + ac->setHealth(filter); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + ac->setPower(on); + ac->send(); +} +#endif // SEND_HAIER_AC_YRW02 + +#if SEND_HITACHI_AC +/// Send a Hitachi A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHitachiAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +void IRac::hitachi(IRHitachiAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(swingv != stdAc::swingv_t::kOff); + ac->setSwingHorizontal(swingh != stdAc::swingh_t::kOff); + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_HITACHI_AC + +#if SEND_HITACHI_AC1 +/// Send a Hitachi1 A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHitachiAc1 object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] power_toggle The power toggle setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] swing_toggle The swing_toggle setting. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @note The sleep mode used is the "Sleep 2" setting. +void IRac::hitachi1(IRHitachiAc1 *ac, const hitachi_ac1_remote_model_t model, + const bool on, const bool power_toggle, + const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool swing_toggle, const int16_t sleep) { + ac->begin(); + ac->setModel(model); + ac->setPower(on); + ac->setPowerToggle(power_toggle); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); + ac->setSwingToggle(swing_toggle); + ac->setSleep((sleep >= 0) ? kHitachiAc1Sleep2 : kHitachiAc1SleepOff); + // No Sleep setting available. + // No Swing(H) setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_HITACHI_AC1 + +#if SEND_HITACHI_AC264 +/// Send a Hitachi 264-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHitachiAc264 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +void IRac::hitachi264(IRHitachiAc264 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setPower(on); + // No Swing(V) setting available. + // No Swing(H) setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_HITACHI_AC264 + +#if SEND_HITACHI_AC296 +/// Send a Hitachi 296-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHitachiAc296 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +void IRac::hitachi296(IRHitachiAc296 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setPower(on); + // No Swing(V) setting available. + // No Swing(H) setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_HITACHI_AC296 + +#if SEND_HITACHI_AC344 +/// Send a Hitachi 344-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHitachiAc344 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +void IRac::hitachi344(IRHitachiAc344 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingH(ac->convertSwingH(swingh)); + ac->setPower(on); + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + + // SwingVToggle is special. Needs to be last method called. + ac->setSwingVToggle(swingv != stdAc::swingv_t::kOff); + ac->send(); +} +#endif // SEND_HITACHI_AC344 + +#if SEND_HITACHI_AC424 +/// Send a Hitachi 424-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRHitachiAc424 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +void IRac::hitachi424(IRHitachiAc424 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setPower(on); + // SwingVToggle is special. Needs to be last method called. + ac->setSwingVToggle(swingv != stdAc::swingv_t::kOff); + // No Swing(H) setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_HITACHI_AC424 + +#if SEND_KELON +/// Send a Kelon A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRKelonAc object to use. +/// @param[in] togglePower Whether to toggle the unit's power +/// @param[in] mode The operation mode setting. +/// @param[in] dryGrade The dehumidification intensity grade +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] toggleSwing Whether to toggle the swing setting +/// @param[in] superCool Run the device in Super cooling mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on +void IRac::kelon(IRKelonAc *ac, const bool togglePower, + const stdAc::opmode_t mode, const int8_t dryGrade, + const float degrees, const stdAc::fanspeed_t fan, + const bool toggleSwing, const bool superCool, + const int16_t sleep) { + ac->begin(); + ac->setMode(IRKelonAc::convertMode(mode)); + ac->setFan(IRKelonAc::convertFan(fan)); + ac->setTemp(static_cast(degrees)); + ac->setSleep(sleep >= 0); + ac->setSupercool(superCool); + ac->setDryGrade(dryGrade); + + ac->setTogglePower(togglePower); + ac->setToggleSwingVertical(toggleSwing); + + ac->send(); +} +#endif // SEND_KELON + +#if SEND_KELVINATOR +/// Send a Kelvinator A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRKelvinatorAC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. XFan, dry filters etc +void IRac::kelvinator(IRKelvinatorAC *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool light, + const bool filter, const bool clean) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan((uint8_t)fan); // No conversion needed. + ac->setSwingVertical(swingv == stdAc::swingv_t::kAuto, // Set auto flag. + ac->convertSwingV(swingv)); + ac->setSwingHorizontal((int8_t)swingh >= 0); + ac->setQuiet(quiet); + ac->setTurbo(turbo); + ac->setLight(light); + ac->setIonFilter(filter); + ac->setXFan(clean); + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_KELVINATOR + +#if SEND_LG +/// Send a LG A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRLgAc object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingv_prev The previous vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] light Turn on the LED/Display mode. +void IRac::lg(IRLgAc *ac, const lg_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev, + const stdAc::swingh_t swingh, const bool light) { + ac->begin(); + ac->setModel(model); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv_prev)); + ac->updateSwingPrev(); + ac->setSwingV(ac->convertSwingV(swingv)); + const uint8_t pos = ac->convertVaneSwingV(swingv); + for (uint8_t vane = 0; vane < kLgAcSwingVMaxVanes; vane++) + ac->setVaneSwingV(vane, pos); + // Toggle the swingv for LG6711A20083V models if we need to. + // i.e. Off to Not-Off, send a toggle. Not-Off to Off, send a toggle. + if ((model == lg_ac_remote_model_t::LG6711A20083V) && + ((swingv == stdAc::swingv_t::kOff) != + (swingv_prev == stdAc::swingv_t::kOff))) + ac->setSwingV(kLgAcSwingVToggle); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); + // No Quiet setting available. + // No Turbo setting available. + ac->setLight(light); + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_LG + +#if SEND_MIDEA +/// Send a Midea A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMideaAC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] sensorTemp The room (iFeel) temperature sensor reading +/// in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] iFeel Whether to enable iFeel (remote temp) mode on the A/C unit. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] quiet_prev The device's previous quiet/silent mode. +/// @param[in] turbo Toggle the device's turbo/powerful mode. +/// @param[in] econo Toggle the device's economical mode. +/// @param[in] light Toggle the LED/Display mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. XFan, dry filters etc +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @note On Danby A/C units, swingv controls the Ion Filter instead. +void IRac::midea(IRMideaAC *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool iFeel, const bool quiet, const bool quiet_prev, + const bool turbo, const bool econo, const bool light, + const bool clean, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setUseCelsius(celsius); + ac->setTemp(degrees, celsius); + if (sensorTemp != kNoTempValue) { + ac->setSensorTemp(sensorTemp, celsius); + } + ac->setEnableSensorTemp(iFeel); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVToggle(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + ac->setQuiet(quiet, quiet_prev); + ac->setTurboToggle(turbo); + ac->setEconoToggle(econo); + ac->setLightToggle(light); + // No Filter setting available. + ac->setCleanToggle(clean); + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + // No Clock setting available. + ac->send(); +} +#endif // SEND_MIDEA + +#if SEND_MIRAGE +/// Send a Mirage 120-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMitsubishiAC object to use. +/// @param[in] state The desired state to send. +void IRac::mirage(IRMirageAc *ac, const stdAc::state_t state) { + ac->begin(); + ac->fromCommon(state); + ac->send(); +} +#endif // SEND_MIRAGE + +#if SEND_MITSUBISHI_AC +/// Send a Mitsubishi A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMitsubishiAC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +/// @note Clock can only be set in 10 minute increments. i.e. % 10. +void IRac::mitsubishi(IRMitsubishiAC *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet, const int16_t clock) { + ac->begin(); + // Uncomment next line if you *really* need the weekly timer enabled via IRac. + // ac->setWeeklyTimerEnabled(true); // Weekly Timer is disabled by default. + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setVane(ac->convertSwingV(swingv)); + ac->setVaneLeft(ac->convertSwingV(swingv)); + ac->setWideVane(ac->convertSwingH(swingh)); + if (quiet) ac->setFan(kMitsubishiAcFanSilent); + ac->setISave10C(false); + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + if (clock >= 0) ac->setClock(clock / 10); // Clock is in 10 min increments. + ac->send(); +} +#endif // SEND_MITSUBISHI_AC + +#if SEND_MITSUBISHI112 +/// Send a Mitsubishi 112-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMitsubishi112 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +void IRac::mitsubishi112(IRMitsubishi112 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv)); + ac->setSwingH(ac->convertSwingH(swingh)); + ac->setQuiet(quiet); + // FIXME - Econo + // ac->setEcono(econo); + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_MITSUBISHI112 + +#if SEND_MITSUBISHI136 +/// Send a Mitsubishi 136-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMitsubishi136 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +void IRac::mitsubishi136(IRMitsubishi136 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool quiet) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv)); + // No Horizontal Swing setting available. + ac->setQuiet(quiet); + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_MITSUBISHI136 + +#if SEND_MITSUBISHIHEAVY +/// Send a Mitsubishi Heavy 88-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMitsubishiHeavy88Ac object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +void IRac::mitsubishiHeavy88(IRMitsubishiHeavy88Ac *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool turbo, const bool econo, + const bool clean) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(ac->convertSwingV(swingv)); + ac->setSwingHorizontal(ac->convertSwingH(swingh)); + // No Quiet setting available. + ac->setTurbo(turbo); + // No Light setting available. + ac->setEcono(econo); + // No Filter setting available. + ac->setClean(clean); + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} + +/// Send a Mitsubishi Heavy 152-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMitsubishiHeavy152Ac object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::mitsubishiHeavy152(IRMitsubishiHeavy152Ac *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, + const bool econo, const bool filter, + const bool clean, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(ac->convertSwingV(swingv)); + ac->setSwingHorizontal(ac->convertSwingH(swingh)); + ac->setSilent(quiet); + ac->setTurbo(turbo); + // No Light setting available. + ac->setEcono(econo); + ac->setClean(clean); + ac->setFilter(filter); + // No Beep setting available. + ac->setNight(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->send(); +} +#endif // SEND_MITSUBISHIHEAVY + +#if SEND_NEOCLIMA +/// Send a Neoclima A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRNeoclimaAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::neoclima(IRNeoclimaAc *ac, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool econo, const bool light, + const bool filter, const int16_t sleep) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees, celsius); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); + // No Quiet setting available. + ac->setTurbo(turbo); + ac->setLight(light); + ac->setEcono(econo); + ac->setIon(filter); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->setPower(on); + ac->send(); +} +#endif // SEND_NEOCLIMA + +#if SEND_PANASONIC_AC +/// Send a Panasonic A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRPanasonicAc object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::panasonic(IRPanasonicAc *ac, const panasonic_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool filter, + const int16_t clock) { + ac->begin(); + ac->setModel(model); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(ac->convertSwingV(swingv)); + ac->setSwingHorizontal(ac->convertSwingH(swingh)); + ac->setQuiet(quiet); + ac->setPowerful(turbo); + ac->setIon(filter); + // No Light setting available. + // No Econo setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + if (clock >= 0) ac->setClock(clock); + ac->send(); +} +#endif // SEND_PANASONIC_AC + +#if SEND_PANASONIC_AC32 +/// Send a Panasonic A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRPanasonicAc32 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +void IRac::panasonic32(IRPanasonicAc32 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh) { + ac->begin(); + ac->setPowerToggle(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(ac->convertSwingV(swingv)); + ac->setSwingHorizontal(swingh != stdAc::swingh_t::kOff); + // No Quiet setting available. + // No Turbo setting available. + // No Filter setting available. + // No Light setting available. + // No Econo setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_PANASONIC_AC32 + +#if SEND_SAMSUNG_AC +/// Send a Samsung A/C message with the supplied settings. +/// @note Multiple IR messages may be generated & sent. +/// @param[in, out] ac A Ptr to an IRSamsungAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Toggle the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] beep Toggle beep setting for receiving IR messages. +/// @param[in] sleep Nr. of minutes for sleep mode. <= 0 is Off, > 0 is on. +/// @param[in] prevpower The power setting from the previous A/C state. +/// @param[in] prevsleep Nr. of minutes for sleep from the previous A/C state. +/// @param[in] forceextended Do we force sending the special extended message? +void IRac::samsung(IRSamsungAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool light, + const bool filter, const bool clean, + const bool beep, const int16_t sleep, + const bool prevpower, const int16_t prevsleep, + const bool forceextended) { + ac->begin(); + ac->stateReset(forceextended || (sleep != prevsleep), prevpower); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(swingv != stdAc::swingv_t::kOff); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); + ac->setQuiet(quiet); + ac->setPowerful(turbo); // FYI, `setEcono(true)` will override this. + ac->setDisplay(light); + ac->setEcono(econo); + ac->setIon(filter); + ac->setClean(clean); // Toggle + ac->setBeep(beep); // Toggle + ac->setSleepTimer((sleep <= 0) ? 0 : sleep); + // No Clock setting available. + // Do setMode() again as it can affect fan speed. + ac->setMode(ac->convertMode(mode)); + ac->send(); +} +#endif // SEND_SAMSUNG_AC + +#if SEND_SANYO_AC +/// Send a Sanyo A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRSanyoAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] sensorTemp The room (iFeel) temperature sensor reading in degrees +/// Celsius. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] iFeel Whether to enable iFeel (remote temp) mode on the A/C unit. +/// @param[in] beep Enable/Disable beeps when receiving IR messages. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::sanyo(IRSanyoAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool iFeel, const bool beep, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + if (sensorTemp != kNoTempValue) { + ac->setSensorTemp(static_cast(round(sensorTemp))); + } else { + ac->setSensorTemp(degrees); // Set the sensor temp to the desired + // (normal) temp. + } + ac->setSensor(!iFeel); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(ac->convertSwingV(swingv)); + // No Horizontal swing setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Econo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + ac->setBeep(beep); + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->send(); +} +#endif // SEND_SANYO_AC + +#if SEND_SANYO_AC88 +/// Send a Sanyo 88-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRSanyoAc88 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::sanyo88(IRSanyoAc88 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool turbo, + const bool filter, const int16_t sleep, + const int16_t clock) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + // No Quiet setting available. + ac->setTurbo(turbo); + // No Econo setting available. + // No Light setting available. + ac->setFilter(filter); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + if (clock >= 0) ac->setClock(clock); + ac->send(); +} +#endif // SEND_SANYO_AC88 + +#if SEND_SHARP_AC +/// Send a Sharp A/C message with the supplied settings. +/// @note Multiple IR messages may be generated & sent. +/// @param[in, out] ac A Ptr to an IRSharpAc object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] prev_power The power setting from the previous A/C state. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingv_prev The previous vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +void IRac::sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, + const bool on, const bool prev_power, + const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingv_t swingv_prev, const bool turbo, + const bool light, const bool filter, const bool clean) { + ac->begin(); + ac->setModel(model); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan, model)); + if (swingv != swingv_prev) ac->setSwingV(ac->convertSwingV(swingv)); + // Econo deliberately not used as it cycles through 3 modes uncontrollably. + // ac->setEconoToggle(econo); + ac->setIon(filter); + // No Horizontal swing setting available. + // No Quiet setting available. + ac->setLightToggle(light); + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + // Do setMode() again as it can affect fan speed and temp. + ac->setMode(ac->convertMode(mode)); + // Clean after mode, as it can affect the mode, temp & fan speed. + if (clean) { + // A/C needs to be off before we can enter clean mode. + ac->setPower(false, prev_power); + ac->send(); + } + ac->setClean(clean); + ac->setPower(on, prev_power); + if (turbo) { + ac->send(); // Send the current state. + // Set up turbo mode as it needs to be sent after everything else. + ac->setTurbo(true); + } + ac->send(); +} +#endif // SEND_SHARP_AC + +#if SEND_TCL112AC +/// Send a TCL 112-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRTcl112Ac object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +void IRac::tcl112(IRTcl112Ac *ac, const tcl_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool light, + const bool econo, const bool filter) { + ac->begin(); + ac->setModel(model); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingVertical(ac->convertSwingV(swingv)); + ac->setSwingHorizontal(swingh != stdAc::swingh_t::kOff); + ac->setQuiet(quiet); + ac->setTurbo(turbo); + ac->setLight(light); + ac->setEcono(econo); + ac->setHealth(filter); + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_TCL112AC + +#if SEND_TECHNIBEL_AC +/// Send a Technibel A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRTechnibelAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::technibel(IRTechnibelAc *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees, !celsius); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->send(); +} +#endif // SEND_TECHNIBEL_AC + +#if SEND_TECO +/// Send a Teco A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRTecoAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::teco(IRTecoAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool light, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + // No Quiet setting available. + // No Turbo setting available. + ac->setLight(light); + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->send(); +} +#endif // SEND_TECO + +#if SEND_TOSHIBA_AC +/// Send a Toshiba A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRToshibaAC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (Pure/ion/pollen/etc) filter mode. +void IRac::toshiba(IRToshibaAC *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool econo, const bool filter) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // The API has no "step" option, so off is off, anything else is on. + ac->setSwing((swingv == stdAc::swingv_t::kOff) ? kToshibaAcSwingOff + : kToshibaAcSwingOn); + // No Horizontal swing setting available. + // No Quiet setting available. + ac->setTurbo(turbo); + ac->setEcono(econo); + // No Light setting available. + ac->setFilter(filter); + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + // Do this last because Toshiba A/C has an odd quirk with how power off works. + ac->setPower(on); + ac->send(); +} +#endif // SEND_TOSHIBA_AC + +#if SEND_TROTEC +/// Send a Trotec A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRTrotecESP object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::trotec(IRTrotecESP *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setSpeed(ac->convertFan(fan)); + // No Vertical swing setting available. + // No Horizontal swing setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->send(); +} +#endif // SEND_TROTEC + +#if SEND_TROTEC_3550 +/// Send a Trotec 3550 A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRTrotecESP object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +void IRac::trotec3550(IRTrotec3550 *ac, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees, celsius); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + // No Quiet setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_TROTEC_3550 + +#if SEND_TRUMA +/// Send a Truma A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRTrumaAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] quiet Run the device quietly if we can. +void IRac::truma(IRTrumaAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const bool quiet) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setQuiet(quiet); // Only available in Cool mode. + // No Vertical swing setting available. + // No Horizontal swing setting available. + // No Turbo setting available. + // No Light setting available. + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_TRUMA + +#if SEND_VESTEL_AC +/// Send a Vestel A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRVestelAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +/// @param[in] sendNormal Do we send a Normal settings message at all? +/// i.e In addition to the clock/time/timer message +void IRac::vestel(IRVestelAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool turbo, const bool filter, const int16_t sleep, + const int16_t clock, const bool sendNormal) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + // No Quiet setting available. + ac->setTurbo(turbo); + // No Light setting available. + ac->setIon(filter); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + if (sendNormal) ac->send(); // Send the normal message. + if (clock >= 0) { + ac->setTime(clock); + ac->send(); // Setting the clock requires a different "timer" message. + } +} +#endif // SEND_VESTEL_AC + +#if SEND_VOLTAS +/// Send a Voltas A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRVoltas object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::voltas(IRVoltas *ac, + const voltas_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool econo, const bool light, + const int16_t sleep) { + ac->begin(); + ac->setModel(model); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); + // No Quiet setting available. + ac->setTurbo(turbo); + ac->setEcono(econo); + ac->setLight(light); + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->send(); +} +#endif // SEND_VOLTAS + +#if SEND_WHIRLPOOL_AC +/// Send a Whirlpool A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRWhirlpoolAc object to use. +/// @param[in] model The A/C model to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::whirlpool(IRWhirlpoolAc *ac, const whirlpool_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool turbo, const bool light, + const int16_t sleep, const int16_t clock) { + ac->begin(); + ac->setModel(model); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + // No Quiet setting available. + ac->setSuper(turbo); + ac->setLight(light); + // No Filter setting available + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + if (clock >= 0) ac->setClock(clock); + ac->setPowerToggle(on); + ac->send(); +} +#endif // SEND_WHIRLPOOL_AC + +#if SEND_TRANSCOLD +/// Send a Transcold A/C message with the supplied settings. +/// @note May result in multiple messages being sent. +/// @param[in, out] ac A Ptr to an IRTranscoldAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @note -1 is Off, >= 0 is on. +void IRac::transcold(IRTranscoldAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh) { + ac->begin(); + ac->setPower(on); + if (!on) { + // after turn off AC no more commands should + // be accepted + ac->send(); + return; + } + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // No Filter setting available. + // No Beep setting available. + // No Clock setting available. + // No Econo setting available. + // No Quiet setting available. + if (swingv != stdAc::swingv_t::kOff || swingh != stdAc::swingh_t::kOff) { + // Swing has a special command that needs to be sent independently. + ac->setSwing(); + ac->send(); + } + + ac->send(); +} +#endif // SEND_TRANSCOLD + +#if SEND_RHOSS +/// Send an Rhoss A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRRhossAc object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swing The swing setting. +void IRac::rhoss(IRRhossAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swing) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setSwing(swing != stdAc::swingv_t::kOff); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // No Quiet setting available. + // No Light setting available. + // No Filter setting available. + // No Turbo setting available. + // No Economy setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + ac->send(); +} +#endif // SEND_RHOSS + +/// Create a new state base on the provided state that has been suitably fixed. +/// @note This is for use with Home Assistant, which requires mode to be off if +/// the power is off. +/// @param[in] state The state_t structure describing the desired a/c state. +/// @return A stdAc::state_t with the needed settings. +stdAc::state_t IRac::cleanState(const stdAc::state_t state) { + stdAc::state_t result = state; + // A hack for Home Assistant, it appears to need/want an Off opmode. + // So enforce the power is off if the mode is also off. + if (state.mode == stdAc::opmode_t::kOff) result.power = false; + return result; +} + +/// Create a new state base on desired & previous states but handle +/// any state changes for options that need to be toggled. +/// @param[in] desired The state_t structure describing the desired a/c state. +/// @param[in] prev A Ptr to the previous state_t structure. +/// @return A stdAc::state_t with the needed settings. +stdAc::state_t IRac::handleToggles(const stdAc::state_t desired, + const stdAc::state_t *prev) { + stdAc::state_t result = desired; + // If we've been given a previous state AND the it's the same A/C basically. + if (prev != NULL && desired.protocol == prev->protocol && + desired.model == prev->model) { + // Check if we have to handle toggle settings for specific A/C protocols. + switch (desired.protocol) { + case decode_type_t::COOLIX: + case decode_type_t::TRANSCOLD: + if ((desired.swingv == stdAc::swingv_t::kOff) ^ + (prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle. + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. + result.turbo = desired.turbo ^ prev->turbo; + result.light = desired.light ^ prev->light; + result.clean = desired.clean ^ prev->clean; + result.sleep = ((desired.sleep >= 0) ^ (prev->sleep >= 0)) ? 0 : -1; + break; + case decode_type_t::DAIKIN128: + result.power = desired.power ^ prev->power; + result.light = desired.light ^ prev->light; + break; + case decode_type_t::ELECTRA_AC: + result.light = desired.light ^ prev->light; + break; + case decode_type_t::FUJITSU_AC: + result.turbo = desired.turbo ^ prev->turbo; + result.econo = desired.econo ^ prev->econo; + break; + case decode_type_t::MIDEA: + result.turbo = desired.turbo ^ prev->turbo; + result.econo = desired.econo ^ prev->econo; + result.light = desired.light ^ prev->light; + result.clean = desired.clean ^ prev->clean; + // FALL THRU + case decode_type_t::CORONA_AC: + case decode_type_t::HITACHI_AC344: + case decode_type_t::HITACHI_AC424: + if ((desired.swingv == stdAc::swingv_t::kOff) ^ + (prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle. + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. + break; + case decode_type_t::SHARP_AC: + result.light = desired.light ^ prev->light; + if ((desired.swingv == stdAc::swingv_t::kOff) ^ + (prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle. + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. + break; + case decode_type_t::KELON: + if ((desired.swingv == stdAc::swingv_t::kOff) ^ + (prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle. + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. + // FALL-THRU + case decode_type_t::AIRWELL: + case decode_type_t::DAIKIN64: + case decode_type_t::PANASONIC_AC32: + case decode_type_t::WHIRLPOOL_AC: + result.power = desired.power ^ prev->power; + break; + case decode_type_t::MIRAGE: + if (desired.model == mirage_ac_remote_model_t::KKG29AC1) + result.light = desired.light ^ prev->light; + result.clean = desired.clean ^ prev->clean; + break; + case decode_type_t::PANASONIC_AC: + // CKP models use a power mode toggle. + if (desired.model == panasonic_ac_remote_model_t::kPanasonicCkp) + result.power = desired.power ^ prev->power; + break; + case decode_type_t::SAMSUNG_AC: + result.beep = desired.beep ^ prev->beep; + result.clean = desired.clean ^ prev->clean; + break; + default: + {}; + } + } + return result; +} + +/// Send A/C message for a given device using common A/C settings. +/// @param[in] vendor The vendor/protocol type. +/// @param[in] model The A/C model if applicable. +/// @param[in] power The power setting. +/// @param[in] mode The operation mode setting. +/// @note Changing mode from "Off" to something else does NOT turn on a device. +/// You need to use `power` for that. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] fan The speed setting for the fan. +/// @note The following are all "if supported" by the underlying A/C classes. +/// @param[in] swingv The vertical swing setting. +/// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] beep Enable/Disable beeps when receiving IR messages. +/// @param[in] sleep Nr. of minutes for sleep mode. +/// -1 is Off, >= 0 is on. Some devices it is the nr. of mins to run for. +/// Others it may be the time to enter/exit sleep mode. +/// i.e. Time in Nr. of mins since midnight. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +/// @return True, if accepted/converted/attempted etc. False, if unsupported. +bool IRac::sendAc(const decode_type_t vendor, const int16_t model, + const bool power, const stdAc::opmode_t mode, + const float degrees, const bool celsius, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool light, const bool filter, const bool clean, + const bool beep, const int16_t sleep, const int16_t clock) { + stdAc::state_t to_send; + initState(&to_send, vendor, model, power, mode, degrees, celsius, fan, swingv, + swingh, quiet, turbo, econo, light, filter, clean, beep, sleep, + clock); + return this->sendAc(to_send, &to_send); +} + +/// Send A/C message for a given device using state_t structures. +/// @param[in] desired The state_t structure describing the desired new ac state +/// @param[in] prev A Ptr to the state_t structure containing the previous state +/// @note Changing mode from "Off" to something else does NOT turn on a device. +/// You need to use `power` for that. +/// @return True, if accepted/converted/attempted etc. False, if unsupported. +bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { + // Convert the temp from Fahrenheit to Celsius if we are not in Celsius mode. + float degC __attribute__((unused)) = + desired.celsius ? desired.degrees : fahrenheitToCelsius(desired.degrees); + // Convert the sensorTemp from Fahrenheit to Celsius if we are not in Celsius + // mode. + float sensorTempC __attribute__((unused)) = + desired.sensorTemperature ? desired.sensorTemperature + : fahrenheitToCelsius(desired.sensorTemperature); + // special `state_t` that is required to be sent based on that. + stdAc::state_t send = this->handleToggles(this->cleanState(desired), prev); + // Some protocols expect a previous state for power. + // Construct a pointer-safe previous power state incase prev is NULL/NULLPTR. +#if (SEND_HITACHI_AC1 || SEND_SAMSUNG_AC || SEND_SHARP_AC) + const bool prev_power = (prev != NULL) ? prev->power : !send.power; + const int16_t prev_sleep = (prev != NULL) ? prev->sleep : -1; +#endif // (SEND_HITACHI_AC1 || SEND_SAMSUNG_AC || SEND_SHARP_AC) +#if (SEND_LG || SEND_SHARP_AC) + const stdAc::swingv_t prev_swingv = (prev != NULL) ? prev->swingv + : stdAc::swingv_t::kOff; +#endif // (SEND_LG || SEND_SHARP_AC) +#if (SEND_HAIER_AC160) + const bool prev_light = (prev != NULL) ? prev->light : !send.light; +#endif // (SEND_HAIER_AC160) +#if SEND_MIDEA + const bool prev_quiet = (prev != NULL) ? prev->quiet : !send.quiet; +#endif // SEND_MIDEA + // Per vendor settings & setup. + switch (send.protocol) { +#if SEND_AIRTON + case AIRTON: + { + IRAirtonAc ac(_pin, _inverted, _modulation); + airton(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.turbo, send.light, send.econo, send.filter, + send.sleep); + break; + } +#endif // SEND_AIRTON +#if SEND_AIRWELL + case AIRWELL: + { + IRAirwellAc ac(_pin, _inverted, _modulation); + airwell(&ac, send.power, send.mode, degC, send.fanspeed); + break; + } +#endif // SEND_AIRWELL +#if SEND_AMCOR + case AMCOR: + { + IRAmcorAc ac(_pin, _inverted, _modulation); + amcor(&ac, send.power, send.mode, degC, send.fanspeed); + break; + } +#endif // SEND_AMCOR +#if SEND_ARGO + case ARGO: + { + if (send.model == argo_ac_remote_model_t::SAC_WREM3) { + IRArgoAC_WREM3 ac(_pin, _inverted, _modulation); + switch (send.command) { + case stdAc::ac_command_t::kSensorTempReport: + argoWrem3_iFeelReport(&ac, sensorTempC); + break; + case stdAc::ac_command_t::kConfigCommand: + /// @warning: this is ABUSING current **common** parameters: + /// @c clock and @c sleep as config key and value + /// Hence, value pre-validation is performed (safe-mode) + /// to avoid accidental device misconfiguration + argoWrem3_ConfigSet(&ac, send.clock, send.sleep, true); + break; + case stdAc::ac_command_t::kTimerCommand: + argoWrem3_SetTimer(&ac, send.power, send.clock, send.sleep); + break; + case stdAc::ac_command_t::kControlCommand: + default: + argoWrem3_ACCommand(&ac, send.power, send.mode, degC, sensorTempC, + send.fanspeed, send.swingv, send.iFeel, send.quiet, send.econo, + send.turbo, send.filter, send.light); + break; + } + OUTPUT_DECODE_RESULTS_FOR_UT(ac); + } else { + IRArgoAC ac(_pin, _inverted, _modulation); + argo(&ac, send.power, send.mode, degC, sensorTempC, send.fanspeed, + send.swingv, send.iFeel, send.turbo, send.sleep); + OUTPUT_DECODE_RESULTS_FOR_UT(ac); + } + break; + } +#endif // SEND_ARGO +#if SEND_BOSCH144 + case BOSCH144: + { + IRBosch144AC ac(_pin, _inverted, _modulation); + bosch144(&ac, send.power, send.mode, degC, send.fanspeed, send.quiet); + break; + } +#endif // SEND_BOSCH144 +#if SEND_CARRIER_AC64 + case CARRIER_AC64: + { + IRCarrierAc64 ac(_pin, _inverted, _modulation); + carrier64(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.sleep); + break; + } +#endif // SEND_CARRIER_AC64 +#if SEND_COOLIX + case COOLIX: + { + IRCoolixAC ac(_pin, _inverted, _modulation); + coolix(&ac, send.power, send.mode, degC, sensorTempC, send.fanspeed, + send.swingv, send.swingh, send.iFeel, send.turbo, send.light, + send.clean, send.sleep); + break; + } +#endif // SEND_COOLIX +#if SEND_CORONA_AC + case CORONA_AC: + { + IRCoronaAc ac(_pin, _inverted, _modulation); + corona(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.econo); + break; + } +#endif // SEND_CORONA_AC +#if SEND_DAIKIN + case DAIKIN: + { + IRDaikinESP ac(_pin, _inverted, _modulation); + daikin(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh, send.quiet, send.turbo, send.econo, send.clean); + break; + } +#endif // SEND_DAIKIN +#if SEND_DAIKIN128 + case DAIKIN128: + { + IRDaikin128 ac(_pin, _inverted, _modulation); + daikin128(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.quiet, send.turbo, send.light, send.econo, send.sleep, + send.clock); + break; + } +#endif // SEND_DAIKIN2 +#if SEND_DAIKIN152 + case DAIKIN152: + { + IRDaikin152 ac(_pin, _inverted, _modulation); + daikin152(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.quiet, send.turbo, send.econo); + break; + } +#endif // SEND_DAIKIN152 +#if SEND_DAIKIN160 + case DAIKIN160: + { + IRDaikin160 ac(_pin, _inverted, _modulation); + daikin160(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv); + break; + } +#endif // SEND_DAIKIN160 +#if SEND_DAIKIN176 + case DAIKIN176: + { + IRDaikin176 ac(_pin, _inverted, _modulation); + daikin176(&ac, send.power, send.mode, degC, send.fanspeed, send.swingh); + break; + } +#endif // SEND_DAIKIN176 +#if SEND_DAIKIN2 + case DAIKIN2: + { + IRDaikin2 ac(_pin, _inverted, _modulation); + daikin2(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh, send.quiet, send.turbo, send.light, send.econo, + send.filter, send.clean, send.beep, send.sleep, send.clock); + break; + } +#endif // SEND_DAIKIN2 +#if SEND_DAIKIN216 + case DAIKIN216: + { + IRDaikin216 ac(_pin, _inverted, _modulation); + daikin216(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh, send.quiet, send.turbo); + break; + } +#endif // SEND_DAIKIN216 +#if SEND_DAIKIN64 + case DAIKIN64: + { + IRDaikin64 ac(_pin, _inverted, _modulation); + daikin64(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.quiet, send.turbo, send.sleep, send.clock); + break; + } +#endif // SEND_DAIKIN64 +#if SEND_DELONGHI_AC + case DELONGHI_AC: + { + IRDelonghiAc ac(_pin, _inverted, _modulation); + delonghiac(&ac, send.power, send.mode, send.celsius, degC, send.fanspeed, + send.turbo, send.sleep); + break; + } +#endif // SEND_DELONGHI_AC +#if SEND_ECOCLIM + case ECOCLIM: + { + IREcoclimAc ac(_pin, _inverted, _modulation); + ecoclim(&ac, send.power, send.mode, degC, sensorTempC, send.fanspeed, + send.iFeel, send.clock); + break; + } +#endif // SEND_ECOCLIM +#if SEND_ELECTRA_AC + case ELECTRA_AC: + { + IRElectraAc ac(_pin, _inverted, _modulation); + electra(&ac, send.power, send.mode, degC, sensorTempC, send.fanspeed, + send.swingv, send.swingh, send.iFeel, send.turbo, send.light, + send.clean); + break; + } +#endif // SEND_ELECTRA_AC +#if SEND_FUJITSU_AC + case FUJITSU_AC: + { + IRFujitsuAC ac(_pin, (fujitsu_ac_remote_model_t)send.model, _inverted, + _modulation); + fujitsu(&ac, (fujitsu_ac_remote_model_t)send.model, send.power, send.mode, + send.celsius, send.degrees, send.fanspeed, + send.swingv, send.swingh, send.quiet, + send.turbo, send.econo, send.filter, send.clean); + break; + } +#endif // SEND_FUJITSU_AC +#if SEND_GOODWEATHER + case GOODWEATHER: + { + IRGoodweatherAc ac(_pin, _inverted, _modulation); + goodweather(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.turbo, send.light, send.sleep); + break; + } +#endif // SEND_GOODWEATHER +#if SEND_GREE + case GREE: + { + IRGreeAC ac(_pin, (gree_ac_remote_model_t)send.model, _inverted, + _modulation); + gree(&ac, (gree_ac_remote_model_t)send.model, send.power, send.mode, + send.celsius, send.degrees, send.fanspeed, send.swingv, send.swingh, + send.turbo, send.econo, send.light, send.clean, send.sleep); + break; + } +#endif // SEND_GREE +#if SEND_HAIER_AC + case HAIER_AC: + { + IRHaierAC ac(_pin, _inverted, _modulation); + haier(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.filter, send.sleep, send.clock); + break; + } +#endif // SEND_HAIER_AC +#if SEND_HAIER_AC160 + case HAIER_AC160: + { + IRHaierAC160 ac(_pin, _inverted, _modulation); + haier160(&ac, send.power, send.mode, send.celsius, send.degrees, + send.fanspeed, send.swingv, send.turbo, send.filter, send.clean, + send.light, prev_light, send.sleep); + break; + } +#endif // SEND_HAIER_AC160 +#if SEND_HAIER_AC176 + case HAIER_AC176: + { + IRHaierAC176 ac(_pin, _inverted, _modulation); + haier176(&ac, (haier_ac176_remote_model_t)send.model, send.power, + send.mode, send.celsius, send.degrees, send.fanspeed, + send.swingv, send.swingh, send.turbo, send.filter, send.sleep); + break; + } +#endif // SEND_HAIER_AC176 +#if SEND_HAIER_AC_YRW02 + case HAIER_AC_YRW02: + { + IRHaierACYRW02 ac(_pin, _inverted, _modulation); + haierYrwo2(&ac, send.power, send.mode, send.celsius, send.degrees, + send.fanspeed, send.swingv, send.swingh, send.turbo, + send.filter, send.sleep); + break; + } +#endif // SEND_HAIER_AC_YRW02 +#if SEND_HITACHI_AC + case HITACHI_AC: + { + IRHitachiAc ac(_pin, _inverted, _modulation); + hitachi(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh); + break; + } +#endif // SEND_HITACHI_AC +#if SEND_HITACHI_AC1 + case HITACHI_AC1: + { + IRHitachiAc1 ac(_pin, _inverted, _modulation); + bool power_toggle = false; + bool swing_toggle = false; + if (prev != NULL) { + power_toggle = (send.power != prev->power); + swing_toggle = (send.swingv != prev->swingv) || + (send.swingh != prev->swingh); + } + hitachi1(&ac, (hitachi_ac1_remote_model_t)send.model, send.power, + power_toggle, send.mode, degC, send.fanspeed, send.swingv, + send.swingh, swing_toggle, send.sleep); + break; + } +#endif // SEND_HITACHI_AC1 +#if SEND_HITACHI_AC264 + case HITACHI_AC264: + { + IRHitachiAc264 ac(_pin, _inverted, _modulation); + hitachi264(&ac, send.power, send.mode, degC, send.fanspeed); + break; + } +#endif // SEND_HITACHI_AC264 +#if SEND_HITACHI_AC296 + case HITACHI_AC296: + { + IRHitachiAc296 ac(_pin, _inverted, _modulation); + hitachi296(&ac, send.power, send.mode, degC, send.fanspeed); + break; + } +#endif // SEND_HITACHI_AC296 +#if SEND_HITACHI_AC344 + case HITACHI_AC344: + { + IRHitachiAc344 ac(_pin, _inverted, _modulation); + hitachi344(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.swingh); + break; + } +#endif // SEND_HITACHI_AC344 +#if SEND_HITACHI_AC424 + case HITACHI_AC424: + { + IRHitachiAc424 ac(_pin, _inverted, _modulation); + hitachi424(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv); + break; + } +#endif // SEND_HITACHI_AC424 +#if SEND_KELON + case KELON: { + IRKelonAc ac(_pin, _inverted, _modulation); + kelon(&ac, send.power, send.mode, 0, send.degrees, send.fanspeed, + send.swingv != stdAc::swingv_t::kOff, send.turbo, send.sleep); + break; + } +#endif +#if SEND_KELVINATOR + case KELVINATOR: + { + IRKelvinatorAC ac(_pin, _inverted, _modulation); + kelvinator(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh, send.quiet, send.turbo, send.light, send.filter, + send.clean); + break; + } +#endif // SEND_KELVINATOR +#if SEND_LG + case LG: + case LG2: + { + IRLgAc ac(_pin, _inverted, _modulation); + lg(&ac, (lg_ac_remote_model_t)send.model, send.power, send.mode, + send.degrees, send.fanspeed, send.swingv, prev_swingv, send.swingh, + send.light); + break; + } +#endif // SEND_LG +#if SEND_MIDEA + case MIDEA: + { + IRMideaAC ac(_pin, _inverted, _modulation); + midea(&ac, send.power, send.mode, send.celsius, send.degrees, + send.sensorTemperature, send.fanspeed, send.swingv, send.iFeel, + send.quiet, prev_quiet, send.turbo, send.econo, send.light, + send.sleep); + break; + } +#endif // SEND_MIDEA +#if SEND_MIRAGE + case MIRAGE: + { + IRMirageAc ac(_pin, _inverted, _modulation); + mirage(&ac, send); + break; + } +#endif // SEND_MIRAGE +#if SEND_MITSUBISHI_AC + case MITSUBISHI_AC: + { + IRMitsubishiAC ac(_pin, _inverted, _modulation); + mitsubishi(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh, send.quiet, send.clock); + break; + } +#endif // SEND_MITSUBISHI_AC +#if SEND_MITSUBISHI112 + case MITSUBISHI112: + { + IRMitsubishi112 ac(_pin, _inverted, _modulation); + mitsubishi112(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.swingh, send.quiet); + break; + } +#endif // SEND_MITSUBISHI112 +#if SEND_MITSUBISHI136 + case MITSUBISHI136: + { + IRMitsubishi136 ac(_pin, _inverted, _modulation); + mitsubishi136(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.quiet); + break; + } +#endif // SEND_MITSUBISHI136 +#if SEND_MITSUBISHIHEAVY + case MITSUBISHI_HEAVY_88: + { + IRMitsubishiHeavy88Ac ac(_pin, _inverted, _modulation); + mitsubishiHeavy88(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.swingh, send.turbo, send.econo, + send.clean); + break; + } + case MITSUBISHI_HEAVY_152: + { + IRMitsubishiHeavy152Ac ac(_pin, _inverted, _modulation); + mitsubishiHeavy152(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.swingh, send.quiet, send.turbo, + send.econo, send.filter, send.clean, send.sleep); + break; + } +#endif // SEND_MITSUBISHIHEAVY +#if SEND_NEOCLIMA + case NEOCLIMA: + { + IRNeoclimaAc ac(_pin, _inverted, _modulation); + neoclima(&ac, send.power, send.mode, send.celsius, send.degrees, + send.fanspeed, send.swingv, send.swingh, send.turbo, + send.econo, send.light, send.filter, send.sleep); + break; + } +#endif // SEND_NEOCLIMA +#if SEND_PANASONIC_AC + case PANASONIC_AC: + { + IRPanasonicAc ac(_pin, _inverted, _modulation); + panasonic(&ac, (panasonic_ac_remote_model_t)send.model, send.power, + send.mode, degC, send.fanspeed, send.swingv, send.swingh, + send.quiet, send.turbo, send.clock); + break; + } +#endif // SEND_PANASONIC_AC +#if SEND_PANASONIC_AC32 + case PANASONIC_AC32: + { + IRPanasonicAc32 ac(_pin, _inverted, _modulation); + panasonic32(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.swingh); + break; + } +#endif // SEND_PANASONIC_AC32 +#if SEND_RHOSS + case RHOSS: + { + IRRhossAc ac(_pin, _inverted, _modulation); + rhoss(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv); + break; + } +#endif // SEND_RHOSS +#if SEND_SAMSUNG_AC + case SAMSUNG_AC: + { + IRSamsungAc ac(_pin, _inverted, _modulation); + samsung(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh, send.quiet, send.turbo, send.econo, send.light, + send.filter, send.clean, send.beep, send.sleep, + prev_power, prev_sleep); + break; + } +#endif // SEND_SAMSUNG_AC +#if SEND_SANYO_AC + case SANYO_AC: + { + IRSanyoAc ac(_pin, _inverted, _modulation); + sanyo(&ac, send.power, send.mode, degC, sensorTempC, send.fanspeed, + send.swingv, send.iFeel, send.beep, send.sleep); + break; + } +#endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + case SANYO_AC88: + { + IRSanyoAc88 ac(_pin, _inverted, _modulation); + sanyo88(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.turbo, send.filter, send.sleep, send.clock); + break; + } +#endif // SEND_SANYO_AC88 +#if SEND_SHARP_AC + case SHARP_AC: + { + IRSharpAc ac(_pin, _inverted, _modulation); + sharp(&ac, (sharp_ac_remote_model_t)send.model, send.power, prev_power, + send.mode, degC, send.fanspeed, send.swingv, prev_swingv, + send.turbo, send.light, send.filter, send.clean); + break; + } +#endif // SEND_SHARP_AC +#if (SEND_TCL112AC || SEND_TEKNOPOINT) + case TCL112AC: + case TEKNOPOINT: + { + IRTcl112Ac ac(_pin, _inverted, _modulation); + tcl_ac_remote_model_t model = (tcl_ac_remote_model_t)send.model; + if (send.protocol == decode_type_t::TEKNOPOINT) + model = tcl_ac_remote_model_t::GZ055BE1; + tcl112(&ac, model, send.power, send.mode, + degC, send.fanspeed, send.swingv, send.swingh, send.quiet, + send.turbo, send.light, send.econo, send.filter); + break; + } +#endif // (SEND_TCL112AC || SEND_TEKNOPOINT) +#if SEND_TECHNIBEL_AC + case TECHNIBEL_AC: + { + IRTechnibelAc ac(_pin, _inverted, _modulation); + technibel(&ac, send.power, send.mode, send.celsius, send.degrees, + send.fanspeed, send.swingv, send.sleep); + break; + } +#endif // SEND_TECHNIBEL_AC +#if SEND_TECO + case TECO: + { + IRTecoAc ac(_pin, _inverted, _modulation); + teco(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.light, send.sleep); + break; + } +#endif // SEND_TECO +#if SEND_TOSHIBA_AC + case TOSHIBA_AC: + { + IRToshibaAC ac(_pin, _inverted, _modulation); + toshiba(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.turbo, send.econo, send.filter); + break; + } +#endif // SEND_TOSHIBA_AC +#if SEND_TROTEC + case TROTEC: + { + IRTrotecESP ac(_pin, _inverted, _modulation); + trotec(&ac, send.power, send.mode, degC, send.fanspeed, send.sleep); + break; + } +#endif // SEND_TROTEC +#if SEND_TROTEC_3550 + case TROTEC_3550: + { + IRTrotec3550 ac(_pin, _inverted, _modulation); + trotec3550(&ac, send.power, send.mode, send.celsius, send.degrees, + send.fanspeed, send.swingv); + break; + } +#endif // SEND_TROTEC_3550 +#if SEND_TRUMA + case TRUMA: + { + IRTrumaAc ac(_pin, _inverted, _modulation); + truma(&ac, send.power, send.mode, degC, send.fanspeed, send.quiet); + break; + } +#endif // SEND_TRUMA +#if SEND_VESTEL_AC + case VESTEL_AC: + { + IRVestelAc ac(_pin, _inverted, _modulation); + vestel(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.turbo, send.filter, send.sleep, send.clock); + break; + } +#endif // SEND_VESTEL_AC +#if SEND_VOLTAS + case VOLTAS: + { + IRVoltas ac(_pin, _inverted, _modulation); + voltas(&ac, (voltas_ac_remote_model_t)send.model, send.power, send.mode, + degC, send.fanspeed, send.swingv, send.swingh, send.turbo, + send.econo, send.light, send.sleep); + break; + } +#endif // SEND_VOLTAS +#if SEND_WHIRLPOOL_AC + case WHIRLPOOL_AC: + { + IRWhirlpoolAc ac(_pin, _inverted, _modulation); + whirlpool(&ac, (whirlpool_ac_remote_model_t)send.model, send.power, + send.mode, degC, send.fanspeed, send.swingv, send.turbo, + send.light, send.sleep, send.clock); + break; + } +#endif // SEND_WHIRLPOOL_AC +#if SEND_TRANSCOLD + case TRANSCOLD: + { + IRTranscoldAc ac(_pin, _inverted, _modulation); + transcold(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.swingh); + break; + } +#endif // SEND_TRANSCOLD_AC + default: + return false; // Fail, didn't match anything. + } + return true; // Success. +} // NOLINT(readability/fn_size) + +/// Update the previous state to the current one. +void IRac::markAsSent(void) { + _prev = next; +} + +/// Send an A/C message based soley on our internal state. +/// @return True, if accepted/converted/attempted. False, if unsupported. +bool IRac::sendAc(void) { + bool success = this->sendAc(next, &_prev); + if (success) this->markAsSent(); + return success; +} + +/// Compare two AirCon states. +/// @note The comparison excludes the clock. +/// @param a A state_t to be compared. +/// @param b A state_t to be compared. +/// @return True if they differ, False if they don't. +bool IRac::cmpStates(const stdAc::state_t a, const stdAc::state_t b) { + return a.protocol != b.protocol || a.model != b.model || a.power != b.power || + a.mode != b.mode || a.degrees != b.degrees || a.celsius != b.celsius || + a.fanspeed != b.fanspeed || a.swingv != b.swingv || + a.swingh != b.swingh || a.quiet != b.quiet || a.turbo != b.turbo || + a.econo != b.econo || a.light != b.light || a.filter != b.filter || + a.clean != b.clean || a.beep != b.beep || a.sleep != b.sleep || + a.command != b.command || a.sensorTemperature != b.sensorTemperature || + a.iFeel != b.iFeel; +} + +/// Check if the internal state has changed from what was previously sent. +/// @note The comparison excludes the clock. +/// @return True if it has changed, False if not. +bool IRac::hasStateChanged(void) { return cmpStates(next, _prev); } + +/// Convert the supplied str into the appropriate enum. +/// @param[in] str A Ptr to a C-style string to be converted. +/// @param[in] def The enum to return if no conversion was possible. +/// @return The equivalent enum. +stdAc::ac_command_t IRac::strToCommandType(const char *str, + const stdAc::ac_command_t def) { + if (!STRCASECMP(str, kControlCommandStr)) + return stdAc::ac_command_t::kControlCommand; + else if (!STRCASECMP(str, kIFeelReportStr) || + !STRCASECMP(str, kIFeelStr)) + return stdAc::ac_command_t::kSensorTempReport; + else if (!STRCASECMP(str, kSetTimerCommandStr) || + !STRCASECMP(str, kTimerStr)) + return stdAc::ac_command_t::kTimerCommand; + else if (!STRCASECMP(str, kConfigCommandStr)) + return stdAc::ac_command_t::kConfigCommand; + else + return def; +} + +/// Convert the supplied str into the appropriate enum. +/// @param[in] str A Ptr to a C-style string to be converted. +/// @param[in] def The enum to return if no conversion was possible. +/// @return The equivalent enum. +stdAc::opmode_t IRac::strToOpmode(const char *str, + const stdAc::opmode_t def) { + if (!STRCASECMP(str, kAutoStr) || + !STRCASECMP(str, kAutomaticStr)) + return stdAc::opmode_t::kAuto; + else if (!STRCASECMP(str, kOffStr) || + !STRCASECMP(str, kStopStr)) + return stdAc::opmode_t::kOff; + else if (!STRCASECMP(str, kCoolStr) || + !STRCASECMP(str, kCoolingStr)) + return stdAc::opmode_t::kCool; + else if (!STRCASECMP(str, kHeatStr) || + !STRCASECMP(str, kHeatingStr)) + return stdAc::opmode_t::kHeat; + else if (!STRCASECMP(str, kDryStr) || + !STRCASECMP(str, kDryingStr) || + !STRCASECMP(str, kDehumidifyStr)) + return stdAc::opmode_t::kDry; + 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)) + return stdAc::opmode_t::kFan; + else + return def; +} + +/// Convert the supplied str into the appropriate enum. +/// @param[in] str A Ptr to a C-style string to be converted. +/// @param[in] def The enum to return if no conversion was possible. +/// @return The equivalent enum. +stdAc::fanspeed_t IRac::strToFanspeed(const char *str, + const stdAc::fanspeed_t def) { + if (!STRCASECMP(str, kAutoStr) || + !STRCASECMP(str, kAutomaticStr)) + return stdAc::fanspeed_t::kAuto; + else if (!STRCASECMP(str, kMinStr) || + !STRCASECMP(str, kMinimumStr) || + !STRCASECMP(str, kLowestStr)) + return stdAc::fanspeed_t::kMin; + else if (!STRCASECMP(str, kLowStr) || + !STRCASECMP(str, kLoStr)) + return stdAc::fanspeed_t::kLow; + else if (!STRCASECMP(str, kMedStr) || + !STRCASECMP(str, kMediumStr) || + !STRCASECMP(str, kMidStr)) + return stdAc::fanspeed_t::kMedium; + else if (!STRCASECMP(str, kHighStr) || + !STRCASECMP(str, kHiStr)) + return stdAc::fanspeed_t::kHigh; + else if (!STRCASECMP(str, kMaxStr) || + !STRCASECMP(str, kMaximumStr) || + !STRCASECMP(str, kHighestStr)) + return stdAc::fanspeed_t::kMax; + else if (!STRCASECMP(str, kMedHighStr)) + return stdAc::fanspeed_t::kMediumHigh; + else + return def; +} + +/// Convert the supplied str into the appropriate enum. +/// @param[in] str A Ptr to a C-style string to be converted. +/// @param[in] def The enum to return if no conversion was possible. +/// @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)) + return stdAc::swingv_t::kAuto; + 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)) + return stdAc::swingv_t::kLowest; + 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)) + return stdAc::swingv_t::kMiddle; + else if (!STRCASECMP(str, kUpperMiddleStr)) + return stdAc::swingv_t::kUpperMiddle; + 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)) + return stdAc::swingv_t::kHighest; + else + return def; +} + +/// Convert the supplied str into the appropriate enum. +/// @param[in] str A Ptr to a C-style string to be converted. +/// @param[in] def The enum to return if no conversion was possible. +/// @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)) + return stdAc::swingh_t::kAuto; + else if (!STRCASECMP(str, kOffStr) || + !STRCASECMP(str, kStopStr)) + return stdAc::swingh_t::kOff; + else if (!STRCASECMP(str, kLeftMaxNoSpaceStr) || // "LeftMax" + !STRCASECMP(str, kLeftMaxStr) || // "Left Max" + !STRCASECMP(str, kMaxLeftNoSpaceStr) || // "MaxLeft" + !STRCASECMP(str, kMaxLeftStr)) // "Max Left" + return stdAc::swingh_t::kLeftMax; + 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)) + return stdAc::swingh_t::kMiddle; + else if (!STRCASECMP(str, kRightStr)) + return stdAc::swingh_t::kRight; + else if (!STRCASECMP(str, kRightMaxNoSpaceStr) || // "RightMax" + !STRCASECMP(str, kRightMaxStr) || // "Right Max" + !STRCASECMP(str, kMaxRightNoSpaceStr) || // "MaxRight" + !STRCASECMP(str, kMaxRightStr)) // "Max Right" + return stdAc::swingh_t::kRightMax; + else if (!STRCASECMP(str, kWideStr)) + return stdAc::swingh_t::kWide; + else + return def; +} + +/// Convert the supplied str into the appropriate enum. +/// @note Assumes str is the model code or an integer >= 1. +/// @param[in] str A Ptr to a C-style string to be converted. +/// @param[in] def The enum to return if no conversion was possible. +/// @return The equivalent enum. +/// @note After adding a new model you should update modelToStr() too. +int16_t IRac::strToModel(const char *str, const int16_t def) { + // Gree + if (!STRCASECMP(str, kYaw1fStr)) { + return gree_ac_remote_model_t::YAW1F; + } else if (!STRCASECMP(str, kYbofbStr)) { + return gree_ac_remote_model_t::YBOFB; + } else if (!STRCASECMP(str, kYx1fsfStr)) { + return gree_ac_remote_model_t::YX1FSF; + // Haier models + } else if (!STRCASECMP(str, kV9014557AStr)) { + return haier_ac176_remote_model_t::V9014557_A; + } else if (!STRCASECMP(str, kV9014557BStr)) { + return haier_ac176_remote_model_t::V9014557_B; + // HitachiAc1 models + } else if (!STRCASECMP(str, kRlt0541htaaStr)) { + return hitachi_ac1_remote_model_t::R_LT0541_HTA_A; + } else if (!STRCASECMP(str, kRlt0541htabStr)) { + return hitachi_ac1_remote_model_t::R_LT0541_HTA_B; + // Fujitsu A/C models + } else if (!STRCASECMP(str, kArrah2eStr)) { + return fujitsu_ac_remote_model_t::ARRAH2E; + } else if (!STRCASECMP(str, kArdb1Str)) { + return fujitsu_ac_remote_model_t::ARDB1; + } else if (!STRCASECMP(str, kArreb1eStr)) { + return fujitsu_ac_remote_model_t::ARREB1E; + } else if (!STRCASECMP(str, kArjw2Str)) { + return fujitsu_ac_remote_model_t::ARJW2; + } else if (!STRCASECMP(str, kArry4Str)) { + return fujitsu_ac_remote_model_t::ARRY4; + } else if (!STRCASECMP(str, kArrew4eStr)) { + return fujitsu_ac_remote_model_t::ARREW4E; + // LG A/C models + } else if (!STRCASECMP(str, kGe6711ar2853mStr)) { + return lg_ac_remote_model_t::GE6711AR2853M; + } else if (!STRCASECMP(str, kAkb75215403Str)) { + return lg_ac_remote_model_t::AKB75215403; + } else if (!STRCASECMP(str, kAkb74955603Str)) { + return lg_ac_remote_model_t::AKB74955603; + } else if (!STRCASECMP(str, kAkb73757604Str)) { + return lg_ac_remote_model_t::AKB73757604; + } else if (!STRCASECMP(str, kLg6711a20083vStr)) { + return lg_ac_remote_model_t::LG6711A20083V; + // Panasonic A/C families + } else if (!STRCASECMP(str, kLkeStr) || + !STRCASECMP(str, kPanasonicLkeStr)) { + return panasonic_ac_remote_model_t::kPanasonicLke; + } else if (!STRCASECMP(str, kNkeStr) || + !STRCASECMP(str, kPanasonicNkeStr)) { + return panasonic_ac_remote_model_t::kPanasonicNke; + } else if (!STRCASECMP(str, kDkeStr) || + !STRCASECMP(str, kPanasonicDkeStr) || + !STRCASECMP(str, kPkrStr) || + !STRCASECMP(str, kPanasonicPkrStr)) { + return panasonic_ac_remote_model_t::kPanasonicDke; + } else if (!STRCASECMP(str, kJkeStr) || + !STRCASECMP(str, kPanasonicJkeStr)) { + return panasonic_ac_remote_model_t::kPanasonicJke; + } else if (!STRCASECMP(str, kCkpStr) || + !STRCASECMP(str, kPanasonicCkpStr)) { + return panasonic_ac_remote_model_t::kPanasonicCkp; + } else if (!STRCASECMP(str, kRkrStr) || + !STRCASECMP(str, kPanasonicRkrStr)) { + return panasonic_ac_remote_model_t::kPanasonicRkr; + // Sharp A/C Models + } else if (!STRCASECMP(str, kA907Str)) { + return sharp_ac_remote_model_t::A907; + } else if (!STRCASECMP(str, kA705Str)) { + return sharp_ac_remote_model_t::A705; + } else if (!STRCASECMP(str, kA903Str)) { + return sharp_ac_remote_model_t::A903; + // TCL A/C Models + } else if (!STRCASECMP(str, kTac09chsdStr)) { + return tcl_ac_remote_model_t::TAC09CHSD; + } else if (!STRCASECMP(str, kGz055be1Str)) { + return tcl_ac_remote_model_t::GZ055BE1; + // Voltas A/C models + } else if (!STRCASECMP(str, k122lzfStr)) { + return voltas_ac_remote_model_t::kVoltas122LZF; + // Whirlpool A/C models + } else if (!STRCASECMP(str, kDg11j13aStr) || + !STRCASECMP(str, kDg11j104Str)) { + return whirlpool_ac_remote_model_t::DG11J13A; + } else if (!STRCASECMP(str, kDg11j191Str)) { + return whirlpool_ac_remote_model_t::DG11J191; + // Argo A/C models + } else if (!STRCASECMP(str, kArgoWrem2Str)) { + return argo_ac_remote_model_t::SAC_WREM2; + } else if (!STRCASECMP(str, kArgoWrem3Str)) { + return argo_ac_remote_model_t::SAC_WREM3; + } else { + int16_t number = atoi(str); + if (number > 0) + return number; + else + return def; + } +} + +/// Convert the supplied str into the appropriate boolean value. +/// @param[in] str A Ptr to a C-style string to be converted. +/// @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, k1Str) || + !STRCASECMP(str, kYesStr) || + !STRCASECMP(str, kTrueStr)) + return true; + else if (!STRCASECMP(str, kOffStr) || + !STRCASECMP(str, k0Str) || + !STRCASECMP(str, kNoStr) || + !STRCASECMP(str, kFalseStr)) + return false; + else + return def; +} + +/// Convert the supplied boolean into the appropriate String. +/// @param[in] value The boolean value to be converted. +/// @return The equivalent String for the locale. +String IRac::boolToString(const bool value) { + return value ? kOnStr : kOffStr; +} + +/// Convert the supplied operation mode into the appropriate String. +/// @param[in] cmdType The enum to be converted. +/// @return The equivalent String for the locale. +String IRac::commandTypeToString(const stdAc::ac_command_t cmdType) { + switch (cmdType) { + case stdAc::ac_command_t::kControlCommand: return kControlCommandStr; + case stdAc::ac_command_t::kSensorTempReport: return kIFeelReportStr; + case stdAc::ac_command_t::kTimerCommand: return kSetTimerCommandStr; + case stdAc::ac_command_t::kConfigCommand: return kConfigCommandStr; + default: return kUnknownStr; + } +} + +/// Convert the supplied operation mode into the appropriate String. +/// @param[in] mode The enum to be converted. +/// @param[in] ha A flag to indicate we want GoogleHome/HomeAssistant output. +/// @return The equivalent String for the locale. +String IRac::opmodeToString(const stdAc::opmode_t mode, const bool ha) { + switch (mode) { + case stdAc::opmode_t::kOff: return kOffStr; + case stdAc::opmode_t::kAuto: return kAutoStr; + case stdAc::opmode_t::kCool: return kCoolStr; + case stdAc::opmode_t::kHeat: return kHeatStr; + case stdAc::opmode_t::kDry: return kDryStr; + case stdAc::opmode_t::kFan: return ha ? kFan_OnlyStr : kFanStr; + default: return kUnknownStr; + } +} + +/// Convert the supplied fan speed enum into the appropriate String. +/// @param[in] speed The enum to be converted. +/// @return The equivalent String for the locale. +String IRac::fanspeedToString(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kAuto: return kAutoStr; + case stdAc::fanspeed_t::kMax: return kMaxStr; + case stdAc::fanspeed_t::kHigh: return kHighStr; + case stdAc::fanspeed_t::kMedium: return kMediumStr; + case stdAc::fanspeed_t::kMediumHigh: return kMedHighStr; + case stdAc::fanspeed_t::kLow: return kLowStr; + case stdAc::fanspeed_t::kMin: return kMinStr; + default: return kUnknownStr; + } +} + +/// Convert the supplied enum into the appropriate String. +/// @param[in] swingv The enum to be converted. +/// @return The equivalent String for the locale. +String IRac::swingvToString(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kOff: return kOffStr; + case stdAc::swingv_t::kAuto: return kAutoStr; + case stdAc::swingv_t::kHighest: return kHighestStr; + case stdAc::swingv_t::kHigh: return kHighStr; + case stdAc::swingv_t::kMiddle: return kMiddleStr; + case stdAc::swingv_t::kUpperMiddle: return kUpperMiddleStr; + case stdAc::swingv_t::kLow: return kLowStr; + case stdAc::swingv_t::kLowest: return kLowestStr; + default: return kUnknownStr; + } +} + +/// Convert the supplied enum into the appropriate String. +/// @param[in] swingh The enum to be converted. +/// @return The equivalent String for the locale. +String IRac::swinghToString(const stdAc::swingh_t swingh) { + switch (swingh) { + case stdAc::swingh_t::kOff: return kOffStr; + case stdAc::swingh_t::kAuto: return kAutoStr; + case stdAc::swingh_t::kLeftMax: return kLeftMaxStr; + case stdAc::swingh_t::kLeft: return kLeftStr; + case stdAc::swingh_t::kMiddle: return kMiddleStr; + case stdAc::swingh_t::kRight: return kRightStr; + case stdAc::swingh_t::kRightMax: return kRightMaxStr; + case stdAc::swingh_t::kWide: return kWideStr; + default: return kUnknownStr; + } +} + +namespace IRAcUtils { + /// Display the human readable state of an A/C message if we can. + /// @param[in] result A Ptr to the captured `decode_results` that contains an + /// A/C mesg. + /// @return A string with the human description of the A/C message. + /// An empty string if we can't. + String resultAcToString(const decode_results * const result) { + switch (result->decode_type) { +#if DECODE_AIRTON + case decode_type_t::AIRTON: { + IRAirtonAc ac(kGpioUnused); + ac.setRaw(result->value); // AIRTON uses value instead of state. + return ac.toString(); + } +#endif // DECODE_AIRTON +#if DECODE_AIRWELL + case decode_type_t::AIRWELL: { + IRAirwellAc ac(kGpioUnused); + ac.setRaw(result->value); // AIRWELL uses value instead of state. + return ac.toString(); + } +#endif // DECODE_AIRWELL +#if DECODE_AMCOR + case decode_type_t::AMCOR: { + IRAmcorAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_AMCOR +#if DECODE_ARGO + case decode_type_t::ARGO: { + if (IRArgoAC_WREM3::isValidWrem3Message(result->state, result->bits, + true)) { + IRArgoAC_WREM3 ac(kGpioUnused); + ac.setRaw(result->state, result->bits / 8); + return ac.toString(); + } + IRArgoAC ac(kGpioUnused); + ac.setRaw(result->state, result->bits / 8); + return ac.toString(); + } +#endif // DECODE_ARGO +#if DECODE_BOSCH144 + case decode_type_t::BOSCH144: { + IRBosch144AC ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_BOSCH144 +#if DECODE_CARRIER_AC64 + case decode_type_t::CARRIER_AC64: { + IRCarrierAc64 ac(kGpioUnused); + ac.setRaw(result->value); // CARRIER_AC64 uses value instead of state. + return ac.toString(); + } +#endif // DECODE_CARRIER_AC64 +#if DECODE_COOLIX + case decode_type_t::COOLIX: { + IRCoolixAC ac(kGpioUnused); + ac.on(); + ac.setRaw(result->value); // Coolix uses value instead of state. + return ac.toString(); + } +#endif // DECODE_COOLIX +#if DECODE_CORONA_AC + case decode_type_t::CORONA_AC: { + IRCoronaAc ac(kGpioUnused); + ac.setRaw(result->state, result->bits / 8); + return ac.toString(); + } +#endif // DECODE_CORONA_AC +#if DECODE_DAIKIN + case decode_type_t::DAIKIN: { + IRDaikinESP ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_DAIKIN +#if DECODE_DAIKIN128 + case decode_type_t::DAIKIN128: { + IRDaikin128 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_DAIKIN128 +#if DECODE_DAIKIN152 + case decode_type_t::DAIKIN152: { + IRDaikin152 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_DAIKIN152 +#if DECODE_DAIKIN160 + case decode_type_t::DAIKIN160: { + IRDaikin160 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_DAIKIN160 +#if DECODE_DAIKIN176 + case decode_type_t::DAIKIN176: { + IRDaikin176 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_DAIKIN160 +#if DECODE_DAIKIN2 + case decode_type_t::DAIKIN2: { + IRDaikin2 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_DAIKIN2 +#if DECODE_DAIKIN216 + case decode_type_t::DAIKIN216: { + IRDaikin216 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_DAIKIN216 +#if DECODE_DAIKIN64 + case decode_type_t::DAIKIN64: { + IRDaikin64 ac(kGpioUnused); + ac.setRaw(result->value); // Daikin64 uses value instead of state. + return ac.toString(); + } +#endif // DECODE_DAIKIN64 +#if DECODE_DELONGHI_AC + case decode_type_t::DELONGHI_AC: { + IRDelonghiAc ac(kGpioUnused); + ac.setRaw(result->value); // DelonghiAc uses value instead of state. + return ac.toString(); + } +#endif // DECODE_DELONGHI_AC +#if DECODE_ECOCLIM + case decode_type_t::ECOCLIM: { + if (result->bits == kEcoclimBits) { + IREcoclimAc ac(kGpioUnused); + ac.setRaw(result->value); // EcoClim uses value instead of state. + return ac.toString(); + } + return ""; + } +#endif // DECODE_ECOCLIM +#if DECODE_ELECTRA_AC + case decode_type_t::ELECTRA_AC: { + IRElectraAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_ELECTRA_AC +#if DECODE_FUJITSU_AC + case decode_type_t::FUJITSU_AC: { + IRFujitsuAC ac(kGpioUnused); + ac.setRaw(result->state, result->bits / 8); + return ac.toString(); + } +#endif // DECODE_FUJITSU_AC +#if DECODE_GOODWEATHER + case decode_type_t::GOODWEATHER: { + IRGoodweatherAc ac(kGpioUnused); + ac.setRaw(result->value); // Goodweather uses value instead of state. + return ac.toString(); + } +#endif // DECODE_GOODWEATHER +#if DECODE_GREE + case decode_type_t::GREE: { + IRGreeAC ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_GREE +#if DECODE_HAIER_AC + case decode_type_t::HAIER_AC: { + IRHaierAC ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HAIER_AC +#if DECODE_HAIER_AC160 + case decode_type_t::HAIER_AC160: { + IRHaierAC160 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HAIER_AC160 +#if DECODE_HAIER_AC176 + case decode_type_t::HAIER_AC176: { + IRHaierAC176 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HAIER_AC176 +#if DECODE_HAIER_AC_YRW02 + case decode_type_t::HAIER_AC_YRW02: { + IRHaierACYRW02 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HAIER_AC_YRW02 +#if DECODE_HITACHI_AC + case decode_type_t::HITACHI_AC: { + IRHitachiAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HITACHI_AC +#if DECODE_HITACHI_AC1 + case decode_type_t::HITACHI_AC1: { + IRHitachiAc1 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HITACHI_AC1 +#if DECODE_HITACHI_AC264 + case decode_type_t::HITACHI_AC264: { + IRHitachiAc264 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HITACHI_AC264 +#if DECODE_HITACHI_AC296 + case decode_type_t::HITACHI_AC296: { + IRHitachiAc296 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HITACHI_AC296 +#if DECODE_HITACHI_AC344 + case decode_type_t::HITACHI_AC344: { + IRHitachiAc344 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HITACHI_AC344 +#if DECODE_HITACHI_AC424 + case decode_type_t::HITACHI_AC424: { + IRHitachiAc424 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HITACHI_AC424 +#if DECODE_KELON + case decode_type_t::KELON: { + IRKelonAc ac(kGpioUnused); + ac.setRaw(result->value); + return ac.toString(); + } +#endif // DECODE_KELON +#if DECODE_KELVINATOR + case decode_type_t::KELVINATOR: { + IRKelvinatorAC ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_KELVINATOR +#if DECODE_LG + case decode_type_t::LG: + case decode_type_t::LG2: { + IRLgAc ac(kGpioUnused); + ac.setRaw(result->value, result->decode_type); // Use value, not state. + return ac.isValidLgAc() ? ac.toString() : ""; + } +#endif // DECODE_LG +#if DECODE_MIDEA + case decode_type_t::MIDEA: { + IRMideaAC ac(kGpioUnused); + ac.setRaw(result->value); // Midea uses value instead of state. + return ac.toString(); + } +#endif // DECODE_MIDEA +#if DECODE_MIRAGE + case decode_type_t::MIRAGE: { + IRMirageAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_MIRAGE +#if DECODE_MITSUBISHI_AC + case decode_type_t::MITSUBISHI_AC: { + IRMitsubishiAC ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_MITSUBISHI_AC +#if DECODE_MITSUBISHI112 + case decode_type_t::MITSUBISHI112: { + IRMitsubishi112 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_MITSUBISHI112 +#if DECODE_MITSUBISHI136 + case decode_type_t::MITSUBISHI136: { + IRMitsubishi136 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_MITSUBISHI136 +#if DECODE_MITSUBISHIHEAVY + case decode_type_t::MITSUBISHI_HEAVY_88: { + IRMitsubishiHeavy88Ac ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } + case decode_type_t::MITSUBISHI_HEAVY_152: { + IRMitsubishiHeavy152Ac ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_MITSUBISHIHEAVY +#if DECODE_NEOCLIMA + case decode_type_t::NEOCLIMA: { + IRNeoclimaAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_NEOCLIMA +#if DECODE_PANASONIC_AC + case decode_type_t::PANASONIC_AC: { + if (result->bits > kPanasonicAcShortBits) { + IRPanasonicAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } + return ""; + } +#endif // DECODE_PANASONIC_AC +#if DECODE_PANASONIC_AC32 + case decode_type_t::PANASONIC_AC32: { + if (result->bits >= kPanasonicAc32Bits) { + IRPanasonicAc32 ac(kGpioUnused); + ac.setRaw(result->value); // Uses value instead of state. + return ac.toString(); + } + return ""; + } +#endif // DECODE_PANASONIC_AC +#if DECODE_RHOSS + case decode_type_t::RHOSS: { + IRRhossAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_RHOSS +#if DECODE_SAMSUNG_AC + case decode_type_t::SAMSUNG_AC: { + IRSamsungAc ac(kGpioUnused); + ac.setRaw(result->state, result->bits / 8); + return ac.toString(); + } +#endif // DECODE_SAMSUNG_AC +#if DECODE_SANYO_AC + case decode_type_t::SANYO_AC: { + IRSanyoAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_SANYO_AC +#if DECODE_SANYO_AC88 + case decode_type_t::SANYO_AC88: { + IRSanyoAc88 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_SANYO_AC88 +#if DECODE_SHARP_AC + case decode_type_t::SHARP_AC: { + IRSharpAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_SHARP_AC +#if (DECODE_TCL112AC || DECODE_TEKNOPOINT) + case decode_type_t::TCL112AC: + case decode_type_t::TEKNOPOINT: { + IRTcl112Ac ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // (DECODE_TCL112AC || DECODE_TEKNOPOINT) +#if DECODE_TECHNIBEL_AC + case decode_type_t::TECHNIBEL_AC: { + IRTechnibelAc ac(kGpioUnused); + ac.setRaw(result->value); // TechnibelAc uses value instead of state. + return ac.toString(); + } +#endif // DECODE_TECHNIBEL_AC +#if DECODE_TECO + case decode_type_t::TECO: { + IRTecoAc ac(kGpioUnused); + ac.setRaw(result->value); // Like Coolix, use value instead of state. + return ac.toString(); + } +#endif // DECODE_TECO +#if DECODE_TOSHIBA_AC + case decode_type_t::TOSHIBA_AC: { + IRToshibaAC ac(kGpioUnused); + ac.setRaw(result->state, result->bits / 8); + return ac.toString(); + } +#endif // DECODE_TOSHIBA_AC +#if DECODE_TRANSCOLD + case decode_type_t::TRANSCOLD: { + IRTranscoldAc ac(kGpioUnused); + ac.on(); + ac.setRaw(result->value); // TRANSCOLD uses value instead of state. + return ac.toString(); + } +#endif // DECODE_TRANSCOLD +#if DECODE_TROTEC + case decode_type_t::TROTEC: { + IRTrotecESP ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_TROTEC +#if DECODE_TROTEC_3550 + case decode_type_t::TROTEC_3550: { + IRTrotec3550 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_TROTEC_3550 +#if DECODE_TRUMA + case decode_type_t::TRUMA: { + IRTrumaAc ac(kGpioUnused); + ac.setRaw(result->value); // Truma uses value instead of state. + return ac.toString(); + } +#endif // DECODE_TRUMA +#if DECODE_VESTEL_AC + case decode_type_t::VESTEL_AC: { + IRVestelAc ac(kGpioUnused); + ac.setRaw(result->value); // Like Coolix, use value instead of state. + return ac.toString(); + } +#endif // DECODE_VESTEL_AC +#if DECODE_VOLTAS + case decode_type_t::VOLTAS: { + IRVoltas ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_VOLTAS +#if DECODE_WHIRLPOOL_AC + case decode_type_t::WHIRLPOOL_AC: { + IRWhirlpoolAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_WHIRLPOOL_AC + default: + return ""; + } + } + + /// Convert a valid IR A/C remote message that we understand enough into a + /// Common A/C state. + /// @param[in] decode A PTR to a successful raw IR decode object. + /// @param[in] result A PTR to a state structure to store the result in. + /// @param[in] prev A PTR to a state structure which has the prev. state. + /// @return A boolean indicating success or failure. + bool decodeToState(const decode_results *decode, stdAc::state_t *result, + const stdAc::state_t *prev +/// @cond IGNORE +// *prev flagged as "unused" due to potential compiler warning when some +// protocols that use it are disabled. It really is used. + __attribute__((unused)) +/// @endcond + ) { + if (decode == NULL || result == NULL) return false; // Safety check. + switch (decode->decode_type) { +#if DECODE_AIRTON + case decode_type_t::AIRTON: { + IRAirtonAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_AIRTON +#if DECODE_AIRWELL + case decode_type_t::AIRWELL: { + IRAirwellAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_AIRWELL +#if DECODE_AMCOR + case decode_type_t::AMCOR: { + IRAmcorAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_AMCOR +#if DECODE_ARGO + case decode_type_t::ARGO: { + const uint16_t length = decode->bits / 8; + if (IRArgoAC_WREM3::isValidWrem3Message(decode->state, + decode->bits, true)) { + IRArgoAC_WREM3 ac(kGpioUnused); + ac.setRaw(decode->state, length); + *result = ac.toCommon(); + } else { + IRArgoAC ac(kGpioUnused); + switch (length) { + case kArgoStateLength: + case kArgoShortStateLength: + ac.setRaw(decode->state, length); + *result = ac.toCommon(); + break; + default: + return false; + } + } + break; + } +#endif // DECODE_ARGO +#if DECODE_BOSCH144 + case decode_type_t::BOSCH144: { + IRBosch144AC ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_BOSCH144 +#if DECODE_CARRIER_AC64 + case decode_type_t::CARRIER_AC64: { + IRCarrierAc64 ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_CARRIER_AC64 +#if DECODE_COOLIX + case decode_type_t::COOLIX: { + IRCoolixAC ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_COOLIX +#if DECODE_CORONA_AC + case decode_type_t::CORONA_AC: { + IRCoronaAc ac(kGpioUnused); + ac.setRaw(decode->state, decode->bits / 8); + *result = ac.toCommon(); + break; + } +#endif // DECODE_CARRIER_AC64 +#if DECODE_DAIKIN + case decode_type_t::DAIKIN: { + IRDaikinESP ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_DAIKIN +#if DECODE_DAIKIN128 + case decode_type_t::DAIKIN128: { + IRDaikin128 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_DAIKIN128 +#if DECODE_DAIKIN152 + case decode_type_t::DAIKIN152: { + IRDaikin152 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_DAIKIN152 +#if DECODE_DAIKIN160 + case decode_type_t::DAIKIN160: { + IRDaikin160 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_DAIKIN160 +#if DECODE_DAIKIN176 + case decode_type_t::DAIKIN176: { + IRDaikin176 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_DAIKIN160 +#if DECODE_DAIKIN2 + case decode_type_t::DAIKIN2: { + IRDaikin2 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_DAIKIN2 +#if DECODE_DAIKIN216 + case decode_type_t::DAIKIN216: { + IRDaikin216 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_DAIKIN216 +#if DECODE_DAIKIN64 + case decode_type_t::DAIKIN64: { + IRDaikin64 ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_DAIKIN64 +#if DECODE_DELONGHI_AC + case decode_type_t::DELONGHI_AC: { + IRDelonghiAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_DELONGHI_AC +#if DECODE_ECOCLIM + case decode_type_t::ECOCLIM: { + if (decode->bits == kEcoclimBits) { + IREcoclimAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + } else { + return false; + } + break; + } +#endif // DECODE_ECOCLIM +#if DECODE_ELECTRA_AC + case decode_type_t::ELECTRA_AC: { + IRElectraAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_ELECTRA_AC +#if DECODE_FUJITSU_AC + case decode_type_t::FUJITSU_AC: { + IRFujitsuAC ac(kGpioUnused); + ac.setRaw(decode->state, decode->bits / 8); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_FUJITSU_AC +#if DECODE_GOODWEATHER + case decode_type_t::GOODWEATHER: { + IRGoodweatherAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_GOODWEATHER +#if DECODE_GREE + case decode_type_t::GREE: { + IRGreeAC ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_GREE +#if DECODE_HAIER_AC + case decode_type_t::HAIER_AC: { + IRHaierAC ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HAIER_AC +#if DECODE_HAIER_AC160 + case decode_type_t::HAIER_AC160: { + IRHaierAC160 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_HAIER_AC160 +#if DECODE_HAIER_AC176 + case decode_type_t::HAIER_AC176: { + IRHaierAC176 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HAIER_AC176 +#if DECODE_HAIER_AC_YRW02 + case decode_type_t::HAIER_AC_YRW02: { + IRHaierACYRW02 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HAIER_AC_YRW02 +#if (DECODE_HITACHI_AC || DECODE_HITACHI_AC2) + case decode_type_t::HITACHI_AC: { + IRHitachiAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // (DECODE_HITACHI_AC || DECODE_HITACHI_AC2) +#if DECODE_HITACHI_AC1 + case decode_type_t::HITACHI_AC1: { + IRHitachiAc1 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HITACHI_AC1 +#if DECODE_HITACHI_AC264 + case decode_type_t::HITACHI_AC264: { + IRHitachiAc264 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HITACHI_AC264 +#if DECODE_HITACHI_AC296 + case decode_type_t::HITACHI_AC296: { + IRHitachiAc296 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HITACHI_AC296 +#if DECODE_HITACHI_AC344 + case decode_type_t::HITACHI_AC344: { + IRHitachiAc344 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HITACHI_AC344 +#if DECODE_HITACHI_AC424 + case decode_type_t::HITACHI_AC424: { + IRHitachiAc424 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HITACHI_AC424 +#if DECODE_KELON + case decode_type_t::KELON: { + IRKelonAc ac(kGpioUnused); + ac.setRaw(decode->value); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_KELON +#if DECODE_KELVINATOR + case decode_type_t::KELVINATOR: { + IRKelvinatorAC ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_KELVINATOR +#if DECODE_LG + case decode_type_t::LG: + case decode_type_t::LG2: { + IRLgAc ac(kGpioUnused); + ac.setRaw(decode->value, decode->decode_type); // Use value, not state. + if (!ac.isValidLgAc()) return false; + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_LG +#if DECODE_MIDEA + case decode_type_t::MIDEA: { + IRMideaAC ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_MIDEA +#if DECODE_MIRAGE + case decode_type_t::MIRAGE: { + IRMirageAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_MIRAGE +#if DECODE_MITSUBISHI_AC + case decode_type_t::MITSUBISHI_AC: { + IRMitsubishiAC ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_MITSUBISHI_AC +#if DECODE_MITSUBISHI112 + case decode_type_t::MITSUBISHI112: { + IRMitsubishi112 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_MITSUBISHI112 +#if DECODE_MITSUBISHI136 + case decode_type_t::MITSUBISHI136: { + IRMitsubishi136 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_MITSUBISHI136 +#if DECODE_MITSUBISHIHEAVY + case decode_type_t::MITSUBISHI_HEAVY_88: { + IRMitsubishiHeavy88Ac ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } + case decode_type_t::MITSUBISHI_HEAVY_152: { + IRMitsubishiHeavy152Ac ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_MITSUBISHIHEAVY +#if DECODE_NEOCLIMA + case decode_type_t::NEOCLIMA: { + IRNeoclimaAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_NEOCLIMA +#if DECODE_PANASONIC_AC + case decode_type_t::PANASONIC_AC: { + IRPanasonicAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_PANASONIC_AC +#if DECODE_PANASONIC_AC32 + case decode_type_t::PANASONIC_AC32: { + IRPanasonicAc32 ac(kGpioUnused); + if (decode->bits >= kPanasonicAc32Bits) { + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(prev); + } else { + return false; + } + break; + } +#endif // DECODE_PANASONIC_AC32 +#if DECODE_RHOSS + case decode_type_t::RHOSS: { + IRRhossAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_RHOSS +#if DECODE_SAMSUNG_AC + case decode_type_t::SAMSUNG_AC: { + IRSamsungAc ac(kGpioUnused); + ac.setRaw(decode->state, decode->bits / 8); + *result = ac.toCommon(); + break; + } +#endif // DECODE_SAMSUNG_AC +#if DECODE_SANYO_AC + case decode_type_t::SANYO_AC: { + IRSanyoAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_SANYO_AC +#if DECODE_SANYO_AC88 + case decode_type_t::SANYO_AC88: { + IRSanyoAc88 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_SANYO_AC88 +#if DECODE_SHARP_AC + case decode_type_t::SHARP_AC: { + IRSharpAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_SHARP_AC +#if (DECODE_TCL112AC || DECODE_TEKNOPOINT) + case decode_type_t::TCL112AC: + case decode_type_t::TEKNOPOINT: { + IRTcl112Ac ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(prev); + // Teknopoint uses the TCL protocol, but with a different model number. + // Just keep the original protocol type ... for now. + result->protocol = decode->decode_type; + break; + } +#endif // (DECODE_TCL112AC || DECODE_TEKNOPOINT) +#if DECODE_TECHNIBEL_AC + case decode_type_t::TECHNIBEL_AC: { + IRTechnibelAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_TECHNIBEL_AC +#if DECODE_TECO + case decode_type_t::TECO: { + IRTecoAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_TECO +#if DECODE_TOSHIBA_AC + case decode_type_t::TOSHIBA_AC: { + IRToshibaAC ac(kGpioUnused); + ac.setRaw(decode->state, decode->bits / 8); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_TOSHIBA_AC +#if DECODE_TRANSCOLD + case decode_type_t::TRANSCOLD: { + IRTranscoldAc ac(kGpioUnused); + ac.setRaw(decode->value); // TRANSCOLD Uses value instead of state. + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_TRANSCOLD +#if DECODE_TROTEC + case decode_type_t::TROTEC: { + IRTrotecESP ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_TROTEC +#if DECODE_TROTEC_3550 + case decode_type_t::TROTEC_3550: { + IRTrotec3550 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_TROTEC_3550 +#if DECODE_TRUMA + case decode_type_t::TRUMA: { + IRTrumaAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_TRUMA +#if DECODE_VESTEL_AC + case decode_type_t::VESTEL_AC: { + IRVestelAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_VESTEL_AC +#if DECODE_VOLTAS + case decode_type_t::VOLTAS: { + IRVoltas ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_VOLTAS +#if DECODE_WHIRLPOOL_AC + case decode_type_t::WHIRLPOOL_AC: { + IRWhirlpoolAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_WHIRLPOOL_AC + default: + return false; + } + return true; + } +} // namespace IRAcUtils diff --git a/src/libraries/IRremoteESP8266/src/IRac.h b/src/libraries/IRremoteESP8266/src/IRac.h new file mode 100644 index 000000000..cf57350d1 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRac.h @@ -0,0 +1,580 @@ +#ifndef IRAC_H_ +#define IRAC_H_ + +// Copyright 2019 David Conran + +#ifndef UNIT_TEST +#include "String.h" +#else +#include +#endif +#include "IRremoteESP8266.h" +#include "ir_Airton.h" +#include "ir_Airwell.h" +#include "ir_Amcor.h" +#include "ir_Argo.h" +#include "ir_Bosch.h" +#include "ir_Carrier.h" +#include "ir_Coolix.h" +#include "ir_Corona.h" +#include "ir_Daikin.h" +#include "ir_Delonghi.h" +#include "ir_Fujitsu.h" +#include "ir_Ecoclim.h" +#include "ir_Electra.h" +#include "ir_Goodweather.h" +#include "ir_Gree.h" +#include "ir_Haier.h" +#include "ir_Hitachi.h" +#include "ir_Kelon.h" +#include "ir_Kelvinator.h" +#include "ir_LG.h" +#include "ir_Midea.h" +#include "ir_Mirage.h" +#include "ir_Mitsubishi.h" +#include "ir_MitsubishiHeavy.h" +#include "ir_Neoclima.h" +#include "ir_Panasonic.h" +#include "ir_Rhoss.h" +#include "ir_Samsung.h" +#include "ir_Sanyo.h" +#include "ir_Sharp.h" +#include "ir_Tcl.h" +#include "ir_Technibel.h" +#include "ir_Teco.h" +#include "ir_Toshiba.h" +#include "ir_Transcold.h" +#include "ir_Trotec.h" +#include "ir_Truma.h" +#include "ir_Vestel.h" +#include "ir_Voltas.h" +#include "ir_Whirlpool.h" + +// Constants +const int8_t kGpioUnused = -1; ///< A placeholder for not using an actual GPIO. + +// Class +/// A universal/common/generic interface for controling supported A/Cs. +class IRac { + public: + explicit IRac(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + static bool isProtocolSupported(const decode_type_t protocol); + static void initState(stdAc::state_t *state, + const decode_type_t vendor, const int16_t model, + const bool power, const stdAc::opmode_t mode, + const float degrees, const bool celsius, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool light, const bool filter, const bool clean, + const bool beep, const int16_t sleep, + const int16_t clock); + static void initState(stdAc::state_t *state); + void markAsSent(void); + bool sendAc(void); + bool sendAc(const stdAc::state_t desired, const stdAc::state_t *prev = NULL); + bool sendAc(const decode_type_t vendor, const int16_t model, + const bool power, const stdAc::opmode_t mode, const float degrees, + const bool celsius, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool light, const bool filter, const bool clean, + const bool beep, const int16_t sleep = -1, + const int16_t clock = -1); + static bool cmpStates(const stdAc::state_t a, const stdAc::state_t b); + static bool strToBool(const char *str, const bool def = false); + static int16_t strToModel(const char *str, const int16_t def = -1); + static stdAc::ac_command_t strToCommandType(const char *str, + const stdAc::ac_command_t def = stdAc::ac_command_t::kControlCommand); + static stdAc::opmode_t strToOpmode( + const char *str, const stdAc::opmode_t def = stdAc::opmode_t::kAuto); + static stdAc::fanspeed_t strToFanspeed( + const char *str, + const stdAc::fanspeed_t def = stdAc::fanspeed_t::kAuto); + static stdAc::swingv_t strToSwingV( + const char *str, const stdAc::swingv_t def = stdAc::swingv_t::kOff); + static stdAc::swingh_t strToSwingH( + const char *str, const stdAc::swingh_t def = stdAc::swingh_t::kOff); + static String boolToString(const bool value); + static String commandTypeToString(const stdAc::ac_command_t cmdType); + static String opmodeToString(const stdAc::opmode_t mode, + const bool ha = false); + static String fanspeedToString(const stdAc::fanspeed_t speed); + static String swingvToString(const stdAc::swingv_t swingv); + static String swinghToString(const stdAc::swingh_t swingh); + stdAc::state_t getState(void); + stdAc::state_t getStatePrev(void); + bool hasStateChanged(void); + stdAc::state_t next; ///< The state we want the device to be in after we send +#ifdef UNIT_TEST + /// @cond IGNORE + /// UT-specific + /// See @c OUTPUT_DECODE_RESULTS_FOR_UT macro description in IRac.cpp + std::shared_ptr _utReceiver = nullptr; + std::unique_ptr _lastDecodeResults = nullptr; + /// @endcond +#else + + private: +#endif // UNIT_TEST + uint16_t _pin; ///< The GPIO to use to transmit messages from. + bool _inverted; ///< IR LED is lit when GPIO is LOW (true) or HIGH (false)? + bool _modulation; ///< Is frequency modulation to be used? + stdAc::state_t _prev; ///< The state we expect the device to currently be in. +#if SEND_AIRTON + void airton(IRAirtonAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool turbo, + const bool light, const bool econo, const bool filter, + const int16_t sleep = -1); +#endif // SEND_AIRTON +#if SEND_AIRWELL + void airwell(IRAirwellAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan); +#endif // SEND_AIRWELL +#if SEND_AMCOR + void amcor(IRAmcorAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan); +#endif // SEND_AMCOR +#if SEND_ARGO + void argo(IRArgoAC *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const float sensorTemp, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool iFeel, const bool turbo, + const int16_t sleep = -1); + void argoWrem3_ACCommand(IRArgoAC_WREM3 *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const float sensorTemp, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool iFeel, const bool night, + const bool econo, const bool turbo, const bool filter, const bool light); + void argoWrem3_iFeelReport(IRArgoAC_WREM3 *ac, const float sensorTemp); + void argoWrem3_ConfigSet(IRArgoAC_WREM3 *ac, const uint8_t param, + const uint8_t value, bool safe = true); + void argoWrem3_SetTimer(IRArgoAC_WREM3 *ac, bool on, + const uint16_t currentTime, const uint16_t delayMinutes); +#endif // SEND_ARGO +#if SEND_BOSCH144 + void bosch144(IRBosch144AC *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, + const bool quiet); +#endif // SEND_BOSCH144 +#if SEND_CARRIER_AC64 +void carrier64(IRCarrierAc64 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const int16_t sleep = -1); +#endif // SEND_CARRIER_AC64 +#if SEND_COOLIX + void coolix(IRCoolixAC *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const float sensorTemp, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool iFeel, const bool turbo, const bool light, + const bool clean, const int16_t sleep = -1); +#endif // SEND_COOLIX +#if SEND_CORONA_AC + void corona(IRCoronaAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool econo); +#endif // SEND_CORONA_AC +#if SEND_DAIKIN + void daikin(IRDaikinESP *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool clean); +#endif // SEND_DAIKIN +#if SEND_DAIKIN128 + void daikin128(IRDaikin128 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, const bool light, + const bool econo, const int16_t sleep = -1, + const int16_t clock = -1); +#endif // SEND_DAIKIN128 +#if SEND_DAIKIN152 + void daikin152(IRDaikin152 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, const bool econo); +#endif // SEND_DAIKIN152 +#if SEND_DAIKIN160 + void daikin160(IRDaikin160 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv); +#endif // SEND_DAIKIN160 +#if SEND_DAIKIN176 + void daikin176(IRDaikin176 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingh_t swingh); +#endif // SEND_DAIKIN176 +#if SEND_DAIKIN2 + void daikin2(IRDaikin2 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool light, + const bool econo, const bool filter, const bool clean, + const bool beep, const int16_t sleep = -1, + const int16_t clock = -1); +#endif // SEND_DAIKIN2 +#if SEND_DAIKIN216 +void daikin216(IRDaikin216 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo); +#endif // SEND_DAIKIN216 +#if SEND_DAIKIN64 + void daikin64(IRDaikin64 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, + const int16_t sleep = -1, const int16_t clock = -1); +#endif // SEND_DAIKIN64 +#if SEND_DELONGHI_AC + void delonghiac(IRDelonghiAc *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const bool turbo, const int16_t sleep = -1); +#endif // SEND_DELONGHI_AC +#if SEND_ECOCLIM +void ecoclim(IREcoclimAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const int16_t sleep = -1, + const int16_t clock = -1); +#endif // SEND_ECOCLIM +#if SEND_ELECTRA_AC +void electra(IRElectraAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, const bool iFeel, const bool turbo, + const bool lighttoggle, const bool clean); +#endif // SEND_ELECTRA_AC +#if SEND_FUJITSU_AC + void fujitsu(IRFujitsuAC *ac, const fujitsu_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool filter, const bool clean, const int16_t sleep = -1); +#endif // SEND_FUJITSU_AC +#if SEND_GOODWEATHER + void goodweather(IRGoodweatherAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool light, + const int16_t sleep = -1); +#endif // SEND_GOODWEATHER +#if SEND_GREE + void gree(IRGreeAC *ac, const gree_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool iFeel, const bool turbo, const bool econo, + const bool light, const bool clean, const int16_t sleep = -1); +#endif // SEND_GREE +#if SEND_HAIER_AC + void haier(IRHaierAC *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool filter, const int16_t sleep = -1, + const int16_t clock = -1); +#endif // SEND_HAIER_AC +#if SEND_HAIER_AC160 + void haier160(IRHaierAC160 *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool quiet, const bool filter, + const bool clean, const bool light, const bool prevlight, + const int16_t sleep = -1); +#endif // SEND_HAIER_AC160 +#if SEND_HAIER_AC176 + void haier176(IRHaierAC176 *ac, + const haier_ac176_remote_model_t model, const bool on, + const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool quiet, const bool filter, + const int16_t sleep = -1); +#endif // SEND_HAIER_AC176 +#if SEND_HAIER_AC_YRW02 + void haierYrwo2(IRHaierACYRW02 *ac, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, const bool turbo, + const bool quiet, const bool filter, + const int16_t sleep = -1); +#endif // SEND_HAIER_AC_YRW02 +#if SEND_HITACHI_AC + void hitachi(IRHitachiAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh); +#endif // SEND_HITACHI_AC +#if SEND_HITACHI_AC1 + void hitachi1(IRHitachiAc1 *ac, const hitachi_ac1_remote_model_t model, + const bool on, const bool power_toggle, + const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool swing_toggle, const int16_t sleep = -1); +#endif // SEND_HITACHI_AC1 +#if SEND_HITACHI_AC264 + void hitachi264(IRHitachiAc264 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan); +#endif // SEND_HITACHI_AC264 +#if SEND_HITACHI_AC296 + void hitachi296(IRHitachiAc296 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan); +#endif // SEND_HITACHI_AC296 +#if SEND_HITACHI_AC344 + void hitachi344(IRHitachiAc344 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh); +#endif // SEND_HITACHI_AC344 +#if SEND_HITACHI_AC424 + void hitachi424(IRHitachiAc424 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv); +#endif // SEND_HITACHI_AC424 +#if SEND_KELON + void kelon(IRKelonAc *ac, const bool togglePower, const stdAc::opmode_t mode, + const int8_t dryGrade, const float degrees, + const stdAc::fanspeed_t fan, const bool toggleSwing, + const bool superCool, const int16_t sleep); +#endif // SEND_KELON +#if SEND_KELVINATOR + void kelvinator(IRKelvinatorAC *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool light, + const bool filter, const bool clean); +#endif // SEND_KELVINATOR +#if SEND_LG + void lg(IRLgAc *ac, const lg_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev, + const stdAc::swingh_t swingh, const bool light); +#endif // SEND_LG +#if SEND_MIDEA + void midea(IRMideaAC *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const float sensorTemp, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool iFeel, const bool quiet, const bool quiet_prev, + const bool turbo, const bool econo, const bool light, + const bool clean, const int16_t sleep = -1); +#endif // SEND_MIDEA +#if SEND_MIRAGE + void mirage(IRMirageAc *ac, const stdAc::state_t state); +#endif // SEND_MIRAGE +#if SEND_MITSUBISHI_AC + void mitsubishi(IRMitsubishiAC *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet, const int16_t clock = -1); +#endif // SEND_MITSUBISHI_AC +#if SEND_MITSUBISHI112 + void mitsubishi112(IRMitsubishi112 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet); +#endif // SEND_MITSUBISHI112 +#if SEND_MITSUBISHI136 + void mitsubishi136(IRMitsubishi136 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool quiet); +#endif // SEND_MITSUBISHI136 +#if SEND_MITSUBISHIHEAVY + void mitsubishiHeavy88(IRMitsubishiHeavy88Ac *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool turbo, const bool econo, const bool clean); + void mitsubishiHeavy152(IRMitsubishiHeavy152Ac *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool filter, const bool clean, + const int16_t sleep = -1); +#endif // SEND_MITSUBISHIHEAVY +#if SEND_NEOCLIMA + void neoclima(IRNeoclimaAc *ac, const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool econo, const bool light, + const bool filter, const int16_t sleep = -1); +#endif // SEND_NEOCLIMA +#if SEND_PANASONIC_AC + void panasonic(IRPanasonicAc *ac, const panasonic_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool filter, + const int16_t clock = -1); +#endif // SEND_PANASONIC_AC +#if SEND_PANASONIC_AC32 + void panasonic32(IRPanasonicAc32 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh); +#endif // SEND_PANASONIC_AC32 +#if SEND_RHOSS + void rhoss(IRRhossAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swing); +#endif // SEND_RHOSS +#if SEND_SAMSUNG_AC + void samsung(IRSamsungAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool econo, + const bool light, const bool filter, const bool clean, + const bool beep, const int16_t sleep = -1, + const bool prevpower = true, const int16_t prevsleep = -1, + const bool forceextended = true); +#endif // SEND_SAMSUNG_AC +#if SEND_SANYO_AC + void sanyo(IRSanyoAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const float sensorTemp, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool iFeel, const bool beep, + const int16_t sleep = -1); +#endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + void sanyo88(IRSanyoAc88 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool turbo, + const bool filter, + const int16_t sleep = -1, const int16_t clock = -1); +#endif // SEND_SANYO_AC88 +#if SEND_SHARP_AC + void sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, + const bool on, const bool prev_power, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev, + const bool turbo, const bool light, + const bool filter, const bool clean); +#endif // SEND_SHARP_AC +#if SEND_TCL112AC + void tcl112(IRTcl112Ac *ac, const tcl_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool quiet, const bool turbo, const bool light, + const bool econo, const bool filter); +#endif // SEND_TCL112AC +#if SEND_TECHNIBEL_AC + void technibel(IRTechnibelAc *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const int16_t sleep = -1); +#endif // SEND_TECHNIBEL_AC +#if SEND_TECO + void teco(IRTecoAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool light, const int16_t sleep = -1); +#endif // SEND_TECO +#if SEND_TOSHIBA_AC + void toshiba(IRToshibaAC *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool turbo, const bool econo, const bool filter); +#endif // SEND_TOSHIBA_AC +#if SEND_TROTEC + void trotec(IRTrotecESP *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const int16_t sleep = -1); +#endif // SEND_TROTEC +#if SEND_TROTEC_3550 + void trotec3550(IRTrotec3550 *ac, + const bool on, const stdAc::opmode_t mode, + const bool celsius, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv); +#endif // SEND_TROTEC_3550 +#if SEND_TRUMA + void truma(IRTrumaAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const bool quiet); +#endif // SEND_TRUMA +#if SEND_VESTEL_AC + void vestel(IRVestelAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool turbo, const bool filter, + const int16_t sleep = -1, const int16_t clock = -1, + const bool sendNormal = true); +#endif // SEND_VESTEL_AC +#if SEND_VOLTAS + void voltas(IRVoltas *ac, const voltas_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool econo, const bool light, + const int16_t sleep = -1); +#endif // SEND_VOLTAS +#if SEND_WHIRLPOOL_AC + void whirlpool(IRWhirlpoolAc *ac, const whirlpool_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, + const bool turbo, const bool light, + const int16_t sleep = -1, const int16_t clock = -1); +#endif // SEND_WHIRLPOOL_AC +#if SEND_TRANSCOLD + void transcold(IRTranscoldAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh); +#endif // SEND_TRANSCOLD +static stdAc::state_t cleanState(const stdAc::state_t state); +static stdAc::state_t handleToggles(const stdAc::state_t desired, + const stdAc::state_t *prev = NULL); +}; // IRac class + +/// Common functions for use with all A/Cs supported by the IRac class. +namespace IRAcUtils { + String resultAcToString(const decode_results * const results); + bool decodeToState(const decode_results *decode, stdAc::state_t *result, + const stdAc::state_t *prev = NULL); +} // namespace IRAcUtils +#endif // IRAC_H_ diff --git a/src/libraries/IRremoteESP8266/src/IRmacros.h b/src/libraries/IRremoteESP8266/src/IRmacros.h new file mode 100644 index 000000000..665286298 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRmacros.h @@ -0,0 +1,53 @@ +#ifndef IRMACROS_H_ +#define IRMACROS_H_ +/**************************************************************** + * Copyright 2022 IRremoteESP8266 project and others + */ +/// @file IRmacros.h + +/** + * VA_OPT_SUPPORTED macro to check if __VA_OPT__ is supported + * Source: https://stackoverflow.com/a/48045656 + */ +/// @cond TEST +#define PP_THIRD_ARG(a, b, c, ...) c +#define VA_OPT_SUPPORTED_I(...) \ + PP_THIRD_ARG(__VA_OPT__(, false), true, false, false) +#define VA_OPT_SUPPORTED VA_OPT_SUPPORTED_I(?) +/// @endcond +/** + * VA_OPT_SUPPORTED end + */ + +/** + * COND() Set of macros to facilitate single-line conditional compilation + * argument checking. + * Found here: https://www.reddit.com/r/C_Programming/comments/ud3xrv/ + * conditional_preprocessor_macro_anyone/ + * + * Usage: + * COND([||/&&...], , ) + * + * NB: If __VA_OPT__ macro not supported, the will be expanded! + */ +/// @cond TEST +#if !VA_OPT_SUPPORTED +// #pragma message("Compiler without __VA_OPT__ support") +#define COND(cond, a, b) a +#else // !VA_OPT_SUPPORTED +#define NOTHING +#define EXPAND(...) __VA_ARGS__ +#define STUFF_P(a, ...) __VA_OPT__(a) +#define STUFF(...) STUFF_P(__VA_ARGS__) +#define VA_TEST_P(a, ...) __VA_OPT__(NO)##THING +#define VA_TEST(...) VA_TEST_P(__VA_ARGS__) +#define NEGATE(a) VA_TEST(a, a) +#define COND_P(cond, a, b) STUFF(a, cond)STUFF(b, NEGATE(cond)) +#define COND(cond, a, b) EXPAND(COND_P(cond, a, b)) +#endif // !VA_OPT_SUPPORTED +/// @endcond +/** + * end of COND() set of macros + */ + +#endif // IRMACROS_H_ diff --git a/src/libraries/IRremoteESP8266/src/IRproto.cpp b/src/libraries/IRremoteESP8266/src/IRproto.cpp new file mode 100644 index 000000000..07007efef --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRproto.cpp @@ -0,0 +1,133 @@ +#include "IRproto.h" + +const char * const ProtocolNames[] = { + "UNUSED", + "RC5", + "RC6", + "NEC", + "SONY", + "PANASONIC", + "JVC", + "SAMSUNG", + "WHYNTER", + "AIWA_RC_T501", + "LG", + "SANYO", + "MITSUBISHI", + "DISH", + "SHARP", + "COOLIX", + "DAIKIN", + "DENON", + "KELVINATOR", + "SHERWOOD", + "MITSUBISHI_AC", + "RCMM", + "SANYO_LC7461", + "RC5X", + "GREE", + "PRONTO", + "NEC_LIKE", + "ARGO", + "TROTEC", + "NIKAI", + "RAW", + "GLOBALCACHE", + "TOSHIBA_AC", + "FUJITSU_AC", + "MIDEA", + "MAGIQUEST", + "LASERTAG", + "CARRIER_AC", + "HAIER_AC", + "MITSUBISHI2", + "HITACHI_AC", + "HITACHI_AC1", + "HITACHI_AC2", + "GICABLE", + "HAIER_AC_YRW02", + "WHIRLPOOL_AC", + "SAMSUNG_AC", + "LUTRON", + "ELECTRA_AC", + "PANASONIC_AC", + "PIONEER", + "LG2", + "MWM", + "DAIKIN2", + "VESTEL_AC", + "TECO", + "SAMSUNG36", + "TCL112AC", + "LEGOPF", + "MITSUBISHI_HEAVY_88", + "MITSUBISHI_HEAVY_152", + "DAIKIN216", + "SHARP_AC", + "GOODWEATHER", + "INAX", + "DAIKIN160", + "NEOCLIMA", + "DAIKIN176", + "DAIKIN128", + "AMCOR", + "DAIKIN152", + "MITSUBISHI136", + "MITSUBISHI112", + "HITACHI_AC424", + "SONY_38K", + "EPSON", + "SYMPHONY", + "HITACHI_AC3", + "DAIKIN64", + "AIRWELL", + "DELONGHI_AC", + "DOSHISHA", + "MULTIBRACKETS", + "CARRIER_AC40", + "CARRIER_AC64", + "HITACHI_AC344", + "CORONA_AC", + "MIDEA24", + "ZEPEAL", + "SANYO_AC", + "VOLTAS", + "METZ", + "TRANSCOLD", + "TECHNIBEL_AC", + "MIRAGE", + "ELITESCREENS", + "PANASONIC_AC32", + "MILESTAG2", + "ECOCLIM", + "XMP", + "TRUMA", + "HAIER_AC176", + "TEKNOPOINT", + "KELON", + "TROTEC_3550", + "SANYO_AC88", + "BOSE", + "ARRIS", + "RHOSS", + "AIRTON", + "COOLIX48", + "HITACHI_AC264", + "KELON168", + "HITACHI_AC296", + "DAIKIN200", + "HAIER_AC160", + "CARRIER_AC128", + "TOTO", + "CLIMABUTLER", + "TCL96AC", + "BOSCH144", + "SANYO_AC152", + "DAIKIN312", + "GORENJE", + "WOWWEE", + "CARRIER_AC84" +}; + + +const int numProtocols = sizeof(ProtocolNames)/sizeof(*ProtocolNames); diff --git a/src/libraries/IRremoteESP8266/src/IRproto.h b/src/libraries/IRremoteESP8266/src/IRproto.h new file mode 100644 index 000000000..0b3f8bb77 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRproto.h @@ -0,0 +1,7 @@ +#ifndef __IRPROTO_H__ +#define __IRPROTO_H__ + +extern const char * const ProtocolNames[]; +extern const int numProtocols; + +#endif // \ No newline at end of file diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.cpp b/src/libraries/IRremoteESP8266/src/IRrecv.cpp new file mode 100644 index 000000000..b3a1b6714 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRrecv.cpp @@ -0,0 +1,2075 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2015 Mark Szabo +// Copyright 2015 Sebastien Warin +// Copyright 2017, 2019 David Conran + +#if !PLATFORM_BEKEN + +#include "IRrecv.h" +#include +#ifndef UNIT_TEST +#if defined(ESP8266) +extern "C" { +#include +#include +} +#endif // ESP8266 +#include "String.h" +#endif // UNIT_TEST +// #include +#ifdef UNIT_TEST +#include +#endif // UNIT_TEST +#include "IRremoteESP8266.h" +#include "IRutils.h" + +#ifdef UNIT_TEST +#undef ICACHE_RAM_ATTR +#define ICACHE_RAM_ATTR +#endif + +#ifndef USE_IRAM_ATTR +#if defined(ESP8266) +#if defined(IRAM_ATTR) +#define USE_IRAM_ATTR IRAM_ATTR +#else // IRAM_ATTR +#define USE_IRAM_ATTR ICACHE_RAM_ATTR +#endif // IRAM_ATTR +#endif // ESP8266 +#if defined(ESP32) +#define USE_IRAM_ATTR IRAM_ATTR +#endif // ESP32 +#endif // USE_IRAM_ATTR + +#define ONCE 0 + +// Updated by David Conran (https://github.com/crankyoldgit) for receiving IR +// code on ESP32 +// Updated by Sebastien Warin (http://sebastien.warin.fr) for receiving IR code +// on ESP8266 +// Updated by markszabo (https://github.com/crankyoldgit/IRremoteESP8266) for +// sending IR code on ESP8266 + +// Globals +#ifndef UNIT_TEST +#if defined(ESP8266) +namespace _IRrecv { +static ETSTimer timer; +} // namespace _IRrecv +#endif // ESP8266 +#if defined(ESP32) +// We need a horrible timer hack for ESP32 Arduino framework < v2.0.0 +#if !defined(_ESP32_IRRECV_TIMER_HACK) +// Version check +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) +// No need for the hack if we are running version >= 2.0.0 +#define _ESP32_IRRECV_TIMER_HACK false +#else // Version check +// If no ESP_ARDUINO_VERSION_MAJOR is defined, or less than 2, then we are +// using an old ESP32 core, so we need the hack. +#define _ESP32_IRRECV_TIMER_HACK true +#endif // Version check +#endif // !defined(_ESP32_IRRECV_TIMER_HACK) + +#if _ESP32_IRRECV_TIMER_HACK +// Required structs/types from: +// https://github.com/espressif/arduino-esp32/blob/6b0114366baf986c155e8173ab7c22bc0c5fcedc/cores/esp32/esp32-hal-timer.c#L28-L58 +// These are needed to be able to directly manipulate the timer registers from +// inside an ISR. This is very very ugly. +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1350 +// Note: This will need to be updated if it ever changes. +// +// Start of Horrible Hack! +typedef struct { + union { + struct { + uint32_t reserved0: 10; + uint32_t alarm_en: 1; + /*When set alarm is enabled*/ + uint32_t level_int_en: 1; + /*When set level type interrupt will be generated during alarm*/ + uint32_t edge_int_en: 1; + /*When set edge type interrupt will be generated during alarm*/ + uint32_t divider: 16; + /*Timer clock (T0/1_clk) pre-scale value.*/ + uint32_t autoreload: 1; + /*When set timer 0/1 auto-reload at alarming is enabled*/ + uint32_t increase: 1; + /*When set timer 0/1 time-base counter increment. + When cleared timer 0 time-base counter decrement.*/ + uint32_t enable: 1; + /*When set timer 0/1 time-base counter is enabled*/ + }; + uint32_t val; + } config; + uint32_t cnt_low; + /*Register to store timer 0/1 time-base counter current value lower 32 + bits.*/ + uint32_t cnt_high; + /*Register to store timer 0 time-base counter current value higher 32 + bits.*/ + uint32_t update; + /*Write any value will trigger a timer 0 time-base counter value update + (timer 0 current value will be stored in registers above)*/ + uint32_t alarm_low; + /*Timer 0 time-base counter value lower 32 bits that will trigger the + alarm*/ + uint32_t alarm_high; + /*Timer 0 time-base counter value higher 32 bits that will trigger the + alarm*/ + uint32_t load_low; + /*Lower 32 bits of the value that will load into timer 0 time-base counter*/ + uint32_t load_high; + /*higher 32 bits of the value that will load into timer 0 time-base + counter*/ + uint32_t reload; + /*Write any value will trigger timer 0 time-base counter reload*/ +} hw_timer_reg_t; + +typedef struct hw_timer_s { + hw_timer_reg_t * dev; + uint8_t num; + uint8_t group; + uint8_t timer; + portMUX_TYPE lock; +} hw_timer_t; +#endif // _ESP32_IRRECV_TIMER_HACK / End of Horrible Hack. + +namespace _IRrecv { +static hw_timer_t * timer = NULL; +} // namespace _IRrecv +#endif // ESP32 +using _IRrecv::timer; +#endif // UNIT_TEST + +namespace _IRrecv { // Namespace extension +#if defined(ESP32) +portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +#endif // ESP32 +volatile irparams_t params; +irparams_t *params_save; // A copy of the interrupt state while decoding. +} // namespace _IRrecv + +#if defined(ESP32) +using _IRrecv::mux; +#endif // ESP32 +using _IRrecv::params; +using _IRrecv::params_save; + +#ifndef UNIT_TEST +#if defined(ESP8266) +/// Interrupt handler for when the timer runs out. +/// It signals to the library that capturing of IR data has stopped. +/// @param[in] arg Unused. (ESP8266 Only) +static void USE_IRAM_ATTR read_timeout(void *arg __attribute__((unused))) { + os_intr_lock(); +#endif // ESP8266 +/// @cond IGNORE +#if defined(ESP32) +/// Interrupt handler for when the timer runs out. +/// It signals to the library that capturing of IR data has stopped. +/// @note ESP32 version +static void USE_IRAM_ATTR read_timeout(void) { +/// @endcond + portENTER_CRITICAL(&mux); +#endif // ESP32 + if (params.rawlen) params.rcvstate = kStopState; +#if defined(ESP8266) + os_intr_unlock(); +#endif // ESP8266 +#if defined(ESP32) + portEXIT_CRITICAL(&mux); +#endif // ESP32 +} + +/// Interrupt handler for changes on the GPIO pin handling incoming IR messages. +static void USE_IRAM_ATTR gpio_intr() { + uint32_t now = micros(); + static uint32_t start = 0; + +#if defined(ESP8266) + uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + os_timer_disarm(&timer); + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); +#endif // ESP8266 + + // Grab a local copy of rawlen to reduce instructions used in IRAM. + // This is an ugly premature optimisation code-wise, but we do everything we + // can to save IRAM. + // It seems referencing the value via the structure uses more instructions. + // Less instructions means faster and less IRAM used. + // N.B. It saves about 13 bytes of IRAM. + uint16_t rawlen = params.rawlen; + + if (rawlen >= params.bufsize) { + params.overflow = true; + params.rcvstate = kStopState; + } + + if (params.rcvstate == kStopState) return; + + if (params.rcvstate == kIdleState) { + params.rcvstate = kMarkState; + params.rawbuf[rawlen] = 1; + } else { + if (now < start) + params.rawbuf[rawlen] = (UINT32_MAX - start + now) / kRawTick; + else + params.rawbuf[rawlen] = (now - start) / kRawTick; + } + params.rawlen++; + + start = now; + +#if defined(ESP8266) + os_timer_arm(&timer, params.timeout, ONCE); +#endif // ESP8266 +#if defined(ESP32) + // Reset the timeout. + // +#if _ESP32_IRRECV_TIMER_HACK + // The following three lines of code are the equiv of: + // `timerWrite(timer, 0);` + // We can't call that routine safely from inside an ISR as that procedure + // is not stored in IRAM. Hence, we do it manually so that it's covered by + // USE_IRAM_ATTR in this ISR. + // @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1350 + // @see https://github.com/espressif/arduino-esp32/blob/6b0114366baf986c155e8173ab7c22bc0c5fcedc/cores/esp32/esp32-hal-timer.c#L106-L110 + timer->dev->load_high = (uint32_t) 0; + timer->dev->load_low = (uint32_t) 0; + timer->dev->reload = 1; + // The next line is the same, but instead replaces: + // `timerAlarmEnable(timer);` + // @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1350 + // @see https://github.com/espressif/arduino-esp32/blob/6b0114366baf986c155e8173ab7c22bc0c5fcedc/cores/esp32/esp32-hal-timer.c#L176-L178 + timer->dev->config.alarm_en = 1; +#else // _ESP32_IRRECV_TIMER_HACK + timerWrite(timer, 0); + timerAlarmEnable(timer); +#endif // _ESP32_IRRECV_TIMER_HACK +#endif // ESP32 +} +#endif // UNIT_TEST + +// Start of IRrecv class ------------------- + +/// Class constructor +/// Args: +/// @param[in] recvpin The GPIO pin the IR receiver module's data pin is +/// connected to. +/// @param[in] bufsize Nr. of entries to have in the capture buffer. +/// (Default: kRawBuf) +/// @param[in] timeout Nr. of milli-Seconds of no signal before we stop +/// capturing data. (Default: kTimeoutMs) +/// @param[in] save_buffer Use a second (save) buffer to decode from. +/// (Default: false) +/// @param[in] timer_num Nr. of the ESP32 timer to use. (0 to 3) (ESP32 Only) +/// or (0 to 1) (ESP32-C3) +#if defined(ESP32) +IRrecv::IRrecv(const uint16_t recvpin, const uint16_t bufsize, + const uint8_t timeout, const bool save_buffer, + const uint8_t timer_num) { + // Ensure we use a valid timer number. + _timer_num = ::min(timer_num, + (uint8_t)( +#ifdef SOC_TIMER_GROUP_TOTAL_TIMERS + SOC_TIMER_GROUP_TOTAL_TIMERS - 1)); +#else // SOC_TIMER_GROUP_TOTAL_TIMERS + 3)); +#endif // SOC_TIMER_GROUP_TOTAL_TIMERS +#else // ESP32 +/// @cond IGNORE +/// Class constructor +/// Args: +/// @param[in] recvpin The GPIO pin the IR receiver module's data pin is +/// connected to. +/// @param[in] bufsize Nr. of entries to have in the capture buffer. +/// (Default: kRawBuf) +/// @param[in] timeout Nr. of milli-Seconds of no signal before we stop +/// capturing data. (Default: kTimeoutMs) +/// @param[in] save_buffer Use a second (save) buffer to decode from. +/// (Default: false) +IRrecv::IRrecv(const uint16_t recvpin, const uint16_t bufsize, + const uint8_t timeout, const bool save_buffer) { +/// @endcond +#endif // ESP32 + params.recvpin = recvpin; + params.bufsize = bufsize; + // Ensure we are going to be able to store all possible values in the + // capture buffer. + params.timeout = ::min(timeout, (uint8_t)kMaxTimeoutMs); + params.rawbuf = new uint16_t[bufsize]; + if (params.rawbuf == NULL) { + DPRINTLN( + "Could not allocate memory for the primary IR buffer.\n" + "Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!"); +#ifndef UNIT_TEST + ESP.restart(); // Mem alloc failure. Reboot. +#endif + } + // If we have been asked to use a save buffer (for decoding), then create one. + if (save_buffer) { + params_save = new irparams_t; + params_save->rawbuf = new uint16_t[bufsize]; + // Check we allocated the memory successfully. + if (params_save->rawbuf == NULL) { + DPRINTLN( + "Could not allocate memory for the second IR buffer.\n" + "Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!"); +#ifndef UNIT_TEST + ESP.restart(); // Mem alloc failure. Reboot. +#endif + } + } else { + params_save = NULL; + } +#if DECODE_HASH + _unknown_threshold = kUnknownThreshold; +#endif // DECODE_HASH + _tolerance = kTolerance; +} + +/// Class destructor +/// Cleans up after the object is no longer needed. +/// e.g. Frees up all memory used by the various buffers, and disables any +/// timers or interrupts used. +IRrecv::~IRrecv(void) { + disableIRIn(); +#if defined(ESP32) + if (timer != NULL) timerEnd(timer); // Cleanup the ESP32 timeout timer. +#endif // ESP32 + delete[] params.rawbuf; + if (params_save != NULL) { + delete[] params_save->rawbuf; + delete params_save; + } +} + +/// Set up and (re)start the IR capture mechanism. +/// @param[in] pullup A flag indicating should the GPIO use the internal pullup +/// resistor. (Default: `false`. i.e. No.) +void IRrecv::enableIRIn(const bool pullup) { + // ESP32's seem to require explicitly setting the GPIO to INPUT etc. + // This wasn't required on the ESP8266s, but it shouldn't hurt to make sure. + if (pullup) { +#ifndef UNIT_TEST + pinMode(params.recvpin, INPUT_PULLUP); + } else { + pinMode(params.recvpin, INPUT); +#endif // UNIT_TEST + } +#if defined(ESP32) + // Initialise the ESP32 timer. + // 80MHz / 80 = 1 uSec granularity. + timer = timerBegin(_timer_num, 80, true); +#ifdef DEBUG + if (timer == NULL) { + DPRINT("FATAL: Unable enable system timer: "); + DPRINTLN((uint16_t)_timer_num); + } +#endif // DEBUG + assert(timer != NULL); // Check we actually got the timer. + // Set the timer so it only fires once, and set it's trigger in uSeconds. + timerAlarmWrite(timer, MS_TO_USEC(params.timeout), ONCE); + // Note: Interrupt needs to be attached before it can be enabled or disabled. + // Note: EDGE (true) is not supported, use LEVEL (false). Ref: #1713 + // See: https://github.com/espressif/arduino-esp32/blob/caef4006af491130136b219c1205bdcf8f08bf2b/cores/esp32/esp32-hal-timer.c#L224-L227 + timerAttachInterrupt(timer, &read_timeout, false); +#endif // ESP32 + + // Initialise state machine variables + resume(); + +#ifndef UNIT_TEST +#if defined(ESP8266) + // Initialise ESP8266 timer. + os_timer_disarm(&timer); + os_timer_setfn(&timer, reinterpret_cast(read_timeout), + NULL); +#endif // ESP8266 + // Attach Interrupt + attachInterrupt(params.recvpin, gpio_intr, CHANGE); +#endif // UNIT_TEST +} + +/// Stop collection of any received IR data. +/// Disable any timers and interrupts. +void IRrecv::disableIRIn(void) { +#ifndef UNIT_TEST +#if defined(ESP8266) + os_timer_disarm(&timer); +#endif // ESP8266 +#if defined(ESP32) + timerAlarmDisable(timer); + timerEnd(timer); +#endif // ESP32 + detachInterrupt(params.recvpin); +#endif // UNIT_TEST +} + +/// Pause collection of received IR data. +/// @see IRrecv class constructor +void IRrecv::pause(void) { + params.rcvstate = kStopState; + params.rawlen = 0; + params.overflow = false; +#if defined(ESP32) + gpio_intr_disable((gpio_num_t)params.recvpin); +#endif // ESP32 +} + +/// Resume collection of received IR data. +/// @note This is required if `decode()` is successful and `save_buffer` was +/// not set when the class was instanciated. +/// @see IRrecv class constructor +void IRrecv::resume(void) { + params.rcvstate = kIdleState; + params.rawlen = 0; + params.overflow = false; +#if defined(ESP32) + timerAlarmDisable(timer); + gpio_intr_enable((gpio_num_t)params.recvpin); +#endif // ESP32 +} + +/// Make a copy of the interrupt state & buffer data. +/// Needed because irparams is marked as volatile, thus memcpy() isn't allowed. +/// Only call this when you know the interrupt handlers won't modify anything. +/// i.e. In kStopState. +/// @param[in] src Pointer to an irparams_t structure to copy from. +/// @param[out] dst Pointer to an irparams_t structure to copy to. +void IRrecv::copyIrParams(volatile irparams_t *src, irparams_t *dst) { + // Typecast src and dst addresses to (char *) + char *csrc = (char *)src; // NOLINT(readability/casting) + char *cdst = (char *)dst; // NOLINT(readability/casting) + + // Save the pointer to the destination's rawbuf so we don't lose it as + // the for-loop/copy after this will overwrite it with src's rawbuf pointer. + // This isn't immediately obvious due to typecasting/different variable names. + uint16_t *dst_rawbuf_ptr; + dst_rawbuf_ptr = dst->rawbuf; + + // Copy contents of src[] to dst[] + for (uint16_t i = 0; i < sizeof(irparams_t); i++) cdst[i] = csrc[i]; + + // Restore the buffer pointer + dst->rawbuf = dst_rawbuf_ptr; + + // Copy the rawbuf + for (uint16_t i = 0; i < dst->bufsize; i++) dst->rawbuf[i] = src->rawbuf[i]; +} + +/// Obtain the maximum number of entries possible in the capture buffer. +/// i.e. It's size. +/// @return The size of the buffer that is in use by the object. +uint16_t IRrecv::getBufSize(void) { return params.bufsize; } + +#if DECODE_HASH +/// Set the minimum length we will consider for reporting UNKNOWN message types. +/// @param[in] length Min nr. of mark/space pulses required to be considered. +void IRrecv::setUnknownThreshold(const uint16_t length) { + _unknown_threshold = length; +} +#endif // DECODE_HASH + + +/// Set the base tolerance percentage for matching incoming IR messages. +/// @param[in] percent An integer percentage. (0-100) +void IRrecv::setTolerance(const uint8_t percent) { + _tolerance = ::min(percent, (uint8_t)100); +} + +/// Get the base tolerance percentage for matching incoming IR messages. +/// @return A integer percentage. +uint8_t IRrecv::getTolerance(void) { return _tolerance; } + +#if ENABLE_NOISE_FILTER_OPTION +/// Remove or merge pulses in the capture buffer that are too short. +/// @param[in,out] results Ptr to the decode_results we are going to filter. +/// @param[in] floor Only allow values in the buffer large than this. +/// (in microSeconds) +void IRrecv::crudeNoiseFilter(decode_results *results, const uint16_t floor) { + if (floor == 0) return; // Nothing to do. + const uint16_t kTickFloor = floor / kRawTick; + const uint16_t kBufSize = getBufSize(); + uint16_t offset = kStartOffset; + while (offset < results->rawlen && offset + 2 < kBufSize) { + uint16_t curr = results->rawbuf[offset]; + uint16_t next = results->rawbuf[offset + 1]; + uint16_t addition = curr + next; + if (curr < kTickFloor) { // Is it too short? + // Shuffle the buffer down. i.e. Remove the mark & space pair. + // Note: `memcpy()` can't be used as rawbuf is `volatile`. + for (uint16_t i = offset + 2; i <= results->rawlen && i < kBufSize; i++) + results->rawbuf[i - 2] = results->rawbuf[i]; + if (offset > 1) { // There is a previous pair we can add to. + // Merge this pair into into the previous space. + results->rawbuf[offset - 1] += addition; + } + results->rawlen -= 2; // Adjust the length. + } else { + offset++; // Move along. + } + } +} +#endif // ENABLE_NOISE_FILTER_OPTION + +/// Decodes the received IR message. +/// If the interrupt state is saved, we will immediately resume waiting +/// for the next IR message to avoid missing messages. +/// @note There is a trade-off here. Saving the state means less time lost until +/// we can receiving the next message vs. using more RAM. Choose appropriately. +/// @param[out] results A PTR to where the decoded IR message will be stored. +/// @param[out] save A PTR to an irparams_t instance in which to save +/// the interrupt's memory/state. NULL means don't save it. +/// @param[in] max_skip Maximum Nr. of pulses at the begining of a capture we +/// can skip when attempting to find a protocol we can successfully decode. +/// This parameter can dramatically improve detection of protocols +/// when there is light IR interference just before an incoming IR +/// message, however, it comes at a steep performace price. +/// (Default is 0. No skipping.) +/// @warning Increasing the `max_skip` value will dramatically (linearly) +/// increase the cpu time & usage to decode protocols. +/// e.g. 0 -> 1 will be a 2x increase in cpu usage/time. +/// 0 -> 2 will be a 3x increase etc. +/// If you are going to do this, consider disabling protocol decoding for +/// protocols you are not expecting. +/// @param[in] noise_floor Pulses below this size (in usecs) will be removed or +/// merged prior to any decoding. This is to try to remove noise/poor +/// readings & slightly increase the chances of a successful decode but at the +/// cost of data fidelity & integrity. +/// (Defaults to 0 usecs. i.e. Don't filter; which is safe!) +/// @warning DANGER: **Here Be Dragons!** +/// If you set the `noise_floor` value too high, it **WILL** break decoding +/// of some protocols. You have been warned! +/// **Any** non-zero value has the potential to **cook** the captured raw data +/// i.e. The raw data is going to lie to you. +/// It may obscure hardware, circuit, & environment issues thus making it +/// impossible to support you accurately or confidently. +/// Values of <= 50 usecs will probably be safe. +/// 51 - 100 usecs **might** be okay. +/// 100 - 150 usecs is "Danger, Will Robinson!". +/// 150 - 200 usecs expect broken protocols. +/// At 200+ usecs, you **have** protocols you can't decode!! +/// @return A boolean indicating if an IR message is ready or not. +bool IRrecv::decode(decode_results *results, irparams_t *save, + uint8_t max_skip, uint16_t noise_floor) { + // Proceed only if an IR message been received. +#ifndef UNIT_TEST + if (params.rcvstate != kStopState) return false; +#endif + + // Clear the entry we are currently pointing to when we got the timeout. + // i.e. Stopped collecting IR data. + // It's junk as we never wrote an entry to it and can only confuse decoding. + // This is done here rather than logically the best place in read_timeout() + // as it saves a few bytes of ICACHE_RAM as that routine is bound to an + // interrupt. decode() is not stored in ICACHE_RAM. + // Another better option would be to zero the entire irparams.rawbuf[] on + // resume() but that is a much more expensive operation compare to this. + // However, don't do this if rawbuf is already full as we stomp over the heap. + // See: https://github.com/crankyoldgit/IRremoteESP8266/issues/1516 + if (!params.overflow) params.rawbuf[params.rawlen] = 0; + + bool resumed = false; // Flag indicating if we have resumed. + + // If we were requested to use a save buffer previously, do so. + if (save == NULL) save = params_save; + + if (save == NULL) { + // We haven't been asked to copy it so use the existing memory. +#ifndef UNIT_TEST + results->rawbuf = params.rawbuf; + results->rawlen = params.rawlen; + results->overflow = params.overflow; +#endif + } else { + copyIrParams(¶ms, save); // Duplicate the interrupt's memory. + resume(); // It's now safe to rearm. The IR message won't be overridden. + resumed = true; + // Point the results at the saved copy. + results->rawbuf = save->rawbuf; + results->rawlen = save->rawlen; + results->overflow = save->overflow; + } + + // Reset any previously partially processed results. + results->decode_type = UNKNOWN; + results->bits = 0; + results->value = 0; + results->address = 0; + results->command = 0; + results->repeat = false; + +#if ENABLE_NOISE_FILTER_OPTION + crudeNoiseFilter(results, noise_floor); +#endif // ENABLE_NOISE_FILTER_OPTION + // Keep looking for protocols until we've run out of entries to skip or we + // find a valid protocol message. + for (uint16_t offset = kStartOffset; + offset <= (max_skip * 2) + kStartOffset; + offset += 2) { +#if DECODE_AIWA_RC_T501 + DPRINTLN("Attempting Aiwa RC T501 decode"); + // Try decodeAiwaRCT501() before decodeSanyoLC7461() & decodeNEC() + // because the protocols are similar. This protocol is more specific than + // those ones, so should go before them. + if (decodeAiwaRCT501(results, offset)) return true; +#endif +#if DECODE_SANYO + DPRINTLN("Attempting Sanyo LC7461 decode"); + // Try decodeSanyoLC7461() before decodeNEC() because the protocols are + // similar in timings & structure, but the Sanyo one is much longer than the + // NEC protocol (42 vs 32 bits) so this one should be tried first to try to + // reduce false detection as a NEC packet. + if (decodeSanyoLC7461(results, offset)) return true; +#endif +#if DECODE_CARRIER_AC + DPRINTLN("Attempting Carrier AC decode"); + // Try decodeCarrierAC() before decodeNEC() because the protocols are + // similar in timings & structure, but the Carrier one is much longer than + // the NEC protocol (3x32 bits vs 1x32 bits) so this one should be tried + // first to try to reduce false detection as a NEC packet. + if (decodeCarrierAC(results, offset)) return true; +#endif +#if DECODE_PIONEER + DPRINTLN("Attempting Pioneer decode"); + // Try decodePioneer() before decodeNEC() because the protocols are + // similar in timings & structure, but the Pioneer one is much longer than + // the NEC protocol (2x32 bits vs 1x32 bits) so this one should be tried + // first to try to reduce false detection as a NEC packet. + if (decodePioneer(results, offset)) return true; +#endif +#if DECODE_EPSON + DPRINTLN("Attempting Epson decode"); + // Try decodeEpson() before decodeNEC() because the protocols are + // similar in timings & structure, but the Epson one is much longer than the + // NEC protocol (3x32 identical bits vs 1x32 bits) so this one should be tried + // first to try to reduce false detection as a NEC packet. + if (decodeEpson(results, offset)) return true; +#endif +#if DECODE_NEC + DPRINTLN("Attempting NEC decode"); + if (decodeNEC(results, offset)) return true; +#endif +#if DECODE_MILESTAG2 + DPRINTLN("Attempting MilesTag2 decode"); + // Try decodeMilestag2() before decodeSony() because the protocols are + // similar in timings & structure, but the Miles one differs in nbits + // so this one should be tried first to try to reduce false detection + if (decodeMilestag2(results, offset, kMilesTag2MsgBits) || + decodeMilestag2(results, offset, kMilesTag2ShotBits)) return true; +#endif +#if DECODE_SONY + DPRINTLN("Attempting Sony decode"); + if (decodeSony(results, offset)) return true; +#endif +#if DECODE_MITSUBISHI + DPRINTLN("Attempting Mitsubishi decode"); + if (decodeMitsubishi(results, offset)) return true; +#endif +#if DECODE_MITSUBISHI_AC + DPRINTLN("Attempting Mitsubishi AC decode"); + if (decodeMitsubishiAC(results, offset)) return true; +#endif +#if DECODE_MITSUBISHI2 + DPRINTLN("Attempting Mitsubishi2 decode"); + if (decodeMitsubishi2(results, offset)) return true; +#endif +#if DECODE_RC5 + DPRINTLN("Attempting RC5 decode"); + if (decodeRC5(results, offset)) return true; +#endif +#if DECODE_RC6 + DPRINTLN("Attempting RC6 decode"); + if (decodeRC6(results, offset)) return true; +#endif +#if DECODE_RCMM + DPRINTLN("Attempting RC-MM decode"); + if (decodeRCMM(results, offset)) return true; +#endif +#if DECODE_FUJITSU_AC + // Fujitsu A/C needs to precede Panasonic and Denon as it has a short + // message which looks exactly the same as a Panasonic/Denon message. + DPRINTLN("Attempting Fujitsu A/C decode"); + if (decodeFujitsuAC(results, offset)) return true; +#endif +#if DECODE_DENON + // Denon needs to precede Panasonic as it is a special case of Panasonic. + DPRINTLN("Attempting Denon decode"); + if (decodeDenon(results, offset, kDenon48Bits) || + decodeDenon(results, offset, kDenonBits) || + decodeDenon(results, offset, kDenonLegacyBits)) + return true; +#endif +#if DECODE_PANASONIC + DPRINTLN("Attempting Panasonic decode"); + if (decodePanasonic(results, offset)) return true; +#endif +#if DECODE_LG + DPRINTLN("Attempting LG (28-bit) decode"); + if (decodeLG(results, offset, kLgBits, true)) return true; + DPRINTLN("Attempting LG (32-bit) decode"); + // LG32 should be tried before Samsung + if (decodeLG(results, offset, kLg32Bits, true)) return true; +#endif +#if DECODE_GICABLE + // Note: Needs to happen before JVC decode, because it looks similar except + // with a required NEC-like repeat code. + DPRINTLN("Attempting GICable decode"); + if (decodeGICable(results, offset)) return true; +#endif +#if DECODE_JVC + DPRINTLN("Attempting JVC decode"); + if (decodeJVC(results, offset)) return true; +#endif +#if DECODE_SAMSUNG + DPRINTLN("Attempting SAMSUNG decode"); + if (decodeSAMSUNG(results, offset)) return true; +#endif +#if DECODE_SAMSUNG36 + DPRINTLN("Attempting Samsung36 decode"); + if (decodeSamsung36(results, offset)) return true; +#endif +#if DECODE_WHYNTER + DPRINTLN("Attempting Whynter decode"); + if (decodeWhynter(results, offset)) return true; +#endif +#if DECODE_DISH + DPRINTLN("Attempting DISH decode"); + if (decodeDISH(results, offset)) return true; +#endif +#if DECODE_SHARP + DPRINTLN("Attempting Sharp decode"); + if (decodeSharp(results, offset)) return true; +#endif +#if DECODE_BOSCH144 + DPRINTLN("Attempting Bosch 144-bit decode"); + // Bosch is similar to Coolix, so it must be attempted before decodeCOOLIX. + if (decodeBosch144(results, offset)) return true; +#endif // DECODE_BOSCH144 +#if DECODE_COOLIX + DPRINTLN("Attempting Coolix 24-bit decode"); + if (decodeCOOLIX(results, offset)) return true; +#endif // DECODE_COOLIX +#if DECODE_NIKAI + DPRINTLN("Attempting Nikai decode"); + if (decodeNikai(results, offset)) return true; +#endif +#if DECODE_KELVINATOR + // Kelvinator based-devices use a similar code to Gree ones, to avoid false + // matches this needs to happen before decodeGree(). + DPRINTLN("Attempting Kelvinator decode"); + if (decodeKelvinator(results, offset)) return true; +#endif +#if DECODE_DAIKIN + DPRINTLN("Attempting Daikin decode"); + if (decodeDaikin(results, offset)) return true; +#endif +#if DECODE_DAIKIN2 + DPRINTLN("Attempting Daikin2 decode"); + if (decodeDaikin2(results, offset)) return true; +#endif +#if DECODE_DAIKIN216 + DPRINTLN("Attempting Daikin216 decode"); + if (decodeDaikin216(results, offset)) return true; +#endif +#if DECODE_TOSHIBA_AC + DPRINTLN("Attempting Toshiba AC 72bit decode"); + if (decodeToshibaAC(results, offset)) return true; + DPRINTLN("Attempting Toshiba AC 80bit decode"); + if (decodeToshibaAC(results, offset, kToshibaACBitsLong)) return true; + DPRINTLN("Attempting Toshiba AC 56bit decode"); + if (decodeToshibaAC(results, offset, kToshibaACBitsShort)) return true; +#endif +#if DECODE_MIDEA + DPRINTLN("Attempting Midea decode"); + if (decodeMidea(results, offset)) return true; +#endif +#if DECODE_MAGIQUEST + DPRINTLN("Attempting Magiquest decode"); + if (decodeMagiQuest(results, offset)) return true; +#endif + /* NOTE: Disabled due to poor quality. +#if DECODE_SANYO + // The Sanyo S866500B decoder is very poor quality & depricated. + // *IF* you are going to enable it, do it near last to avoid false positive + // matches. + DPRINTLN("Attempting Sanyo SA8650B decode"); + if (decodeSanyo(results, offset)) + return true; +#endif + */ +#if DECODE_NEC + // Some devices send NEC-like codes that don't follow the true NEC spec. + // This should detect those. e.g. Apple TV remote etc. + // This needs to be done after all other codes that use strict and some + // other protocols that are NEC-like as well, as turning off strict may + // cause this to match other valid protocols. + DPRINTLN("Attempting NEC (non-strict) decode"); + if (decodeNEC(results, offset, kNECBits, false)) { + results->decode_type = NEC_LIKE; + return true; + } +#endif +#if DECODE_LASERTAG + DPRINTLN("Attempting Lasertag decode"); + if (decodeLasertag(results, offset)) return true; +#endif +#if DECODE_GREE + // Gree based-devices use a similar code to Kelvinator ones, to avoid false + // matches this needs to happen after decodeKelvinator(). + DPRINTLN("Attempting Gree decode"); + if (decodeGree(results, offset)) return true; +#endif +#if DECODE_HAIER_AC + DPRINTLN("Attempting Haier AC decode"); + if (decodeHaierAC(results, offset)) return true; +#endif +#if DECODE_HAIER_AC_YRW02 + DPRINTLN("Attempting Haier AC YR-W02 decode"); + if (decodeHaierACYRW02(results, offset)) return true; +#endif +#if DECODE_HAIER_AC176 + DPRINTLN("Attempting Haier AC 176 bit decode"); + if (decodeHaierAC176(results, offset)) return true; +#endif // DECODE_HAIER_AC176 +#if DECODE_HITACHI_AC424 + // HitachiAc424 should be checked before HitachiAC, HitachiAC2, + // & HitachiAC184 + DPRINTLN("Attempting Hitachi AC 424 decode"); + if (decodeHitachiAc424(results, offset, kHitachiAc424Bits)) return true; +#endif // DECODE_HITACHI_AC424 +#if DECODE_MITSUBISHI136 + // Needs to happen before HitachiAc3 decode. + DPRINTLN("Attempting Mitsubishi136 decode"); + if (decodeMitsubishi136(results, offset)) return true; +#endif // DECODE_MITSUBISHI136 +#if DECODE_HITACHI_AC3 + // HitachiAc3 should be checked before HitachiAC & HitachiAC2 + // Attempt normal before the short version. + DPRINTLN("Attempting Hitachi AC3 decode"); + // Order these in decreasing bit size, as it is more optimal. + if (decodeHitachiAc3(results, offset, kHitachiAc3Bits) || + decodeHitachiAc3(results, offset, kHitachiAc3Bits - 4 * 8) || + decodeHitachiAc3(results, offset, kHitachiAc3Bits - 6 * 8) || + decodeHitachiAc3(results, offset, kHitachiAc3MinBits + 2 * 8) || + decodeHitachiAc3(results, offset, kHitachiAc3MinBits)) + return true; +#endif // DECODE_HITACHI_AC3 +#if DECODE_HITACHI_AC344 + // HitachiAC344 should be checked before HitachiAC + DPRINTLN("Attempting Hitachi AC344 decode"); + if (decodeHitachiAC(results, offset, kHitachiAc344Bits, true, false)) + return true; +#endif // DECODE_HITACHI_AC344 +#if DECODE_HITACHI_AC264 + // HitachiAC264 should be checked before HitachiAC + DPRINTLN("Attempting Hitachi AC264 decode"); + if (decodeHitachiAC(results, offset, kHitachiAc264Bits, true, false)) + return true; +#endif // DECODE_HITACHI_AC264 +#if DECODE_HITACHI_AC296 + // HitachiAC296 should be checked before HitachiAC + DPRINTLN("Attempting Hitachi AC296 decode"); + if (decodeHitachiAc296(results, offset, kHitachiAc296Bits, true)) + return true; +#endif // DECODE_HITACHI_AC296 +#if DECODE_HITACHI_AC2 + // HitachiAC2 should be checked before HitachiAC + DPRINTLN("Attempting Hitachi AC2 decode"); + if (decodeHitachiAC(results, offset, kHitachiAc2Bits)) return true; +#endif // DECODE_HITACHI_AC2 +#if DECODE_HITACHI_AC + DPRINTLN("Attempting Hitachi AC decode"); + if (decodeHitachiAC(results, offset, kHitachiAcBits)) return true; +#endif +#if DECODE_HITACHI_AC1 + DPRINTLN("Attempting Hitachi AC1 decode"); + if (decodeHitachiAC(results, offset, kHitachiAc1Bits)) return true; +#endif +#if DECODE_WHIRLPOOL_AC + DPRINTLN("Attempting Whirlpool AC decode"); + if (decodeWhirlpoolAC(results, offset)) return true; +#endif +#if DECODE_SAMSUNG_AC + DPRINTLN("Attempting Samsung AC (extended) decode"); + // Check the extended size first, as it should fail fast due to longer + // length. + if (decodeSamsungAC(results, offset, kSamsungAcExtendedBits)) return true; + // Now check for the more common length. + DPRINTLN("Attempting Samsung AC decode"); + if (decodeSamsungAC(results, offset, kSamsungAcBits)) return true; +#endif +#if DECODE_ELECTRA_AC + DPRINTLN("Attempting Electra AC decode"); + if (decodeElectraAC(results, offset)) return true; +#endif +#if DECODE_PANASONIC_AC + DPRINTLN("Attempting Panasonic AC decode"); + if (decodePanasonicAC(results, offset)) return true; + DPRINTLN("Attempting Panasonic AC short decode"); + if (decodePanasonicAC(results, offset, kPanasonicAcShortBits)) return true; +#endif +#if DECODE_LUTRON + DPRINTLN("Attempting Lutron decode"); + if (decodeLutron(results, offset)) return true; +#endif +#if DECODE_MWM + DPRINTLN("Attempting MWM decode"); + if (decodeMWM(results, offset)) return true; +#endif +#if DECODE_VESTEL_AC + DPRINTLN("Attempting Vestel AC decode"); + if (decodeVestelAc(results, offset)) return true; +#endif +#if DECODE_MITSUBISHI112 || DECODE_TCL112AC + // Mitsubish112 and Tcl112 share the same decoder. + DPRINTLN("Attempting Mitsubishi112/TCL112AC decode"); + if (decodeMitsubishi112(results, offset)) return true; +#endif // DECODE_MITSUBISHI112 || DECODE_TCL112AC +#if DECODE_TECO + DPRINTLN("Attempting Teco decode"); + if (decodeTeco(results, offset)) return true; +#endif +#if DECODE_LEGOPF + DPRINTLN("Attempting LEGOPF decode"); + if (decodeLegoPf(results, offset)) return true; +#endif +#if DECODE_MITSUBISHIHEAVY + DPRINTLN("Attempting MITSUBISHIHEAVY (152 bit) decode"); + if (decodeMitsubishiHeavy(results, offset, kMitsubishiHeavy152Bits)) + return true; + DPRINTLN("Attempting MITSUBISHIHEAVY (88 bit) decode"); + if (decodeMitsubishiHeavy(results, offset, kMitsubishiHeavy88Bits)) + return true; +#endif +#if DECODE_ARGO + DPRINTLN("Attempting Argo WREM3 decode (AC Control)"); + if (decodeArgoWREM3(results, offset, kArgo3AcControlStateLength * 8, true)) + return true; + DPRINTLN("Attempting Argo WREM3 decode (iFeel report)"); + if (decodeArgoWREM3(results, offset, kArgo3iFeelReportStateLength * 8, true)) + return true; + DPRINTLN("Attempting Argo WREM3 decode (Config)"); + if (decodeArgoWREM3(results, offset, kArgo3ConfigStateLength * 8, true)) + return true; + DPRINTLN("Attempting Argo WREM3 decode (Timer)"); + if (decodeArgoWREM3(results, offset, kArgo3TimerStateLength * 8, true)) + return true; + DPRINTLN("Attempting Argo WREM2 decode"); + if (decodeArgo(results, offset, kArgoBits) || + decodeArgo(results, offset, kArgoShortBits, false)) return true; +#endif // DECODE_ARGO +#if DECODE_SHARP_AC + DPRINTLN("Attempting SHARP_AC decode"); + if (decodeSharpAc(results, offset)) return true; +#endif +#if DECODE_GOODWEATHER + DPRINTLN("Attempting GOODWEATHER decode"); + if (decodeGoodweather(results, offset)) return true; +#endif // DECODE_GOODWEATHER +#if DECODE_INAX + DPRINTLN("Attempting Inax decode"); + if (decodeInax(results, offset)) return true; +#endif // DECODE_INAX +#if DECODE_TROTEC + DPRINTLN("Attempting Trotec decode"); + if (decodeTrotec(results, offset)) return true; +#endif // DECODE_TROTEC +#if DECODE_TROTEC_3550 + DPRINTLN("Attempting Trotec 3550 decode"); + if (decodeTrotec3550(results, offset)) return true; +#endif // DECODE_TROTEC_3550 +#if DECODE_DAIKIN160 + DPRINTLN("Attempting Daikin160 decode"); + if (decodeDaikin160(results, offset)) return true; +#endif // DECODE_DAIKIN160 +#if DECODE_NEOCLIMA + DPRINTLN("Attempting Neoclima decode"); + if (decodeNeoclima(results, offset)) return true; +#endif // DECODE_NEOCLIMA +#if DECODE_DAIKIN176 + DPRINTLN("Attempting Daikin176 decode"); + if (decodeDaikin176(results, offset)) return true; +#endif // DECODE_DAIKIN176 +#if DECODE_DAIKIN128 + DPRINTLN("Attempting Daikin128 decode"); + if (decodeDaikin128(results, offset)) return true; +#endif // DECODE_DAIKIN128 +#if DECODE_AMCOR + DPRINTLN("Attempting Amcor decode"); + if (decodeAmcor(results, offset)) return true; +#endif // DECODE_AMCOR +#if DECODE_DAIKIN152 + DPRINTLN("Attempting Daikin152 decode"); + if (decodeDaikin152(results, offset)) return true; +#endif // DECODE_DAIKIN152 +#if DECODE_SYMPHONY + DPRINTLN("Attempting Symphony decode"); + if (decodeSymphony(results, offset)) return true; +#endif // DECODE_SYMPHONY +#if DECODE_DAIKIN64 + DPRINTLN("Attempting Daikin64 decode"); + if (decodeDaikin64(results, offset)) return true; +#endif // DECODE_DAIKIN64 +#if DECODE_AIRWELL + DPRINTLN("Attempting Airwell decode"); + if (decodeAirwell(results, offset)) return true; +#endif // DECODE_AIRWELL +#if DECODE_DELONGHI_AC + DPRINTLN("Attempting Delonghi AC decode"); + if (decodeDelonghiAc(results, offset)) return true; +#endif // DECODE_DELONGHI_AC +#if DECODE_DOSHISHA + DPRINTLN("Attempting Doshisha decode"); + if (decodeDoshisha(results, offset)) return true; +#endif // DECODE_DOSHISHA +#if DECODE_TRUMA + // Needs to happen before decodeMultibrackets() as they can appear similar. + DPRINTLN("Attempting Truma decode"); + if (decodeTruma(results, offset)) return true; +#endif // DECODE_TRUMA +#if DECODE_MULTIBRACKETS + DPRINTLN("Attempting Multibrackets decode"); + if (decodeMultibrackets(results, offset)) return true; +#endif // DECODE_MULTIBRACKETS +#if DECODE_CARRIER_AC40 + DPRINTLN("Attempting Carrier 40bit decode"); + if (decodeCarrierAC40(results, offset)) return true; +#endif // DECODE_CARRIER_AC40 +#if DECODE_CARRIER_AC64 + DPRINTLN("Attempting Carrier 64bit decode"); + if (decodeCarrierAC64(results, offset)) return true; +#endif // DECODE_CARRIER_AC64 +#if DECODE_TECHNIBEL_AC + DPRINTLN("Attempting Technibel AC decode"); + if (decodeTechnibelAc(results, offset)) return true; +#endif // DECODE_TECHNIBEL_AC +#if DECODE_CORONA_AC + DPRINTLN("Attempting CoronaAc decode"); + if (decodeCoronaAc(results, offset)) return true; +#endif // DECODE_CORONA_AC +#if DECODE_MIDEA24 + DPRINTLN("Attempting Midea-Nec decode"); + if (decodeMidea24(results, offset)) return true; +#endif // DECODE_MIDEA24 +#if DECODE_ZEPEAL + DPRINTLN("Attempting Zepeal decode"); + if (decodeZepeal(results, offset)) return true; +#endif // DECODE_ZEPEAL +#if DECODE_SANYO_AC + DPRINTLN("Attempting Sanyo AC decode"); + if (decodeSanyoAc(results, offset)) return true; +#endif // DECODE_SANYO_AC +#if DECODE_VOLTAS + DPRINTLN("Attempting Voltas decode"); + if (decodeVoltas(results)) return true; +#endif // DECODE_VOLTAS +#if DECODE_METZ + DPRINTLN("Attempting Metz decode"); + if (decodeMetz(results, offset)) return true; +#endif // DECODE_METZ +#if DECODE_TRANSCOLD + DPRINTLN("Attempting Transcold decode"); + if (decodeTranscold(results, offset)) return true; +#endif // DECODE_TRANSCOLD +#if DECODE_MIRAGE + DPRINTLN("Attempting Mirage decode"); + if (decodeMirage(results, offset)) return true; +#endif // DECODE_MIRAGE +#if DECODE_ELITESCREENS + DPRINTLN("Attempting EliteScreens decode"); + if (decodeElitescreens(results, offset)) return true; +#endif // DECODE_ELITESCREENS +#if DECODE_PANASONIC_AC32 + DPRINTLN("Attempting Panasonic AC (32bit) long decode"); + if (decodePanasonicAC32(results, offset, kPanasonicAc32Bits)) return true; + DPRINTLN("Attempting Panasonic AC (32bit) short decode"); + if (decodePanasonicAC32(results, offset, kPanasonicAc32Bits / 2)) + return true; +#endif // DECODE_PANASONIC_AC32 +#if DECODE_ECOCLIM + DPRINTLN("Attempting Ecoclim decode"); + if (decodeEcoclim(results, offset, kEcoclimBits) || + decodeEcoclim(results, offset, kEcoclimShortBits)) return true; +#endif // DECODE_ECOCLIM +#if DECODE_XMP + DPRINTLN("Attempting XMP decode"); + if (decodeXmp(results, offset, kXmpBits)) return true; +#endif // DECODE_XMP +#if DECODE_TEKNOPOINT + DPRINTLN("Attempting Teknopoint decode"); + if (decodeTeknopoint(results, offset)) return true; +#endif // DECODE_TEKNOPOINT +#if DECODE_KELON168 + DPRINTLN("Attempting Kelon 168-bit decode"); + if (decodeKelon168(results, offset)) return true; +#endif // DECODE_KELON168 +#if DECODE_KELON + DPRINTLN("Attempting Kelon 48-bit decode"); + if (decodeKelon(results, offset)) return true; +#endif // DECODE_KELON +#if DECODE_SANYO_AC88 + DPRINTLN("Attempting SanyoAc88 decode"); + if (decodeSanyoAc88(results, offset)) return true; +#endif // DECODE_SANYO_AC88 +#if DECODE_BOSE + DPRINTLN("Attempting Bose decode"); + if (decodeBose(results, offset)) return true; +#endif // DECODE_BOSE +#if DECODE_ARRIS + DPRINTLN("Attempting Arris decode"); + if (decodeArris(results, offset)) return true; +#endif // DECODE_ARRIS +#if DECODE_RHOSS + DPRINTLN("Attempting Rhoss decode"); + if (decodeRhoss(results, offset)) return true; +#endif // DECODE_RHOSS +#if DECODE_AIRTON + DPRINTLN("Attempting Airton decode"); + if (decodeAirton(results, offset)) return true; +#endif // DECODE_AIRTON +#if DECODE_COOLIX48 + DPRINTLN("Attempting Coolix 48-bit decode"); + if (decodeCoolix48(results, offset)) return true; +#endif // DECODE_COOLIX48 +#if DECODE_DAIKIN200 + DPRINTLN("Attempting Daikin 200-bit decode"); + if (decodeDaikin200(results, offset)) return true; +#endif // DECODE_DAIKIN200 +#if DECODE_HAIER_AC160 + DPRINTLN("Attempting Haier AC 160 bit decode"); + if (decodeHaierAC160(results, offset)) return true; +#endif // DECODE_HAIER_AC160 +#if DECODE_CARRIER_AC128 + DPRINTLN("Attempting Carrier AC 128-bit decode"); + if (decodeCarrierAC128(results, offset)) return true; +#endif // DECODE_CARRIER_AC128 +#if DECODE_TOTO + DPRINTLN("Attempting Toto 48/24-bit decode"); + if (decodeToto(results, offset, kTotoLongBits) || // Long needs to be first + decodeToto(results, offset, kTotoShortBits)) return true; +#endif // DECODE_TOTO +#if DECODE_CLIMABUTLER + DPRINTLN("Attempting ClimaButler decode"); + if (decodeClimaButler(results)) return true; +#endif // DECODE_CLIMABUTLER +#if DECODE_TCL96AC + DPRINTLN("Attempting TCL AC 96-bit decode"); + if (decodeTcl96Ac(results, offset)) return true; +#endif // DECODE_TCL96AC +#if DECODE_SANYO_AC152 + DPRINTLN("Attempting Sanyo AC 152-bit decode"); + if (decodeSanyoAc152(results, offset)) return true; +#endif // DECODE_SANYO_AC152 +#if DECODE_DAIKIN312 + DPRINTLN("Attempting Daikin 312-bit decode"); + if (decodeDaikin312(results, offset)) return true; +#endif // DECODE_DAIKIN312 +#if DECODE_GORENJE + DPRINTLN("Attempting GORENJE decode"); + if (decodeGorenje(results, offset)) return true; +#endif // DECODE_GORENJE +#if DECODE_WOWWEE + DPRINTLN("Attempting WOWWEE decode"); + if (decodeWowwee(results, offset)) return true; +#endif // DECODE_WOWWEE +#if DECODE_CARRIER_AC84 + DPRINTLN("Attempting Carrier A/C 84-bit decode"); + if (decodeCarrierAC84(results, offset)) return true; +#endif // DECODE_CARRIER_AC84 + // Typically new protocols are added above this line. + } +#if DECODE_HASH + // decodeHash returns a hash on any input. + // Thus, it needs to be last in the list. + // If you add any decodes, add them before this. + if (decodeHash(results)) { + return true; + } +#endif // DECODE_HASH + // Throw away and start over + if (!resumed) // Check if we have already resumed. + resume(); + return false; +} // NOLINT(readability/fn_size) + +/// Convert the tolerance percentage into something valid. +/// @param[in] percentage An integer percentage. +uint8_t IRrecv::_validTolerance(const uint8_t percentage) { + return (percentage > 100) ? _tolerance : percentage; +} + +/// Calculate the lower bound of the nr. of ticks. +/// @param[in] usecs Nr. of uSeconds. +/// @param[in] tolerance Percent as an integer. e.g. 10 is 10% +/// @param[in] delta A non-scaling amount to reduce usecs by. +/// @return Nr. of ticks. +uint32_t IRrecv::ticksLow(const uint32_t usecs, const uint8_t tolerance, + const uint16_t delta) { + // max() used to ensure the result can't drop below 0 before the cast. + return ((uint32_t)::max( + (int32_t)(usecs * (1.0 - _validTolerance(tolerance) / 100.0) - delta), + (int32_t)0)); +} + +/// Calculate the upper bound of the nr. of ticks. +/// @param[in] usecs Nr. of uSeconds. +/// @param[in] tolerance Percent as an integer. e.g. 10 is 10% +/// @param[in] delta A non-scaling amount to increase usecs by. +/// @return Nr. of ticks. +uint32_t IRrecv::ticksHigh(const uint32_t usecs, const uint8_t tolerance, + const uint16_t delta) { + return ((uint32_t)(usecs * (1.0 + _validTolerance(tolerance) / 100.0)) + 1 + + delta); +} + +/// Check if we match a pulse(measured) with the desired within +/// +/-tolerance percent and/or +/- a fixed delta range. +/// @param[in] measured The recorded period of the signal pulse. +/// @param[in] desired The expected period (in usecs) we are matching against. +/// @param[in] tolerance A percentage expressed as an integer. e.g. 10 is 10%. +/// @param[in] delta A non-scaling (+/-) error margin (in useconds). +/// @return A Boolean. true if it matches, false if it doesn't. +bool IRrecv::match(uint32_t measured, uint32_t desired, uint8_t tolerance, + uint16_t delta) { + measured *= kRawTick; // Convert to uSecs. + DPRINT("Matching: "); + DPRINT(ticksLow(desired, tolerance, delta)); + DPRINT(" <= "); + DPRINT(measured); + DPRINT(" <= "); + DPRINTLN(ticksHigh(desired, tolerance, delta)); +#ifdef UNIT_TEST + // Sanity checks that we don't have values that cause integer over/underflow. + // Only performed during testing so there is no performance hit in normal + // operation. + assert(ticksLow(desired, tolerance, delta) <= desired); + // Check if we overflowed. (UINT32_MAX >> 3 is approx 9 minutes!) + assert(ticksHigh(desired, tolerance, delta) < UINT32_MAX >> 3); + // Check if our high mark is below where we started. This could happen. + // If there is a legit case, then this should be removed. + assert(ticksHigh(desired, tolerance, delta) >= desired); +#endif // UNIT_TEST + return (measured >= ticksLow(desired, tolerance, delta) && + measured <= ticksHigh(desired, tolerance, delta)); +} + +/// Check if we match a pulse(measured) of at least desired within +/// tolerance percent and/or a fixed delta margin. +/// @param[in] measured The recorded period of the signal pulse. +/// @param[in] desired The expected period (in usecs) we are matching against. +/// @param[in] tolerance A percentage expressed as an integer. e.g. 10 is 10%. +/// @param[in] delta A non-scaling amount to reduce usecs by. +/// @return A Boolean. true if it matches, false if it doesn't. +bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired, + uint8_t tolerance, uint16_t delta) { + measured *= kRawTick; // Convert to uSecs. + DPRINT("Matching ATLEAST "); + DPRINT(measured); + DPRINT(" vs "); + DPRINT(desired); + DPRINT(". Matching: "); + DPRINT(measured); + DPRINT(" >= "); + DPRINT(ticksLow(::min(desired, (uint32_t)MS_TO_USEC(params.timeout)), + tolerance, delta)); + DPRINT(" [min("); + DPRINT(ticksLow(desired, tolerance, delta)); + DPRINT(", "); + DPRINT(ticksLow(MS_TO_USEC(params.timeout), tolerance, delta)); + DPRINTLN(")]"); +#ifdef UNIT_TEST + // Sanity checks that we don't have values that cause integer over/underflow. + // Only performed during testing so there is no performance hit in normal + // operation. + assert(ticksLow(desired, tolerance, delta) <= desired); + // Check if we overflowed. (UINT32_MAX >> 3 is approx 9 minutes!) + assert(ticksHigh(desired, tolerance, delta) < UINT32_MAX >> 3); + // Check if our high mark is below where we started. This could happen. + // If there is a legit case, then this should be removed. + assert(ticksHigh(desired, tolerance, delta) >= desired); +#endif // UNIT_TEST + // We really should never get a value of 0, except as the last value + // in the buffer. If that is the case, then assume infinity and return true. + if (measured == 0) return true; + return measured >= ticksLow(::min(desired, + (uint32_t)MS_TO_USEC(params.timeout)), + tolerance, delta); +} + +/// Check if we match a mark signal(measured) with the desired within +/// +/-tolerance percent, after an expected is excess is added. +/// @param[in] measured The recorded period of the signal pulse. +/// @param[in] desired The expected period (in usecs) we are matching against. +/// @param[in] tolerance A percentage expressed as an integer. e.g. 10 is 10%. +/// @param[in] excess A non-scaling amount to reduce usecs by. +/// @return A Boolean. true if it matches, false if it doesn't. +bool IRrecv::matchMark(uint32_t measured, uint32_t desired, uint8_t tolerance, + int16_t excess) { + DPRINT("Matching MARK "); + DPRINT(measured * kRawTick); + DPRINT(" vs "); + DPRINT(desired); + DPRINT(" + "); + DPRINT(excess); + DPRINT(". "); + return match(measured, desired + excess, tolerance); +} + +/// Check if we match a mark signal(measured) with the desired within a +/// range (in uSeconds) either side of the desired, after an expected is excess +/// is added. +/// @param[in] measured The recorded period of the signal pulse. +/// @param[in] desired The expected period (in usecs) we are matching against. +/// @param[in] range The range limit from desired to accept in uSeconds. +/// @param[in] excess A non-scaling amount to reduce usecs by. +/// @return A Boolean. true if it matches, false if it doesn't. +bool IRrecv::matchMarkRange(const uint32_t measured, const uint32_t desired, + const uint16_t range, const int16_t excess) { + DPRINT("Matching MARK "); + DPRINT(measured * kRawTick); + DPRINT(" vs "); + DPRINT(desired); + DPRINT(" + "); + DPRINT(excess); + DPRINT(". "); + return match(measured, desired + excess, 0, range); +} + +/// Check if we match a space signal(measured) with the desired within +/// +/-tolerance percent, after an expected is excess is removed. +/// @param[in] measured The recorded period of the signal pulse. +/// @param[in] desired The expected period (in usecs) we are matching against. +/// @param[in] tolerance A percentage expressed as an integer. e.g. 10 is 10%. +/// @param[in] excess A non-scaling amount to reduce usecs by. +/// @return A Boolean. true if it matches, false if it doesn't. +bool IRrecv::matchSpace(uint32_t measured, uint32_t desired, uint8_t tolerance, + int16_t excess) { + DPRINT("Matching SPACE "); + DPRINT(measured * kRawTick); + DPRINT(" vs "); + DPRINT(desired); + DPRINT(" - "); + DPRINT(excess); + DPRINT(". "); + return match(measured, desired - excess, tolerance); +} + +/// Check if we match a space signal(measured) with the desired within a +/// range (in uSeconds) either side of the desired, after an expected is excess +/// is removed. +/// @param[in] measured The recorded period of the signal pulse. +/// @param[in] desired The expected period (in usecs) we are matching against. +/// @param[in] range The range limit from desired to accept in uSeconds. +/// @param[in] excess A non-scaling amount to reduce usecs by. +/// @return A Boolean. true if it matches, false if it doesn't. +bool IRrecv::matchSpaceRange(const uint32_t measured, const uint32_t desired, + const uint16_t range, const int16_t excess) { + DPRINT("Matching SPACE "); + DPRINT(measured * kRawTick); + DPRINT(" vs "); + DPRINT(desired); + DPRINT(" - "); + DPRINT(excess); + DPRINT(". "); + return match(measured, desired - excess, 0, range); +} + +#if DECODE_HASH +/// Compare two tick values. +/// @param[in] oldval Nr. of ticks. +/// @param[in] newval Nr. of ticks. +/// @return 0 if newval is shorter, 1 if it is equal, & 2 if it is longer. +/// @note Use a tolerance of 20% +uint16_t IRrecv::compare(const uint16_t oldval, const uint16_t newval) { + if (newval < oldval * 0.8) + return 0; + else if (oldval < newval * 0.8) + return 2; + else + return 1; +} + +/// Decode any arbitrary IR message into a 32-bit code value. +/// Instead of decoding using a standard encoding scheme +/// (e.g. Sony, NEC, RC5), the code is hashed to a 32-bit value. +/// +/// The algorithm: look at the sequence of MARK signals, and see if each one +/// is shorter (0), the same length (1), or longer (2) than the previous. +/// Do the same with the SPACE signals. Hash the resulting sequence of 0's, +/// 1's, and 2's to a 32-bit value. This will give a unique value for each +/// different code (probably), for most code systems. +/// @see http://arcfn.com/2010/01/using-arbitrary-remotes-with-arduino.html +/// @note This isn't a "real" decoding, just an arbitrary value. +/// Hopefully this code is unique for each button. +bool IRrecv::decodeHash(decode_results *results) { + // Require at least some samples to prevent triggering on noise + if (results->rawlen < _unknown_threshold) return false; + int32_t hash = kFnvBasis32; + // 'rawlen - 2' to avoid the look ahead from going out of bounds. + // Should probably be -3 to avoid comparing the trailing space entry, + // however it is left this way for compatibility with previously captured + // values. + for (uint16_t i = 1; i < results->rawlen - 2; i++) { + uint16_t value = compare(results->rawbuf[i], results->rawbuf[i + 2]); + // Add value into the hash + hash = (hash * kFnvPrime32) ^ value; + } + results->value = hash & 0xFFFFFFFF; + results->bits = results->rawlen / 2; + results->address = 0; + results->command = 0; + results->decode_type = UNKNOWN; + return true; +} +#endif // DECODE_HASH + +/// Match & decode the typical data section of an IR message. +/// The data value is stored in the least significant bits reguardless of the +/// bit ordering requested. +/// @param[in] data_ptr A pointer to where we are at in the capture buffer. +/// @param[in] nbits Nr. of data bits we expect. +/// @param[in] onemark Nr. of uSeconds in an expected mark signal for a '1' bit. +/// @param[in] onespace Nr. of uSecs in an expected space signal for a '1' bit. +/// @param[in] zeromark Nr. of uSecs in an expected mark signal for a '0' bit. +/// @param[in] zerospace Nr. of uSecs in an expected space signal for a '0' bit. +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @param[in] expectlastspace Do we expect a space at the end of the message? +/// @return A match_result_t structure containing the success (or not), the +/// data value, and how many buffer entries were used. +match_result_t IRrecv::matchData( + volatile uint16_t *data_ptr, const uint16_t nbits, const uint16_t onemark, + const uint32_t onespace, const uint16_t zeromark, const uint32_t zerospace, + const uint8_t tolerance, const int16_t excess, const bool MSBfirst, + const bool expectlastspace) { + match_result_t result; + result.success = false; // Fail by default. + result.data = 0; + if (expectlastspace) { // We are expecting data with a final space. + for (result.used = 0; result.used < nbits * 2; + result.used += 2, data_ptr += 2) { + // Is the bit a '1'? + if (matchMark(*data_ptr, onemark, tolerance, excess) && + matchSpace(*(data_ptr + 1), onespace, tolerance, excess)) { + result.data = (result.data << 1) | 1; + } else if (matchMark(*data_ptr, zeromark, tolerance, excess) && + matchSpace(*(data_ptr + 1), zerospace, tolerance, excess)) { + result.data <<= 1; // The bit is a '0'. + } else { + if (!MSBfirst) result.data = reverseBits(result.data, result.used / 2); + return result; // It's neither, so fail. + } + } + result.success = true; + } else { // We are expecting data without a final space. + // Match all but the last bit, as it may not match easily. + result = matchData(data_ptr, nbits ? nbits - 1 : 0, onemark, onespace, + zeromark, zerospace, tolerance, excess, true, true); + if (result.success) { + // Is the bit a '1'? + if (matchMark(*(data_ptr + result.used), onemark, tolerance, excess)) + result.data = (result.data << 1) | 1; + else if (matchMark(*(data_ptr + result.used), zeromark, tolerance, + excess)) + result.data <<= 1; // The bit is a '0'. + else + result.success = false; + if (result.success) result.used++; + } + } + if (!MSBfirst) result.data = reverseBits(result.data, nbits); + return result; +} + +/// Match & decode the typical data section of an IR message. +/// The bytes are stored at result_ptr. The first byte in the result equates to +/// the first byte encountered, and so on. +/// @param[in] data_ptr A pointer to where we are at in the capture buffer. +/// @param[out] result_ptr A ptr to where to start storing the bytes we decoded. +/// @param[in] remaining The size of the capture buffer remaining. +/// @param[in] nbytes Nr. of data bytes we expect. +/// @param[in] onemark Nr. of uSeconds in an expected mark signal for a '1' bit. +/// @param[in] onespace Nr. of uSecs in an expected space signal for a '1' bit. +/// @param[in] zeromark Nr. of uSecs in an expected mark signal for a '0' bit. +/// @param[in] zerospace Nr. of uSecs in an expected space signal for a '0' bit. +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @param[in] expectlastspace Do we expect a space at the end of the message? +/// @return If successful, how many buffer entries were used. Otherwise 0. +uint16_t IRrecv::matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr, + const uint16_t remaining, const uint16_t nbytes, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint8_t tolerance, const int16_t excess, + const bool MSBfirst, const bool expectlastspace) { + // Check if there is enough capture buffer to possibly have the desired bytes. + if (remaining + expectlastspace < (nbytes * 8 * 2) + 1) + return 0; // Nope, so abort. + uint16_t offset = 0; + for (uint16_t byte_pos = 0; byte_pos < nbytes; byte_pos++) { + bool lastspace = (byte_pos + 1 == nbytes) ? expectlastspace : true; + match_result_t result = matchData(data_ptr + offset, 8, onemark, onespace, + zeromark, zerospace, tolerance, excess, + MSBfirst, lastspace); + if (result.success == false) return 0; // Fail + result_ptr[byte_pos] = (uint8_t)result.data; + offset += result.used; + } + return offset; +} + +/// Match & decode a generic/typical IR message. +/// The data is stored in result_bits_ptr or result_bytes_ptr depending on flag +/// `use_bits`. +/// @note Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean +/// skip that requirement. +/// +/// @param[in] data_ptr A pointer to where we are at in the capture buffer. +/// @param[out] result_bits_ptr A pointer to where to start storing the bits we +/// decoded. +/// @param[out] result_bytes_ptr A pointer to where to start storing the bytes +/// we decoded. +/// @param[in] use_bits A flag indicating if we are to decode bits or bytes. +/// @param[in] remaining The size of the capture buffer remaining. +/// @param[in] nbits Nr. of data bits we expect. +/// @param[in] hdrmark Nr. of uSeconds for the expected header mark signal. +/// @param[in] hdrspace Nr. of uSeconds for the expected header space signal. +/// @param[in] onemark Nr. of uSeconds in an expected mark signal for a '1' bit. +/// @param[in] onespace Nr. of uSecs in an expected space signal for a '1' bit. +/// @param[in] zeromark Nr. of uSecs in an expected mark signal for a '0' bit. +/// @param[in] zerospace Nr. of uSecs in an expected space signal for a '0' bit. +/// @param[in] footermark Nr. of uSeconds for the expected footer mark signal. +/// @param[in] footerspace Nr. of uSeconds for the expected footer space/gap +/// signal. +/// @param[in] atleast Is the match on the footerspace a matchAtLeast or +/// matchSpace? +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @return If successful, how many buffer entries were used. Otherwise 0. +uint16_t IRrecv::_matchGeneric(volatile uint16_t *data_ptr, + uint64_t *result_bits_ptr, + uint8_t *result_bytes_ptr, + const bool use_bits, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t onemark, + const uint32_t onespace, + const uint16_t zeromark, + const uint32_t zerospace, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast, + const uint8_t tolerance, + const int16_t excess, + const bool MSBfirst) { + // If we are expecting byte sizes, check it's a factor of 8 or fail. + if (!use_bits && nbits % 8 != 0) return 0; + // Calculate if we expect a trailing space in the data section. + const bool kexpectspace = footermark || (onespace != zerospace); + // Calculate how much remaining buffer is required. + uint16_t min_remaining = nbits * 2 - (kexpectspace ? 0 : 1); + + if (hdrmark) min_remaining++; + if (hdrspace) min_remaining++; + if (footermark) min_remaining++; + // Don't need to extend for footerspace because it could be the end of message + + // Check if there is enough capture buffer to possibly have the message. + if (remaining < min_remaining) return 0; // Nope, so abort. + uint16_t offset = 0; + + // Header + if (hdrmark && !matchMark(*(data_ptr + offset++), hdrmark, tolerance, excess)) + return 0; + if (hdrspace && !matchSpace(*(data_ptr + offset++), hdrspace, tolerance, + excess)) + return 0; + + // Data + if (use_bits) { // Bits. + match_result_t result = IRrecv::matchData(data_ptr + offset, nbits, + onemark, onespace, + zeromark, zerospace, tolerance, + excess, MSBfirst, kexpectspace); + if (!result.success) return 0; + *result_bits_ptr = result.data; + offset += result.used; + } else { // bytes + uint16_t data_used = IRrecv::matchBytes(data_ptr + offset, result_bytes_ptr, + remaining - offset, nbits / 8, + onemark, onespace, + zeromark, zerospace, tolerance, + excess, MSBfirst, kexpectspace); + if (!data_used) return 0; + offset += data_used; + } + // Footer + if (footermark && !matchMark(*(data_ptr + offset++), footermark, tolerance, + excess)) + return 0; + // If we have something still to match & haven't reached the end of the buffer + if (footerspace && offset < remaining) { + if (atleast) { + if (!matchAtLeast(*(data_ptr + offset), footerspace, tolerance, excess)) + return 0; + } else { + if (!matchSpace(*(data_ptr + offset), footerspace, tolerance, excess)) + return 0; + } + offset++; + } + return offset; +} + +/// Match & decode a generic/typical <= 64bit IR message. +/// The data is stored at result_ptr. +/// @note Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean +/// skip that requirement. +/// +/// @param[in] data_ptr: A pointer to where we are at in the capture buffer. +/// @param[out] result_ptr A ptr to where to start storing the bits we decoded. +/// @param[in] remaining The size of the capture buffer remaining. +/// @param[in] nbits Nr. of data bits we expect. +/// @param[in] hdrmark Nr. of uSeconds for the expected header mark signal. +/// @param[in] hdrspace Nr. of uSeconds for the expected header space signal. +/// @param[in] onemark Nr. of uSeconds in an expected mark signal for a '1' bit. +/// @param[in] onespace Nr. of uSecs in an expected space signal for a '1' bit. +/// @param[in] zeromark Nr. of uSecs in an expected mark signal for a '0' bit. +/// @param[in] zerospace Nr. of uSecs in an expected space signal for a '0' bit. +/// @param[in] footermark Nr. of uSeconds for the expected footer mark signal. +/// @param[in] footerspace Nr. of uSeconds for the expected footer space/gap +/// signal. +/// @param[in] atleast Is the match on the footerspace a matchAtLeast or +/// matchSpace? +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @return If successful, how many buffer entries were used. Otherwise 0. +uint16_t IRrecv::matchGeneric(volatile uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t onemark, + const uint32_t onespace, + const uint16_t zeromark, + const uint32_t zerospace, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast, + const uint8_t tolerance, + const int16_t excess, + const bool MSBfirst) { + return _matchGeneric(data_ptr, result_ptr, NULL, true, remaining, nbits, + hdrmark, hdrspace, onemark, onespace, + zeromark, zerospace, footermark, footerspace, atleast, + tolerance, excess, MSBfirst); +} + +/// Match & decode a generic/typical > 64bit IR message. +/// The bytes are stored at result_ptr. The first byte in the result equates to +/// the first byte encountered, and so on. +/// @note Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean +/// skip that requirement. +/// @param[in] data_ptr: A pointer to where we are at in the capture buffer. +/// @param[out] result_ptr A ptr to where to start storing the bytes we decoded. +/// @param[in] remaining The size of the capture buffer remaining. +/// @param[in] nbits Nr. of data bits we expect. +/// @param[in] hdrmark Nr. of uSeconds for the expected header mark signal. +/// @param[in] hdrspace Nr. of uSeconds for the expected header space signal. +/// @param[in] onemark Nr. of uSeconds in an expected mark signal for a '1' bit. +/// @param[in] onespace Nr. of uSecs in an expected space signal for a '1' bit. +/// @param[in] zeromark Nr. of uSecs in an expected mark signal for a '0' bit. +/// @param[in] zerospace Nr. of uSecs in an expected space signal for a '0' bit. +/// @param[in] footermark Nr. of uSeconds for the expected footer mark signal. +/// @param[in] footerspace Nr. of uSeconds for the expected footer space/gap +/// signal. +/// @param[in] atleast Is the match on the footerspace a matchAtLeast or +/// matchSpace? +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @return If successful, how many buffer entries were used. Otherwise 0. +uint16_t IRrecv::matchGeneric(volatile uint16_t *data_ptr, + uint8_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t onemark, + const uint32_t onespace, + const uint16_t zeromark, + const uint32_t zerospace, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast, + const uint8_t tolerance, + const int16_t excess, + const bool MSBfirst) { + return _matchGeneric(data_ptr, NULL, result_ptr, false, remaining, nbits, + hdrmark, hdrspace, onemark, onespace, + zeromark, zerospace, footermark, footerspace, atleast, + tolerance, excess, MSBfirst); +} + +/// Match & decode a generic/typical constant bit time <= 64bit IR message. +/// The data is stored at result_ptr. +/// @note Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean +/// skip that requirement. +/// @param[in] data_ptr A pointer to where we are at in the capture buffer. +/// @note `data_ptr` is assumed to be pointing to a "Mark", not a "Space". +/// @param[out] result_ptr A ptr to where to start storing the bits we decoded. +/// @param[in] remaining The size of the capture buffer remaining. +/// @param[in] nbits Nr. of data bits we expect. +/// @param[in] hdrmark Nr. of uSeconds for the expected header mark signal. +/// @param[in] hdrspace Nr. of uSeconds for the expected header space signal. +/// @param[in] one Nr. of uSeconds in an expected mark signal for a '1' bit. +/// @param[in] zero Nr. of uSeconds in an expected mark signal for a '0' bit. +/// @param[in] footermark Nr. of uSeconds for the expected footer mark signal. +/// @param[in] footerspace Nr. of uSeconds for the expected footer space/gap +/// signal. +/// @param[in] atleast Is the match on the footerspace a matchAtLeast or +/// matchSpace? +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @return If successful, how many buffer entries were used. Otherwise 0. +/// @note Parameters one + zero add up to the total time for a bit. +/// e.g. mark(one) + space(zero) is a `1`, mark(zero) + space(one) is a `0`. +uint16_t IRrecv::matchGenericConstBitTime(volatile uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t one, + const uint32_t zero, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast, + const uint8_t tolerance, + const int16_t excess, + const bool MSBfirst) { + uint16_t offset = 0; + uint64_t result = 0; + // If we expect a footermark, then this can be processed like normal. + if (footermark) + return _matchGeneric(data_ptr, result_ptr, NULL, true, remaining, nbits, + hdrmark, hdrspace, one, zero, zero, one, + footermark, footerspace, atleast, + tolerance, excess, MSBfirst); + // Overwise handle like normal, except for the last bit. and no footer. + uint16_t bits = (nbits > 0) ? nbits - 1 : 0; // Make sure we don't underflow. + offset = _matchGeneric(data_ptr, &result, NULL, true, remaining, bits, + hdrmark, hdrspace, one, zero, zero, one, 0, 0, false, + tolerance, excess, true); + if (!offset) return 0; // Didn't match. + // Now for the last bit. + if (remaining <= offset) return 0; // Not enough buffer. + result <<= 1; + bool last_bit = 0; + // Is the mark a '1' or a `0`? + if (matchMark(*(data_ptr + offset), one, tolerance, excess)) { // 1 + last_bit = 1; + result |= 1; + } else if (matchMark(*(data_ptr + offset), zero, tolerance, excess)) { // 0 + last_bit = 0; + } else { + return 0; // It's neither, so fail. + } + offset++; + uint32_t expected_space = (last_bit ? zero : one) + footerspace; + // If we are not at the end of the buffer, check for at least the expected + // space value. + if (remaining > offset) { + if (atleast) { + if (!matchAtLeast(*(data_ptr + offset), expected_space, tolerance, + excess)) + return false; + } else { + if (!matchSpace(*(data_ptr + offset), expected_space, tolerance)) + return false; + } + offset++; + } + if (!MSBfirst) result = reverseBits(result, nbits); + *result_ptr = result; + return offset; +} + +/// Match & decode a Manchester Code <= 64bit IR message. +/// The data is stored at result_ptr. +/// @note Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean +/// skip that requirement. +/// @param[in] data_ptr A pointer to where we are at in the capture buffer. +/// @note `data_ptr` is assumed to be pointing to a "Mark", not a "Space". +/// @param[out] result_ptr A ptr to where to start storing the bits we decoded. +/// @param[in] remaining The size of the capture buffer remaining. +/// @param[in] nbits Nr. of data bits we expect. +/// @param[in] hdrmark Nr. of uSeconds for the expected header mark signal. +/// @param[in] hdrspace Nr. of uSeconds for the expected header space signal. +/// @param[in] half_period Nr. of uSeconds for half the clock's period. +/// i.e. 1/2 wavelength +/// @param[in] footermark Nr. of uSeconds for the expected footer mark signal. +/// @param[in] footerspace Nr. of uSeconds for the expected footer space/gap +/// signal. +/// @param[in] atleast Is the match on the footerspace a matchAtLeast or +/// matchSpace? +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @param[in] GEThomas Use G.E. Thomas (true) or IEEE 802.3 (false) convention? +/// @return If successful, how many buffer entries were used. Otherwise 0. +/// @see https://en.wikipedia.org/wiki/Manchester_code +/// @see http://ww1.microchip.com/downloads/en/AppNotes/Atmel-9164-Manchester-Coding-Basics_Application-Note.pdf +uint16_t IRrecv::matchManchester(volatile const uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t half_period, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast, + const uint8_t tolerance, + const int16_t excess, + const bool MSBfirst, + const bool GEThomas) { + uint16_t offset = 0; + uint16_t bank = 0; + uint16_t entry = 0; + + // Calculate how much remaining buffer is required. + // Shortest case is nbits. Longest case is 2 * nbits. + uint16_t min_remaining = nbits; + + if (hdrmark) min_remaining++; + if (hdrspace) min_remaining++; + if (footermark) min_remaining++; + // Don't need to extend for footerspace because it could be the end of message + + // Check if there is enough capture buffer to possibly have the message. + if (remaining < min_remaining) return 0; // Nope, so abort. + + // Header + if (hdrmark) { + entry = *(data_ptr + offset++); + if (!hdrspace) { // If we have no Header Space ... + // Do we have a data 'mark' half period merged with the header mark? + if (matchMark(entry, hdrmark + half_period, + tolerance, excess)) { + // Looks like we do. + bank = entry * kRawTick - hdrmark; + } else if (!matchMark(entry, hdrmark, tolerance, excess)) { + return 0; // It's not a normal header mark, so fail. + } + } else if (!matchMark(entry, hdrmark, tolerance, excess)) { + return 0; // It's not a normal header mark, so fail. + } + } + if (hdrspace) { + entry = *(data_ptr + offset++); + // Check to see if the header space has merged with a data space half period + if (matchSpace(entry, hdrspace + half_period, tolerance, excess)) { + // Looks like we do. + bank = entry * kRawTick - hdrspace; + } else if (!matchSpace(entry, hdrspace, tolerance, excess)) { + return 0; // It's not a normal header space, so fail. + } + } + + if (!match(bank / kRawTick, half_period, tolerance, excess)) bank = 0; + // Data + uint16_t used = matchManchesterData(data_ptr + offset, result_ptr, + remaining - offset, nbits, half_period, + bank, tolerance, excess, MSBfirst, + GEThomas); + if (!used) return 0; // Data did match. + offset += used; + // Footer + if (footermark && + !(matchMark(*(data_ptr + offset), footermark + half_period, + tolerance, excess) || + matchMark(*(data_ptr + offset), footermark, tolerance, excess))) + return 0; + offset++; + // If we have something still to match & haven't reached the end of the buffer + if (footerspace && offset < remaining) { + if (atleast) { + if (!matchAtLeast(*(data_ptr + offset), footerspace, tolerance, excess)) + return 0; + } else { + if (!matchSpace(*(data_ptr + offset), footerspace, tolerance, excess) && + !matchSpace(*(data_ptr + offset), footerspace + half_period, + tolerance, excess)) + return 0; + } + offset++; + } + return offset; +} + +/// Match & decode a Manchester Code data (<= 64bits. +/// @param[in] data_ptr A pointer to where we are at in the capture buffer. +/// @note `data_ptr` is assumed to be pointing to a "Mark", not a "Space". +/// @param[out] result_ptr A ptr to where to start storing the bits we decoded. +/// @param[in] remaining The size of the capture buffer remaining. +/// @param[in] nbits Nr. of data bits we expect. +/// @param[in] half_period Nr. of uSeconds for half the clock's period. +/// i.e. 1/2 wavelength +/// @param[in] tolerance Percentage error margin to allow. (Default: kUseDefTol) +/// @param[in] starting_balance Amount of uSeconds to assume exists prior to +/// the current value pointed too. +/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) +/// @param[in] MSBfirst Bit order to save the data in. (Def: true) +/// true is Most Significant Bit First Order, false is Least Significant First +/// @param[in] GEThomas Use G.E. Thomas (true) or IEEE 802.3 (false) convention? +/// @return If successful, how many buffer entries were used. Otherwise 0. +/// @see https://en.wikipedia.org/wiki/Manchester_code +/// @see http://ww1.microchip.com/downloads/en/AppNotes/Atmel-9164-Manchester-Coding-Basics_Application-Note.pdf +/// @todo Clean up and optimise this. It is just "get it working code" atm. +uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t half_period, + const uint16_t starting_balance, + const uint8_t tolerance, + const int16_t excess, + const bool MSBfirst, + const bool GEThomas) { + DPRINTLN("DEBUG: Entered matchManchesterData"); + uint16_t offset = 0; + uint64_t data = 0; + uint16_t nr_half_periods = 0; + const uint16_t expected_half_periods = nbits * 2; + // Flip the bit if we have a starting balance. ie. Carry over from the header. + bool currentBit = starting_balance ? !GEThomas : GEThomas; + const uint16_t raw_half_period = half_period / kRawTick; + + // Calculate how much remaining buffer is required. + // Shortest case is nbits. Longest case is 2 * nbits. + uint16_t min_remaining = nbits; + + // Check if there is enough capture buffer to possibly have the message. + if (remaining < min_remaining) { + DPRINTLN("DEBUG: Ran out of capture buffer!"); + return 0; // Nope, so abort. + } + + // Convert to ticks. Optimisation: Saves on math/extra instructions later. + uint16_t bank = starting_balance / kRawTick; + + // Data + // Loop through the buffer till we run out of buffer, or nr of half periods. + // Possible patterns are: + // short + short = 1 bit (Add the value of the previous bit again) + // short + long + short = 2 bits (Add the previous bit again, then flip & add) + // short + long + long + short = 3 bits (add prev, flip & add, flip & add) + // We can't start with a long. + // + // The general approach is thus: + // Check we have a short interval, next or in the bank. + // If the next timing value is long, act according and reset the bank to + // a short balance. + // or + // If it is short, act accordingly and declare the bank empty. + // Repeat. + while ((offset < remaining || bank) && + nr_half_periods < expected_half_periods) { + // Get the next entry if we haven't anything existing to process. + DPRINT("DEBUG: Offset = "); + DPRINTLN(offset); + if (!bank) bank = *(data_ptr + offset++); + DPRINT("DEBUG: Bank = "); + DPRINTLN(bank * kRawTick); + // Check if we don't have a short interval. + DPRINTLN("DEBUG: Checking for short interval"); + if (!match(bank, half_period, tolerance, excess)) { + DPRINTLN("DEBUG: It is. Exiting"); + return 0; // Not valid. + } + // We've succeeded in matching half a period, so count it. + nr_half_periods++; + DPRINT("DEBUG: Half Periods = "); + DPRINTLN(nr_half_periods); + // We've now used up our bank, so refill it with the next item, unless we + // are at the end of the capture buffer. + // If we are assume a single half period of "space". + if (offset < remaining) { + DPRINT("DEBUG: Offset = "); + DPRINTLN(offset); + bank = *(data_ptr + offset++); + } else if (offset == remaining) { + bank = raw_half_period; + } else { + return 0; // We are out of buffer, so abort! + } + DPRINT("DEBUG: Bank = "); + DPRINTLN(bank * kRawTick); + + // Shift the data along and add our new bit. + DPRINT("DEBUG: Adding bit: "); + DPRINTLN((currentBit ? "1" : "0")); + data <<= 1; + data |= currentBit; + + // Check if we have a long interval. + if (match(bank, half_period * 2, tolerance, excess)) { + // It is, so flip the bit we need to append, and remove a half_period of + // time from the bank. + DPRINTLN("DEBUG: long interval detected"); + currentBit = !currentBit; + bank -= raw_half_period; + } else if (match(bank, half_period, tolerance, excess)) { + // It is a short interval, so eat up all the time and move on. + DPRINTLN("DEBUG: short interval detected"); + bank = 0; + } else if (nr_half_periods == expected_half_periods - 1 && + matchAtLeast(bank, half_period, tolerance, excess)) { + // We are at the end of the data & it is a short interval, so eat up all + // the time and move on. + bank = 0; + // Reduce the offset as we are at the end of the data doing a + // matchAtLeast() because we could be processing part of a footer. + offset--; + } else { + // The length isn't what we expected (neither long or short), so bail. + return 0; + } + nr_half_periods++; + } + + // Clean up and process the data. + if (!MSBfirst) data = reverseBits(data, nbits); + // Trim the data to size. + *result_ptr = GETBITS64(data, 0, nbits); + return offset; +} + +#if UNIT_TEST +/// Unit test helper to get access to the params structure. +volatile irparams_t *IRrecv::_getParamsPtr(void) { + return ¶ms; +} +#endif // UNIT_TEST +// End of IRrecv class ------------------- + +#endif \ No newline at end of file diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.h b/src/libraries/IRremoteESP8266/src/IRrecv.h new file mode 100644 index 000000000..019791a32 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRrecv.h @@ -0,0 +1,882 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2015 Mark Szabo +// Copyright 2015 Sebastien Warin +// Copyright 2017 David Conran + +#ifndef IRRECV_H_ +#define IRRECV_H_ + +#ifndef UNIT_TEST +//#include "String.h" +#endif +#include +#define __STDC_LIMIT_MACROS +#include +#include "IRremoteESP8266.h" + +// Constants +const uint16_t kHeader = 2; // Usual nr. of header entries. +const uint16_t kFooter = 2; // Usual nr. of footer (stop bits) entries. +const uint16_t kStartOffset = 1; // Usual rawbuf entry to start from. +#define MS_TO_USEC(x) ((x) * 1000U) // Convert milli-Seconds to micro-Seconds. +// Marks tend to be 100us too long, and spaces 100us too short +// when received due to sensor lag. +const uint16_t kMarkExcess = 50; +const uint16_t kRawBuf = 100; // Default length of raw capture buffer +const uint64_t kRepeat = UINT64_MAX; +// Default min size of reported UNKNOWN messages. +const uint16_t kUnknownThreshold = 6; + +// receiver states +const uint8_t kIdleState = 2; +const uint8_t kMarkState = 3; +const uint8_t kSpaceState = 4; +const uint8_t kStopState = 5; +const uint8_t kTolerance = 25; // default percent tolerance in measurements. +const uint8_t kUseDefTol = 255; // Indicate to use the class default tolerance. +const uint16_t kRawTick = 2; // Capture tick to uSec factor. +#define RAWTICK kRawTick // Deprecated. For legacy user code support only. +// How long (ms) before we give up wait for more data? +// Don't exceed kMaxTimeoutMs without a good reason. +// That is the capture buffers maximum value size. (UINT16_MAX / kRawTick) +// Typically messages/protocols tend to repeat around the 100ms timeframe, +// thus we should timeout before that to give us some time to try to decode +// before we need to start capturing a possible new message. +// Typically 15ms suits most applications. However, some protocols demand a +// higher value. e.g. 90ms for XMP-1 and some aircon units. +const uint8_t kTimeoutMs = 15; // In MilliSeconds. +#define TIMEOUT_MS kTimeoutMs // For legacy documentation. +const uint16_t kMaxTimeoutMs = kRawTick * (UINT16_MAX / MS_TO_USEC(1)); + +// Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param +const uint32_t kFnvPrime32 = 16777619UL; +const uint32_t kFnvBasis32 = 2166136261UL; + +#ifdef ESP32 +// Which of the ESP32 timers to use by default. +// (3 for most ESP32s, 1 for ESP32-C3s) +#ifdef SOC_TIMER_GROUP_TOTAL_TIMERS +const uint8_t kDefaultESP32Timer = SOC_TIMER_GROUP_TOTAL_TIMERS - 1; +#else // SOC_TIMER_GROUP_TOTAL_TIMERS +const uint8_t kDefaultESP32Timer = 3; +#endif // SOC_TIMER_GROUP_TOTAL_TIMERS +#endif // ESP32 + +#if DECODE_AC +// Hitachi AC is the current largest state size. +const uint16_t kStateSizeMax = kHitachiAc2StateLength; +#else // DECODE_AC +// Just define something (a uint64_t) +const uint16_t kStateSizeMax = sizeof(uint64_t); +#endif // DECODE_AC + +// Types + +/// Information for the interrupt handler +typedef struct { + uint8_t recvpin; // pin for IR data from detector + uint8_t rcvstate; // state machine + uint16_t timer; // state timer, counts 50uS ticks. + uint16_t bufsize; // max. nr. of entries in the capture buffer. + uint16_t *rawbuf; // raw data + // uint16_t is used for rawlen as it saves 3 bytes of iram in the interrupt + // handler. Don't ask why, I don't know. It just does. + uint16_t rawlen; // counter of entries in rawbuf. + uint8_t overflow; // Buffer overflow indicator. + uint8_t timeout; // Nr. of milliSeconds before we give up. +} irparams_t; + +/// Results from a data match +typedef struct { + bool success; // Was the match successful? + uint64_t data; // The data found. + uint16_t used; // How many buffer positions were used. +} match_result_t; + +// Classes + +/// Results returned from the decoder +class decode_results { + public: + decode_type_t decode_type; // NEC, SONY, RC5, UNKNOWN + // value, address, & command are all mutually exclusive with state. + // i.e. They MUST NOT be used at the same time as state, so we can use a union + // structure to save us a handful of valuable bytes of memory. + union { + struct { + uint64_t value; // Decoded value + uint32_t address; // Decoded device address. + uint32_t command; // Decoded command. + }; + uint8_t state[kStateSizeMax]; // Multi-byte results. + }; + uint16_t bits; // Number of bits in decoded value + volatile uint16_t *rawbuf; // Raw intervals in .5 us ticks + uint16_t rawlen; // Number of records in rawbuf. + bool overflow; + bool repeat; // Is the result a repeat code? +}; + +/// Class for receiving IR messages. +class IRrecv { + public: +#if defined(ESP32) + explicit IRrecv(const uint16_t recvpin, const uint16_t bufsize = kRawBuf, + const uint8_t timeout = kTimeoutMs, + const bool save_buffer = false, + const uint8_t timer_num = kDefaultESP32Timer); // Constructor +#else // ESP32 + explicit IRrecv(const uint16_t recvpin, const uint16_t bufsize = kRawBuf, + const uint8_t timeout = kTimeoutMs, + const bool save_buffer = false); // Constructor +#endif // ESP32 + ~IRrecv(void); // Destructor + void setTolerance(const uint8_t percent = kTolerance); + uint8_t getTolerance(void); + bool decode(decode_results *results, irparams_t *save = NULL, + uint8_t max_skip = 0, uint16_t noise_floor = 0); + void enableIRIn(const bool pullup = false); + void disableIRIn(void); + void pause(void); + void resume(void); + uint16_t getBufSize(void); +#if DECODE_HASH + void setUnknownThreshold(const uint16_t length); +#endif + bool match(const uint32_t measured, const uint32_t desired, + const uint8_t tolerance = kUseDefTol, + const uint16_t delta = 0); + bool matchMark(const uint32_t measured, const uint32_t desired, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess); + bool matchMarkRange(const uint32_t measured, const uint32_t desired, + const uint16_t range = 100, + const int16_t excess = kMarkExcess); + bool matchSpace(const uint32_t measured, const uint32_t desired, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess); + bool matchSpaceRange(const uint32_t measured, const uint32_t desired, + const uint16_t range = 100, + const int16_t excess = kMarkExcess); +#ifndef UNIT_TEST + + private: +#endif + irparams_t *irparams_save; + uint8_t _tolerance; +#if defined(ESP32) + uint8_t _timer_num; +#endif // defined(ESP32) +#if DECODE_HASH + uint16_t _unknown_threshold; +#endif +#ifdef UNIT_TEST + volatile irparams_t *_getParamsPtr(void); +#endif // UNIT_TEST + // These are called by decode + uint8_t _validTolerance(const uint8_t percentage); + void copyIrParams(volatile irparams_t *src, irparams_t *dst); + uint16_t compare(const uint16_t oldval, const uint16_t newval); + uint32_t ticksLow(const uint32_t usecs, + const uint8_t tolerance = kUseDefTol, + const uint16_t delta = 0); + uint32_t ticksHigh(const uint32_t usecs, + const uint8_t tolerance = kUseDefTol, + const uint16_t delta = 0); + bool matchAtLeast(const uint32_t measured, const uint32_t desired, + const uint8_t tolerance = kUseDefTol, + const uint16_t delta = 0); + uint16_t _matchGeneric(volatile uint16_t *data_ptr, + uint64_t *result_bits_ptr, + uint8_t *result_ptr, + const bool use_bits, + const uint16_t remaining, + const uint16_t required, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t onemark, + const uint32_t onespace, + const uint16_t zeromark, + const uint32_t zerospace, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast = false, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true); + match_result_t matchData(volatile uint16_t *data_ptr, const uint16_t nbits, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true, + const bool expectlastspace = true); + uint16_t matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr, + const uint16_t remaining, const uint16_t nbytes, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true, + const bool expectlastspace = true); + uint16_t matchGeneric(volatile uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, const uint16_t nbits, + const uint16_t hdrmark, const uint32_t hdrspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, const uint32_t footerspace, + const bool atleast = false, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true); + uint16_t matchGeneric(volatile uint16_t *data_ptr, uint8_t *result_ptr, + const uint16_t remaining, const uint16_t nbits, + const uint16_t hdrmark, const uint32_t hdrspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast = false, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true); + uint16_t matchGenericConstBitTime(volatile uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t one, + const uint32_t zero, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast = false, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true); + uint16_t matchManchesterData(volatile const uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t half_period, + const uint16_t starting_balance = 0, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true, + const bool GEThomas = true); + uint16_t matchManchester(volatile const uint16_t *data_ptr, + uint64_t *result_ptr, + const uint16_t remaining, + const uint16_t nbits, + const uint16_t hdrmark, + const uint32_t hdrspace, + const uint16_t clock_period, + const uint16_t footermark, + const uint32_t footerspace, + const bool atleast = false, + const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const bool MSBfirst = true, + const bool GEThomas = true); + void crudeNoiseFilter(decode_results *results, const uint16_t floor = 0); + bool decodeHash(decode_results *results); +#if DECODE_VOLTAS + bool decodeVoltas(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kVoltasBits, + const bool strict = true); +#endif // DECODE_VOLTAS +#if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || DECODE_SANYO) + bool decodeNEC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kNECBits, const bool strict = true); +#endif +#if DECODE_ARGO + bool decodeArgo(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kArgoBits, const bool strict = true); + bool decodeArgoWREM3(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kArgo3AcControlStateLength * 8, + const bool strict = true); +#endif // DECODE_ARGO +#if DECODE_ARRIS + bool decodeArris(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kArrisBits, const bool strict = true); +#endif // DECODE_ARRIS +#if DECODE_SONY + bool decodeSony(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kSonyMinBits, + const bool strict = false); +#endif +#if DECODE_SANYO + // DISABLED due to poor quality. + // bool decodeSanyo(decode_results *results, uint16_t offset = kStartOffset, + // uint16_t nbits = kSanyoSA8650BBits, + // bool strict = false); + bool decodeSanyoLC7461(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kSanyoLC7461Bits, + const bool strict = true); +#endif +#if DECODE_SANYO_AC + bool decodeSanyoAc(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kSanyoAcBits, + const bool strict = true); +#endif // DECODE_SANYO_AC +#if DECODE_SANYO_AC88 + bool decodeSanyoAc88(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kSanyoAc88Bits, + const bool strict = true); +#endif // DECODE_SANYO_AC88 +#if DECODE_SANYO_AC152 + bool decodeSanyoAc152(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kSanyoAc152Bits, + const bool strict = true); +#endif // DECODE_SANYO_AC152 +#if DECODE_MITSUBISHI + bool decodeMitsubishi(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kMitsubishiBits, + const bool strict = true); +#endif +#if DECODE_MITSUBISHI2 + bool decodeMitsubishi2(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kMitsubishiBits, + const bool strict = true); +#endif +#if DECODE_MITSUBISHI_AC + bool decodeMitsubishiAC(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kMitsubishiACBits, + const bool strict = false); +#endif +#if DECODE_MITSUBISHI136 + bool decodeMitsubishi136(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kMitsubishi136Bits, + const bool strict = true); +#endif +#if DECODE_MITSUBISHI112 + bool decodeMitsubishi112(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kMitsubishi112Bits, + const bool strict = true); +#endif +#if DECODE_MITSUBISHIHEAVY + bool decodeMitsubishiHeavy(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kMitsubishiHeavy152Bits, + const bool strict = true); +#endif +#if (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG || DECODE_MWM) + int16_t getRClevel(decode_results *results, uint16_t *offset, uint16_t *used, + uint16_t bitTime, const uint8_t tolerance = kUseDefTol, + const int16_t excess = kMarkExcess, + const uint16_t delta = 0, const uint8_t maxwidth = 3); +#endif +#if DECODE_RC5 + bool decodeRC5(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kRC5XBits, + const bool strict = true); +#endif +#if DECODE_RC6 + bool decodeRC6(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kRC6Mode0Bits, + const bool strict = false); +#endif +#if DECODE_RCMM + bool decodeRCMM(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kRCMMBits, + const bool strict = false); +#endif +#if (DECODE_PANASONIC || DECODE_DENON) + bool decodePanasonic(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kPanasonicBits, + const bool strict = false, + const uint32_t manufacturer = kPanasonicManufacturer); +#endif +#if DECODE_LG + bool decodeLG(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kLgBits, + const bool strict = false); +#endif +#if DECODE_INAX + bool decodeInax(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kInaxBits, + const bool strict = true); +#endif // DECODE_INAX +#if DECODE_JVC + bool decodeJVC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kJvcBits, + const bool strict = true); +#endif +#if DECODE_SAMSUNG + bool decodeSAMSUNG(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kSamsungBits, + const bool strict = true); +#endif +#if DECODE_SAMSUNG + bool decodeSamsung36(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kSamsung36Bits, + const bool strict = true); +#endif +#if DECODE_SAMSUNG_AC + bool decodeSamsungAC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kSamsungAcBits, + const bool strict = true); +#endif +#if DECODE_WHYNTER + bool decodeWhynter(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kWhynterBits, + const bool strict = true); +#endif +#if DECODE_COOLIX + bool decodeCOOLIX(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kCoolixBits, + const bool strict = true); +#endif // DECODE_COOLIX +#if DECODE_COOLIX48 + bool decodeCoolix48(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kCoolix48Bits, + const bool strict = true); +#endif // DECODE_COOLIX48 +#if DECODE_DENON + bool decodeDenon(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDenonBits, + const bool strict = true); +#endif +#if DECODE_DISH + bool decodeDISH(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDishBits, + const bool strict = true); +#endif +#if (DECODE_SHARP || DECODE_DENON) + bool decodeSharp(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kSharpBits, + const bool strict = true, const bool expansion = true); +#endif +#if DECODE_SHARP_AC + bool decodeSharpAc(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kSharpAcBits, + const bool strict = true); +#endif +#if DECODE_AIWA_RC_T501 + bool decodeAiwaRCT501(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kAiwaRcT501Bits, + const bool strict = true); +#endif +#if DECODE_NIKAI + bool decodeNikai(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kNikaiBits, + const bool strict = true); +#endif +#if DECODE_MAGIQUEST + bool decodeMagiQuest(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kMagiquestBits, + const bool strict = true); +#endif +#if DECODE_KELVINATOR + bool decodeKelvinator(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kKelvinatorBits, + const bool strict = true); +#endif +#if DECODE_DAIKIN + bool decodeDaikin(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikinBits, + const bool strict = true); +#endif +#if DECODE_DAIKIN64 + bool decodeDaikin64(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin64Bits, + const bool strict = true); +#endif // DECODE_DAIKIN64 +#if DECODE_DAIKIN128 + bool decodeDaikin128(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin128Bits, + const bool strict = true); +#endif // DECODE_DAIKIN128 +#if DECODE_DAIKIN152 + bool decodeDaikin152(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin152Bits, + const bool strict = true); +#endif // DECODE_DAIKIN152 +#if DECODE_DAIKIN160 + bool decodeDaikin160(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin160Bits, + const bool strict = true); +#endif // DECODE_DAIKIN160 +#if DECODE_DAIKIN176 + bool decodeDaikin176(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin176Bits, + const bool strict = true); +#endif // DECODE_DAIKIN176 +#if DECODE_DAIKIN2 + bool decodeDaikin2(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin2Bits, + const bool strict = true); +#endif +#if DECODE_DAIKIN200 + bool decodeDaikin200(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin200Bits, + const bool strict = true); +#endif // DECODE_DAIKIN200 +#if DECODE_DAIKIN216 + bool decodeDaikin216(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin216Bits, + const bool strict = true); +#endif // DECODE_DAIKIN216 +#if DECODE_DAIKIN312 + bool decodeDaikin312(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDaikin312Bits, + const bool strict = true); +#endif // DECODE_DAIKIN312 +#if DECODE_TOSHIBA_AC + bool decodeToshibaAC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kToshibaACBits, + const bool strict = true); +#endif +#if DECODE_TROTEC + bool decodeTrotec(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTrotecBits, + const bool strict = true); +#endif // DECODE_TROTEC +#if DECODE_TROTEC_3550 + bool decodeTrotec3550(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTrotecBits, + const bool strict = true); +#endif // DECODE_TROTEC_3550 +#if DECODE_MIDEA + bool decodeMidea(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kMideaBits, + const bool strict = true); +#endif // DECODE_MIDEA +#if DECODE_MIDEA24 + bool decodeMidea24(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kMidea24Bits, + const bool strict = true); +#endif // DECODE_MIDEA24 +#if DECODE_FUJITSU_AC + bool decodeFujitsuAC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kFujitsuAcBits, + const bool strict = false); +#endif +#if DECODE_LASERTAG + bool decodeLasertag(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kLasertagBits, + const bool strict = true); +#endif +#if DECODE_MILESTAG2 + bool decodeMilestag2(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kMilesTag2ShotBits, + const bool strict = true); +#endif +#if DECODE_CARRIER_AC + bool decodeCarrierAC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kCarrierAcBits, + const bool strict = true); +#endif // DECODE_CARRIER_AC +#if DECODE_CARRIER_AC40 + bool decodeCarrierAC40(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kCarrierAc40Bits, + const bool strict = true); +#endif // DECODE_CARRIER_AC40 +#if DECODE_CARRIER_AC84 + bool decodeCarrierAC84(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kCarrierAc84Bits, + const bool strict = true); +#endif // DECODE_CARRIER_AC84 +#if DECODE_CARRIER_AC64 + bool decodeCarrierAC64(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kCarrierAc64Bits, + const bool strict = true); +#endif // DECODE_CARRIER_AC64 +#if DECODE_CARRIER_AC128 + bool decodeCarrierAC128(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kCarrierAc128Bits, + const bool strict = true); +#endif // DECODE_CARRIER_AC128 +#if DECODE_GOODWEATHER + bool decodeGoodweather(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kGoodweatherBits, + const bool strict = true); +#endif // DECODE_GOODWEATHER +#if DECODE_GORENJE + bool decodeGorenje(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kGorenjeBits, + const bool strict = true); +#endif // DECODE_GORENJE +#if DECODE_GREE + bool decodeGree(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kGreeBits, + const bool strict = true); +#endif +#if (DECODE_HAIER_AC | DECODE_HAIER_AC_YRW02 || DECODE_HAIER_AC160 || \ + DECODE_HAIER_AC176) + bool decodeHaierAC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kHaierACBits, + const bool strict = true); +#endif +#if DECODE_HAIER_AC_YRW02 + bool decodeHaierACYRW02(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kHaierACYRW02Bits, + const bool strict = true); +#endif +#if DECODE_HAIER_AC160 + bool decodeHaierAC160(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kHaierAC160Bits, + const bool strict = true); +#endif // DECODE_HAIER_AC160 +#if DECODE_HAIER_AC176 + bool decodeHaierAC176(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kHaierAC176Bits, + const bool strict = true); +#endif // DECODE_HAIER_AC176 +#if (DECODE_HITACHI_AC || DECODE_HITACHI_AC2 || DECODE_HITACHI_AC264 || \ + DECODE_HITACHI_AC344) + bool decodeHitachiAC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kHitachiAcBits, + const bool strict = true, const bool MSBfirst = true); +#endif // (DECODE_HITACHI_AC || DECODE_HITACHI_AC2 || DECODE_HITACHI_AC264 || + // DECODE_HITACHI_AC344) +#if DECODE_HITACHI_AC1 + bool decodeHitachiAC1(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kHitachiAc1Bits, + const bool strict = true); +#endif +#if DECODE_HITACHI_AC3 + bool decodeHitachiAc3(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kHitachiAc3Bits, + const bool strict = true); +#endif // DECODE_HITACHI_AC3 +#if DECODE_HITACHI_AC296 + bool decodeHitachiAc296(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kHitachiAc296Bits, + const bool strict = true); +#endif // DECODE_HITACHI_AC296 +#if DECODE_HITACHI_AC424 + bool decodeHitachiAc424(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kHitachiAc424Bits, + const bool strict = true); +#endif // DECODE_HITACHI_AC424 +#if DECODE_GICABLE + bool decodeGICable(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kGicableBits, + const bool strict = true); +#endif +#if DECODE_WHIRLPOOL_AC + bool decodeWhirlpoolAC(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kWhirlpoolAcBits, + const bool strict = true); +#endif +#if DECODE_LUTRON + bool decodeLutron(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kLutronBits, + const bool strict = true); +#endif +#if DECODE_ELECTRA_AC + bool decodeElectraAC(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kElectraAcBits, + const bool strict = true); +#endif +#if DECODE_PANASONIC_AC + bool decodePanasonicAC(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kPanasonicAcBits, + const bool strict = true); +#endif // DECODE_PANASONIC_AC +#if DECODE_PANASONIC_AC32 + bool decodePanasonicAC32(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kPanasonicAc32Bits, + const bool strict = true); +#endif // DECODE_PANASONIC_AC32 +#if DECODE_PIONEER + bool decodePioneer(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kPioneerBits, + const bool strict = true); +#endif +#if DECODE_MWM + bool decodeMWM(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = 24, + const bool strict = true); +#endif +#if DECODE_VESTEL_AC + bool decodeVestelAc(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kVestelAcBits, + const bool strict = true); +#endif +#if DECODE_TECO + bool decodeTeco(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTecoBits, + const bool strict = false); +#endif +#if DECODE_LEGOPF + bool decodeLegoPf(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kLegoPfBits, + const bool strict = true); +#endif +#if DECODE_NEOCLIMA + bool decodeNeoclima(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kNeoclimaBits, + const bool strict = true); +#endif // DECODE_NEOCLIMA +#if DECODE_AMCOR + bool decodeAmcor(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kAmcorBits, + const bool strict = true); +#endif // DECODE_AMCOR +#if DECODE_EPSON + bool decodeEpson(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kEpsonBits, + const bool strict = true); +#endif // DECODE_EPSON +#if DECODE_SYMPHONY + bool decodeSymphony(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kSymphonyBits, + const bool strict = true); +#endif // DECODE_SYMPHONY +#if DECODE_AIRWELL + bool decodeAirwell(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kAirwellBits, + const bool strict = true); +#endif // DECODE_AIRWELL +#if DECODE_DELONGHI_AC + bool decodeDelonghiAc(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDelonghiAcBits, + const bool strict = true); +#endif // DECODE_DELONGHI_AC +#if DECODE_DOSHISHA + bool decodeDoshisha(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDoshishaBits, + const bool strict = true); +#endif // DECODE_DOSHISHA +#if DECODE_MULTIBRACKETS + bool decodeMultibrackets(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kMultibracketsBits, + const bool strict = true); +#endif // DECODE_MULTIBRACKETS +#if DECODE_TECHNIBEL_AC + bool decodeTechnibelAc(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kTechnibelAcBits, + const bool strict = true); +#endif // DECODE_TECHNIBEL_AC +#if DECODE_CORONA_AC + bool decodeCoronaAc(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kCoronaAcBitsShort, + const bool strict = true); +#endif // DECODE_CORONA_AC +#if DECODE_ZEPEAL + bool decodeZepeal(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kZepealBits, + const bool strict = true); +#endif // DECODE_ZEPEAL +#if DECODE_METZ + bool decodeMetz(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kMetzBits, + const bool strict = true); +#endif // DECODE_METZ +#if DECODE_TRANSCOLD + bool decodeTranscold(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTranscoldBits, + const bool strict = true); +#endif // DECODE_TRANSCOLD +#if DECODE_MIRAGE + bool decodeMirage(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kMirageBits, + const bool strict = true); +#endif // DECODE_MIRAGE +#if DECODE_ELITESCREENS + bool decodeElitescreens(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kEliteScreensBits, + const bool strict = true); +#endif // DECODE_ELITESCREENS +#if DECODE_ECOCLIM + bool decodeEcoclim(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kEcoclimBits, + const bool strict = true); +#endif // DECODE_ECOCLIM +#if DECODE_XMP + bool decodeXmp(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kXmpBits, const bool strict = true); +#endif // DECODE_XMP +#if DECODE_TRUMA + bool decodeTruma(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTrumaBits, const bool strict = true); +#endif // DECODE_TRUMA +#if DECODE_TEKNOPOINT + bool decodeTeknopoint(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTeknopointBits, + const bool strict = true); +#endif // DECODE_TEKNOPOINT +#if DECODE_KELON + bool decodeKelon(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kKelonBits, const bool strict = true); +#endif // DECODE_KELON +#if DECODE_KELON168 + bool decodeKelon168(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kKelon168Bits, + const bool strict = true); +#endif // DECODE_KELON168 +#if DECODE_BOSE + bool decodeBose(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kBoseBits, const bool strict = true); +#endif // DECODE_BOSE +#if DECODE_RHOSS + bool decodeRhoss(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kRhossBits, const bool strict = true); +#endif // DECODE_RHOSS +#if DECODE_AIRTON + bool decodeAirton(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kAirtonBits, + const bool strict = true); +#endif // DECODE_AIRTON +#if DECODE_TOTO + bool decodeToto(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTotoBits, + const bool strict = true); +#endif // DECODE_TOTO +#if DECODE_CLIMABUTLER + bool decodeClimaButler(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kClimaButlerBits, + const bool strict = true); +#endif // DECODE_CLIMABUTLER +#if DECODE_TCL96AC + bool decodeTcl96Ac(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kTcl96AcBits, + const bool strict = true); +#endif // DECODE_TCL96AC +#if DECODE_BOSCH144 + bool decodeBosch144(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kBosch144Bits, + const bool strict = true); +#endif // DECODE_BOSCH144 +#if DECODE_WOWWEE + bool decodeWowwee(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kWowweeBits, + const bool strict = true); +#endif // DECODE_WOWWEE +}; + +#endif // IRRECV_H_ diff --git a/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h b/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h new file mode 100644 index 000000000..76a4d1370 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h @@ -0,0 +1,1512 @@ + /*************************************************** + * IRremote for ESP8266 + * + * Based on the IRremote library for Arduino by Ken Shirriff + * Version 0.11 August, 2009 + * Copyright 2009 Ken Shirriff + * For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html + * + * Edited by Mitra to add new controller SANYO + * + * Interrupt code based on NECIRrcv by Joe Knapp + * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556 + * Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-an-arduino/ + * + * JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post) + * LG added by Darryl Smith (based on the JVC protocol) + * Whynter A/C ARC-110WD added by Francesco Meschia + * Coolix A/C / heatpump added by (send) bakrus & (decode) crankyoldgit + * Denon: sendDenon, decodeDenon added by Massimiliano Pinto + (from https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp) + * Kelvinator A/C and Sherwood added by crankyoldgit + * Mitsubishi (TV) sending added by crankyoldgit + * Pronto code sending added by crankyoldgit + * Mitsubishi & Toshiba A/C added by crankyoldgit + * (derived from https://github.com/r45635/HVAC-IR-Control) + * DISH decode by marcosamarinho + * Gree Heatpump sending added by Ville Skyttä (scop) + * (derived from https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.cpp) + * Updated by markszabo (https://github.com/crankyoldgit/IRremoteESP8266) for sending IR code on ESP8266 + * Updated by Sebastien Warin (http://sebastien.warin.fr) for receiving IR code on ESP8266 + * + * Updated by sillyfrog for Daikin, adopted from + * (https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/) + * Fujitsu A/C code added by jonnygraham + * Trotec AC code by stufisher + * Carrier & Haier AC code by crankyoldgit + * Vestel AC code by Erdem U. Altınyurt + * Teco AC code by Fabien Valthier (hcoohb) + * Mitsubishi 112 AC Code by kuchel77 + * Kelon AC code by Davide Depau (Depau) + * + * GPL license, all text above must be included in any redistribution + ****************************************************/ + +#ifndef IRREMOTEESP8266_H_ +#define IRREMOTEESP8266_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifdef UNIT_TEST +#include +//#include +#endif // UNIT_TEST + +// Library Version Information +// Major version number (X.x.x) +#define _IRREMOTEESP8266_VERSION_MAJOR 2 +// Minor version number (x.X.x) +#define _IRREMOTEESP8266_VERSION_MINOR 8 +// Patch version number (x.x.X) +#define _IRREMOTEESP8266_VERSION_PATCH 4 +// Macro to convert version info into an integer +#define _IRREMOTEESP8266_VERSION_VAL(major, minor, patch) \ + (((major) << 16) | ((minor) << 8) | (patch)) +// Macro to convert literal into a string +#define MKSTR_HELPER(x) #x +#define MKSTR(x) MKSTR_HELPER(x) +// Integer version +#define _IRREMOTEESP8266_VERSION _IRREMOTEESP8266_VERSION_VAL(\ + _IRREMOTEESP8266_VERSION_MAJOR, \ + _IRREMOTEESP8266_VERSION_MINOR, \ + _IRREMOTEESP8266_VERSION_PATCH) +// String version +#define _IRREMOTEESP8266_VERSION_STR MKSTR(_IRREMOTEESP8266_VERSION_MAJOR) "." \ + MKSTR(_IRREMOTEESP8266_VERSION_MINOR) "." \ + MKSTR(_IRREMOTEESP8266_VERSION_PATCH) +// String version (DEPRECATED) +#define _IRREMOTEESP8266_VERSION_ _IRREMOTEESP8266_VERSION_STR + +// Set the language & locale for the library. See the `locale` dir for options. +#ifndef _IR_LOCALE_ +#define _IR_LOCALE_ en-US +#endif // _IR_LOCALE_ + +// Do we enable all the protocols by default (true), or disable them (false)? +// This allows users of the library to disable or enable all protocols at +// compile-time with `-D_IR_ENABLE_DEFAULT_=true` or +// `-D_IR_ENABLE_DEFAULT_=false` compiler flags respectively. +// Everything is included by default. +// e.g. If you only want to enable use of he NEC protocol to save program space, +// you would use something like: +// `-D_IR_ENABLE_DEFAULT_=false -DDECODE_NEC=true -DSEND_NEC=true` +// +// or alter your 'platform.ini' file accordingly: +// ``` +// build_flags = -D_IR_ENABLE_DEFAULT_=false +// -DDECODE_NEC=true +// -DSEND_NEC=true +// ``` +// If you want to enable support for every protocol *except* _decoding_ the +// Kelvinator protocol, you would use: +// `-DDECODE_KELVINATOR=false` +#ifndef _IR_ENABLE_DEFAULT_ +#define _IR_ENABLE_DEFAULT_ true // Unless set externally, the default is on. +#endif // _IR_ENABLE_DEFAULT_ + +// Supported IR protocols +// Each protocol you include costs memory and, during decode, costs time +// Disable (set to false) all the protocols you do not need/want! +// The Air Conditioner protocols are the most expensive memory-wise. +// + +// Semi-unique code for unknown messages +#ifndef DECODE_HASH +#define DECODE_HASH _IR_ENABLE_DEFAULT_ +#endif // DECODE_HASH + +#ifndef SEND_RAW +#define SEND_RAW _IR_ENABLE_DEFAULT_ +#endif // SEND_RAW + +#ifndef DECODE_NEC +#define DECODE_NEC _IR_ENABLE_DEFAULT_ +#endif // DECODE_NEC +#ifndef SEND_NEC +#define SEND_NEC _IR_ENABLE_DEFAULT_ +#endif // SEND_NEC + +#ifndef DECODE_SHERWOOD +#define DECODE_SHERWOOD false // Not applicable. Actually is DECODE_NEC +#endif // DECODE_SHERWOOD +#ifndef SEND_SHERWOOD +#define SEND_SHERWOOD _IR_ENABLE_DEFAULT_ +#endif // SEND_SHERWOOD + +#ifndef DECODE_RC5 +#define DECODE_RC5 _IR_ENABLE_DEFAULT_ +#endif // DECODE_RC5 +#ifndef SEND_RC5 +#define SEND_RC5 _IR_ENABLE_DEFAULT_ +#endif // SEND_RC5 + +#ifndef DECODE_RC6 +#define DECODE_RC6 _IR_ENABLE_DEFAULT_ +#endif // DECODE_RC6 +#ifndef SEND_RC6 +#define SEND_RC6 _IR_ENABLE_DEFAULT_ +#endif // SEND_RC6 + +#ifndef DECODE_RCMM +#define DECODE_RCMM _IR_ENABLE_DEFAULT_ +#endif // DECODE_RCMM +#ifndef SEND_RCMM +#define SEND_RCMM _IR_ENABLE_DEFAULT_ +#endif // SEND_RCMM + +#ifndef DECODE_SONY +#define DECODE_SONY _IR_ENABLE_DEFAULT_ +#endif // DECODE_SONY +#ifndef SEND_SONY +#define SEND_SONY _IR_ENABLE_DEFAULT_ +#endif // SEND_SONY + +#ifndef DECODE_PANASONIC +#define DECODE_PANASONIC _IR_ENABLE_DEFAULT_ +#endif // DECODE_PANASONIC +#ifndef SEND_PANASONIC +#define SEND_PANASONIC _IR_ENABLE_DEFAULT_ +#endif // SEND_PANASONIC + +#ifndef DECODE_JVC +#define DECODE_JVC _IR_ENABLE_DEFAULT_ +#endif // DECODE_JVC +#ifndef SEND_JVC +#define SEND_JVC _IR_ENABLE_DEFAULT_ +#endif // SEND_JVC + +#ifndef DECODE_SAMSUNG +#define DECODE_SAMSUNG _IR_ENABLE_DEFAULT_ +#endif // DECODE_SAMSUNG +#ifndef SEND_SAMSUNG +#define SEND_SAMSUNG _IR_ENABLE_DEFAULT_ +#endif // SEND_SAMSUNG + +#ifndef DECODE_SAMSUNG36 +#define DECODE_SAMSUNG36 _IR_ENABLE_DEFAULT_ +#endif // DECODE_SAMSUNG36 +#ifndef SEND_SAMSUNG36 +#define SEND_SAMSUNG36 _IR_ENABLE_DEFAULT_ +#endif // SEND_SAMSUNG36 + +#ifndef DECODE_SAMSUNG_AC +#define DECODE_SAMSUNG_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_SAMSUNG_AC +#ifndef SEND_SAMSUNG_AC +#define SEND_SAMSUNG_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_SAMSUNG_AC + +#ifndef DECODE_WHYNTER +#define DECODE_WHYNTER _IR_ENABLE_DEFAULT_ +#endif // DECODE_WHYNTER +#ifndef SEND_WHYNTER +#define SEND_WHYNTER _IR_ENABLE_DEFAULT_ +#endif // SEND_WHYNTER + +#ifndef DECODE_AIWA_RC_T501 +#define DECODE_AIWA_RC_T501 _IR_ENABLE_DEFAULT_ +#endif // DECODE_AIWA_RC_T501 +#ifndef SEND_AIWA_RC_T501 +#define SEND_AIWA_RC_T501 _IR_ENABLE_DEFAULT_ +#endif // SEND_AIWA_RC_T501 + +#ifndef DECODE_LG +#define DECODE_LG _IR_ENABLE_DEFAULT_ +#endif // DECODE_LG +#ifndef SEND_LG +#define SEND_LG _IR_ENABLE_DEFAULT_ +#endif // SEND_LG + +#ifndef DECODE_SANYO +#define DECODE_SANYO _IR_ENABLE_DEFAULT_ +#endif // DECODE_SANYO +#ifndef SEND_SANYO +#define SEND_SANYO _IR_ENABLE_DEFAULT_ +#endif // SEND_SANYO + +#ifndef DECODE_SANYO_AC +#define DECODE_SANYO_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_SANYO_AC +#ifndef SEND_SANYO_AC +#define SEND_SANYO_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_SANYO_AC + +#ifndef DECODE_SANYO_AC88 +#define DECODE_SANYO_AC88 _IR_ENABLE_DEFAULT_ +#endif // DECODE_SANYO_AC88 +#ifndef SEND_SANYO_AC88 +#define SEND_SANYO_AC88 _IR_ENABLE_DEFAULT_ +#endif // SEND_SANYO_AC88 + +#ifndef DECODE_SANYO_AC152 +#define DECODE_SANYO_AC152 _IR_ENABLE_DEFAULT_ +#endif // DECODE_SANYO_AC152 +#ifndef SEND_SANYO_AC152 +#define SEND_SANYO_AC152 _IR_ENABLE_DEFAULT_ +#endif // SEND_SANYO_AC152 + +#ifndef DECODE_MITSUBISHI +#define DECODE_MITSUBISHI _IR_ENABLE_DEFAULT_ +#endif // DECODE_MITSUBISHI +#ifndef SEND_MITSUBISHI +#define SEND_MITSUBISHI _IR_ENABLE_DEFAULT_ +#endif // SEND_MITSUBISHI + +#ifndef DECODE_MITSUBISHI2 +#define DECODE_MITSUBISHI2 _IR_ENABLE_DEFAULT_ +#endif // DECODE_MITSUBISHI2 +#ifndef SEND_MITSUBISHI2 +#define SEND_MITSUBISHI2 _IR_ENABLE_DEFAULT_ +#endif // SEND_MITSUBISHI2 + +#ifndef DECODE_DISH +#define DECODE_DISH _IR_ENABLE_DEFAULT_ +#endif // DECODE_DISH +#ifndef SEND_DISH +#define SEND_DISH _IR_ENABLE_DEFAULT_ +#endif // SEND_DISH + +#ifndef DECODE_SHARP +#define DECODE_SHARP _IR_ENABLE_DEFAULT_ +#endif // DECODE_SHARP +#ifndef SEND_SHARP +#define SEND_SHARP _IR_ENABLE_DEFAULT_ +#endif // SEND_SHARP + +#ifndef DECODE_SHARP_AC +#define DECODE_SHARP_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_SHARP_AC +#ifndef SEND_SHARP_AC +#define SEND_SHARP_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_SHARP_AC + +#ifndef DECODE_DENON +#define DECODE_DENON _IR_ENABLE_DEFAULT_ +#endif // DECODE_DENON +#ifndef SEND_DENON +#define SEND_DENON _IR_ENABLE_DEFAULT_ +#endif // SEND_DENON + +#ifndef DECODE_KELVINATOR +#define DECODE_KELVINATOR _IR_ENABLE_DEFAULT_ +#endif // DECODE_KELVINATOR +#ifndef SEND_KELVINATOR +#define SEND_KELVINATOR _IR_ENABLE_DEFAULT_ +#endif // SEND_KELVINATOR + +#ifndef DECODE_MITSUBISHI_AC +#define DECODE_MITSUBISHI_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_MITSUBISHI_AC +#ifndef SEND_MITSUBISHI_AC +#define SEND_MITSUBISHI_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_MITSUBISHI_AC + +#ifndef DECODE_MITSUBISHI136 +#define DECODE_MITSUBISHI136 _IR_ENABLE_DEFAULT_ +#endif // DECODE_MITSUBISHI136 +#ifndef SEND_MITSUBISHI136 +#define SEND_MITSUBISHI136 _IR_ENABLE_DEFAULT_ +#endif // SEND_MITSUBISHI136 + +#ifndef DECODE_MITSUBISHI112 +#define DECODE_MITSUBISHI112 _IR_ENABLE_DEFAULT_ +#endif // DECODE_MITSUBISHI112 +#ifndef SEND_MITSUBISHI112 +#define SEND_MITSUBISHI112 _IR_ENABLE_DEFAULT_ +#endif // SEND_MITSUBISHI112 + +#ifndef DECODE_FUJITSU_AC +#define DECODE_FUJITSU_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_FUJITSU_AC +#ifndef SEND_FUJITSU_AC +#define SEND_FUJITSU_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_FUJITSU_AC + +#ifndef DECODE_INAX +#define DECODE_INAX _IR_ENABLE_DEFAULT_ +#endif // DECODE_INAX +#ifndef SEND_INAX +#define SEND_INAX _IR_ENABLE_DEFAULT_ +#endif // SEND_INAX + +#ifndef DECODE_DAIKIN +#define DECODE_DAIKIN _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN +#ifndef SEND_DAIKIN +#define SEND_DAIKIN _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN + +#ifndef DECODE_COOLIX +#define DECODE_COOLIX _IR_ENABLE_DEFAULT_ +#endif // DECODE_COOLIX +#ifndef SEND_COOLIX +#define SEND_COOLIX _IR_ENABLE_DEFAULT_ +#endif // SEND_COOLIX + +#ifndef DECODE_COOLIX48 +#define DECODE_COOLIX48 _IR_ENABLE_DEFAULT_ +#endif // DECODE_COOLIX48 +#ifndef SEND_COOLIX48 +#define SEND_COOLIX48 _IR_ENABLE_DEFAULT_ +#endif // SEND_COOLIX48 + +#ifndef DECODE_GLOBALCACHE +#define DECODE_GLOBALCACHE false // Not applicable. +#endif // DECODE_GLOBALCACHE +#ifndef SEND_GLOBALCACHE +#define SEND_GLOBALCACHE _IR_ENABLE_DEFAULT_ +#endif // SEND_GLOBALCACHE + +#ifndef DECODE_GOODWEATHER +#define DECODE_GOODWEATHER _IR_ENABLE_DEFAULT_ +#endif // DECODE_GOODWEATHER +#ifndef SEND_GOODWEATHER +#define SEND_GOODWEATHER _IR_ENABLE_DEFAULT_ +#endif // SEND_GOODWEATHER + +#ifndef DECODE_GREE +#define DECODE_GREE _IR_ENABLE_DEFAULT_ +#endif // DECODE_GREE +#ifndef SEND_GREE +#define SEND_GREE _IR_ENABLE_DEFAULT_ +#endif // SEND_GREE + +#ifndef DECODE_PRONTO +#define DECODE_PRONTO false // Not applicable. +#endif // DECODE_PRONTO +#ifndef SEND_PRONTO +#define SEND_PRONTO _IR_ENABLE_DEFAULT_ +#endif // SEND_PRONTO + +#ifndef DECODE_ARGO +#define DECODE_ARGO _IR_ENABLE_DEFAULT_ +#endif // DECODE_ARGO +#ifndef SEND_ARGO +#define SEND_ARGO _IR_ENABLE_DEFAULT_ +#endif // SEND_ARGO + +#ifndef DECODE_TROTEC +#define DECODE_TROTEC _IR_ENABLE_DEFAULT_ +#endif // DECODE_TROTEC +#ifndef SEND_TROTEC +#define SEND_TROTEC _IR_ENABLE_DEFAULT_ +#endif // SEND_TROTEC + +#ifndef DECODE_TROTEC_3550 +#define DECODE_TROTEC_3550 _IR_ENABLE_DEFAULT_ +#endif // DECODE_TROTEC_3550 +#ifndef SEND_TROTEC_3550 +#define SEND_TROTEC_3550 _IR_ENABLE_DEFAULT_ +#endif // SEND_TROTEC_3550 + +#ifndef DECODE_NIKAI +#define DECODE_NIKAI _IR_ENABLE_DEFAULT_ +#endif // DECODE_NIKAI +#ifndef SEND_NIKAI +#define SEND_NIKAI _IR_ENABLE_DEFAULT_ +#endif // SEND_NIKAI + +#ifndef DECODE_TOSHIBA_AC +#define DECODE_TOSHIBA_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_TOSHIBA_AC +#ifndef SEND_TOSHIBA_AC +#define SEND_TOSHIBA_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_TOSHIBA_AC + +#ifndef DECODE_MAGIQUEST +#define DECODE_MAGIQUEST _IR_ENABLE_DEFAULT_ +#endif // DECODE_MAGIQUEST +#ifndef SEND_MAGIQUEST +#define SEND_MAGIQUEST _IR_ENABLE_DEFAULT_ +#endif // SEND_MAGIQUEST + +#ifndef DECODE_MIDEA +#define DECODE_MIDEA _IR_ENABLE_DEFAULT_ +#endif // DECODE_MIDEA +#ifndef SEND_MIDEA +#define SEND_MIDEA _IR_ENABLE_DEFAULT_ +#endif // SEND_MIDEA + +#ifndef DECODE_MIDEA24 +#define DECODE_MIDEA24 _IR_ENABLE_DEFAULT_ +#endif // DECODE_MIDEA24 +#ifndef SEND_MIDEA24 +#define SEND_MIDEA24 _IR_ENABLE_DEFAULT_ +#endif // SEND_MIDEA24 + +#ifndef DECODE_LASERTAG +#define DECODE_LASERTAG _IR_ENABLE_DEFAULT_ +#endif // DECODE_LASERTAG +#ifndef SEND_LASERTAG +#define SEND_LASERTAG _IR_ENABLE_DEFAULT_ +#endif // SEND_LASERTAG + +#ifndef DECODE_CARRIER_AC +#define DECODE_CARRIER_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_CARRIER_AC +#ifndef SEND_CARRIER_AC +#define SEND_CARRIER_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_CARRIER_AC + +#ifndef DECODE_CARRIER_AC40 +#define DECODE_CARRIER_AC40 _IR_ENABLE_DEFAULT_ +#endif // DECODE_CARRIER_AC40 +#ifndef SEND_CARRIER_AC40 +#define SEND_CARRIER_AC40 _IR_ENABLE_DEFAULT_ +#endif // SEND_CARRIER_AC40 + +#ifndef DECODE_CARRIER_AC64 +#define DECODE_CARRIER_AC64 _IR_ENABLE_DEFAULT_ +#endif // DECODE_CARRIER_AC64 +#ifndef SEND_CARRIER_AC64 +#define SEND_CARRIER_AC64 _IR_ENABLE_DEFAULT_ +#endif // SEND_CARRIER_AC64 + +#ifndef DECODE_CARRIER_AC128 +#define DECODE_CARRIER_AC128 _IR_ENABLE_DEFAULT_ +#endif // DECODE_CARRIER_AC128 +#ifndef SEND_CARRIER_AC128 +#define SEND_CARRIER_AC128 _IR_ENABLE_DEFAULT_ +#endif // SEND_CARRIER_AC128 + +#ifndef DECODE_HAIER_AC +#define DECODE_HAIER_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_HAIER_AC +#ifndef SEND_HAIER_AC +#define SEND_HAIER_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_HAIER_AC + +#ifndef DECODE_HITACHI_AC +#define DECODE_HITACHI_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC +#ifndef SEND_HITACHI_AC +#define SEND_HITACHI_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC + +#ifndef DECODE_HITACHI_AC1 +#define DECODE_HITACHI_AC1 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC1 +#ifndef SEND_HITACHI_AC1 +#define SEND_HITACHI_AC1 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC1 + +#ifndef DECODE_HITACHI_AC2 +#define DECODE_HITACHI_AC2 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC2 +#ifndef SEND_HITACHI_AC2 +#define SEND_HITACHI_AC2 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC2 + +#ifndef DECODE_HITACHI_AC3 +#define DECODE_HITACHI_AC3 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC3 +#ifndef SEND_HITACHI_AC3 +#define SEND_HITACHI_AC3 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC3 + +#ifndef DECODE_HITACHI_AC264 +#define DECODE_HITACHI_AC264 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC264 +#ifndef SEND_HITACHI_AC264 +#define SEND_HITACHI_AC264 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC264 + +#ifndef DECODE_HITACHI_AC296 +#define DECODE_HITACHI_AC296 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC296 +#ifndef SEND_HITACHI_AC296 +#define SEND_HITACHI_AC296 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC296 + +#ifndef DECODE_HITACHI_AC344 +#define DECODE_HITACHI_AC344 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC344 +#ifndef SEND_HITACHI_AC344 +#define SEND_HITACHI_AC344 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC344 + +#ifndef DECODE_HITACHI_AC424 +#define DECODE_HITACHI_AC424 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC424 +#ifndef SEND_HITACHI_AC424 +#define SEND_HITACHI_AC424 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC424 + +#ifndef DECODE_GICABLE +#define DECODE_GICABLE _IR_ENABLE_DEFAULT_ +#endif // DECODE_GICABLE +#ifndef SEND_GICABLE +#define SEND_GICABLE _IR_ENABLE_DEFAULT_ +#endif // SEND_GICABLE + +#ifndef DECODE_HAIER_AC_YRW02 +#define DECODE_HAIER_AC_YRW02 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HAIER_AC_YRW02 +#ifndef SEND_HAIER_AC_YRW02 +#define SEND_HAIER_AC_YRW02 _IR_ENABLE_DEFAULT_ +#endif // SEND_HAIER_AC_YRW02 + +#ifndef DECODE_WHIRLPOOL_AC +#define DECODE_WHIRLPOOL_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_WHIRLPOOL_AC +#ifndef SEND_WHIRLPOOL_AC +#define SEND_WHIRLPOOL_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_WHIRLPOOL_AC + +#ifndef DECODE_LUTRON +#define DECODE_LUTRON _IR_ENABLE_DEFAULT_ +#endif // DECODE_LUTRON +#ifndef SEND_LUTRON +#define SEND_LUTRON _IR_ENABLE_DEFAULT_ +#endif // SEND_LUTRON + +#ifndef DECODE_ELECTRA_AC +#define DECODE_ELECTRA_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_ELECTRA_AC +#ifndef SEND_ELECTRA_AC +#define SEND_ELECTRA_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_ELECTRA_AC + +#ifndef DECODE_PANASONIC_AC +#define DECODE_PANASONIC_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_PANASONIC_AC +#ifndef SEND_PANASONIC_AC +#define SEND_PANASONIC_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_PANASONIC_AC + +#ifndef DECODE_PANASONIC_AC32 +#define DECODE_PANASONIC_AC32 _IR_ENABLE_DEFAULT_ +#endif // DECODE_PANASONIC_AC32 +#ifndef SEND_PANASONIC_AC32 +#define SEND_PANASONIC_AC32 _IR_ENABLE_DEFAULT_ +#endif // SEND_PANASONIC_AC32 + +#ifndef DECODE_MWM +#define DECODE_MWM _IR_ENABLE_DEFAULT_ +#endif // DECODE_MWM +#ifndef SEND_MWM +#define SEND_MWM _IR_ENABLE_DEFAULT_ +#endif // SEND_MWM + +#ifndef DECODE_PIONEER +#define DECODE_PIONEER _IR_ENABLE_DEFAULT_ +#endif // DECODE_PIONEER +#ifndef SEND_PIONEER +#define SEND_PIONEER _IR_ENABLE_DEFAULT_ +#endif // SEND_PIONEER + +#ifndef DECODE_DAIKIN2 +#define DECODE_DAIKIN2 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN2 +#ifndef SEND_DAIKIN2 +#define SEND_DAIKIN2 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN2 + +#ifndef DECODE_VESTEL_AC +#define DECODE_VESTEL_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_VESTEL_AC +#ifndef SEND_VESTEL_AC +#define SEND_VESTEL_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_VESTEL_AC + +#ifndef DECODE_TECO +#define DECODE_TECO _IR_ENABLE_DEFAULT_ +#endif // DECODE_TECO +#ifndef SEND_TECO +#define SEND_TECO _IR_ENABLE_DEFAULT_ +#endif // SEND_TECO + +#ifndef DECODE_TCL96AC +#define DECODE_TCL96AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_TCL96AC +#ifndef SEND_TCL96AC +#define SEND_TCL96AC _IR_ENABLE_DEFAULT_ +#endif // SEND_TCL96AC + +#ifndef DECODE_TCL112AC +#define DECODE_TCL112AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_TCL112AC +#ifndef SEND_TCL112AC +#define SEND_TCL112AC _IR_ENABLE_DEFAULT_ +#endif // SEND_TCL112AC + +#ifndef DECODE_LEGOPF +#define DECODE_LEGOPF _IR_ENABLE_DEFAULT_ +#endif // DECODE_LEGOPF +#ifndef SEND_LEGOPF +#define SEND_LEGOPF _IR_ENABLE_DEFAULT_ +#endif // SEND_LEGOPF + +#ifndef DECODE_MITSUBISHIHEAVY +#define DECODE_MITSUBISHIHEAVY _IR_ENABLE_DEFAULT_ +#endif // DECODE_MITSUBISHIHEAVY +#ifndef SEND_MITSUBISHIHEAVY +#define SEND_MITSUBISHIHEAVY _IR_ENABLE_DEFAULT_ +#endif // SEND_MITSUBISHIHEAVY + +#ifndef DECODE_DAIKIN216 +#define DECODE_DAIKIN216 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN216 +#ifndef SEND_DAIKIN216 +#define SEND_DAIKIN216 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN216 + +#ifndef DECODE_DAIKIN160 +#define DECODE_DAIKIN160 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN160 +#ifndef SEND_DAIKIN160 +#define SEND_DAIKIN160 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN160 + +#ifndef DECODE_NEOCLIMA +#define DECODE_NEOCLIMA _IR_ENABLE_DEFAULT_ +#endif // DECODE_NEOCLIMA +#ifndef SEND_NEOCLIMA +#define SEND_NEOCLIMA _IR_ENABLE_DEFAULT_ +#endif // SEND_NEOCLIMA + +#ifndef DECODE_DAIKIN176 +#define DECODE_DAIKIN176 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN176 +#ifndef SEND_DAIKIN176 +#define SEND_DAIKIN176 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN176 + +#ifndef DECODE_DAIKIN128 +#define DECODE_DAIKIN128 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN128 +#ifndef SEND_DAIKIN128 +#define SEND_DAIKIN128 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN128 + +#ifndef DECODE_AMCOR +#define DECODE_AMCOR _IR_ENABLE_DEFAULT_ +#endif // DECODE_AMCOR +#ifndef SEND_AMCOR +#define SEND_AMCOR _IR_ENABLE_DEFAULT_ +#endif // SEND_AMCOR + +#ifndef DECODE_DAIKIN152 +#define DECODE_DAIKIN152 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN152 +#ifndef SEND_DAIKIN152 +#define SEND_DAIKIN152 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN152 + +#ifndef DECODE_EPSON +#define DECODE_EPSON _IR_ENABLE_DEFAULT_ +#endif // DECODE_EPSON +#ifndef SEND_EPSON +#define SEND_EPSON _IR_ENABLE_DEFAULT_ +#endif // SEND_EPSON + +#ifndef DECODE_SYMPHONY +#define DECODE_SYMPHONY _IR_ENABLE_DEFAULT_ +#endif // DECODE_SYMPHONY +#ifndef SEND_SYMPHONY +#define SEND_SYMPHONY _IR_ENABLE_DEFAULT_ +#endif // SEND_SYMPHONY + +#ifndef DECODE_DAIKIN64 +#define DECODE_DAIKIN64 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN64 +#ifndef SEND_DAIKIN64 +#define SEND_DAIKIN64 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN64 + +#ifndef DECODE_AIRWELL +#define DECODE_AIRWELL _IR_ENABLE_DEFAULT_ +#endif // DECODE_AIRWELL +#ifndef SEND_AIRWELL +#define SEND_AIRWELL _IR_ENABLE_DEFAULT_ +#endif // SEND_AIRWELL + +#ifndef DECODE_DELONGHI_AC +#define DECODE_DELONGHI_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_DELONGHI_AC +#ifndef SEND_DELONGHI_AC +#define SEND_DELONGHI_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_DELONGHI_AC + +#ifndef DECODE_DOSHISHA +#define DECODE_DOSHISHA _IR_ENABLE_DEFAULT_ +#endif // DECODE_DOSHISHA +#ifndef SEND_DOSHISHA +#define SEND_DOSHISHA _IR_ENABLE_DEFAULT_ +#endif // SEND_DOSHISHA + +#ifndef DECODE_MULTIBRACKETS +#define DECODE_MULTIBRACKETS _IR_ENABLE_DEFAULT_ +#endif // DECODE_MULTIBRACKETS +#ifndef SEND_MULTIBRACKETS +#define SEND_MULTIBRACKETS _IR_ENABLE_DEFAULT_ +#endif // SEND_MULTIBRACKETS + +#ifndef DECODE_TECHNIBEL_AC +#define DECODE_TECHNIBEL_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_TECHNIBEL_AC +#ifndef SEND_TECHNIBEL_AC +#define SEND_TECHNIBEL_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_TECHNIBEL_AC + +#ifndef DECODE_CORONA_AC +#define DECODE_CORONA_AC _IR_ENABLE_DEFAULT_ +#endif // DECODE_CORONA_AC +#ifndef SEND_CORONA_AC +#define SEND_CORONA_AC _IR_ENABLE_DEFAULT_ +#endif // SEND_CORONA_AC + +#ifndef DECODE_ZEPEAL +#define DECODE_ZEPEAL _IR_ENABLE_DEFAULT_ +#endif // DECODE_ZEPEAL +#ifndef SEND_ZEPEAL +#define SEND_ZEPEAL _IR_ENABLE_DEFAULT_ +#endif // SEND_ZEPEAL + +#ifndef DECODE_VOLTAS +#define DECODE_VOLTAS _IR_ENABLE_DEFAULT_ +#endif // DECODE_VOLTAS +#ifndef SEND_VOLTAS +#define SEND_VOLTAS _IR_ENABLE_DEFAULT_ +#endif // SEND_VOLTAS + +#ifndef DECODE_METZ +#define DECODE_METZ _IR_ENABLE_DEFAULT_ +#endif // DECODE_METZ +#ifndef SEND_METZ +#define SEND_METZ _IR_ENABLE_DEFAULT_ +#endif // SEND_METZ + +#ifndef DECODE_TRANSCOLD +#define DECODE_TRANSCOLD _IR_ENABLE_DEFAULT_ +#endif // DECODE_TRANSCOLD +#ifndef SEND_TRANSCOLD +#define SEND_TRANSCOLD _IR_ENABLE_DEFAULT_ +#endif // SEND_TRANSCOLD + +#ifndef DECODE_MIRAGE +#define DECODE_MIRAGE _IR_ENABLE_DEFAULT_ +#endif // DECODE_MIRAGE +#ifndef SEND_MIRAGE +#define SEND_MIRAGE _IR_ENABLE_DEFAULT_ +#endif // SEND_MIRAGE + +#ifndef DECODE_ELITESCREENS +#define DECODE_ELITESCREENS _IR_ENABLE_DEFAULT_ +#endif // DECODE_ELITESCREENS +#ifndef SEND_ELITESCREENS +#define SEND_ELITESCREENS _IR_ENABLE_DEFAULT_ +#endif // SEND_ELITESCREENS + +#ifndef DECODE_MILESTAG2 +#define DECODE_MILESTAG2 _IR_ENABLE_DEFAULT_ +#endif // DECODE_MILESTAG2 +#ifndef SEND_MILESTAG2 +#define SEND_MILESTAG2 _IR_ENABLE_DEFAULT_ +#endif // SEND_MILESTAG2 + +#ifndef DECODE_ECOCLIM +#define DECODE_ECOCLIM _IR_ENABLE_DEFAULT_ +#endif // DECODE_ECOCLIM +#ifndef SEND_ECOCLIM +#define SEND_ECOCLIM _IR_ENABLE_DEFAULT_ +#endif // SEND_ECOCLIM + +#ifndef DECODE_XMP +#define DECODE_XMP _IR_ENABLE_DEFAULT_ +#endif // DECODE_XMP +#ifndef SEND_XMP +#define SEND_XMP _IR_ENABLE_DEFAULT_ +#endif // SEND_XMP + +#ifndef DECODE_TRUMA +#define DECODE_TRUMA _IR_ENABLE_DEFAULT_ +#endif // DECODE_TRUMA +#ifndef SEND_TRUMA +#define SEND_TRUMA _IR_ENABLE_DEFAULT_ +#endif // SEND_TRUMA + +#ifndef DECODE_HAIER_AC176 +#define DECODE_HAIER_AC176 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HAIER_AC176 +#ifndef SEND_HAIER_AC176 +#define SEND_HAIER_AC176 _IR_ENABLE_DEFAULT_ +#endif // SEND_HAIER_AC176 + +#ifndef DECODE_TEKNOPOINT +#define DECODE_TEKNOPOINT _IR_ENABLE_DEFAULT_ +#endif // DECODE_TEKNOPOINT +#ifndef SEND_TEKNOPOINT +#define SEND_TEKNOPOINT _IR_ENABLE_DEFAULT_ +#endif // SEND_TEKNOPOINT + +#ifndef DECODE_KELON +#define DECODE_KELON _IR_ENABLE_DEFAULT_ +#endif // DECODE_KELON +#ifndef SEND_KELON +#define SEND_KELON _IR_ENABLE_DEFAULT_ +#endif // SEND_KELON + +#ifndef DECODE_BOSE +#define DECODE_BOSE _IR_ENABLE_DEFAULT_ +#endif // DECODE_BOSE +#ifndef SEND_BOSE +#define SEND_BOSE _IR_ENABLE_DEFAULT_ +#endif // SEND_BOSE + +#ifndef DECODE_ARRIS +#define DECODE_ARRIS _IR_ENABLE_DEFAULT_ +#endif // DECODE_ARRIS +#ifndef SEND_ARRIS +#define SEND_ARRIS _IR_ENABLE_DEFAULT_ +#endif // SEND_ARRIS + +#ifndef DECODE_RHOSS +#define DECODE_RHOSS _IR_ENABLE_DEFAULT_ +#endif // DECODE_RHOSS +#ifndef SEND_RHOSS +#define SEND_RHOSS _IR_ENABLE_DEFAULT_ +#endif // SEND_RHOSS + +#ifndef DECODE_AIRTON +#define DECODE_AIRTON _IR_ENABLE_DEFAULT_ +#endif // DECODE_AIRTON +#ifndef SEND_AIRTON +#define SEND_AIRTON _IR_ENABLE_DEFAULT_ +#endif // SEND_AIRTON + +#ifndef DECODE_KELON168 +#define DECODE_KELON168 _IR_ENABLE_DEFAULT_ +#endif // DECODE_KELON168 +#ifndef SEND_KELON168 +#define SEND_KELON168 _IR_ENABLE_DEFAULT_ +#endif // SEND_KELON168 + +#ifndef DECODE_DAIKIN200 +#define DECODE_DAIKIN200 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN200 +#ifndef SEND_DAIKIN200 +#define SEND_DAIKIN200 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN200 + +#ifndef DECODE_HAIER_AC160 +#define DECODE_HAIER_AC160 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HAIER_AC160 +#ifndef SEND_HAIER_AC160 +#define SEND_HAIER_AC160 _IR_ENABLE_DEFAULT_ +#endif // SEND_HAIER_AC160 + +#ifndef DECODE_TOTO +#define DECODE_TOTO _IR_ENABLE_DEFAULT_ +#endif // DECODE_TOTO +#ifndef SEND_TOTO +#define SEND_TOTO _IR_ENABLE_DEFAULT_ +#endif // SEND_TOTO + +#ifndef DECODE_CLIMABUTLER +#define DECODE_CLIMABUTLER _IR_ENABLE_DEFAULT_ +#endif // DECODE_CLIMABUTLER +#ifndef SEND_CLIMABUTLER +#define SEND_CLIMABUTLER _IR_ENABLE_DEFAULT_ +#endif // SEND_CLIMABUTLER + +#ifndef DECODE_BOSCH144 +#define DECODE_BOSCH144 _IR_ENABLE_DEFAULT_ +#endif // DECODE_BOSCH144 +#ifndef SEND_BOSCH144 +#define SEND_BOSCH144 _IR_ENABLE_DEFAULT_ +#endif // SEND_BOSCH144 + +#ifndef DECODE_DAIKIN312 +#define DECODE_DAIKIN312 _IR_ENABLE_DEFAULT_ +#endif // DECODE_DAIKIN312 +#ifndef SEND_DAIKIN312 +#define SEND_DAIKIN312 _IR_ENABLE_DEFAULT_ +#endif // SEND_DAIKIN312 + +#ifndef DECODE_GORENJE +#define DECODE_GORENJE _IR_ENABLE_DEFAULT_ +#endif // DECODE_GORENJE +#ifndef SEND_GORENJE +#define SEND_GORENJE _IR_ENABLE_DEFAULT_ +#endif // SEND_GORENJE + +#ifndef DECODE_WOWWEE +#define DECODE_WOWWEE _IR_ENABLE_DEFAULT_ +#endif // DECODE_WOWWEE +#ifndef SEND_WOWWEE +#define SEND_WOWWEE _IR_ENABLE_DEFAULT_ +#endif // SEND_WOWWEE + +#ifndef DECODE_CARRIER_AC84 +#define DECODE_CARRIER_AC84 _IR_ENABLE_DEFAULT_ +#endif // DECODE_CARRIER_AC84 +#ifndef SEND_CARRIER_AC84 +#define SEND_CARRIER_AC84 _IR_ENABLE_DEFAULT_ +#endif // SEND_CARRIER_AC84 + +#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \ + DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \ + DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \ + DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2 || DECODE_HAIER_AC_YRW02 || \ + DECODE_WHIRLPOOL_AC || DECODE_SAMSUNG_AC || DECODE_ELECTRA_AC || \ + DECODE_PANASONIC_AC || DECODE_MWM || DECODE_DAIKIN2 || \ + DECODE_VESTEL_AC || DECODE_TCL112AC || DECODE_MITSUBISHIHEAVY || \ + DECODE_DAIKIN216 || DECODE_SHARP_AC || DECODE_DAIKIN160 || \ + DECODE_NEOCLIMA || DECODE_DAIKIN176 || DECODE_DAIKIN128 || \ + DECODE_AMCOR || DECODE_DAIKIN152 || DECODE_MITSUBISHI136 || \ + DECODE_MITSUBISHI112 || DECODE_HITACHI_AC424 || DECODE_HITACHI_AC3 || \ + DECODE_HITACHI_AC344 || DECODE_CORONA_AC || DECODE_SANYO_AC || \ + DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \ + DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \ + DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \ + DECODE_KELON168 || DECODE_HITACHI_AC296 || DECODE_CARRIER_AC128 || \ + DECODE_DAIKIN200 || DECODE_HAIER_AC160 || DECODE_TCL96AC || \ + DECODE_BOSCH144 || DECODE_SANYO_AC152 || DECODE_DAIKIN312 || \ + DECODE_CARRIER_AC84 || \ + false) + // Add any DECODE to the above if it uses result->state (see kStateSizeMax) + // you might also want to add the protocol to hasACState function +#define DECODE_AC true // We need some common infrastructure for decoding A/Cs. +#else +#define DECODE_AC false // We don't need that infrastructure. +#endif + +// Use millisecond 'delay()' calls where we can to avoid tripping the WDT. +// Note: If you plan to send IR messages in the callbacks of the AsyncWebserver +// library, you need to set ALLOW_DELAY_CALLS to false. +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/430 +#ifndef ALLOW_DELAY_CALLS +#define ALLOW_DELAY_CALLS true +#endif // ALLOW_DELAY_CALLS + +// Enable a run-time settable high-pass filter on captured data **before** +// trying any protocol decoding. +// i.e. Try to remove/merge any really short pulses detected in the raw data. +// Note: Even when this option is enabled, it is _off_ by default, and requires +// a user who knows what they are doing to enable it. +// The option to disable this feature is here if your project is _really_ +// tight on resources. i.e. Saves a small handful of bytes and cpu time. +// WARNING: If you use this feature at runtime, you can no longer trust the +// **raw** data captured. It will now have been slightly **cooked**! +// DANGER: If you set the `noise_floor` value too high, it **WILL** break +// decoding of some protocols. You have been warned. Here Be Dragons! +// +// See: `irrecv::decode()` in IRrecv.cpp for more info. +#ifndef ENABLE_NOISE_FILTER_OPTION +#define ENABLE_NOISE_FILTER_OPTION true +#endif // ENABLE_NOISE_FILTER_OPTION + +/// Enumerator for defining and numbering of supported IR protocol. +/// @note Always add to the end of the list and should never remove entries +/// or change order. Projects may save the type number for later usage +/// so numbering should always stay the same. +enum decode_type_t { + UNKNOWN = -1, + UNUSED = 0, + RC5, + RC6, + NEC, + SONY, + PANASONIC, // (5) + JVC, + SAMSUNG, + WHYNTER, + AIWA_RC_T501, + LG, // (10) + SANYO, + MITSUBISHI, + DISH, + SHARP, + COOLIX, // (15) + DAIKIN, + DENON, + KELVINATOR, + SHERWOOD, + MITSUBISHI_AC, // (20) + RCMM, + SANYO_LC7461, + RC5X, + GREE, + PRONTO, // Technically not a protocol, but an encoding. (25) + NEC_LIKE, + ARGO, + TROTEC, + NIKAI, + RAW, // Technically not a protocol, but an encoding. (30) + GLOBALCACHE, // Technically not a protocol, but an encoding. + TOSHIBA_AC, + FUJITSU_AC, + MIDEA, + MAGIQUEST, // (35) + LASERTAG, + CARRIER_AC, + HAIER_AC, + MITSUBISHI2, + HITACHI_AC, // (40) + HITACHI_AC1, + HITACHI_AC2, + GICABLE, + HAIER_AC_YRW02, + WHIRLPOOL_AC, // (45) + SAMSUNG_AC, + LUTRON, + ELECTRA_AC, + PANASONIC_AC, + PIONEER, // (50) + LG2, + MWM, + DAIKIN2, + VESTEL_AC, + TECO, // (55) + SAMSUNG36, + TCL112AC, + LEGOPF, + MITSUBISHI_HEAVY_88, + MITSUBISHI_HEAVY_152, // 60 + DAIKIN216, + SHARP_AC, + GOODWEATHER, + INAX, + DAIKIN160, // 65 + NEOCLIMA, + DAIKIN176, + DAIKIN128, + AMCOR, + DAIKIN152, // 70 + MITSUBISHI136, + MITSUBISHI112, + HITACHI_AC424, + SONY_38K, + EPSON, // 75 + SYMPHONY, + HITACHI_AC3, + DAIKIN64, + AIRWELL, + DELONGHI_AC, // 80 + DOSHISHA, + MULTIBRACKETS, + CARRIER_AC40, + CARRIER_AC64, + HITACHI_AC344, // 85 + CORONA_AC, + MIDEA24, + ZEPEAL, + SANYO_AC, + VOLTAS, // 90 + METZ, + TRANSCOLD, + TECHNIBEL_AC, + MIRAGE, + ELITESCREENS, // 95 + PANASONIC_AC32, + MILESTAG2, + ECOCLIM, + XMP, + TRUMA, // 100 + HAIER_AC176, + TEKNOPOINT, + KELON, + TROTEC_3550, + SANYO_AC88, // 105 + BOSE, + ARRIS, + RHOSS, + AIRTON, + COOLIX48, // 110 + HITACHI_AC264, + KELON168, + HITACHI_AC296, + DAIKIN200, + HAIER_AC160, // 115 + CARRIER_AC128, + TOTO, + CLIMABUTLER, + TCL96AC, + BOSCH144, // 120 + SANYO_AC152, + DAIKIN312, + GORENJE, + WOWWEE, + CARRIER_AC84, // 125 + // Add new entries before this one, and update it to point to the last entry. + kLastDecodeType = CARRIER_AC84, +}; + +// Message lengths & required repeat values +const uint16_t kNoRepeat = 0; +const uint16_t kSingleRepeat = 1; + +const uint16_t kAirtonBits = 56; +const uint16_t kAirtonDefaultRepeat = kNoRepeat; +const uint16_t kAirwellBits = 34; +const uint16_t kAirwellMinRepeats = 2; +const uint16_t kAiwaRcT501Bits = 15; +const uint16_t kAiwaRcT501MinRepeats = kSingleRepeat; +const uint16_t kAlokaBits = 32; +const uint16_t kAmcorStateLength = 8; +const uint16_t kAmcorBits = kAmcorStateLength * 8; +const uint16_t kAmcorDefaultRepeat = kSingleRepeat; +const uint16_t kArgoStateLength = 12; +const uint16_t kArgoShortStateLength = 4; +const uint16_t kArgoBits = kArgoStateLength * 8; +const uint16_t kArgoShortBits = kArgoShortStateLength * 8; +const uint16_t kArgo3AcControlStateLength = 6; // Bytes +const uint16_t kArgo3iFeelReportStateLength = 2; // Bytes +const uint16_t kArgo3TimerStateLength = 9; // Bytes +const uint16_t kArgo3ConfigStateLength = 4; // Bytes +const uint16_t kArgoDefaultRepeat = kNoRepeat; +const uint16_t kArrisBits = 32; +const uint16_t kBosch144StateLength = 18; +const uint16_t kBosch144Bits = kBosch144StateLength * 8; +const uint16_t kCoolixBits = 24; +const uint16_t kCoolix48Bits = kCoolixBits * 2; +const uint16_t kCoolixDefaultRepeat = kSingleRepeat; +const uint16_t kCarrierAcBits = 32; +const uint16_t kCarrierAcMinRepeat = kNoRepeat; +const uint16_t kCarrierAc40Bits = 40; +const uint16_t kCarrierAc40MinRepeat = 2; +const uint16_t kCarrierAc64Bits = 64; +const uint16_t kCarrierAc64MinRepeat = kNoRepeat; +const uint16_t kCarrierAc84StateLength = 11; +const uint16_t kCarrierAc84Bits = kCarrierAc84StateLength * 8 - 4; +const uint16_t kCarrierAc84MinRepeat = kNoRepeat; +const uint16_t kCarrierAc128StateLength = 16; +const uint16_t kCarrierAc128Bits = kCarrierAc128StateLength * 8; +const uint16_t kCarrierAc128MinRepeat = kNoRepeat; +const uint16_t kCoronaAcStateLengthShort = 7; +const uint16_t kCoronaAcStateLength = kCoronaAcStateLengthShort * 3; +const uint16_t kCoronaAcBitsShort = kCoronaAcStateLengthShort * 8; +const uint16_t kCoronaAcBits = kCoronaAcStateLength * 8; +const uint16_t kDaikinStateLength = 35; +const uint16_t kDaikinBits = kDaikinStateLength * 8; +const uint16_t kDaikinStateLengthShort = kDaikinStateLength - 8; +const uint16_t kDaikinBitsShort = kDaikinStateLengthShort * 8; +const uint16_t kDaikinDefaultRepeat = kNoRepeat; +const uint16_t kDaikin2StateLength = 39; +const uint16_t kDaikin2Bits = kDaikin2StateLength * 8; +const uint16_t kDaikin2DefaultRepeat = kNoRepeat; +const uint16_t kDaikin64Bits = 64; +const uint16_t kDaikin64DefaultRepeat = kNoRepeat; +const uint16_t kDaikin160StateLength = 20; +const uint16_t kDaikin160Bits = kDaikin160StateLength * 8; +const uint16_t kDaikin160DefaultRepeat = kNoRepeat; +const uint16_t kDaikin128StateLength = 16; +const uint16_t kDaikin128Bits = kDaikin128StateLength * 8; +const uint16_t kDaikin128DefaultRepeat = kNoRepeat; +const uint16_t kDaikin152StateLength = 19; +const uint16_t kDaikin152Bits = kDaikin152StateLength * 8; +const uint16_t kDaikin152DefaultRepeat = kNoRepeat; +const uint16_t kDaikin176StateLength = 22; +const uint16_t kDaikin176Bits = kDaikin176StateLength * 8; +const uint16_t kDaikin176DefaultRepeat = kNoRepeat; +const uint16_t kDaikin200StateLength = 25; +const uint16_t kDaikin200Bits = kDaikin200StateLength * 8; +const uint16_t kDaikin200DefaultRepeat = kNoRepeat; +const uint16_t kDaikin216StateLength = 27; +const uint16_t kDaikin216Bits = kDaikin216StateLength * 8; +const uint16_t kDaikin216DefaultRepeat = kNoRepeat; +const uint16_t kDaikin312StateLength = 39; +const uint16_t kDaikin312Bits = kDaikin312StateLength * 8; +const uint16_t kDaikin312DefaultRepeat = kNoRepeat; +const uint16_t kDelonghiAcBits = 64; +const uint16_t kDelonghiAcDefaultRepeat = kNoRepeat; +const uint16_t kTechnibelAcBits = 56; +const uint16_t kTechnibelAcDefaultRepeat = kNoRepeat; +const uint16_t kDenonBits = 15; +const uint16_t kDenon48Bits = 48; +const uint16_t kDenonLegacyBits = 14; +const uint16_t kDishBits = 16; +const uint16_t kDishMinRepeat = 3; +const uint16_t kDoshishaBits = 40; +const uint16_t kEcoclimBits = 56; +const uint16_t kEcoclimShortBits = 15; +const uint16_t kEpsonBits = 32; +const uint16_t kEpsonMinRepeat = 2; +const uint16_t kElectraAcStateLength = 13; +const uint16_t kElectraAcBits = kElectraAcStateLength * 8; +const uint16_t kElectraAcMinRepeat = kNoRepeat; +const uint16_t kEliteScreensBits = 32; +const uint16_t kEliteScreensDefaultRepeat = kSingleRepeat; +const uint16_t kFujitsuAcMinRepeat = kNoRepeat; +const uint16_t kFujitsuAcStateLength = 16; +const uint16_t kFujitsuAcStateLengthShort = 7; +const uint16_t kFujitsuAcBits = kFujitsuAcStateLength * 8; +const uint16_t kFujitsuAcMinBits = (kFujitsuAcStateLengthShort - 1) * 8; +const uint16_t kGicableBits = 16; +const uint16_t kGicableMinRepeat = kSingleRepeat; +const uint16_t kGoodweatherBits = 48; +const uint16_t kGoodweatherMinRepeat = kNoRepeat; +const uint16_t kGorenjeBits = 8; +const uint16_t kGreeStateLength = 8; +const uint16_t kGreeBits = kGreeStateLength * 8; +const uint16_t kGreeDefaultRepeat = kNoRepeat; +const uint16_t kHaierACStateLength = 9; +const uint16_t kHaierACBits = kHaierACStateLength * 8; +const uint16_t kHaierAcDefaultRepeat = kNoRepeat; +const uint16_t kHaierACYRW02StateLength = 14; +const uint16_t kHaierACYRW02Bits = kHaierACYRW02StateLength * 8; +const uint16_t kHaierAcYrw02DefaultRepeat = kNoRepeat; +const uint16_t kHaierAC160StateLength = 20; +const uint16_t kHaierAC160Bits = kHaierAC160StateLength * 8; +const uint16_t kHaierAc160DefaultRepeat = kNoRepeat; +const uint16_t kHaierAC176StateLength = 22; +const uint16_t kHaierAC176Bits = kHaierAC176StateLength * 8; +const uint16_t kHaierAc176DefaultRepeat = kNoRepeat; +const uint16_t kHitachiAcStateLength = 28; +const uint16_t kHitachiAcBits = kHitachiAcStateLength * 8; +const uint16_t kHitachiAcDefaultRepeat = kNoRepeat; +const uint16_t kHitachiAc1StateLength = 13; +const uint16_t kHitachiAc1Bits = kHitachiAc1StateLength * 8; +const uint16_t kHitachiAc2StateLength = 53; +const uint16_t kHitachiAc2Bits = kHitachiAc2StateLength * 8; +const uint16_t kHitachiAc3StateLength = 27; +const uint16_t kHitachiAc3Bits = kHitachiAc3StateLength * 8; +const uint16_t kHitachiAc3MinStateLength = 15; +const uint16_t kHitachiAc3MinBits = kHitachiAc3MinStateLength * 8; +const uint16_t kHitachiAc264StateLength = 33; +const uint16_t kHitachiAc264Bits = kHitachiAc264StateLength * 8; +const uint16_t kHitachiAc296StateLength = 37; +const uint16_t kHitachiAc296Bits = kHitachiAc296StateLength * 8; +const uint16_t kHitachiAc344StateLength = 43; +const uint16_t kHitachiAc344Bits = kHitachiAc344StateLength * 8; +const uint16_t kHitachiAc424StateLength = 53; +const uint16_t kHitachiAc424Bits = kHitachiAc424StateLength * 8; +const uint16_t kInaxBits = 24; +const uint16_t kInaxMinRepeat = kSingleRepeat; +const uint16_t kJvcBits = 16; +const uint16_t kKelonBits = 48; +const uint16_t kKelon168StateLength = 21; +const uint16_t kKelon168Bits = kKelon168StateLength * 8; +const uint16_t kKelvinatorStateLength = 16; +const uint16_t kKelvinatorBits = kKelvinatorStateLength * 8; +const uint16_t kKelvinatorDefaultRepeat = kNoRepeat; +const uint16_t kLasertagBits = 13; +const uint16_t kLasertagMinRepeat = kNoRepeat; +const uint16_t kLegoPfBits = 16; +const uint16_t kLegoPfMinRepeat = kNoRepeat; +const uint16_t kLgBits = 28; +const uint16_t kLg32Bits = 32; +const uint16_t kLgDefaultRepeat = kNoRepeat; +const uint16_t kLutronBits = 35; +const uint16_t kMagiquestBits = 56; +const uint16_t kMetzBits = 19; +const uint16_t kMetzMinRepeat = kNoRepeat; +const uint16_t kMideaBits = 48; +const uint16_t kMideaMinRepeat = kNoRepeat; +const uint16_t kMidea24Bits = 24; +const uint16_t kMidea24MinRepeat = kSingleRepeat; +const uint16_t kMirageStateLength = 15; +const uint16_t kMirageBits = kMirageStateLength * 8; +const uint16_t kMirageMinRepeat = kNoRepeat; +const uint16_t kMitsubishiBits = 16; +// TODO(anyone): Verify that the Mitsubishi repeat is really needed. +// Based on marcosamarinho's code. +const uint16_t kMitsubishiMinRepeat = kSingleRepeat; +const uint16_t kMitsubishiACStateLength = 18; +const uint16_t kMitsubishiACBits = kMitsubishiACStateLength * 8; +const uint16_t kMitsubishiACMinRepeat = kSingleRepeat; +const uint16_t kMitsubishi136StateLength = 17; +const uint16_t kMitsubishi136Bits = kMitsubishi136StateLength * 8; +const uint16_t kMitsubishi136MinRepeat = kNoRepeat; +const uint16_t kMitsubishi112StateLength = 14; +const uint16_t kMitsubishi112Bits = kMitsubishi112StateLength * 8; +const uint16_t kMitsubishi112MinRepeat = kNoRepeat; +const uint16_t kMitsubishiHeavy88StateLength = 11; +const uint16_t kMitsubishiHeavy88Bits = kMitsubishiHeavy88StateLength * 8; +const uint16_t kMitsubishiHeavy88MinRepeat = kNoRepeat; +const uint16_t kMitsubishiHeavy152StateLength = 19; +const uint16_t kMitsubishiHeavy152Bits = kMitsubishiHeavy152StateLength * 8; +const uint16_t kMitsubishiHeavy152MinRepeat = kNoRepeat; +const uint16_t kMultibracketsBits = 8; +const uint16_t kMultibracketsDefaultRepeat = kSingleRepeat; +const uint16_t kNikaiBits = 24; +const uint16_t kNECBits = 32; +const uint16_t kNeoclimaStateLength = 12; +const uint16_t kNeoclimaBits = kNeoclimaStateLength * 8; +const uint16_t kNeoclimaMinRepeat = kNoRepeat; +const uint16_t kPanasonicBits = 48; +const uint32_t kPanasonicManufacturer = 0x4004; +const uint16_t kPanasonicAcStateLength = 27; +const uint16_t kPanasonicAcStateShortLength = 16; +const uint16_t kPanasonicAcBits = kPanasonicAcStateLength * 8; +const uint16_t kPanasonicAcShortBits = kPanasonicAcStateShortLength * 8; +const uint16_t kPanasonicAcDefaultRepeat = kNoRepeat; +const uint16_t kPanasonicAc32Bits = 32; +const uint16_t kPioneerBits = 64; +const uint16_t kProntoMinLength = 6; +const uint16_t kRC5RawBits = 14; +const uint16_t kRC5Bits = kRC5RawBits - 2; +const uint16_t kRC5XBits = kRC5RawBits - 1; +const uint16_t kRC6Mode0Bits = 20; // Excludes the 'start' bit. +const uint16_t kRC6_36Bits = 36; // Excludes the 'start' bit. +const uint16_t kRCMMBits = 24; +const uint16_t kSamsungBits = 32; +const uint16_t kSamsung36Bits = 36; +const uint16_t kSamsungAcStateLength = 14; +const uint16_t kSamsungAcBits = kSamsungAcStateLength * 8; +const uint16_t kSamsungAcExtendedStateLength = 21; +const uint16_t kSamsungAcExtendedBits = kSamsungAcExtendedStateLength * 8; +const uint16_t kSamsungAcDefaultRepeat = kNoRepeat; +const uint16_t kSanyoAcStateLength = 9; +const uint16_t kSanyoAcBits = kSanyoAcStateLength * 8; +const uint16_t kSanyoAc88StateLength = 11; +const uint16_t kSanyoAc88Bits = kSanyoAc88StateLength * 8; +const uint16_t kSanyoAc88MinRepeat = 2; +const uint16_t kSanyoAc152StateLength = 19; +const uint16_t kSanyoAc152Bits = kSanyoAc152StateLength * 8; +const uint16_t kSanyoAc152MinRepeat = kNoRepeat; +const uint16_t kSanyoSA8650BBits = 12; +const uint16_t kSanyoLC7461AddressBits = 13; +const uint16_t kSanyoLC7461CommandBits = 8; +const uint16_t kSanyoLC7461Bits = (kSanyoLC7461AddressBits + + kSanyoLC7461CommandBits) * 2; +const uint8_t kSharpAddressBits = 5; +const uint8_t kSharpCommandBits = 8; +const uint16_t kSharpBits = kSharpAddressBits + kSharpCommandBits + 2; // 15 +const uint16_t kSharpAcStateLength = 13; +const uint16_t kSharpAcBits = kSharpAcStateLength * 8; // 104 +const uint16_t kSharpAcDefaultRepeat = kNoRepeat; +const uint8_t kSherwoodBits = kNECBits; +const uint16_t kSherwoodMinRepeat = kSingleRepeat; +const uint16_t kSony12Bits = 12; +const uint16_t kSony15Bits = 15; +const uint16_t kSony20Bits = 20; +const uint16_t kSonyMinBits = 12; +const uint16_t kSonyMinRepeat = 2; +const uint16_t kSymphonyBits = 12; +const uint16_t kSymphonyDefaultRepeat = 3; +const uint16_t kTcl96AcStateLength = 12; +const uint16_t kTcl96AcBits = kTcl96AcStateLength * 8; +const uint16_t kTcl96AcDefaultRepeat = kNoRepeat; +const uint16_t kTcl112AcStateLength = 14; +const uint16_t kTcl112AcBits = kTcl112AcStateLength * 8; +const uint16_t kTcl112AcDefaultRepeat = kNoRepeat; +const uint16_t kTecoBits = 35; +const uint16_t kTecoDefaultRepeat = kNoRepeat; +const uint16_t kTeknopointStateLength = 14; +const uint16_t kTeknopointBits = kTeknopointStateLength * 8; +const uint16_t kToshibaACStateLength = 9; +const uint16_t kToshibaACBits = kToshibaACStateLength * 8; +const uint16_t kToshibaACMinRepeat = kSingleRepeat; +const uint16_t kToshibaACStateLengthShort = kToshibaACStateLength - 2; +const uint16_t kToshibaACBitsShort = kToshibaACStateLengthShort * 8; +const uint16_t kToshibaACStateLengthLong = kToshibaACStateLength + 1; +const uint16_t kToshibaACBitsLong = kToshibaACStateLengthLong * 8; +const uint16_t kTotoBits = 24; +const uint16_t kTotoShortBits = kTotoBits; +const uint16_t kTotoLongBits = kTotoShortBits * 2; +const uint16_t kTotoDefaultRepeat = kSingleRepeat; +const uint16_t kTranscoldBits = 24; +const uint16_t kTranscoldDefaultRepeat = kNoRepeat; +const uint16_t kTrotecStateLength = 9; +const uint16_t kTrotecBits = kTrotecStateLength * 8; +const uint16_t kTrotecDefaultRepeat = kNoRepeat; +const uint16_t kTrumaBits = 56; +const uint16_t kWhirlpoolAcStateLength = 21; +const uint16_t kWhirlpoolAcBits = kWhirlpoolAcStateLength * 8; +const uint16_t kWhirlpoolAcDefaultRepeat = kNoRepeat; +const uint16_t kWhynterBits = 32; +const uint16_t kWowweeBits = 11; +const uint16_t kWowweeDefaultRepeat = kNoRepeat; +const uint8_t kVestelAcBits = 56; +const uint16_t kXmpBits = 64; +const uint16_t kZepealBits = 16; +const uint16_t kZepealMinRepeat = 4; +const uint16_t kVoltasBits = 80; +const uint16_t kVoltasStateLength = 10; +const uint16_t kMilesTag2ShotBits = 14; +const uint16_t kMilesTag2MsgBits = 24; +const uint16_t kMilesMinRepeat = 0; +const uint16_t kBoseBits = 16; +const uint16_t kRhossStateLength = 12; +const uint16_t kRhossBits = kRhossStateLength * 8; +const uint16_t kRhossDefaultRepeat = 0; +const uint16_t kClimaButlerBits = 52; + + +// Legacy defines. (Deprecated) +#define AIWA_RC_T501_BITS kAiwaRcT501Bits +#define ARGO_COMMAND_LENGTH kArgoStateLength +#define COOLIX_BITS kCoolixBits +#define CARRIER_AC_BITS kCarrierAcBits +#define DAIKIN_COMMAND_LENGTH kDaikinStateLength +#define DENON_BITS kDenonBits +#define DENON_48_BITS kDenon48Bits +#define DENON_LEGACY_BITS kDenonLegacyBits +#define DISH_BITS kDishBits +#define FUJITSU_AC_MIN_REPEAT kFujitsuAcMinRepeat +#define FUJITSU_AC_STATE_LENGTH kFujitsuAcStateLength +#define FUJITSU_AC_STATE_LENGTH_SHORT kFujitsuAcStateLengthShort +#define FUJITSU_AC_BITS kFujitsuAcBits +#define FUJITSU_AC_MIN_BITS kFujitsuAcMinBits +#define GICABLE_BITS kGicableBits +#define GREE_STATE_LENGTH kGreeStateLength +#define HAIER_AC_STATE_LENGTH kHaierACStateLength +#define HAIER_AC_YRW02_STATE_LENGTH kHaierACYRW02StateLength +#define HITACHI_AC_STATE_LENGTH kHitachiAcStateLength +#define HITACHI_AC_BITS kHitachiAcBits +#define HITACHI_AC1_STATE_LENGTH kHitachiAc1StateLength +#define HITACHI_AC1_BITS kHitachiAc1Bits +#define HITACHI_AC2_STATE_LENGTH kHitachiAc2StateLength +#define HITACHI_AC2_BITS kHitachiAc2Bits +#define HITACHI_AC296_STATE_LENGTH kHitachiAc296StateLength +#define HITACHI_AC296_BITS kHitachiAc296Bits +#define JVC_BITS kJvcBits +#define KELVINATOR_STATE_LENGTH kKelvinatorStateLength +#define LASERTAG_BITS kLasertagBits +#define LG_BITS kLgBits +#define LG32_BITS kLg32Bits +#define MAGIQUEST_BITS kMagiquestBits +#define MIDEA_BITS kMideaBits +#define MITSUBISHI_BITS kMitsubishiBits +#define MITSUBISHI_AC_STATE_LENGTH kMitsubishiACStateLength +#define NEC_BITS kNECBits +#define NIKAI_BITS kNikaiBits +#define PANASONIC_BITS kPanasonicBits +#define RC5_BITS kRC5Bits +#define RC5X_BITS kRC5XBits +#define RC6_MODE0_BITS kRC6Mode0Bits +#define RC6_36_BITS kRC6_36Bits +#define RCMM_BITS kRCMMBits +#define SANYO_LC7461_BITS kSanyoLC7461Bits +#define SAMSUNG_BITS kSamsungBits +#define SANYO_SA8650B_BITS kSanyoSA8650BBits +#define SHARP_BITS kSharpBits +#define SHERWOOD_BITS kSherwoodBits +#define SONY_12_BITS kSony12Bits +#define SONY_15_BITS kSony15Bits +#define SONY_20_BITS kSony20Bits +#define TOSHIBA_AC_STATE_LENGTH kToshibaACStateLength +#define TROTEC_COMMAND_LENGTH kTrotecStateLength +#define WHYNTER_BITS kWhynterBits + +// Turn on Debugging information by uncommenting the following line. +// #define DEBUG 1 + +#ifdef DEBUG +#ifdef UNIT_TEST +#define DPRINT(x) do { std::cout << x; } while (0) +#define DPRINTLN(x) do { std::cout << x << std::endl; } while (0) +#endif // UNIT_TEST +#ifdef ARDUINO +#define DPRINT(x) do { Serial.print(x); } while (0) +#define DPRINTLN(x) do { Serial.println(x); } while (0) +#endif // ARDUINO +#else // DEBUG +#define DPRINT(x) +#define DPRINTLN(x) +#endif // DEBUG + +#ifdef UNIT_TEST +#ifndef F +// Create a no-op F() macro so the code base still compiles outside of the +// Arduino framework. Thus we can safely use the Arduino 'F()' macro through-out +// the code base. That macro stores constants in Flash (PROGMEM) memory. +// See: https://github.com/crankyoldgit/IRremoteESP8266/issues/667 +#define F(x) x +#endif // F +typedef std::string String; +#endif // UNIT_TEST + +#endif // IRREMOTEESP8266_H_ diff --git a/src/libraries/IRremoteESP8266/src/IRsend.cpp b/src/libraries/IRremoteESP8266/src/IRsend.cpp new file mode 100644 index 000000000..24cba1ffe --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRsend.cpp @@ -0,0 +1,1463 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2015 Mark Szabo +// Copyright 2017,2019 David Conran + +#include "IRsend.h" +#ifndef UNIT_TEST +#include "String.h" +#include "minmax.h" +#else +#define __STDC_LIMIT_MACROS +#include +#endif +// #include +#ifdef UNIT_TEST +#include +#endif +#include "IRtimer.h" + +#if PLATFORM_BEKEN +//TODO: check these? +typedef enum { + LOW = 0, + HIGH = 1, + CHANGE = 2, + FALLING = 3, + RISING = 4, +} PinStatus; + +typedef enum { + INPUT = 0x0, + OUTPUT = 0x1, + INPUT_PULLUP = 0x2, + INPUT_PULLDOWN = 0x3, +} PinMode; +#endif + + +/// Constructor for an IRsend object. +/// @param[in] IRsendPin Which GPIO pin to use when sending an IR command. +/// @param[in] inverted Optional flag to invert the output. (default = false) +/// e.g. LED is illuminated when GPIO is LOW rather than HIGH. +/// @warning Setting `inverted` to something other than the default could +/// easily destroy your IR LED if you are overdriving it. +/// Unless you *REALLY* know what you are doing, don't change this. +/// @param[in] use_modulation Do we do frequency modulation during transmission? +/// i.e. If not, assume a 100% duty cycle. Ignore attempts to change the +/// duty cycle etc. +IRsend::IRsend(uint16_t IRsendPin, bool inverted, bool use_modulation) + : IRpin(IRsendPin), periodOffset(kPeriodOffset) { + if (inverted) { + outputOn = LOW; + outputOff = HIGH; + } else { + outputOn = HIGH; + outputOff = LOW; + } + modulation = use_modulation; + if (modulation) + _dutycycle = kDutyDefault; + else + _dutycycle = kDutyMax; +} + +/// Enable the pin for output. +void IRsend::begin() { +#ifndef UNIT_TEST +// pinMode(IRpin, OUTPUT); +#endif + ledOff(); // Ensure the LED is in a known safe state when we start. +} + +/// Turn off the IR LED. +void IRsend::ledOff() { +#ifndef UNIT_TEST + // digitalWrite(IRpin, outputOff); +#endif +} + +/// Turn on the IR LED. +void IRsend::ledOn() { +#ifndef UNIT_TEST +// digitalWrite(IRpin, outputOn); +#endif +} + +/// Calculate the period for a given frequency. +/// @param[in] hz Frequency in Hz. +/// @param[in] use_offset Should we use the calculated offset or not? +/// @return nr. of uSeconds. +/// @note (T = 1/f) +uint32_t IRsend::calcUSecPeriod(uint32_t hz, bool use_offset) { + if (hz == 0) hz = 1; // Avoid Zero hz. Divide by Zero is nasty. + uint32_t period = + (1000000UL + hz / 2) / hz; // The equiv of round(1000000/hz). + // Apply the offset and ensure we don't result in a <= 0 value. + if (use_offset) + return ::max((uint32_t)1, period + periodOffset); + else + return ::max((uint32_t)1, period); +} + +/// Set the output frequency modulation and duty cycle. +/// @param[in] freq The freq we want to modulate at. +/// Assumes < 1000 means kHz else Hz. +/// @param[in] duty Percentage duty cycle of the LED. +/// e.g. 25 = 25% = 1/4 on, 3/4 off. +/// If you are not sure, try 50 percent. +/// This is ignored if modulation is disabled at object instantiation. +/// @note Integer timing functions & math mean we can't do fractions of +/// microseconds timing. Thus minor changes to the freq & duty values may have +/// limited effect. You've been warned. +void IRsend::enableIROut(uint32_t freq, uint8_t duty) { + #if 0 + // Set the duty cycle to use if we want freq. modulation. + if (modulation) { + _dutycycle = ::min(duty, kDutyMax); + } else { + _dutycycle = kDutyMax; + } + if (freq < 1000) // Were we given kHz? Supports the old call usage. + freq *= 1000; +#ifdef UNIT_TEST + _freq_unittest = freq; +#endif // UNIT_TEST + uint32_t period = calcUSecPeriod(freq); + // Nr. of uSeconds the LED will be on per pulse. + onTimePeriod = (period * _dutycycle) / kDutyMax; + // Nr. of uSeconds the LED will be off per pulse. + offTimePeriod = period - onTimePeriod; + #endif //0 +} + +#if ALLOW_DELAY_CALLS +/// An ESP8266 RTOS watch-dog timer friendly version of delayMicroseconds(). +/// @param[in] usec Nr. of uSeconds to delay for. +void IRsend::_delayMicroseconds(uint32_t usec) { + // delayMicroseconds() is only accurate to 16383us. + // Ref: https://www.arduino.cc/en/Reference/delayMicroseconds + if (usec <= kMaxAccurateUsecDelay) { +#ifndef UNIT_TEST +// delayMicroseconds(usec); +#endif + } else { +#ifndef UNIT_TEST + // Invoke a delay(), where possible, to avoid triggering the WDT. +// delay(usec / 1000UL); // Delay for as many whole milliseconds as we can. + // Delay the remaining sub-millisecond. +// delayMicroseconds(static_cast(usec % 1000UL)); +#endif + } +} +#else // ALLOW_DELAY_CALLS +/// A version of delayMicroseconds() that handles large values and does NOT use +/// the watch-dog friendly delay() calls where appropriate. +/// @note Use this only if you know what you are doing as it may cause the WDT +/// to reset the ESP8266. +void IRsend::_delayMicroseconds(uint32_t usec) { + for (; usec > kMaxAccurateUsecDelay; usec -= kMaxAccurateUsecDelay) +#ifndef UNIT_TEST + delayMicroseconds(kMaxAccurateUsecDelay); + delayMicroseconds(static_cast(usec)); +#endif // UNIT_TEST +} +#endif // ALLOW_DELAY_CALLS + +/// Modulate the IR LED for the given period (usec) and at the duty cycle set. +/// @param[in] usec The period of time to modulate the IR LED for, in +/// microseconds. +/// @return Nr. of pulses actually sent. +/// @note +/// The ESP8266 has no good way to do hardware PWM, so we have to do it all +/// in software. There is a horrible kludge/brilliant hack to use the second +/// serial TX line to do fairly accurate hardware PWM, but it is only +/// available on a single specific GPIO and only available on some modules. +/// e.g. It's not available on the ESP-01 module. +/// Hence, for greater compatibility & choice, we don't use that method. +/// Ref: +/// https://www.analysir.com/blog/2017/01/29/updated-esp8266-nodemcu-backdoor-upwm-hack-for-ir-signals/ +uint16_t IRsend::mark(uint16_t usec) { + #if 0 + // Handle the simple case of no required frequency modulation. + if (!modulation || _dutycycle >= 100) { + ledOn(); + _delayMicroseconds(usec); + ledOff(); + return 1; + } + + // Not simple, so do it assuming frequency modulation. + uint16_t counter = 0; + IRtimer usecTimer = IRtimer(); + // Cache the time taken so far. This saves us calling time, and we can be + // assured that we can't have odd math problems. i.e. unsigned under/overflow. + uint32_t elapsed = usecTimer.elapsed(); + + while (elapsed < usec) { // Loop until we've met/exceeded our required time. + ledOn(); + // Calculate how long we should pulse on for. + // e.g. Are we to close to the end of our requested mark time (usec)? + _delayMicroseconds(::min((uint32_t)onTimePeriod, usec - elapsed)); + ledOff(); + counter++; + if (elapsed + onTimePeriod >= usec) + return counter; // LED is now off & we've passed our allotted time. + // Wait for the lesser of the rest of the duty cycle, or the time remaining. + _delayMicroseconds( + ::min(usec - elapsed - onTimePeriod, (uint32_t)offTimePeriod)); + elapsed = usecTimer.elapsed(); // Update & recache the actual elapsed time. + } + #endif //0 + return 0; +} + +/// Turn the pin (LED) off for a given time. +/// Sends an IR space for the specified number of microseconds. +/// A space is no output, so the PWM output is disabled. +/// @param[in] time Time in microseconds (us). +void IRsend::space(uint32_t time) { + #if 0 + ledOff(); + if (time == 0) return; + _delayMicroseconds(time); + #endif +} + +/// Calculate & set any offsets to account for execution times during sending. +/// +/// @param[in] hz The frequency to calibrate at >= 1000Hz. Default is 38000Hz. +/// @return The calculated period offset (in uSeconds) which is now in use. +/// e.g. -5. +/// @note This will generate an 65535us mark() IR LED signal. +/// This only needs to be called once, if at all. +int8_t IRsend::calibrate(uint16_t hz) { + #if 0 + if (hz < 1000) // Were we given kHz? Supports the old call usage. + hz *= 1000; + periodOffset = 0; // Turn off any existing offset while we calibrate. + enableIROut(hz); + IRtimer usecTimer = IRtimer(); // Start a timer *just* before we do the call. + uint16_t pulses = mark(UINT16_MAX); // Generate a PWM of 65,535 us. (Max.) + uint32_t timeTaken = usecTimer.elapsed(); // Record the time it took. + // While it shouldn't be necessary, assume at least 1 pulse, to avoid a + // divide by 0 situation. + pulses = ::max(pulses, (uint16_t)1U); + uint32_t calcPeriod = calcUSecPeriod(hz); // e.g. @38kHz it should be 26us. + // Assuming 38kHz for the example calculations: + // In a 65535us pulse, we should have 2520.5769 pulses @ 26us periods. + // e.g. 65535.0us / 26us = 2520.5769 + // This should have caused approx 2520 loops through the main loop in mark(). + // The average over that many interations should give us a reasonable + // approximation at what offset we need to use to account for instruction + // execution times. + // + // Calculate the actual period from the actual time & the actual pulses + // generated. + double actualPeriod = (double)timeTaken / (double)pulses; + // Store the difference between the actual time per period vs. calculated. + periodOffset = (int8_t)((double)calcPeriod - actualPeriod); + return periodOffset; + #endif + return 0; +} + +/// Generic method for sending data that is common to most protocols. +/// Will send leading or trailing 0's if the nbits is larger than the number +/// of bits in data. +/// @param[in] onemark Nr. of usecs for the led to be pulsed for a '1' bit. +/// @param[in] onespace Nr. of usecs for the led to be fully off for a '1' bit. +/// @param[in] zeromark Nr. of usecs for the led to be pulsed for a '0' bit. +/// @param[in] zerospace Nr. of usecs for the led to be fully off for a '0' bit. +/// @param[in] data The data to be transmitted. +/// @param[in] nbits Nr. of bits of data to be sent. +/// @param[in] MSBfirst Flag for bit transmission order. +/// Defaults to MSB->LSB order. +void IRsend::sendData(uint16_t onemark, uint32_t onespace, uint16_t zeromark, + uint32_t zerospace, uint64_t data, uint16_t nbits, + bool MSBfirst) { + if (nbits == 0) // If we are asked to send nothing, just return. + return; + if (MSBfirst) { // Send the MSB first. + // Send 0's until we get down to a bit size we can actually manage. + while (nbits > sizeof(data) * 8) { + mark(zeromark); + space(zerospace); + nbits--; + } + // Send the supplied data. + for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1) + if (data & mask) { // Send a 1 + mark(onemark); + space(onespace); + } else { // Send a 0 + mark(zeromark); + space(zerospace); + } + } else { // Send the Least Significant Bit (LSB) first / MSB last. + for (uint16_t bit = 0; bit < nbits; bit++, data >>= 1) + if (data & 1) { // Send a 1 + mark(onemark); + space(onespace); + } else { // Send a 0 + mark(zeromark); + space(zerospace); + } + } +} + +/// Generic method for sending simple protocol messages. +/// Will send leading or trailing 0's if the nbits is larger than the number +/// of bits in data. +/// @param[in] headermark Nr. of usecs for the led to be pulsed for the header +/// mark. A value of 0 means no header mark. +/// @param[in] headerspace Nr. of usecs for the led to be off after the header +/// mark. A value of 0 means no header space. +/// @param[in] onemark Nr. of usecs for the led to be pulsed for a '1' bit. +/// @param[in] onespace Nr. of usecs for the led to be fully off for a '1' bit. +/// @param[in] zeromark Nr. of usecs for the led to be pulsed for a '0' bit. +/// @param[in] zerospace Nr. of usecs for the led to be fully off for a '0' bit. +/// @param[in] footermark Nr. of usecs for the led to be pulsed for the footer +/// mark. A value of 0 means no footer mark. +/// @param[in] gap Nr. of usecs for the led to be off after the footer mark. +/// This is effectively the gap between messages. +/// A value of 0 means no gap space. +/// @param[in] data The data to be transmitted. +/// @param[in] nbits Nr. of bits of data to be sent. +/// @param[in] frequency The frequency we want to modulate at. (Hz/kHz) +/// @param[in] MSBfirst Flag for bit transmission order. +/// Defaults to MSB->LSB order. +/// @param[in] repeat Nr. of extra times the message will be sent. +/// e.g. 0 = 1 message sent, 1 = 1 initial + 1 repeat = 2 messages +/// @param[in] dutycycle Percentage duty cycle of the LED. +/// e.g. 25 = 25% = 1/4 on, 3/4 off. +/// If you are not sure, try 50 percent. +/// @note Assumes a frequency < 1000 means kHz otherwise it is in Hz. +/// Most common value is 38000 or 38, for 38kHz. +void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, const uint32_t gap, + const uint64_t data, const uint16_t nbits, + const uint16_t frequency, const bool MSBfirst, + const uint16_t repeat, const uint8_t dutycycle) { + sendGeneric(headermark, headerspace, onemark, onespace, zeromark, zerospace, + footermark, gap, 0U, data, nbits, frequency, MSBfirst, repeat, + dutycycle); +} + +/// Generic method for sending simple protocol messages. +/// Will send leading or trailing 0's if the nbits is larger than the number +/// of bits in data. +/// @param[in] headermark Nr. of usecs for the led to be pulsed for the header +/// mark. A value of 0 means no header mark. +/// @param[in] headerspace Nr. of usecs for the led to be off after the header +/// mark. A value of 0 means no header space. +/// @param[in] onemark Nr. of usecs for the led to be pulsed for a '1' bit. +/// @param[in] onespace Nr. of usecs for the led to be fully off for a '1' bit. +/// @param[in] zeromark Nr. of usecs for the led to be pulsed for a '0' bit. +/// @param[in] zerospace Nr. of usecs for the led to be fully off for a '0' bit. +/// @param[in] footermark Nr. of usecs for the led to be pulsed for the footer +/// mark. A value of 0 means no footer mark. +/// @param[in] gap Nr. of usecs for the led to be off after the footer mark. +/// This is effectively the gap between messages. +/// A value of 0 means no gap space. +/// @param[in] mesgtime Min. nr. of usecs a single message needs to be. +/// This is effectively the min. total length of a single message. +/// @param[in] data The data to be transmitted. +/// @param[in] nbits Nr. of bits of data to be sent. +/// @param[in] frequency The frequency we want to modulate at. (Hz/kHz) +/// @param[in] MSBfirst Flag for bit transmission order. +/// Defaults to MSB->LSB order. +/// @param[in] repeat Nr. of extra times the message will be sent. +/// e.g. 0 = 1 message sent, 1 = 1 initial + 1 repeat = 2 messages +/// @param[in] dutycycle Percentage duty cycle of the LED. +/// e.g. 25 = 25% = 1/4 on, 3/4 off. +/// If you are not sure, try 50 percent. +/// @note Assumes a frequency < 1000 means kHz otherwise it is in Hz. +/// Most common value is 38000 or 38, for 38kHz. +void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, const uint32_t gap, + const uint32_t mesgtime, const uint64_t data, + const uint16_t nbits, const uint16_t frequency, + const bool MSBfirst, const uint16_t repeat, + const uint8_t dutycycle) { + // Setup + enableIROut(frequency, dutycycle); + IRtimer usecs = IRtimer(); + + // We always send a message, even for repeat=0, hence '<= repeat'. + for (uint16_t r = 0; r <= repeat; r++) { + usecs.reset(); + + // Header + if (headermark) mark(headermark); + if (headerspace) space(headerspace); + + // Data + sendData(onemark, onespace, zeromark, zerospace, data, nbits, MSBfirst); + + // Footer + if (footermark) mark(footermark); + uint32_t elapsed = usecs.elapsed(); + // Avoid potential unsigned integer underflow. e.g. when mesgtime is 0. + if (elapsed >= mesgtime) + space(gap); + else + space(::max(gap, mesgtime - elapsed)); + } +} + +/// Generic method for sending simple protocol messages. +/// @param[in] headermark Nr. of usecs for the led to be pulsed for the header +/// mark. A value of 0 means no header mark. +/// @param[in] headerspace Nr. of usecs for the led to be off after the header +/// mark. A value of 0 means no header space. +/// @param[in] onemark Nr. of usecs for the led to be pulsed for a '1' bit. +/// @param[in] onespace Nr. of usecs for the led to be fully off for a '1' bit. +/// @param[in] zeromark Nr. of usecs for the led to be pulsed for a '0' bit. +/// @param[in] zerospace Nr. of usecs for the led to be fully off for a '0' bit. +/// @param[in] footermark Nr. of usecs for the led to be pulsed for the footer +/// mark. A value of 0 means no footer mark. +/// @param[in] gap Nr. of usecs for the led to be off after the footer mark. +/// This is effectively the gap between messages. +/// A value of 0 means no gap space. +/// @param[in] dataptr Pointer to the data to be transmitted. +/// @param[in] nbytes Nr. of bytes of data to be sent. +/// @param[in] frequency The frequency we want to modulate at. (Hz/kHz) +/// @param[in] MSBfirst Flag for bit transmission order. +/// Defaults to MSB->LSB order. +/// @param[in] repeat Nr. of extra times the message will be sent. +/// e.g. 0 = 1 message sent, 1 = 1 initial + 1 repeat = 2 messages +/// @param[in] dutycycle Percentage duty cycle of the LED. +/// e.g. 25 = 25% = 1/4 on, 3/4 off. +/// If you are not sure, try 50 percent. +/// @note Assumes a frequency < 1000 means kHz otherwise it is in Hz. +/// Most common value is 38000 or 38, for 38kHz. +void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, const uint32_t gap, + const uint8_t *dataptr, const uint16_t nbytes, + const uint16_t frequency, const bool MSBfirst, + const uint16_t repeat, const uint8_t dutycycle) { + // Setup + enableIROut(frequency, dutycycle); + // We always send a message, even for repeat=0, hence '<= repeat'. + for (uint16_t r = 0; r <= repeat; r++) { + // Header + if (headermark) mark(headermark); + if (headerspace) space(headerspace); + + // Data + for (uint16_t i = 0; i < nbytes; i++) + sendData(onemark, onespace, zeromark, zerospace, *(dataptr + i), 8, + MSBfirst); + + // Footer + if (footermark) mark(footermark); + space(gap); + } +} + +/// Generic method for sending Manchester code data. +/// Will send leading or trailing 0's if the nbits is larger than the number +/// of bits in data. +/// @param[in] half_period Nr. of uSeconds for half the clock's period. +/// (1/2 wavelength) +/// @param[in] data The data to be transmitted. +/// @param[in] nbits Nr. of bits of data to be sent. +/// @param[in] MSBfirst Flag for bit transmission order. +/// Defaults to MSB->LSB order. +/// @param[in] GEThomas Use G.E. Thomas (true/default) or IEEE 802.3 (false). +void IRsend::sendManchesterData(const uint16_t half_period, + const uint64_t data, + const uint16_t nbits, const bool MSBfirst, + const bool GEThomas) { + if (nbits == 0) return; // Nothing to send. + uint16_t bits = nbits; + uint64_t copy = (GEThomas) ? data : ~data; + + if (MSBfirst) { // Send the MSB first. + // Send 0's until we get down to a bit size we can actually manage. + if (bits > (sizeof(data) * 8)) { + sendManchesterData(half_period, 0ULL, bits - sizeof(data) * 8, MSBfirst, + GEThomas); + bits = sizeof(data) * 8; + } + // Send the supplied data. + for (uint64_t mask = 1ULL << (bits - 1); mask; mask >>= 1) + if (copy & mask) { + mark(half_period); + space(half_period); + } else { + space(half_period); + mark(half_period); + } + } else { // Send the Least Significant Bit (LSB) first / MSB last. + for (bits = 0; bits < nbits; bits++, copy >>= 1) + if (copy & 1) { + mark(half_period); + space(half_period); + } else { + space(half_period); + mark(half_period); + } + } +} + +/// Generic method for sending Manchester code messages. +/// Will send leading or trailing 0's if the nbits is larger than the number +/// @param[in] headermark Nr. of usecs for the led to be pulsed for the header +/// mark. A value of 0 means no header mark. +/// @param[in] headerspace Nr. of usecs for the led to be off after the header +/// mark. A value of 0 means no header space. +/// @param[in] half_period Nr. of uSeconds for half the clock's period. +/// (1/2 wavelength) +/// @param[in] footermark Nr. of usecs for the led to be pulsed for the footer +/// mark. A value of 0 means no footer mark. +/// @param[in] gap Min. nr. of usecs for the led to be off after the footer +/// mark. This is effectively the absolute minimum gap between messages. +/// @param[in] data The data to be transmitted. +/// @param[in] nbits Nr. of bits of data to be sent. +/// @param[in] frequency The frequency we want to modulate at. (Hz/kHz) +/// @param[in] MSBfirst Flag for bit transmission order. +/// Defaults to MSB->LSB order. +/// @param[in] repeat Nr. of extra times the message will be sent. +/// e.g. 0 = 1 message sent, 1 = 1 initial + 1 repeat = 2 messages +/// @param[in] dutycycle Percentage duty cycle of the LED. +/// e.g. 25 = 25% = 1/4 on, 3/4 off. +/// If you are not sure, try 50 percent. +/// @param[in] GEThomas Use G.E. Thomas (true/default) or IEEE 802.3 (false). +/// @note Assumes a frequency < 1000 means kHz otherwise it is in Hz. +/// Most common value is 38000 or 38, for 38kHz. +void IRsend::sendManchester(const uint16_t headermark, + const uint32_t headerspace, + const uint16_t half_period, + const uint16_t footermark, const uint32_t gap, + const uint64_t data, const uint16_t nbits, + const uint16_t frequency, const bool MSBfirst, + const uint16_t repeat, const uint8_t dutycycle, + const bool GEThomas) { + // Setup + enableIROut(frequency, dutycycle); + + // We always send a message, even for repeat=0, hence '<= repeat'. + for (uint16_t r = 0; r <= repeat; r++) { + // Header + if (headermark) mark(headermark); + if (headerspace) space(headerspace); + // Data + sendManchesterData(half_period, data, nbits, MSBfirst, GEThomas); + // Footer + if (footermark) mark(footermark); + if (gap) space(gap); + } +} + +#if SEND_RAW +/// Send a raw IRremote message. +/// +/// @param[in] buf An array of uint16_t's that has microseconds elements. +/// @param[in] len Nr. of elements in the buf[] array. +/// @param[in] hz Frequency to send the message at. (kHz < 1000; Hz >= 1000) +/// @note Even elements are Mark times (On), Odd elements are Space times (Off). +/// Ref: +/// examples/IRrecvDumpV2/IRrecvDumpV2.ino (or later) +void IRsend::sendRaw(const uint16_t buf[], const uint16_t len, + const uint16_t hz) { + // Set IR carrier frequency + enableIROut(hz); + for (uint16_t i = 0; i < len; i++) { + if (i & 1) { // Odd bit. + space(buf[i]); + } else { // Even bit. + mark(buf[i]); + } + } + ledOff(); // We potentially have ended with a mark(), so turn of the LED. +} +#endif // SEND_RAW + +/// Get the minimum number of repeats for a given protocol. +/// @param[in] protocol Protocol number/type of the message you want to send. +/// @return The number of repeats required. +uint16_t IRsend::minRepeats(const decode_type_t protocol) { + switch (protocol) { + // Single repeats + case AIWA_RC_T501: + case AMCOR: + case COOLIX: + case COOLIX48: + case ELITESCREENS: + case GICABLE: + case INAX: + case MIDEA24: + case MITSUBISHI: + case MITSUBISHI2: + case MITSUBISHI_AC: + case MULTIBRACKETS: + case SHERWOOD: + case TOSHIBA_AC: + case TOTO: + return kSingleRepeat; + // Special + case AIRWELL: + return kAirwellMinRepeats; + case CARRIER_AC40: + return kCarrierAc40MinRepeat; + case DISH: + return kDishMinRepeat; + case EPSON: + return kEpsonMinRepeat; + case SANYO_AC88: + return kSanyoAc88MinRepeat; + case SONY: + return kSonyMinRepeat; + case SONY_38K: + return kSonyMinRepeat + 1; + case SYMPHONY: + return kSymphonyDefaultRepeat; + case ZEPEAL: + return kZepealMinRepeat; + default: + return kNoRepeat; + } +} + +/// Get the default number of bits for a given protocol. +/// @param[in] protocol Protocol number/type you want the default bit size for. +/// @return The number of bits. +uint16_t IRsend::defaultBits(const decode_type_t protocol) { + switch (protocol) { + case MULTIBRACKETS: + case GORENJE: + return 8; + case WOWWEE: + return 11; + case RC5: + case SYMPHONY: + return 12; + case LASERTAG: + case RC5X: + return 13; + case AIWA_RC_T501: + case DENON: + case SHARP: + return 15; + case BOSE: + case DISH: + case GICABLE: + case JVC: + case LEGOPF: + case MITSUBISHI: + case MITSUBISHI2: + case ZEPEAL: + return 16; + case METZ: + return 19; + case RC6: + case SONY: + case SONY_38K: + return 20; + case COOLIX: + case INAX: + case MIDEA24: + case NIKAI: + case RCMM: + case TOTO: + case TRANSCOLD: + return 24; + case LG: + case LG2: + return 28; + case ARRIS: + case CARRIER_AC: + case ELITESCREENS: + case EPSON: + case NEC: + case NEC_LIKE: + case PANASONIC_AC32: + case SAMSUNG: + case SHERWOOD: + case WHYNTER: + return 32; + case AIRWELL: + return 34; + case LUTRON: + case TECO: + return 35; + case SAMSUNG36: + return 36; + case CARRIER_AC40: + return kCarrierAc40Bits; // 40 + case DOSHISHA: + return kDoshishaBits; // 40 + case SANYO_LC7461: + return kSanyoLC7461Bits; // 42 + case COOLIX48: + case GOODWEATHER: + case KELON: + case MIDEA: + case PANASONIC: + return 48; + case CLIMABUTLER: + return kClimaButlerBits; // 52 + case AIRTON: + case ECOCLIM: + case MAGIQUEST: + case VESTEL_AC: + case TECHNIBEL_AC: + case TRUMA: + return 56; + case AMCOR: + case CARRIER_AC64: + case DELONGHI_AC: + case PIONEER: + return 64; + case ARGO: + return kArgoBits; + case BOSCH144: + return kBosch144Bits; + case CORONA_AC: + return kCoronaAcBits; + case CARRIER_AC84: + return kCarrierAc84Bits; + case CARRIER_AC128: + return kCarrierAc128Bits; + case DAIKIN: + return kDaikinBits; + case DAIKIN128: + return kDaikin128Bits; + case DAIKIN152: + return kDaikin152Bits; + case DAIKIN160: + return kDaikin160Bits; + case DAIKIN176: + return kDaikin176Bits; + case DAIKIN2: + return kDaikin2Bits; + case DAIKIN200: + return kDaikin200Bits; + case DAIKIN216: + return kDaikin216Bits; + case DAIKIN312: + return kDaikin312Bits; + case DAIKIN64: + return kDaikin64Bits; + case ELECTRA_AC: + return kElectraAcBits; + case GREE: + return kGreeBits; + case HAIER_AC: + return kHaierACBits; + case HAIER_AC_YRW02: + return kHaierACYRW02Bits; + case HAIER_AC160: + return kHaierAC160Bits; + case HAIER_AC176: + return kHaierAC176Bits; + case HITACHI_AC: + return kHitachiAcBits; + case HITACHI_AC1: + return kHitachiAc1Bits; + case HITACHI_AC2: + return kHitachiAc2Bits; + case HITACHI_AC3: + return kHitachiAc3Bits; + case HITACHI_AC264: + return kHitachiAc264Bits; + case HITACHI_AC296: + return kHitachiAc296Bits; + case HITACHI_AC344: + return kHitachiAc344Bits; + case HITACHI_AC424: + return kHitachiAc424Bits; + case KELON168: + return kKelon168Bits; + case KELVINATOR: + return kKelvinatorBits; + case MILESTAG2: + return kMilesTag2ShotBits; + case MIRAGE: + return kMirageBits; + case MITSUBISHI_AC: + return kMitsubishiACBits; + case MITSUBISHI136: + return kMitsubishi136Bits; + case MITSUBISHI112: + return kMitsubishi112Bits; + case MITSUBISHI_HEAVY_152: + return kMitsubishiHeavy152Bits; + case MITSUBISHI_HEAVY_88: + return kMitsubishiHeavy88Bits; + case NEOCLIMA: + return kNeoclimaBits; + case PANASONIC_AC: + return kPanasonicAcBits; + case RHOSS: + return kRhossBits; + case SAMSUNG_AC: + return kSamsungAcBits; + case SANYO_AC: + return kSanyoAcBits; + case SANYO_AC88: + return kSanyoAc88Bits; + case SANYO_AC152: + return kSanyoAc152Bits; + case SHARP_AC: + return kSharpAcBits; + case TCL96AC: + return kTcl96AcBits; + case TCL112AC: + return kTcl112AcBits; + case TEKNOPOINT: + return kTeknopointBits; + case TOSHIBA_AC: + return kToshibaACBits; + case TROTEC: + case TROTEC_3550: + return kTrotecBits; + case VOLTAS: + return kVoltasBits; + case WHIRLPOOL_AC: + return kWhirlpoolAcBits; + case XMP: + return kXmpBits; + // No default amount of bits. + case FUJITSU_AC: + case MWM: + default: + return 0; + } +} + +/// Send a simple (up to 64 bits) IR message of a given type. +/// An unknown/unsupported type will send nothing. +/// @param[in] type Protocol number/type of the message you want to send. +/// @param[in] data The data you want to send (up to 64 bits). +/// @param[in] nbits How many bits long the message is to be. +/// @param[in] repeat How many repeats to do? +/// @return True if it is a type we can attempt to send, false if not. +bool IRsend::send(const decode_type_t type, const uint64_t data, + const uint16_t nbits, const uint16_t repeat) { + uint16_t min_repeat __attribute__((unused)) = + ::max(IRsend::minRepeats(type), repeat); + switch (type) { +#if SEND_AIRTON + case AIRTON: + sendAirton(data, nbits, min_repeat); + break; +#endif // SEND_AIRTON +#if SEND_AIRWELL + case AIRWELL: + sendAirwell(data, nbits, min_repeat); + break; +#endif +#if SEND_AIWA_RC_T501 + case AIWA_RC_T501: + sendAiwaRCT501(data, nbits, min_repeat); + break; +#endif // SEND_AIWA_RC_T501 +#if SEND_ARRIS + case ARRIS: + sendArris(data, nbits, min_repeat); + break; +#endif // SEND_ARRIS +#if SEND_BOSE + case BOSE: + sendBose(data, nbits, min_repeat); + break; +#endif // SEND_BOSE +#if SEND_CARRIER_AC + case CARRIER_AC: + sendCarrierAC(data, nbits, min_repeat); + break; +#endif +#if SEND_CARRIER_AC40 + case CARRIER_AC40: + sendCarrierAC40(data, nbits, min_repeat); + break; +#endif // SEND_CARRIER_AC40 +#if SEND_CARRIER_AC64 + case CARRIER_AC64: + sendCarrierAC64(data, nbits, min_repeat); + break; +#endif // SEND_CARRIER_AC64 +#if SEND_CLIMABUTLER + case CLIMABUTLER: + sendClimaButler(data, nbits, min_repeat); + break; +#endif // SEND_CLIMABUTLER +#if SEND_COOLIX + case COOLIX: + sendCOOLIX(data, nbits, min_repeat); + break; +#endif // SEND_COOLIX +#if SEND_COOLIX48 + case COOLIX48: + sendCoolix48(data, nbits, min_repeat); + break; +#endif // SEND_COOLIX48 +#if SEND_DAIKIN64 + case DAIKIN64: + sendDaikin64(data, nbits, min_repeat); + break; +#endif +#if SEND_DELONGHI_AC + case DELONGHI_AC: + sendDelonghiAc(data, nbits, min_repeat); + break; +#endif +#if SEND_DENON + case DENON: + sendDenon(data, nbits, min_repeat); + break; +#endif +#if SEND_DISH + case DISH: + sendDISH(data, nbits, min_repeat); + break; +#endif +#if SEND_DOSHISHA + case DOSHISHA: + sendDoshisha(data, nbits, min_repeat); + break; +#endif +#if SEND_ECOCLIM + case ECOCLIM: + sendEcoclim(data, nbits, min_repeat); + break; +#endif // SEND_ECOCLIM +#if SEND_ELITESCREENS + case ELITESCREENS: + sendElitescreens(data, nbits, min_repeat); + break; +#endif // SEND_ELITESCREENS +#if SEND_EPSON + case EPSON: + sendEpson(data, nbits, min_repeat); + break; +#endif +#if SEND_GICABLE + case GICABLE: + sendGICable(data, nbits, min_repeat); + break; +#endif +#if SEND_GOODWEATHER + case GOODWEATHER: + sendGoodweather(data, nbits, min_repeat); + break; +#endif +#if SEND_GORENJE + case GORENJE: + sendGorenje(data, nbits, min_repeat); + break; +#endif +#if SEND_GREE + case GREE: + sendGree(data, nbits, min_repeat); + break; +#endif +#if SEND_INAX + case INAX: + sendInax(data, nbits, min_repeat); + break; +#endif // SEND_INAX +#if SEND_JVC + case JVC: + sendJVC(data, nbits, min_repeat); + break; +#endif +#if SEND_KELON + case KELON: + sendKelon(data, nbits, min_repeat); + break; +#endif // SEND_KELON +#if SEND_LASERTAG + case LASERTAG: + sendLasertag(data, nbits, min_repeat); + break; +#endif +#if SEND_LEGOPF + case LEGOPF: + sendLegoPf(data, nbits, min_repeat); + break; +#endif +#if SEND_LG + case LG: + sendLG(data, nbits, min_repeat); + break; + case LG2: + sendLG2(data, nbits, min_repeat); + break; +#endif +#if SEND_LUTRON + case LUTRON: + sendLutron(data, nbits, min_repeat); + break; +#endif +#if SEND_MAGIQUEST + case MAGIQUEST: + sendMagiQuest(data, nbits, min_repeat); + break; +#endif // SEND_MAGIQUEST +#if SEND_METZ + case METZ: + sendMetz(data, nbits, min_repeat); + break; +#endif // SEND_METZ +#if SEND_MIDEA + case MIDEA: + sendMidea(data, nbits, min_repeat); + break; +#endif // SEND_MIDEA +#if SEND_MIDEA24 + case MIDEA24: + sendMidea24(data, nbits, min_repeat); + break; +#endif // SEND_MIDEA24 +#if SEND_MILESTAG2 + case MILESTAG2: + sendMilestag2(data, nbits, min_repeat); + break; +#endif // SEND_MILESTAG2 +#if SEND_MITSUBISHI + case MITSUBISHI: + sendMitsubishi(data, nbits, min_repeat); + break; +#endif +#if SEND_MITSUBISHI2 + case MITSUBISHI2: + sendMitsubishi2(data, nbits, min_repeat); + break; +#endif +#if SEND_MULTIBRACKETS + case MULTIBRACKETS: + sendMultibrackets(data, nbits, min_repeat); + break; +#endif +#if SEND_NIKAI + case NIKAI: + sendNikai(data, nbits, min_repeat); + break; +#endif +#if SEND_NEC + case NEC: + case NEC_LIKE: + sendNEC(data, nbits, min_repeat); + break; +#endif +#if SEND_PANASONIC + case PANASONIC: + sendPanasonic64(data, nbits, min_repeat); + break; +#endif // SEND_PANASONIC +#if SEND_PANASONIC_AC32 + case PANASONIC_AC32: + sendPanasonicAC32(data, nbits, min_repeat); + break; +#endif // SEND_PANASONIC_AC32 +#if SEND_PIONEER + case PIONEER: + sendPioneer(data, nbits, min_repeat); + break; +#endif +#if SEND_RC5 + case RC5: + case RC5X: + sendRC5(data, nbits, min_repeat); + break; +#endif +#if SEND_RC6 + case RC6: + sendRC6(data, nbits, min_repeat); + break; +#endif +#if SEND_RCMM + case RCMM: + sendRCMM(data, nbits, min_repeat); + break; +#endif +#if SEND_SAMSUNG + case SAMSUNG: + sendSAMSUNG(data, nbits, min_repeat); + break; +#endif +#if SEND_SAMSUNG36 + case SAMSUNG36: + sendSamsung36(data, nbits, min_repeat); + break; +#endif +#if SEND_SANYO + case SANYO_LC7461: + sendSanyoLC7461(data, nbits, min_repeat); + break; +#endif +#if SEND_SHARP + case SHARP: + sendSharpRaw(data, nbits, min_repeat); + break; +#endif +#if SEND_SHERWOOD + case SHERWOOD: + sendSherwood(data, nbits, min_repeat); + break; +#endif +#if SEND_SONY + case SONY: + sendSony(data, nbits, min_repeat); + break; + case SONY_38K: + sendSony38(data, nbits, min_repeat); + break; +#endif +#if SEND_SYMPHONY + case SYMPHONY: + sendSymphony(data, nbits, min_repeat); + break; +#endif +#if SEND_TECHNIBEL_AC + case TECHNIBEL_AC: + sendTechnibelAc(data, nbits, min_repeat); + break; +#endif +#if SEND_TECO + case TECO: + sendTeco(data, nbits, min_repeat); + break; +#endif // SEND_TECO +#if SEND_TOTO + case TOTO: + sendToto(data, nbits, min_repeat); + break; +#endif // SEND_TOTO +#if SEND_TRANSCOLD + case TRANSCOLD: + sendTranscold(data, nbits, min_repeat); + break; +#endif // SEND_TRANSCOLD +#if SEND_TRUMA + case TRUMA: + sendTruma(data, nbits, min_repeat); + break; +#endif // SEND_TRUMA +#if SEND_VESTEL_AC + case VESTEL_AC: + sendVestelAc(data, nbits, min_repeat); + break; +#endif +#if SEND_WHYNTER + case WHYNTER: + sendWhynter(data, nbits, min_repeat); + break; +#endif +#if SEND_WOWWEE + case WOWWEE: + sendWowwee(data, nbits, min_repeat); + break; +#endif // SEND_WOWWEE +#if SEND_XMP + case XMP: + sendXmp(data, nbits, min_repeat); + break; +#endif +#if SEND_ZEPEAL + case ZEPEAL: + sendZepeal(data, nbits, min_repeat); + break; +#endif // SEND_ZEPEAL + default: + return false; + } + return true; +} + +/// Send a complex (>= 64 bits) IR message of a given type. +/// An unknown/unsupported type will send nothing. +/// @param[in] type Protocol number/type of the message you want to send. +/// @param[in] state A pointer to the array of bytes that make up the state[]. +/// @param[in] nbytes How many bytes are in the state. +/// @return True if it is a type we can attempt to send, false if not. +bool IRsend::send(const decode_type_t type, const uint8_t *state, + const uint16_t nbytes) { + switch (type) { +#if SEND_VOLTAS + case VOLTAS: + sendVoltas(state, nbytes); + break; +#endif // SEND_VOLTAS +#if SEND_AMCOR + case AMCOR: + sendAmcor(state, nbytes); + break; +#endif +#if SEND_ARGO + case ARGO: + sendArgo(state, nbytes); + break; +#endif // SEND_ARGO +#if SEND_BOSCH144 + case BOSCH144: + sendBosch144(state, nbytes); + break; +#endif // SEND_BOSCH144 +#if SEND_CARRIER_AC84 + case CARRIER_AC84: + sendCarrierAC84(state, nbytes); + break; +#endif // SEND_CARRIER_AC84 +#if SEND_CARRIER_AC128 + case CARRIER_AC128: + sendCarrierAC128(state, nbytes); + break; +#endif // SEND_CARRIER_AC128 +#if SEND_CORONA_AC + case CORONA_AC: + sendCoronaAc(state, nbytes); + break; +#endif // SEND_ARGO +#if SEND_DAIKIN + case DAIKIN: + sendDaikin(state, nbytes); + break; +#endif // SEND_DAIKIN +#if SEND_DAIKIN128 + case DAIKIN128: + sendDaikin128(state, nbytes); + break; +#endif // SEND_DAIKIN128 +#if SEND_DAIKIN152 + case DAIKIN152: + sendDaikin152(state, nbytes); + break; +#endif // SEND_DAIKIN152 +#if SEND_DAIKIN160 + case DAIKIN160: + sendDaikin160(state, nbytes); + break; +#endif // SEND_DAIKIN160 +#if SEND_DAIKIN176 + case DAIKIN176: + sendDaikin176(state, nbytes); + break; +#endif // SEND_DAIKIN176 +#if SEND_DAIKIN2 + case DAIKIN2: + sendDaikin2(state, nbytes); + break; +#endif // SEND_DAIKIN2 +#if SEND_DAIKIN200 + case DAIKIN200: + sendDaikin200(state, nbytes); + break; +#endif // SEND_DAIKIN200 +#if SEND_DAIKIN216 + case DAIKIN216: + sendDaikin216(state, nbytes); + break; +#endif // SEND_DAIKIN216 +#if SEND_DAIKIN312 + case DAIKIN312: + sendDaikin312(state, nbytes); + break; +#endif // SEND_DAIKIN312 +#if SEND_ELECTRA_AC + case ELECTRA_AC: + sendElectraAC(state, nbytes); + break; +#endif // SEND_ELECTRA_AC +#if SEND_FUJITSU_AC + case FUJITSU_AC: + sendFujitsuAC(state, nbytes); + break; +#endif // SEND_FUJITSU_AC +#if SEND_GREE + case GREE: + sendGree(state, nbytes); + break; +#endif // SEND_GREE +#if SEND_HAIER_AC + case HAIER_AC: + sendHaierAC(state, nbytes); + break; +#endif // SEND_HAIER_AC +#if SEND_HAIER_AC_YRW02 + case HAIER_AC_YRW02: + sendHaierACYRW02(state, nbytes); + break; +#endif // SEND_HAIER_AC_YRW02 +#if SEND_HAIER_AC160 + case HAIER_AC160: + sendHaierAC160(state, nbytes); + break; +#endif // SEND_HAIER_AC160 +#if SEND_HAIER_AC176 + case HAIER_AC176: + sendHaierAC176(state, nbytes); + break; +#endif // SEND_HAIER_AC176 +#if SEND_HITACHI_AC + case HITACHI_AC: + sendHitachiAC(state, nbytes); + break; +#endif // SEND_HITACHI_AC +#if SEND_HITACHI_AC1 + case HITACHI_AC1: + sendHitachiAC1(state, nbytes); + break; +#endif // SEND_HITACHI_AC1 +#if SEND_HITACHI_AC2 + case HITACHI_AC2: + sendHitachiAC2(state, nbytes); + break; +#endif // SEND_HITACHI_AC2 +#if SEND_HITACHI_AC3 + case HITACHI_AC3: + sendHitachiAc3(state, nbytes); + break; +#endif // SEND_HITACHI_AC3 +#if SEND_HITACHI_AC264 + case HITACHI_AC264: + sendHitachiAc264(state, nbytes); + break; +#endif // SEND_HITACHI_AC264 +#if SEND_HITACHI_AC296 + case HITACHI_AC296: + sendHitachiAc296(state, nbytes); + break; +#endif // SEND_HITACHI_AC296 +#if SEND_HITACHI_AC344 + case HITACHI_AC344: + sendHitachiAc344(state, nbytes); + break; +#endif // SEND_HITACHI_AC344 +#if SEND_HITACHI_AC424 + case HITACHI_AC424: + sendHitachiAc424(state, nbytes); + break; +#endif // SEND_HITACHI_AC424 +#if SEND_KELON168 + case KELON168: + sendKelon168(state, nbytes); + break; +#endif // SEND_KELON168 +#if SEND_KELVINATOR + case KELVINATOR: + sendKelvinator(state, nbytes); + break; +#endif // SEND_KELVINATOR +#if SEND_MIRAGE + case MIRAGE: + sendMirage(state, nbytes); + break; +#endif // SEND_MIRAGE +#if SEND_MITSUBISHI_AC + case MITSUBISHI_AC: + sendMitsubishiAC(state, nbytes); + break; +#endif // SEND_MITSUBISHI_AC +#if SEND_MITSUBISHI136 + case MITSUBISHI136: + sendMitsubishi136(state, nbytes); + break; +#endif // SEND_MITSUBISHI136 +#if SEND_MITSUBISHI112 + case MITSUBISHI112: + sendMitsubishi112(state, nbytes); + break; +#endif // SEND_MITSUBISHI112 +#if SEND_MITSUBISHIHEAVY + case MITSUBISHI_HEAVY_88: + sendMitsubishiHeavy88(state, nbytes); + break; + case MITSUBISHI_HEAVY_152: + sendMitsubishiHeavy152(state, nbytes); + break; +#endif // SEND_MITSUBISHIHEAVY +#if SEND_MWM + case MWM: + sendMWM(state, nbytes); + break; +#endif // SEND_MWM +#if SEND_NEOCLIMA + case NEOCLIMA: + sendNeoclima(state, nbytes); + break; +#endif // SEND_NEOCLIMA +#if SEND_PANASONIC_AC + case PANASONIC_AC: + sendPanasonicAC(state, nbytes); + break; +#endif // SEND_PANASONIC_AC +#if SEND_RHOSS + case RHOSS: + sendRhoss(state, nbytes); + break; +#endif // SEND_RHOSS +#if SEND_SAMSUNG_AC + case SAMSUNG_AC: + sendSamsungAC(state, nbytes); + break; +#endif // SEND_SAMSUNG_AC +#if SEND_SANYO_AC + case SANYO_AC: + sendSanyoAc(state, nbytes); + break; +#endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + case SANYO_AC88: + sendSanyoAc88(state, nbytes); + break; +#endif // SEND_SANYO_AC88 +#if SEND_SANYO_AC152 + case SANYO_AC152: + sendSanyoAc152(state, nbytes); + break; +#endif // SEND_SANYO_AC152 +#if SEND_SHARP_AC + case SHARP_AC: + sendSharpAc(state, nbytes); + break; +#endif // SEND_SHARP_AC +#if SEND_TCL96AC + case TCL96AC: + sendTcl96Ac(state, nbytes); + break; +#endif // SEND_TCL96AC +#if SEND_TCL112AC + case TCL112AC: + sendTcl112Ac(state, nbytes); + break; +#endif // SEND_TCL112AC +#if SEND_TEKNOPOINT + case TEKNOPOINT: + sendTeknopoint(state, nbytes); + break; +#endif // SEND_TEKNOPOINT +#if SEND_TOSHIBA_AC + case TOSHIBA_AC: + sendToshibaAC(state, nbytes); + break; +#endif // SEND_TOSHIBA_AC +#if SEND_TROTEC + case TROTEC: + sendTrotec(state, nbytes); + break; +#endif // SEND_TROTEC +#if SEND_TROTEC_3550 + case TROTEC_3550: + sendTrotec3550(state, nbytes); + break; +#endif // SEND_TROTEC_3550 +#if SEND_WHIRLPOOL_AC + case WHIRLPOOL_AC: + sendWhirlpoolAC(state, nbytes); + break; +#endif // SEND_WHIRLPOOL_AC + default: + return false; + } + return true; +} diff --git a/src/libraries/IRremoteESP8266/src/IRsend.h b/src/libraries/IRremoteESP8266/src/IRsend.h new file mode 100644 index 000000000..74becd66d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRsend.h @@ -0,0 +1,916 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2015 Mark Szabo +// Copyright 2017 David Conran +#ifndef IRSEND_H_ +#define IRSEND_H_ + +#define __STDC_LIMIT_MACROS +#include +#include "IRremoteESP8266.h" + +// Originally from https://github.com/shirriff/Arduino-IRremote/ +// Updated by markszabo (https://github.com/crankyoldgit/IRremoteESP8266) for +// sending IR code on ESP8266 + + +// Constants +// Offset (in microseconds) to use in Period time calculations to account for +// code excution time in producing the software PWM signal. +#if defined(ESP32) +// Calculated on a generic ESP-WROOM-32 board with v3.2-18 SDK @ 240MHz +const int8_t kPeriodOffset = -2; +#elif (defined(ESP8266) && F_CPU == 160000000L) // NOLINT(whitespace/parens) +// Calculated on an ESP8266 NodeMCU v2 board using: +// v2.6.0 with v2.5.2 ESP core @ 160MHz +const int8_t kPeriodOffset = -2; +#else // (defined(ESP8266) && F_CPU == 160000000L) +// Calculated on ESP8266 Wemos D1 mini using v2.4.1 with v2.4.0 ESP core @ 40MHz +const int8_t kPeriodOffset = -5; +#endif // (defined(ESP8266) && F_CPU == 160000000L) + + + + +const uint8_t kDutyDefault = 50; // Percentage +const uint8_t kDutyMax = 100; // Percentage +// delayMicroseconds() is only accurate to 16383us. +// Ref: https://www.arduino.cc/en/Reference/delayMicroseconds +const uint16_t kMaxAccurateUsecDelay = 16383; +// Usecs to wait between messages we don't know the proper gap time. +const uint32_t kDefaultMessageGap = 100000; +/// Placeholder for missing sensor temp value +/// @note Not using "-1" as it may be a valid external temp +const float kNoTempValue = -100.0; + +/// Enumerators and Structures for the Common A/C API. +namespace stdAc { +/// Common A/C settings for A/C operating modes. +enum class opmode_t { + kOff = -1, + kAuto = 0, + kCool = 1, + kHeat = 2, + kDry = 3, + kFan = 4, + // Add new entries before this one, and update it to point to the last entry + kLastOpmodeEnum = kFan, +}; + +/// Common A/C settings for Fan Speeds. +enum class fanspeed_t { + kAuto = 0, + kMin = 1, + kLow = 2, + kMedium = 3, + kHigh = 4, + kMax = 5, + kMediumHigh = 6, + // Add new entries before this one, and update it to point to the last entry + kLastFanspeedEnum = kMediumHigh, +}; + +/// Common A/C settings for Vertical Swing. +enum class swingv_t { + kOff = -1, + kAuto = 0, + kHighest = 1, + kHigh = 2, + kMiddle = 3, + kLow = 4, + kLowest = 5, + kUpperMiddle = 6, + // Add new entries before this one, and update it to point to the last entry + kLastSwingvEnum = kUpperMiddle, +}; + +/// @brief Tyoe of A/C command (if the remote uses different codes for each) +/// @note Most remotes support only a single command or aggregate multiple +/// into one (e.g. control+timer). Use @c kControlCommand in such case +enum class ac_command_t { + kControlCommand = 0, + kSensorTempReport = 1, + kTimerCommand = 2, + kConfigCommand = 3, + // Add new entries before this one, and update it to point to the last entry + kLastAcCommandEnum = kConfigCommand, +}; + +/// Common A/C settings for Horizontal Swing. +enum class swingh_t { + kOff = -1, + kAuto = 0, // a.k.a. On. + kLeftMax = 1, + kLeft = 2, + kMiddle = 3, + kRight = 4, + kRightMax = 5, + kWide = 6, // a.k.a. left & right at the same time. + // Add new entries before this one, and update it to point to the last entry + kLastSwinghEnum = kWide, +}; + +/// Structure to hold a common A/C state. +struct state_t { + decode_type_t protocol = decode_type_t::UNKNOWN; + int16_t model = -1; // `-1` means unused. + bool power = false; + stdAc::opmode_t mode = stdAc::opmode_t::kOff; + float degrees = 25; + bool celsius = true; + stdAc::fanspeed_t fanspeed = stdAc::fanspeed_t::kAuto; + stdAc::swingv_t swingv = stdAc::swingv_t::kOff; + stdAc::swingh_t swingh = stdAc::swingh_t::kOff; + bool quiet = false; + bool turbo = false; + bool econo = false; + bool light = false; + bool filter = false; + bool clean = false; + bool beep = false; + int16_t sleep = -1; // `-1` means off. + int16_t clock = -1; // `-1` means not set. + stdAc::ac_command_t command = stdAc::ac_command_t::kControlCommand; + bool iFeel = false; + float sensorTemperature = kNoTempValue; // `kNoTempValue` means not set. +}; +}; // namespace stdAc + +/// Fujitsu A/C model numbers +enum fujitsu_ac_remote_model_t { + ARRAH2E = 1, ///< (1) AR-RAH2E, AR-RAC1E, AR-RAE1E, AR-RCE1E, AR-RAH2U, + ///< AR-REG1U (Default) + ///< Warning: Use on incorrect models can cause the A/C to lock + ///< up, requring the A/C to be physically powered off to fix. + ///< e.g. AR-RAH1U may lock up with a Swing command. + ARDB1, ///< (2) AR-DB1, AR-DL10 (AR-DL10 swing doesn't work) + ARREB1E, ///< (3) AR-REB1E, AR-RAH1U (Similar to ARRAH2E but no horiz + ///< control) + ARJW2, ///< (4) AR-JW2 (Same as ARDB1 but with horiz control) + ARRY4, ///< (5) AR-RY4 (Same as AR-RAH2E but with clean & filter) + ARREW4E, ///< (6) Similar to ARRAH2E, but with different temp config. +}; + +/// Gree A/C model numbers +enum gree_ac_remote_model_t { + YAW1F = 1, // (1) Ultimate, EKOKAI, RusClimate (Default) + YBOFB, // (2) Green, YBOFB2, YAPOF3 + YX1FSF, // (3) Soleus Air window unit (Similar to YAW1F, but with an + // Operation mode of Energy Saver (Econo)) +}; + +/// HAIER_AC176 A/C model numbers +enum haier_ac176_remote_model_t { + V9014557_A = 1, // (1) V9014557 Remote in "A" setting. (Default) + V9014557_B, // (2) V9014557 Remote in "B" setting. +}; + +/// HITACHI_AC1 A/C model numbers +enum hitachi_ac1_remote_model_t { + R_LT0541_HTA_A = 1, // (1) R-LT0541-HTA Remote in "A" setting. (Default) + R_LT0541_HTA_B, // (2) R-LT0541-HTA Remote in "B" setting. +}; + +/// MIRAGE A/C model numbers +enum mirage_ac_remote_model_t { + KKG9AC1 = 1, // (1) KKG9A-C1 Remote. (Default) + KKG29AC1, // (2) KKG29A-C1 Remote. +}; + +/// Panasonic A/C model numbers +enum panasonic_ac_remote_model_t { + kPanasonicUnknown = 0, + kPanasonicLke = 1, + kPanasonicNke = 2, + kPanasonicDke = 3, // PKR too. + kPanasonicJke = 4, + kPanasonicCkp = 5, + kPanasonicRkr = 6, +}; + +/// Sharp A/C model numbers +enum sharp_ac_remote_model_t { + A907 = 1, + A705 = 2, + A903 = 3, // 820 too +}; + +/// TCL (& Teknopoint) A/C model numbers +enum tcl_ac_remote_model_t { + TAC09CHSD = 1, + GZ055BE1 = 2, // Also Teknopoint GZ01-BEJ0-000 +}; + +/// Voltas A/C model numbers +enum voltas_ac_remote_model_t { + kVoltasUnknown = 0, // Full Function + kVoltas122LZF = 1, // (1) 122LZF (No SwingH support) (Default) +}; + +/// Whirlpool A/C model numbers +enum whirlpool_ac_remote_model_t { + DG11J13A = 1, // DG11J1-04 too + DG11J191, +}; + +/// LG A/C model numbers +enum lg_ac_remote_model_t { + GE6711AR2853M = 1, // (1) LG 28-bit Protocol (default) + AKB75215403, // (2) LG2 28-bit Protocol + AKB74955603, // (3) LG2 28-bit Protocol variant + AKB73757604, // (4) LG2 Variant of AKB74955603 + LG6711A20083V, // (5) Same as GE6711AR2853M, but only SwingV toggle. +}; + +/// Argo A/C model numbers +enum argo_ac_remote_model_t { + SAC_WREM2 = 1, // (1) ARGO WREM2 remote (default) + SAC_WREM3 // (2) ARGO WREM3 remote (touch buttons), bit-len vary by cmd +}; + +// Classes + +/// Class for sending all basic IR protocols. +/// @note Originally from https://github.com/shirriff/Arduino-IRremote/ +/// Updated by markszabo (https://github.com/crankyoldgit/IRremoteESP8266) for +/// sending IR code on ESP8266 +class IRsend { + public: + explicit IRsend(uint16_t IRsendPin, bool inverted = false, + bool use_modulation = true); + void begin(); + void enableIROut(uint32_t freq, uint8_t duty = kDutyDefault); + + virtual void _delayMicroseconds(uint32_t usec); + virtual uint16_t mark(uint16_t usec); + virtual void space(uint32_t usec); + + int8_t calibrate(uint16_t hz = 38000U); + void sendRaw(const uint16_t buf[], const uint16_t len, const uint16_t hz); + void sendData(uint16_t onemark, uint32_t onespace, uint16_t zeromark, + uint32_t zerospace, uint64_t data, uint16_t nbits, + bool MSBfirst = true); + void sendManchesterData(const uint16_t half_period, const uint64_t data, + const uint16_t nbits, const bool MSBfirst = true, + const bool GEThomas = true); + void sendManchester(const uint16_t headermark, const uint32_t headerspace, + const uint16_t half_period, const uint16_t footermark, + const uint32_t gap, const uint64_t data, + const uint16_t nbits, const uint16_t frequency = 38, + const bool MSBfirst = true, + const uint16_t repeat = kNoRepeat, + const uint8_t dutycycle = kDutyDefault, + const bool GEThomas = true); + void sendGeneric(const uint16_t headermark, const uint32_t headerspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, const uint32_t gap, + const uint64_t data, const uint16_t nbits, + const uint16_t frequency, const bool MSBfirst, + const uint16_t repeat, const uint8_t dutycycle); + void sendGeneric(const uint16_t headermark, const uint32_t headerspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, const uint32_t gap, + const uint32_t mesgtime, const uint64_t data, + const uint16_t nbits, const uint16_t frequency, + const bool MSBfirst, const uint16_t repeat, + const uint8_t dutycycle); + void sendGeneric(const uint16_t headermark, const uint32_t headerspace, + const uint16_t onemark, const uint32_t onespace, + const uint16_t zeromark, const uint32_t zerospace, + const uint16_t footermark, const uint32_t gap, + const uint8_t *dataptr, const uint16_t nbytes, + const uint16_t frequency, const bool MSBfirst, + const uint16_t repeat, const uint8_t dutycycle); + static uint16_t minRepeats(const decode_type_t protocol); + static uint16_t defaultBits(const decode_type_t protocol); + bool send(const decode_type_t type, const uint64_t data, + const uint16_t nbits, const uint16_t repeat = kNoRepeat); + bool send(const decode_type_t type, const uint8_t *state, + const uint16_t nbytes); +#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO || \ + SEND_MIDEA24) + void sendNEC(uint64_t data, uint16_t nbits = kNECBits, + uint16_t repeat = kNoRepeat); + uint32_t encodeNEC(uint16_t address, uint16_t command); +#endif +#if SEND_SONY + // sendSony() should typically be called with repeat=2 as Sony devices + // expect the code to be sent at least 3 times. (code + 2 repeats = 3 codes) + // Legacy use of this procedure was to only send a single code so call it with + // repeat=0 for backward compatibility. As of v2.0 it defaults to sending + // a Sony command that will be accepted be a device. + void sendSony(const uint64_t data, const uint16_t nbits = kSony20Bits, + const uint16_t repeat = kSonyMinRepeat); + void sendSony38(const uint64_t data, const uint16_t nbits = kSony20Bits, + const uint16_t repeat = kSonyMinRepeat + 1); + uint32_t encodeSony(const uint16_t nbits, const uint16_t command, + const uint16_t address, const uint16_t extended = 0); +#endif // SEND_SONY +#if SEND_SHERWOOD + void sendSherwood(uint64_t data, uint16_t nbits = kSherwoodBits, + uint16_t repeat = kSherwoodMinRepeat); +#endif +#if SEND_SAMSUNG + void sendSAMSUNG(const uint64_t data, const uint16_t nbits = kSamsungBits, + const uint16_t repeat = kNoRepeat); + uint32_t encodeSAMSUNG(const uint8_t customer, const uint8_t command); +#endif +#if SEND_SAMSUNG36 + void sendSamsung36(const uint64_t data, const uint16_t nbits = kSamsung36Bits, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_SAMSUNG_AC + void sendSamsungAC(const unsigned char data[], + const uint16_t nbytes = kSamsungAcStateLength, + const uint16_t repeat = kSamsungAcDefaultRepeat); +#endif +#if SEND_LG + void sendLG(uint64_t data, uint16_t nbits = kLgBits, + uint16_t repeat = kNoRepeat); + void sendLG2(uint64_t data, uint16_t nbits = kLgBits, + uint16_t repeat = kNoRepeat); + uint32_t encodeLG(uint16_t address, uint16_t command); +#endif +#if (SEND_SHARP || SEND_DENON) + uint32_t encodeSharp(const uint16_t address, const uint16_t command, + const uint16_t expansion = 1, const uint16_t check = 0, + const bool MSBfirst = false); + void sendSharp(const uint16_t address, const uint16_t command, + const uint16_t nbits = kSharpBits, + const uint16_t repeat = kNoRepeat); + void sendSharpRaw(const uint64_t data, const uint16_t nbits = kSharpBits, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_SHARP_AC + void sendSharpAc(const unsigned char data[], + const uint16_t nbytes = kSharpAcStateLength, + const uint16_t repeat = kSharpAcDefaultRepeat); +#endif // SEND_SHARP_AC +#if SEND_JVC + void sendJVC(uint64_t data, uint16_t nbits = kJvcBits, + uint16_t repeat = kNoRepeat); + uint16_t encodeJVC(uint8_t address, uint8_t command); +#endif +#if SEND_DENON + void sendDenon(uint64_t data, uint16_t nbits = kDenonBits, + uint16_t repeat = kNoRepeat); +#endif +#if SEND_SANYO + uint64_t encodeSanyoLC7461(uint16_t address, uint8_t command); + void sendSanyoLC7461(const uint64_t data, + const uint16_t nbits = kSanyoLC7461Bits, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_SANYO_AC + void sendSanyoAc(const uint8_t *data, + const uint16_t nbytes = kSanyoAcStateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + void sendSanyoAc88(const uint8_t *data, + const uint16_t nbytes = kSanyoAc88StateLength, + const uint16_t repeat = kSanyoAc88MinRepeat); +#endif // SEND_SANYO_AC88 +#if SEND_SANYO_AC152 + void sendSanyoAc152(const uint8_t *data, + const uint16_t nbytes = kSanyoAc152StateLength, + const uint16_t repeat = kSanyoAc152MinRepeat); +#endif // SEND_SANYO_AC152 +#if SEND_DISH + // sendDISH() should typically be called with repeat=3 as DISH devices + // expect the code to be sent at least 4 times. (code + 3 repeats = 4 codes) + // Legacy use of this procedure was only to send a single code + // so use repeat=0 for backward compatibility. + void sendDISH(uint64_t data, uint16_t nbits = kDishBits, + uint16_t repeat = kDishMinRepeat); +#endif +#if (SEND_PANASONIC || SEND_DENON) + void sendPanasonic64(const uint64_t data, + const uint16_t nbits = kPanasonicBits, + const uint16_t repeat = kNoRepeat); + void sendPanasonic(const uint16_t address, const uint32_t data, + const uint16_t nbits = kPanasonicBits, + const uint16_t repeat = kNoRepeat); + uint64_t encodePanasonic(const uint16_t manufacturer, const uint8_t device, + const uint8_t subdevice, const uint8_t function); +#endif +#if SEND_RC5 + void sendRC5(const uint64_t data, uint16_t nbits = kRC5XBits, + const uint16_t repeat = kNoRepeat); + uint16_t encodeRC5(const uint8_t address, const uint8_t command, + const bool key_released = false); + uint16_t encodeRC5X(const uint8_t address, const uint8_t command, + const bool key_released = false); + uint64_t toggleRC5(const uint64_t data); +#endif +#if SEND_RC6 + void sendRC6(const uint64_t data, const uint16_t nbits = kRC6Mode0Bits, + const uint16_t repeat = kNoRepeat); + uint64_t encodeRC6(const uint32_t address, const uint8_t command, + const uint16_t mode = kRC6Mode0Bits); + uint64_t toggleRC6(const uint64_t data, const uint16_t nbits = kRC6Mode0Bits); +#endif +#if SEND_RCMM + void sendRCMM(uint64_t data, uint16_t nbits = kRCMMBits, + uint16_t repeat = kNoRepeat); +#endif +#if SEND_COOLIX + void sendCOOLIX(const uint64_t data, const uint16_t nbits = kCoolixBits, + const uint16_t repeat = kCoolixDefaultRepeat); +#endif // SEND_COOLIX +#if SEND_COOLIX48 + void sendCoolix48(const uint64_t data, const uint16_t nbits = kCoolix48Bits, + const uint16_t repeat = kCoolixDefaultRepeat); +#endif // SEND_COOLIX48 +#if SEND_WHYNTER + void sendWhynter(const uint64_t data, const uint16_t nbits = kWhynterBits, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_MIRAGE + void sendMirage(const unsigned char data[], + const uint16_t nbytes = kMirageStateLength, + const uint16_t repeat = kMirageMinRepeat); +#endif // SEND_MIRAGE +#if SEND_MITSUBISHI + void sendMitsubishi(uint64_t data, uint16_t nbits = kMitsubishiBits, + uint16_t repeat = kMitsubishiMinRepeat); +#endif +#if SEND_MITSUBISHI136 + void sendMitsubishi136(const unsigned char data[], + const uint16_t nbytes = kMitsubishi136StateLength, + const uint16_t repeat = kMitsubishi136MinRepeat); +#endif +#if SEND_MITSUBISHI112 + void sendMitsubishi112(const unsigned char data[], + const uint16_t nbytes = kMitsubishi112StateLength, + const uint16_t repeat = kMitsubishi112MinRepeat); +#endif +#if SEND_MITSUBISHI2 + void sendMitsubishi2(uint64_t data, uint16_t nbits = kMitsubishiBits, + uint16_t repeat = kMitsubishiMinRepeat); +#endif +#if SEND_MITSUBISHI_AC + void sendMitsubishiAC(const unsigned char data[], + const uint16_t nbytes = kMitsubishiACStateLength, + const uint16_t repeat = kMitsubishiACMinRepeat); +#endif +#if SEND_MITSUBISHIHEAVY + void sendMitsubishiHeavy88( + const unsigned char data[], + const uint16_t nbytes = kMitsubishiHeavy88StateLength, + const uint16_t repeat = kMitsubishiHeavy88MinRepeat); + void sendMitsubishiHeavy152( + const unsigned char data[], + const uint16_t nbytes = kMitsubishiHeavy152StateLength, + const uint16_t repeat = kMitsubishiHeavy152MinRepeat); +#endif +#if SEND_FUJITSU_AC + void sendFujitsuAC(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat = kFujitsuAcMinRepeat); +#endif +#if SEND_INAX + void sendInax(const uint64_t data, const uint16_t nbits = kInaxBits, + const uint16_t repeat = kInaxMinRepeat); +#endif // SEND_INAX +#if SEND_GLOBALCACHE + void sendGC(uint16_t buf[], uint16_t len); +#endif +#if SEND_KELVINATOR + void sendKelvinator(const unsigned char data[], + const uint16_t nbytes = kKelvinatorStateLength, + const uint16_t repeat = kKelvinatorDefaultRepeat); +#endif +#if SEND_DAIKIN + void sendDaikin(const unsigned char data[], + const uint16_t nbytes = kDaikinStateLength, + const uint16_t repeat = kDaikinDefaultRepeat); +#endif +#if SEND_DAIKIN64 + void sendDaikin64(const uint64_t data, const uint16_t nbits = kDaikin64Bits, + const uint16_t repeat = kDaikin64DefaultRepeat); +#endif // SEND_DAIKIN64 +#if SEND_DAIKIN128 + void sendDaikin128(const unsigned char data[], + const uint16_t nbytes = kDaikin128StateLength, + const uint16_t repeat = kDaikin128DefaultRepeat); +#endif // SEND_DAIKIN128 +#if SEND_DAIKIN152 + void sendDaikin152(const unsigned char data[], + const uint16_t nbytes = kDaikin152StateLength, + const uint16_t repeat = kDaikin152DefaultRepeat); +#endif // SEND_DAIKIN152 +#if SEND_DAIKIN160 + void sendDaikin160(const unsigned char data[], + const uint16_t nbytes = kDaikin160StateLength, + const uint16_t repeat = kDaikin160DefaultRepeat); +#endif // SEND_DAIKIN160 +#if SEND_DAIKIN176 + void sendDaikin176(const unsigned char data[], + const uint16_t nbytes = kDaikin176StateLength, + const uint16_t repeat = kDaikin176DefaultRepeat); +#endif // SEND_DAIKIN176 +#if SEND_DAIKIN2 + void sendDaikin2(const unsigned char data[], + const uint16_t nbytes = kDaikin2StateLength, + const uint16_t repeat = kDaikin2DefaultRepeat); +#endif +#if SEND_DAIKIN200 + void sendDaikin200(const unsigned char data[], + const uint16_t nbytes = kDaikin200StateLength, + const uint16_t repeat = kDaikin200DefaultRepeat); +#endif // SEND_DAIKIN200 +#if SEND_DAIKIN216 + void sendDaikin216(const unsigned char data[], + const uint16_t nbytes = kDaikin216StateLength, + const uint16_t repeat = kDaikin216DefaultRepeat); +#endif // SEND_DAIKIN216 +#if SEND_DAIKIN312 + void sendDaikin312(const unsigned char data[], + const uint16_t nbytes = kDaikin312StateLength, + const uint16_t repeat = kDaikin312DefaultRepeat); +#endif // SEND_DAIKIN312 +#if SEND_AIWA_RC_T501 + void sendAiwaRCT501(uint64_t data, uint16_t nbits = kAiwaRcT501Bits, + uint16_t repeat = kAiwaRcT501MinRepeats); +#endif +#if SEND_GREE + void sendGree(const uint64_t data, const uint16_t nbits = kGreeBits, + const uint16_t repeat = kGreeDefaultRepeat); + void sendGree(const uint8_t data[], const uint16_t nbytes = kGreeStateLength, + const uint16_t repeat = kGreeDefaultRepeat); +#endif +#if SEND_GOODWEATHER + void sendGoodweather(const uint64_t data, + const uint16_t nbits = kGoodweatherBits, + const uint16_t repeat = kGoodweatherMinRepeat); +#endif // SEND_GOODWEATHER +#if SEND_GORENJE + void sendGorenje(const uint64_t data, const uint16_t nbits = kGorenjeBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_GORENJE +#if SEND_PRONTO + void sendPronto(uint16_t data[], uint16_t len, uint16_t repeat = kNoRepeat); +#endif +#if SEND_ARGO + void sendArgo(const unsigned char data[], + const uint16_t nbytes = kArgoStateLength, + const uint16_t repeat = kArgoDefaultRepeat, + bool sendFooter = false); + void sendArgoWREM3(const unsigned char data[], + const uint16_t nbytes = kArgoStateLength, + const uint16_t repeat = kArgoDefaultRepeat); +#endif // SEND_ARGO +#if SEND_TROTEC + void sendTrotec(const unsigned char data[], + const uint16_t nbytes = kTrotecStateLength, + const uint16_t repeat = kTrotecDefaultRepeat); +#endif // SEND_TROTEC +#if SEND_TROTEC_3550 + void sendTrotec3550(const unsigned char data[], + const uint16_t nbytes = kTrotecStateLength, + const uint16_t repeat = kTrotecDefaultRepeat); +#endif // SEND_TROTEC_3550 +#if SEND_NIKAI + void sendNikai(uint64_t data, uint16_t nbits = kNikaiBits, + uint16_t repeat = kNoRepeat); +#endif +#if SEND_TOSHIBA_AC + void sendToshibaAC(const uint8_t data[], + const uint16_t nbytes = kToshibaACStateLength, + const uint16_t repeat = kToshibaACMinRepeat); +#endif +#if SEND_MIDEA + void sendMidea(uint64_t data, uint16_t nbits = kMideaBits, + uint16_t repeat = kMideaMinRepeat); +#endif // SEND_MIDEA +#if SEND_MIDEA24 + void sendMidea24(const uint64_t data, const uint16_t nbits = kMidea24Bits, + const uint16_t repeat = kMidea24MinRepeat); +#endif // SEND_MIDEA24 +#if SEND_MAGIQUEST + void sendMagiQuest(const uint64_t data, const uint16_t nbits = kMagiquestBits, + const uint16_t repeat = kNoRepeat); + uint64_t encodeMagiQuest(const uint32_t wand_id, const uint16_t magnitude); +#endif +#if SEND_LASERTAG + void sendLasertag(uint64_t data, uint16_t nbits = kLasertagBits, + uint16_t repeat = kLasertagMinRepeat); +#endif +#if SEND_CARRIER_AC + void sendCarrierAC(uint64_t data, uint16_t nbits = kCarrierAcBits, + uint16_t repeat = kCarrierAcMinRepeat); +#endif +#if SEND_CARRIER_AC40 + void sendCarrierAC40(uint64_t data, uint16_t nbits = kCarrierAc40Bits, + uint16_t repeat = kCarrierAc40MinRepeat); +#endif +#if SEND_CARRIER_AC64 + void sendCarrierAC64(uint64_t data, uint16_t nbits = kCarrierAc64Bits, + uint16_t repeat = kCarrierAc64MinRepeat); +#endif +#if SEND_CARRIER_AC84 + void sendCarrierAC84(const uint8_t data[], + const uint16_t nbytes = kCarrierAc84StateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_CARRIER_AC84 +#if SEND_CARRIER_AC128 + void sendCarrierAC128(const uint8_t data[], + uint16_t nbytes = kCarrierAc128StateLength, + uint16_t repeat = kCarrierAc128MinRepeat); +#endif // SEND_CARRIER_AC128 +#if (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC176) + void sendHaierAC(const unsigned char data[], + const uint16_t nbytes = kHaierACStateLength, + const uint16_t repeat = kHaierAcDefaultRepeat); +#endif // (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC176) +#if SEND_HAIER_AC_YRW02 + void sendHaierACYRW02(const unsigned char data[], + const uint16_t nbytes = kHaierACYRW02StateLength, + const uint16_t repeat = kHaierAcYrw02DefaultRepeat); +#endif // SEND_HAIER_AC_YRW02 +#if SEND_HAIER_AC160 + void sendHaierAC160(const unsigned char data[], + const uint16_t nbytes = kHaierAC160StateLength, + const uint16_t repeat = kHaierAc160DefaultRepeat); +#endif // SEND_HAIER_AC160 +#if SEND_HAIER_AC176 + void sendHaierAC176(const unsigned char data[], + const uint16_t nbytes = kHaierAC176StateLength, + const uint16_t repeat = kHaierAc176DefaultRepeat); +#endif // SEND_HAIER_AC176 +#if SEND_HITACHI_AC + void sendHitachiAC(const unsigned char data[], + const uint16_t nbytes = kHitachiAcStateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif +#if SEND_HITACHI_AC1 + void sendHitachiAC1(const unsigned char data[], + const uint16_t nbytes = kHitachiAc1StateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif +#if SEND_HITACHI_AC2 + void sendHitachiAC2(const unsigned char data[], + const uint16_t nbytes = kHitachiAc2StateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif +#if SEND_HITACHI_AC3 + void sendHitachiAc3(const unsigned char data[], + const uint16_t nbytes, // No default as there as so many + // different sizes + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif // SEND_HITACHI_AC3 +#if SEND_HITACHI_AC264 + void sendHitachiAc264(const unsigned char data[], + const uint16_t nbytes = kHitachiAc264StateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif // SEND_HITACHI_AC264 +#if SEND_HITACHI_AC296 + void sendHitachiAc296(const unsigned char data[], + const uint16_t nbytes = kHitachiAc296StateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif // SEND_HITACHI_AC296 +#if SEND_HITACHI_AC344 + void sendHitachiAc344(const unsigned char data[], + const uint16_t nbytes = kHitachiAc344StateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif // SEND_HITACHI_AC344 +#if SEND_HITACHI_AC424 + void sendHitachiAc424(const unsigned char data[], + const uint16_t nbytes = kHitachiAc424StateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif // SEND_HITACHI_AC424 +#if SEND_GICABLE + void sendGICable(uint64_t data, uint16_t nbits = kGicableBits, + uint16_t repeat = kGicableMinRepeat); +#endif +#if SEND_WHIRLPOOL_AC + void sendWhirlpoolAC(const unsigned char data[], + const uint16_t nbytes = kWhirlpoolAcStateLength, + const uint16_t repeat = kWhirlpoolAcDefaultRepeat); +#endif +#if SEND_LUTRON + void sendLutron(uint64_t data, uint16_t nbits = kLutronBits, + uint16_t repeat = kNoRepeat); +#endif +#if SEND_ELECTRA_AC + void sendElectraAC(const unsigned char data[], + const uint16_t nbytes = kElectraAcStateLength, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_PANASONIC_AC + void sendPanasonicAC(const unsigned char data[], + const uint16_t nbytes = kPanasonicAcStateLength, + const uint16_t repeat = kPanasonicAcDefaultRepeat); +#endif // SEND_PANASONIC_AC +#if SEND_PANASONIC_AC32 + void sendPanasonicAC32(const uint64_t data, + const uint16_t nbits = kPanasonicAc32Bits, + const uint16_t repeat = kPanasonicAcDefaultRepeat); +#endif // SEND_PANASONIC_AC32 +#if SEND_PIONEER + void sendPioneer(const uint64_t data, const uint16_t nbits = kPioneerBits, + const uint16_t repeat = kNoRepeat); + uint64_t encodePioneer(uint16_t address, uint16_t command); +#endif +#if SEND_MWM + void sendMWM(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_VESTEL_AC + void sendVestelAc(const uint64_t data, const uint16_t nbits = kVestelAcBits, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_TCL96AC + void sendTcl96Ac(const unsigned char data[], + const uint16_t nbytes = kTcl96AcStateLength, + const uint16_t repeat = kTcl96AcDefaultRepeat); +#endif // SEND_TCL96AC +#if SEND_TCL112AC + void sendTcl112Ac(const unsigned char data[], + const uint16_t nbytes = kTcl112AcStateLength, + const uint16_t repeat = kTcl112AcDefaultRepeat); +#endif // SEND_TCL112AC +#if SEND_TECO + void sendTeco(const uint64_t data, const uint16_t nbits = kTecoBits, + const uint16_t repeat = kNoRepeat); +#endif +#if SEND_LEGOPF + void sendLegoPf(const uint64_t data, const uint16_t nbits = kLegoPfBits, + const uint16_t repeat = kLegoPfMinRepeat); +#endif +#if SEND_NEOCLIMA + void sendNeoclima(const unsigned char data[], + const uint16_t nbytes = kNeoclimaStateLength, + const uint16_t repeat = kNeoclimaMinRepeat); +#endif // SEND_NEOCLIMA +#if SEND_AMCOR + void sendAmcor(const unsigned char data[], + const uint16_t nbytes = kAmcorStateLength, + const uint16_t repeat = kAmcorDefaultRepeat); +#endif // SEND_AMCOR +#if SEND_EPSON + void sendEpson(uint64_t data, uint16_t nbits = kEpsonBits, + uint16_t repeat = kEpsonMinRepeat); +#endif +#if SEND_SYMPHONY + void sendSymphony(uint64_t data, uint16_t nbits = kSymphonyBits, + uint16_t repeat = kSymphonyDefaultRepeat); +#endif +#if SEND_AIRWELL + void sendAirwell(uint64_t data, uint16_t nbits = kAirwellBits, + uint16_t repeat = kAirwellMinRepeats); +#endif +#if SEND_DELONGHI_AC + void sendDelonghiAc(uint64_t data, uint16_t nbits = kDelonghiAcBits, + uint16_t repeat = kDelonghiAcDefaultRepeat); +#endif +#if SEND_DOSHISHA + void sendDoshisha(const uint64_t data, uint16_t nbits = kDoshishaBits, + const uint16_t repeat = kNoRepeat); + uint64_t encodeDoshisha(const uint8_t command, const uint8_t channel = 0); +#endif // SEND_DOSHISHA +#if SEND_MULTIBRACKETS + void sendMultibrackets(const uint64_t data, + const uint16_t nbits = kMultibracketsBits, + const uint16_t repeat = kMultibracketsDefaultRepeat); +#endif +#if SEND_TECHNIBEL_AC + void sendTechnibelAc(uint64_t data, uint16_t nbits = kTechnibelAcBits, + uint16_t repeat = kTechnibelAcDefaultRepeat); +#endif +#if SEND_CORONA_AC + void sendCoronaAc(const uint8_t data[], + const uint16_t nbytes = kCoronaAcStateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_CORONA_AC +#if SEND_ZEPEAL + void sendZepeal(const uint64_t data, + const uint16_t nbits = kZepealBits, + const uint16_t repeat = kZepealMinRepeat); +#endif // SEND_ZEPEAL +#if SEND_VOLTAS + void sendVoltas(const unsigned char data[], + const uint16_t nbytes = kVoltasStateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_VOLTAS +#if SEND_METZ + void sendMetz(const uint64_t data, + const uint16_t nbits = kMetzBits, + const uint16_t repeat = kMetzMinRepeat); + static uint32_t encodeMetz(const uint8_t address, const uint8_t command, + const bool toggle = false); +#endif // SEND_METZ +#if SEND_TRANSCOLD + void sendTranscold(const uint64_t data, const uint16_t nbits = kTranscoldBits, + const uint16_t repeat = kTranscoldDefaultRepeat); +#endif // SEND_TRANSCOLD +#if SEND_ELITESCREENS + void sendElitescreens(const uint64_t data, + const uint16_t nbits = kEliteScreensBits, + const uint16_t repeat = kEliteScreensDefaultRepeat); +#endif // SEND_ELITESCREENS +#if SEND_MILESTAG2 + // Since There 2 types of transmissions + // (14bits for Shooting by default, you can set 24 bit for msg delivery) + void sendMilestag2(const uint64_t data, + const uint16_t nbits = kMilesTag2ShotBits, + const uint16_t repeat = kMilesMinRepeat); +#endif // SEND_MILESTAG2 +#if SEND_ECOCLIM + void sendEcoclim(const uint64_t data, const uint16_t nbits = kEcoclimBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_ECOCLIM +#if SEND_XMP + void sendXmp(const uint64_t data, const uint16_t nbits = kXmpBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_XMP +#if SEND_TRUMA + void sendTruma(const uint64_t data, const uint16_t nbits = kTrumaBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_TRUMA +#if SEND_TEKNOPOINT + void sendTeknopoint(const unsigned char data[], + const uint16_t nbytes = kTeknopointStateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_TEKNOPOINT +#if SEND_KELON + void sendKelon(const uint64_t data, const uint16_t nbits = kKelonBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_KELON +#if SEND_KELON168 + void sendKelon168(const unsigned char data[], + const uint16_t nbytes = kKelon168StateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_KELON168 +#if SEND_BOSE + void sendBose(const uint64_t data, const uint16_t nbits = kBoseBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_BOSE +#if SEND_ARRIS + void sendArris(const uint64_t data, const uint16_t nbits = kArrisBits, + const uint16_t repeat = kNoRepeat); + static uint32_t toggleArrisRelease(const uint32_t data); + static uint32_t encodeArris(const uint32_t command, const bool release); +#endif // SEND_ARRIS +#if SEND_RHOSS + void sendRhoss(const unsigned char data[], + const uint16_t nbytes = kRhossStateLength, + const uint16_t repeat = kRhossDefaultRepeat); +#endif // SEND_RHOSS +#if SEND_AIRTON + void sendAirton(const uint64_t data, const uint16_t nbits = kAirtonBits, + const uint16_t repeat = kAirtonDefaultRepeat); +#endif // SEND_AIRTON +#if SEND_TOTO + void sendToto(const uint64_t data, const uint16_t nbits = kTotoBits, + const uint16_t repeat = kTotoDefaultRepeat); +#endif // SEND_TOTO +#if SEND_CLIMABUTLER + void sendClimaButler(const uint64_t data, + const uint16_t nbits = kClimaButlerBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_CLIMABUTLER +#if SEND_BOSCH144 + void sendBosch144(const unsigned char data[], + const uint16_t nbytes = kBosch144StateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_BOSCH144 +#if SEND_WOWWEE + void sendWowwee(const uint64_t data, const uint16_t nbits = kWowweeBits, + const uint16_t repeat = kWowweeDefaultRepeat); +#endif // SEND_WOWWEE + + protected: +#ifdef UNIT_TEST +#ifndef HIGH +#define HIGH 0x1 +#endif +#ifndef LOW +#define LOW 0x0 +#endif +#endif // UNIT_TEST + uint8_t outputOn; + uint8_t outputOff; + virtual void ledOff(); + virtual void ledOn(); +#ifndef UNIT_TEST + + private: +#else + uint32_t _freq_unittest; +#endif // UNIT_TEST + uint16_t onTimePeriod; + uint16_t offTimePeriod; + uint16_t IRpin; + int8_t periodOffset; + uint8_t _dutycycle; + bool modulation; + uint32_t calcUSecPeriod(uint32_t hz, bool use_offset = true); +#if SEND_SONY + void _sendSony(const uint64_t data, const uint16_t nbits, + const uint16_t repeat, const uint16_t freq); +#endif // SEND_SONY +}; + +#endif // IRSEND_H_ diff --git a/src/libraries/IRremoteESP8266/src/IRtext.cpp b/src/libraries/IRremoteESP8266/src/IRtext.cpp new file mode 100644 index 000000000..7755c6c33 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRtext.cpp @@ -0,0 +1,559 @@ +// 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 "String.h" +#endif // UNIT_TEST +#include "IRremoteESP8266.h" +#include "i18n.h" + +#include "IRmacros.h" + +#ifndef PROGMEM +#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 +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(k1Str, D_STR_1); ///< "1" +IRTEXT_CONST_STRING(k0Str, D_STR_0); ///< "0" +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(kConfigCommandStr, D_STR_CONFIG); ///< "Config" +IRTEXT_CONST_STRING(kControlCommandStr, D_STR_CONTROL); ///< "Control" +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(kIFeelReportStr, D_STR_IFEELREPORT); ///< "IFeel Report" +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(kISeeStr, D_STR_ISEE); ///< "ISee" +IRTEXT_CONST_STRING(kAbsenseDetectStr, D_STR_ABSENSEDETECT); + ///< "AbsenseDetect" +IRTEXT_CONST_STRING(kDirectIndirectModeStr, D_STR_DIRECTINDIRECTMODE); + ///< "Direct/Indirect mode" +IRTEXT_CONST_STRING(kDirectStr, D_STR_DIRECT); ///< "Direct" +IRTEXT_CONST_STRING(kIndirectStr, D_STR_INDIRECT); ///< "Indirect" + +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" +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(kUpperMiddleStr, D_STR_UPPER_MIDDLE); ///< "Upper-Middle" +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" +IRTEXT_CONST_STRING(kLockStr, D_STR_LOCK); ///< "Lock" + +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(kCoolingStr, D_STR_COOLING); ///< "Cooling" +IRTEXT_CONST_STRING(kHeatStr, D_STR_HEAT); ///< "Heat" +IRTEXT_CONST_STRING(kHeatingStr, D_STR_HEATING); ///< "Heating" +IRTEXT_CONST_STRING(kDryStr, D_STR_DRY); ///< "Dry" +IRTEXT_CONST_STRING(kDryingStr, D_STR_DRYING); ///< "Drying" +IRTEXT_CONST_STRING(kDehumidifyStr, D_STR_DEHUMIDIFY); ///< "Dehumidify" +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 +IRTEXT_CONST_STRING(kFanOnlyStr, D_STR_FANONLY); ///< "fan-only" +IRTEXT_CONST_STRING(kFan_OnlyStr, D_STR_FAN_ONLY); ///< "fan_only" (HA/legacy) +IRTEXT_CONST_STRING(kFanOnlyWithSpaceStr, D_STR_FANSPACEONLY); ///< "Fan Only" +IRTEXT_CONST_STRING(kFanOnlyNoSpaceStr, D_STR_FANONLYNOSPACE); ///< "FanOnly" + +IRTEXT_CONST_STRING(kRecycleStr, D_STR_RECYCLE); ///< "Recycle" + +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(kMedHighStr, D_STR_MED_HIGH); ///< "Med-high" +IRTEXT_CONST_STRING(kMedStr, D_STR_MED); ///< "Med" +IRTEXT_CONST_STRING(kMediumStr, D_STR_MEDIUM); ///< "Medium" + +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(kMaxRightNoSpaceStr, D_STR_MAXRIGHT_NOSPACE); ///< + ///< "MaxRight" +IRTEXT_CONST_STRING(kRightMaxStr, D_STR_RIGHTMAX); ///< "Right Max" +IRTEXT_CONST_STRING(kRightMaxNoSpaceStr, 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(kMaxLeftNoSpaceStr, D_STR_MAXLEFT_NOSPACE); ///< "MaxLeft" +IRTEXT_CONST_STRING(kLeftMaxStr, D_STR_LEFTMAX); ///< "Left Max" +IRTEXT_CONST_STRING(kLeftMaxNoSpaceStr, 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. +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" +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" +IRTEXT_CONST_STRING(kTurboToggleStr, D_STR_TURBOTOGGLE); ///< "Turbo Toggle" +IRTEXT_CONST_STRING(kSetTimerCommandStr, D_STR_SET_TIMER); ///< "Set Timer" +IRTEXT_CONST_STRING(kScheduleStr, D_STR_SCHEDULE); ///< "Schedule" +IRTEXT_CONST_STRING(kChStr, D_STR_CH); ///< "CH#" +IRTEXT_CONST_STRING(kTimerActiveDaysStr, D_STR_TIMER_ACTIVE_DAYS); +///< "TimerActiveDays" +IRTEXT_CONST_STRING(kKeyStr, D_STR_KEY); ///< "Key" +IRTEXT_CONST_STRING(kValueStr, D_STR_VALUE); ///< "Value" + +// Separators & Punctuation +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); ///< ": " +IRTEXT_CONST_STRING(kDashStr, D_STR_DASH); ///< "-" + +// IRutils +// - Time +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" +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" + +IRTEXT_CONST_STRING(kRepeatStr, D_STR_REPEAT); ///< "Repeat" +IRTEXT_CONST_STRING(kCodeStr, D_STR_CODE); ///< "Code" +IRTEXT_CONST_STRING(kBitsStr, D_STR_BITS); ///< "Bits" + +// Model Names +IRTEXT_CONST_STRING(kYaw1fStr, D_STR_YAW1F); ///< "YAW1F" +IRTEXT_CONST_STRING(kYbofbStr, D_STR_YBOFB); ///< "YBOFB" +IRTEXT_CONST_STRING(kYx1fsfStr, D_STR_YX1FSF); ///< "YX1FSF" +IRTEXT_CONST_STRING(kV9014557AStr, D_STR_V9014557_A); ///< "V9014557-A" +IRTEXT_CONST_STRING(kV9014557BStr, D_STR_V9014557_B); ///< "V9014557-B" +IRTEXT_CONST_STRING(kRlt0541htaaStr, D_STR_RLT0541HTA_A); ///< "R-LT0541-HTA-A" +IRTEXT_CONST_STRING(kRlt0541htabStr, D_STR_RLT0541HTA_B); ///< "R-LT0541-HTA-B" +IRTEXT_CONST_STRING(kArrah2eStr, D_STR_ARRAH2E); ///< "ARRAH2E" +IRTEXT_CONST_STRING(kArdb1Str, D_STR_ARDB1); ///< "ARDB1" +IRTEXT_CONST_STRING(kArreb1eStr, D_STR_ARREB1E); ///< "ARREB1E" +IRTEXT_CONST_STRING(kArjw2Str, D_STR_ARJW2); ///< "ARJW2" +IRTEXT_CONST_STRING(kArry4Str, D_STR_ARRY4); ///< "ARRY4" +IRTEXT_CONST_STRING(kArrew4eStr, D_STR_ARREW4E); ///< "ARREW4E" +IRTEXT_CONST_STRING(kGe6711ar2853mStr, D_STR_GE6711AR2853M); ///< + ///< "GE6711AR2853M" +IRTEXT_CONST_STRING(kAkb75215403Str, D_STR_AKB75215403); ///< "AKB75215403" +IRTEXT_CONST_STRING(kAkb74955603Str, D_STR_AKB74955603); ///< "AKB74955603" +IRTEXT_CONST_STRING(kAkb73757604Str, D_STR_AKB73757604); ///< "AKB73757604" +IRTEXT_CONST_STRING(kLg6711a20083vStr, D_STR_LG6711A20083V); ///< + ///< "LG6711A20083V" +IRTEXT_CONST_STRING(kKkg9ac1Str, D_STR_KKG9AC1); ///< "KKG9AC1" +IRTEXT_CONST_STRING(kKkg29ac1Str, D_STR_KKG29AC1); ///< "KKG29AC1" +IRTEXT_CONST_STRING(kLkeStr, D_STR_LKE); ///< "LKE" +IRTEXT_CONST_STRING(kNkeStr, D_STR_NKE); ///< "NKE" +IRTEXT_CONST_STRING(kDkeStr, D_STR_DKE); ///< "DKE" +IRTEXT_CONST_STRING(kPkrStr, D_STR_PKR); ///< "PKR" +IRTEXT_CONST_STRING(kJkeStr, D_STR_JKE); ///< "JKE" +IRTEXT_CONST_STRING(kCkpStr, D_STR_CKP); ///< "CKP" +IRTEXT_CONST_STRING(kRkrStr, D_STR_RKR); ///< "RKR" +IRTEXT_CONST_STRING(kPanasonicLkeStr, D_STR_PANASONICLKE); ///< "PANASONICLKE" +IRTEXT_CONST_STRING(kPanasonicNkeStr, D_STR_PANASONICNKE); ///< "PANASONICNKE" +IRTEXT_CONST_STRING(kPanasonicDkeStr, D_STR_PANASONICDKE); ///< "PANASONICDKE" +IRTEXT_CONST_STRING(kPanasonicPkrStr, D_STR_PANASONICPKR); ///< "PANASONICPKR" +IRTEXT_CONST_STRING(kPanasonicJkeStr, D_STR_PANASONICJKE); ///< "PANASONICJKE" +IRTEXT_CONST_STRING(kPanasonicCkpStr, D_STR_PANASONICCKP); ///< "PANASONICCKP" +IRTEXT_CONST_STRING(kPanasonicRkrStr, D_STR_PANASONICRKR); ///< "PANASONICRKR" +IRTEXT_CONST_STRING(kA907Str, D_STR_A907); ///< "A907" +IRTEXT_CONST_STRING(kA705Str, D_STR_A705); ///< "A705" +IRTEXT_CONST_STRING(kA903Str, D_STR_A903); ///< "A903" +IRTEXT_CONST_STRING(kTac09chsdStr, D_STR_TAC09CHSD); ///< "TAC09CHSD" +IRTEXT_CONST_STRING(kGz055be1Str, D_STR_GZ055BE1); ///< "GZ055BE1" +IRTEXT_CONST_STRING(k122lzfStr, D_STR_122LZF); ///< "122LZF" +IRTEXT_CONST_STRING(kDg11j13aStr, D_STR_DG11J13A); ///< "DG11J13A" +IRTEXT_CONST_STRING(kDg11j104Str, D_STR_DG11J104); ///< "DG11J104" +IRTEXT_CONST_STRING(kDg11j191Str, D_STR_DG11J191); ///< "DG11J191" +IRTEXT_CONST_STRING(kArgoWrem2Str, D_STR_ARGO_WREM2); ///< "WREM3" +IRTEXT_CONST_STRING(kArgoWrem3Str, D_STR_ARGO_WREM3); ///< "WREM3" + +#define D_STR_UNSUPPORTED "?" // Unsupported protocols will be showing as + // a question mark, check for length > 1 + // to show only currently included protocols +// Protocol Names +// Needs to be in decode_type_t order. +IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) { + D_STR_UNUSED "\x0" + COND(DECODE_RC5 || SEND_RC5, + D_STR_RC5, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_RC6 || SEND_RC6, + D_STR_RC6, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_NEC || SEND_NEC, + D_STR_NEC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SONY || SEND_SONY, + D_STR_SONY, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_PANASONIC || SEND_PANASONIC, + D_STR_PANASONIC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_JVC || SEND_JVC, + D_STR_JVC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SAMSUNG || SEND_SAMSUNG, + D_STR_SAMSUNG, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_WHYNTER || SEND_WHYNTER, + D_STR_WHYNTER, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_AIWA_RC_T501 || SEND_AIWA_RC_T501, + D_STR_AIWA_RC_T501, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_LG || SEND_LG, + D_STR_LG, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SANYO || SEND_SANYO, + D_STR_SANYO, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MITSUBISHI || SEND_MITSUBISHI, + D_STR_MITSUBISHI, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DISH || SEND_DISH, + D_STR_DISH, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SHARP || SEND_SHARP, + D_STR_SHARP, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_COOLIX || SEND_COOLIX, + D_STR_COOLIX, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN || SEND_DAIKIN, + D_STR_DAIKIN, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DENON || SEND_DENON, + D_STR_DENON, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_KELVINATOR || SEND_KELVINATOR, + D_STR_KELVINATOR, D_STR_UNSUPPORTED) "\x0" + COND(SEND_SHERWOOD, + D_STR_SHERWOOD, D_STR_UNSUPPORTED) "\x0" // SEND-ONLY + COND(DECODE_MITSUBISHI_AC || SEND_MITSUBISHI_AC, + D_STR_MITSUBISHI_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_RCMM || SEND_RCMM, + D_STR_RCMM, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SANYO || SEND_SANYO, + D_STR_SANYO_LC7461, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_RC5 || SEND_RC5, + D_STR_RC5X, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_GREE || SEND_GREE, + D_STR_GREE, D_STR_UNSUPPORTED) "\x0" + COND(SEND_PRONTO, + D_STR_PRONTO, D_STR_UNSUPPORTED) "\x0" // SEND-ONLY + COND(DECODE_NEC || SEND_NEC, + D_STR_NEC_LIKE, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_ARGO || SEND_ARGO, + D_STR_ARGO, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TROTEC || SEND_TROTEC, + D_STR_TROTEC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_NIKAI || SEND_NIKAI, + D_STR_NIKAI, D_STR_UNSUPPORTED) "\x0" + COND(SEND_RAW, + D_STR_RAW, D_STR_UNSUPPORTED) "\x0" // SEND-ONLY + COND(SEND_GLOBALCACHE, + D_STR_GLOBALCACHE, D_STR_UNSUPPORTED) "\x0" // SEND + COND(DECODE_TOSHIBA_AC || SEND_TOSHIBA_AC, + D_STR_TOSHIBA_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_FUJITSU_AC || SEND_FUJITSU_AC, + D_STR_FUJITSU_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MIDEA || SEND_MIDEA, + D_STR_MIDEA, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MAGIQUEST || SEND_MAGIQUEST, + D_STR_MAGIQUEST, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_LASERTAG || SEND_LASERTAG, + D_STR_LASERTAG, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_CARRIER_AC || SEND_CARRIER_AC, + D_STR_CARRIER_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HAIER_AC || SEND_HAIER_AC, + D_STR_HAIER_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MITSUBISHI2 || SEND_MITSUBISHI2, + D_STR_MITSUBISHI2, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC || SEND_HITACHI_AC, + D_STR_HITACHI_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC1 || SEND_HITACHI_AC1, + D_STR_HITACHI_AC1, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC2 || SEND_HITACHI_AC2, + D_STR_HITACHI_AC2, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_GICABLE || SEND_GICABLE, + D_STR_GICABLE, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HAIER_AC_YRW02 || SEND_HAIER_AC_YRW02, + D_STR_HAIER_AC_YRW02, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_WHIRLPOOL_AC || SEND_WHIRLPOOL_AC, + D_STR_WHIRLPOOL_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SAMSUNG_AC || SEND_SAMSUNG_AC, + D_STR_SAMSUNG_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_LUTRON || SEND_LUTRON, + D_STR_LUTRON, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_ELECTRA_AC || SEND_ELECTRA_AC, + D_STR_ELECTRA_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_PANASONIC_AC || SEND_PANASONIC_AC, + D_STR_PANASONIC_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_PIONEER || SEND_PIONEER, + D_STR_PIONEER, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_LG || SEND_LG, + D_STR_LG2, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MWM || SEND_MWM, + D_STR_MWM, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN2 || SEND_DAIKIN2, + D_STR_DAIKIN2, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_VESTEL_AC || SEND_VESTEL_AC, + D_STR_VESTEL_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TECO || SEND_TECO, + D_STR_TECO, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SAMSUNG36 || SEND_SAMSUNG36, + D_STR_SAMSUNG36, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TCL112AC || SEND_TCL112AC, + D_STR_TCL112AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_LEGOPF || SEND_LEGOPF, + D_STR_LEGOPF, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MITSUBISHIHEAVY || SEND_MITSUBISHIHEAVY, + D_STR_MITSUBISHI_HEAVY_88, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MITSUBISHIHEAVY || SEND_MITSUBISHIHEAVY, + D_STR_MITSUBISHI_HEAVY_152, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN216 || SEND_DAIKIN216, + D_STR_DAIKIN216, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SHARP_AC || SEND_SHARP_AC, + D_STR_SHARP_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_GOODWEATHER || SEND_GOODWEATHER, + D_STR_GOODWEATHER, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_INAX || SEND_INAX, + D_STR_INAX, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN160 || SEND_DAIKIN160, + D_STR_DAIKIN160, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_NEOCLIMA || SEND_NEOCLIMA, + D_STR_NEOCLIMA, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN176 || SEND_DAIKIN176, + D_STR_DAIKIN176, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN128 || SEND_DAIKIN128, + D_STR_DAIKIN128, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_AMCOR || SEND_AMCOR, + D_STR_AMCOR, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN152 || SEND_DAIKIN152, + D_STR_DAIKIN152, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MITSUBISHI136 || SEND_MITSUBISHI136, + D_STR_MITSUBISHI136, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MITSUBISHI112 || SEND_MITSUBISHI112, + D_STR_MITSUBISHI112, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC424 || SEND_HITACHI_AC424, + D_STR_HITACHI_AC424, D_STR_UNSUPPORTED) "\x0" + COND(SEND_SONY, + D_STR_SONY_38K, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_EPSON || SEND_EPSON, + D_STR_EPSON, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SYMPHONY || SEND_SYMPHONY, + D_STR_SYMPHONY, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC3 || SEND_HITACHI_AC3, + D_STR_HITACHI_AC3, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN64 || SEND_DAIKIN64, + D_STR_DAIKIN64, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_AIRWELL || SEND_AIRWELL, + D_STR_AIRWELL, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DELONGHI_AC || SEND_DELONGHI_AC, + D_STR_DELONGHI_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DOSHISHA || SEND_DOSHISHA, + D_STR_DOSHISHA, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MULTIBRACKETS || SEND_MULTIBRACKETS, + D_STR_MULTIBRACKETS, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_CARRIER_AC40 || SEND_CARRIER_AC40, + D_STR_CARRIER_AC40, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_CARRIER_AC64 || SEND_CARRIER_AC64, + D_STR_CARRIER_AC64, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC344 || SEND_HITACHI_AC344, + D_STR_HITACHI_AC344, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_CORONA_AC || SEND_CORONA_AC, + D_STR_CORONA_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MIDEA24 || SEND_MIDEA24, + D_STR_MIDEA24, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_ZEPEAL || SEND_ZEPEAL, + D_STR_ZEPEAL, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SANYO_AC || SEND_SANYO_AC, + D_STR_SANYO_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_VOLTAS || SEND_VOLTAS, + D_STR_VOLTAS, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_METZ || SEND_METZ, + D_STR_METZ, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TRANSCOLD || SEND_TRANSCOLD, + D_STR_TRANSCOLD, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TECHNIBEL_AC || SEND_TECHNIBEL_AC, + D_STR_TECHNIBEL_AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MIRAGE || SEND_MIRAGE, + D_STR_MIRAGE, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_ELITESCREENS || SEND_ELITESCREENS, + D_STR_ELITESCREENS, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_PANASONIC_AC32 || SEND_PANASONIC_AC32, + D_STR_PANASONIC_AC32, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_MILESTAG2 || SEND_MILESTAG2, + D_STR_MILESTAG2, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_ECOCLIM || SEND_ECOCLIM, + D_STR_ECOCLIM, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_XMP || SEND_XMP, + D_STR_XMP, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TRUMA || SEND_TRUMA, + D_STR_TRUMA, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HAIER_AC176 || SEND_HAIER_AC176, + D_STR_HAIER_AC176, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TEKNOPOINT || SEND_TEKNOPOINT, + D_STR_TEKNOPOINT, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_KELON || SEND_KELON, + D_STR_KELON, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TROTEC_3550 || SEND_TROTEC_3550, + D_STR_TROTEC_3550, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SANYO_AC88 || SEND_SANYO_AC88, + D_STR_SANYO_AC88, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_BOSE || SEND_BOSE, + D_STR_BOSE, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_ARRIS || SEND_ARRIS, + D_STR_ARRIS, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_RHOSS || SEND_RHOSS, + D_STR_RHOSS, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_AIRTON || SEND_AIRTON, + D_STR_AIRTON, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_COOLIX48 || SEND_COOLIX48, + D_STR_COOLIX48, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC264 || SEND_HITACHI_AC264, + D_STR_HITACHI_AC264, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_KELON168 || SEND_KELON168, + D_STR_KELON168, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HITACHI_AC296 || SEND_HITACHI_AC296, + D_STR_HITACHI_AC296, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN200 || SEND_DAIKIN200, + D_STR_DAIKIN200, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_HAIER_AC160 || SEND_HAIER_AC160, + D_STR_HAIER_AC160, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_CARRIER_AC128 || SEND_CARRIER_AC128, + D_STR_CARRIER_AC128, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TOTO || SEND_TOTO, + D_STR_TOTO, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_CLIMABUTLER || SEND_CLIMABUTLER, + D_STR_CLIMABUTLER, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_TCL96AC || SEND_TCL96AC, + D_STR_TCL96AC, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_BOSCH144 || SEND_BOSCH144, + D_STR_BOSCH144, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_SANYO_AC152 || SEND_SANYO_AC152, + D_STR_SANYO_AC152, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DAIKIN312 || SEND_DAIKIN312, + D_STR_DAIKIN312, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_GORENJE || SEND_GORENJE, + D_STR_GORENJE, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_WOWWEE || SEND_WOWWEE, + D_STR_WOWWEE, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_CARRIER_AC84 || SEND_CARRIER_AC84, + D_STR_CARRIER_AC84, D_STR_UNSUPPORTED) "\x0" + ///< New protocol (macro) strings should be added just above this line. + "\x0" ///< This string requires double null termination. +}; +IRTEXT_CONST_BLOB_PTR(kAllProtocolNamesStr); diff --git a/src/libraries/IRremoteESP8266/src/IRtext.h b/src/libraries/IRremoteESP8266/src/IRtext.h new file mode 100644 index 000000000..15d2690b7 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRtext.h @@ -0,0 +1,256 @@ +// Copyright 2019-2022 - David Conran (@crankyoldgit) +// This header file is to be included in files **other than** 'IRtext.cpp'. +// +// WARNING: Do not edit this file! This file is automatically generated by +// '../tools/generate_irtext_h.sh'. + +#ifndef IRTEXT_H_ +#define IRTEXT_H_ + +#include "i18n.h" + +// 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(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(k0Str); +extern IRTEXT_CONST_PTR(k10CHeatStr); +extern IRTEXT_CONST_PTR(k122lzfStr); +extern IRTEXT_CONST_PTR(k1Str); +extern IRTEXT_CONST_PTR(k3DStr); +extern IRTEXT_CONST_PTR(k6thSenseStr); +extern IRTEXT_CONST_PTR(k8CHeatStr); +extern IRTEXT_CONST_PTR(kA705Str); +extern IRTEXT_CONST_PTR(kA903Str); +extern IRTEXT_CONST_PTR(kA907Str); +extern IRTEXT_CONST_PTR(kAbsenseDetectStr); +extern IRTEXT_CONST_PTR(kAirFlowStr); +extern IRTEXT_CONST_PTR(kAkb73757604Str); +extern IRTEXT_CONST_PTR(kAkb74955603Str); +extern IRTEXT_CONST_PTR(kAkb75215403Str); +extern IRTEXT_CONST_PTR(kArdb1Str); +extern IRTEXT_CONST_PTR(kArgoWrem2Str); +extern IRTEXT_CONST_PTR(kArgoWrem3Str); +extern IRTEXT_CONST_PTR(kArjw2Str); +extern IRTEXT_CONST_PTR(kArrah2eStr); +extern IRTEXT_CONST_PTR(kArreb1eStr); +extern IRTEXT_CONST_PTR(kArrew4eStr); +extern IRTEXT_CONST_PTR(kArry4Str); +extern IRTEXT_CONST_PTR(kAutoStr); +extern IRTEXT_CONST_PTR(kAutomaticStr); +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(kChStr); +extern IRTEXT_CONST_PTR(kCirculateStr); +extern IRTEXT_CONST_PTR(kCkpStr); +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(kCommaSpaceStr); +extern IRTEXT_CONST_PTR(kCommandStr); +extern IRTEXT_CONST_PTR(kConfigCommandStr); +extern IRTEXT_CONST_PTR(kControlCommandStr); +extern IRTEXT_CONST_PTR(kCoolStr); +extern IRTEXT_CONST_PTR(kCoolingStr); +extern IRTEXT_CONST_PTR(kDashStr); +extern IRTEXT_CONST_PTR(kDayStr); +extern IRTEXT_CONST_PTR(kDaysStr); +extern IRTEXT_CONST_PTR(kDehumidifyStr); +extern IRTEXT_CONST_PTR(kDg11j104Str); +extern IRTEXT_CONST_PTR(kDg11j13aStr); +extern IRTEXT_CONST_PTR(kDg11j191Str); +extern IRTEXT_CONST_PTR(kDirectIndirectModeStr); +extern IRTEXT_CONST_PTR(kDirectStr); +extern IRTEXT_CONST_PTR(kDisplayTempStr); +extern IRTEXT_CONST_PTR(kDkeStr); +extern IRTEXT_CONST_PTR(kDownStr); +extern IRTEXT_CONST_PTR(kDryStr); +extern IRTEXT_CONST_PTR(kDryingStr); +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(kFanOnlyStr); +extern IRTEXT_CONST_PTR(kFanOnlyWithSpaceStr); +extern IRTEXT_CONST_PTR(kFanStr); +extern IRTEXT_CONST_PTR(kFan_OnlyStr); +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(kGe6711ar2853mStr); +extern IRTEXT_CONST_PTR(kGz055be1Str); +extern IRTEXT_CONST_PTR(kHealthStr); +extern IRTEXT_CONST_PTR(kHeatStr); +extern IRTEXT_CONST_PTR(kHeatingStr); +extern IRTEXT_CONST_PTR(kHiStr); +extern IRTEXT_CONST_PTR(kHighStr); +extern IRTEXT_CONST_PTR(kHighestStr); +extern IRTEXT_CONST_PTR(kHoldStr); +extern IRTEXT_CONST_PTR(kHourStr); +extern IRTEXT_CONST_PTR(kHoursStr); +extern IRTEXT_CONST_PTR(kHumidStr); +extern IRTEXT_CONST_PTR(kIFeelReportStr); +extern IRTEXT_CONST_PTR(kIFeelStr); +extern IRTEXT_CONST_PTR(kISeeStr); +extern IRTEXT_CONST_PTR(kIdStr); +extern IRTEXT_CONST_PTR(kIndirectStr); +extern IRTEXT_CONST_PTR(kInsideStr); +extern IRTEXT_CONST_PTR(kIonStr); +extern IRTEXT_CONST_PTR(kJkeStr); +extern IRTEXT_CONST_PTR(kKeyStr); +extern IRTEXT_CONST_PTR(kKkg29ac1Str); +extern IRTEXT_CONST_PTR(kKkg9ac1Str); +extern IRTEXT_CONST_PTR(kLastStr); +extern IRTEXT_CONST_PTR(kLeftMaxNoSpaceStr); +extern IRTEXT_CONST_PTR(kLeftMaxStr); +extern IRTEXT_CONST_PTR(kLeftStr); +extern IRTEXT_CONST_PTR(kLg6711a20083vStr); +extern IRTEXT_CONST_PTR(kLightStr); +extern IRTEXT_CONST_PTR(kLightToggleStr); +extern IRTEXT_CONST_PTR(kLkeStr); +extern IRTEXT_CONST_PTR(kLoStr); +extern IRTEXT_CONST_PTR(kLockStr); +extern IRTEXT_CONST_PTR(kLoudStr); +extern IRTEXT_CONST_PTR(kLowStr); +extern IRTEXT_CONST_PTR(kLowerStr); +extern IRTEXT_CONST_PTR(kLowestStr); +extern IRTEXT_CONST_PTR(kManualStr); +extern IRTEXT_CONST_PTR(kMaxLeftNoSpaceStr); +extern IRTEXT_CONST_PTR(kMaxLeftStr); +extern IRTEXT_CONST_PTR(kMaxRightNoSpaceStr); +extern IRTEXT_CONST_PTR(kMaxRightStr); +extern IRTEXT_CONST_PTR(kMaxStr); +extern IRTEXT_CONST_PTR(kMaximumStr); +extern IRTEXT_CONST_PTR(kMedHighStr); +extern IRTEXT_CONST_PTR(kMedStr); +extern IRTEXT_CONST_PTR(kMediumStr); +extern IRTEXT_CONST_PTR(kMidStr); +extern IRTEXT_CONST_PTR(kMiddleStr); +extern IRTEXT_CONST_PTR(kMinStr); +extern IRTEXT_CONST_PTR(kMinimumStr); +extern IRTEXT_CONST_PTR(kMinuteStr); +extern IRTEXT_CONST_PTR(kMinutesStr); +extern IRTEXT_CONST_PTR(kModeStr); +extern IRTEXT_CONST_PTR(kModelStr); +extern IRTEXT_CONST_PTR(kMouldStr); +extern IRTEXT_CONST_PTR(kMoveStr); +extern IRTEXT_CONST_PTR(kNAStr); +extern IRTEXT_CONST_PTR(kNightStr); +extern IRTEXT_CONST_PTR(kNkeStr); +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(kPanasonicCkpStr); +extern IRTEXT_CONST_PTR(kPanasonicDkeStr); +extern IRTEXT_CONST_PTR(kPanasonicJkeStr); +extern IRTEXT_CONST_PTR(kPanasonicLkeStr); +extern IRTEXT_CONST_PTR(kPanasonicNkeStr); +extern IRTEXT_CONST_PTR(kPanasonicPkrStr); +extern IRTEXT_CONST_PTR(kPanasonicRkrStr); +extern IRTEXT_CONST_PTR(kPkrStr); +extern IRTEXT_CONST_PTR(kPowerButtonStr); +extern IRTEXT_CONST_PTR(kPowerStr); +extern IRTEXT_CONST_PTR(kPowerToggleStr); +extern IRTEXT_CONST_PTR(kPowerfulStr); +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(kRightMaxNoSpaceStr); +extern IRTEXT_CONST_PTR(kRightMaxStr); +extern IRTEXT_CONST_PTR(kRightStr); +extern IRTEXT_CONST_PTR(kRkrStr); +extern IRTEXT_CONST_PTR(kRlt0541htaaStr); +extern IRTEXT_CONST_PTR(kRlt0541htabStr); +extern IRTEXT_CONST_PTR(kRoomStr); +extern IRTEXT_CONST_PTR(kSaveStr); +extern IRTEXT_CONST_PTR(kScheduleStr); +extern IRTEXT_CONST_PTR(kSecondStr); +extern IRTEXT_CONST_PTR(kSecondsStr); +extern IRTEXT_CONST_PTR(kSensorReportStr); +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(kTac09chsdStr); +extern IRTEXT_CONST_PTR(kTempDownStr); +extern IRTEXT_CONST_PTR(kTempStr); +extern IRTEXT_CONST_PTR(kTempUpStr); +extern IRTEXT_CONST_PTR(kThreeLetterDayOfWeekStr); +extern IRTEXT_CONST_PTR(kTimerActiveDaysStr); +extern IRTEXT_CONST_PTR(kTimerModeStr); +extern IRTEXT_CONST_PTR(kSetTimerCommandStr); +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(kUpStr); +extern IRTEXT_CONST_PTR(kUpperStr); +extern IRTEXT_CONST_PTR(kUpperMiddleStr); +extern IRTEXT_CONST_PTR(kValueStr); +extern IRTEXT_CONST_PTR(kV9014557AStr); +extern IRTEXT_CONST_PTR(kV9014557BStr); +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(kYaw1fStr); +extern IRTEXT_CONST_PTR(kYbofbStr); +extern IRTEXT_CONST_PTR(kYesStr); +extern IRTEXT_CONST_PTR(kYx1fsfStr); +extern IRTEXT_CONST_PTR(kZoneFollowStr); +extern IRTEXT_CONST_PTR(kAllProtocolNamesStr); + +#endif // IRTEXT_H_ diff --git a/src/libraries/IRremoteESP8266/src/IRtimer.cpp b/src/libraries/IRremoteESP8266/src/IRtimer.cpp new file mode 100644 index 000000000..c1dbac0cf --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRtimer.cpp @@ -0,0 +1,96 @@ +// Copyright 2017 David Conran + +#include "IRtimer.h" +#ifndef UNIT_TEST +#include "String.h" +#endif + + +#if PLATFORM_BEKEN +//TODO +static uint32_t micros(void) +{ + //TODO + return 0; +} + +static uint32_t millis(void) +{ + //TODO + return 0; +} + +#endif + + +#ifdef UNIT_TEST +// Used to help simulate elapsed time in unit tests. +uint32_t _IRtimer_unittest_now = 0; +uint32_t _TimerMs_unittest_now = 0; +#endif // UNIT_TEST + +/// Class constructor. +IRtimer::IRtimer() { reset(); } + +/// Resets the IRtimer object. I.e. The counter starts again from now. +void IRtimer::reset() { +#ifndef UNIT_TEST + start = micros(); +#else + start = _IRtimer_unittest_now; +#endif +} + +/// Calculate how many microseconds have elapsed since the timer was started. +/// @return Nr. of microseconds. +uint32_t IRtimer::elapsed() { +#ifndef UNIT_TEST + uint32_t now = micros(); +#else + uint32_t now = _IRtimer_unittest_now; +#endif + if (start <= now) // Check if the system timer has wrapped. + return now - start; // No wrap. + else + return UINT32_MAX - start + now; // Has wrapped. +} + +/// Add time to the timer to simulate elapsed time. +/// @param[in] usecs Nr. of uSeconds to be added. +/// @note Only used in unit testing. +#ifdef UNIT_TEST +void IRtimer::add(uint32_t usecs) { _IRtimer_unittest_now += usecs; } +#endif // UNIT_TEST + +/// Class constructor. +TimerMs::TimerMs() { reset(); } + +/// Resets the TimerMs object. I.e. The counter starts again from now. +void TimerMs::reset() { +#ifndef UNIT_TEST + start = millis(); +#else + start = _TimerMs_unittest_now; +#endif +} + +/// Calculate how many milliseconds have elapsed since the timer was started. +/// @return Nr. of milliseconds. +uint32_t TimerMs::elapsed() { +#ifndef UNIT_TEST + uint32_t now = millis(); +#else + uint32_t now = _TimerMs_unittest_now; +#endif + if (start <= now) // Check if the system timer has wrapped. + return now - start; // No wrap. + else + return UINT32_MAX - start + now; // Has wrapped. +} + +/// Add time to the timer to simulate elapsed time. +/// @param[in] msecs Nr. of mSeconds to be added. +/// @note Only used in unit testing. +#ifdef UNIT_TEST +void TimerMs::add(uint32_t msecs) { _IRtimer_unittest_now += msecs; } +#endif // UNIT_TEST diff --git a/src/libraries/IRremoteESP8266/src/IRtimer.h b/src/libraries/IRremoteESP8266/src/IRtimer.h new file mode 100644 index 000000000..659819d8f --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRtimer.h @@ -0,0 +1,40 @@ +// Copyright 2017 David Conran + +#ifndef IRTIMER_H_ +#define IRTIMER_H_ + +#define __STDC_LIMIT_MACROS +#include + +// Classes + +/// This class offers a simple counter in micro-seconds since instantiated. +/// @note Handles when the system timer wraps around (once). +class IRtimer { + public: + IRtimer(); + void reset(); + uint32_t elapsed(); +#ifdef UNIT_TEST + static void add(uint32_t usecs); +#endif // UNIT_TEST + + private: + uint32_t start; ///< Time in uSeconds when the class was instantiated/reset. +}; + +/// This class offers a simple counter in milli-seconds since instantiated. +/// @note Handles when the system timer wraps around (once). +class TimerMs { + public: + TimerMs(); + void reset(); + uint32_t elapsed(); +#ifdef UNIT_TEST + static void add(uint32_t msecs); +#endif // UNIT_TEST + + private: + uint32_t start; ///< Time in mSeconds when the class was instantiated/reset. +}; +#endif // IRTIMER_H_ diff --git a/src/libraries/IRremoteESP8266/src/IRutils.cpp b/src/libraries/IRremoteESP8266/src/IRutils.cpp new file mode 100644 index 000000000..709c2554c --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRutils.cpp @@ -0,0 +1,1441 @@ +// Copyright 2017-2021 David Conran + +#include "IRutils.h" +#ifndef UNIT_TEST +#include "String.h" +#endif + +#define __STDC_LIMIT_MACROS +#include +#include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRtext.h" +#include "minmax.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 = ::min(nbits, (uint16_t)(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); + 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; + } + } + } + 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 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: + 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; + 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 (((uint16_t)(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 = (uint32_t)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 = (uint64_t)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 diff --git a/src/libraries/IRremoteESP8266/src/IRutils.h b/src/libraries/IRremoteESP8266/src/IRutils.h new file mode 100644 index 000000000..1d5785cd2 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/IRutils.h @@ -0,0 +1,156 @@ +#ifndef IRUTILS_H_ +#define IRUTILS_H_ + +// Copyright 2017 David Conran + +#ifndef UNIT_TEST +// TODO: +#include "String.h" +#endif +#define __STDC_LIMIT_MACROS +#include +#ifndef ARDUINO +//#include +#endif +#include "IRremoteESP8266.h" +#include "IRrecv.h" + +const uint8_t kNibbleSize = 4; +const uint8_t kLowNibble = 0; +const uint8_t kHighNibble = 4; +const uint8_t kModeBitsSize = 3; +uint64_t reverseBits(uint64_t input, uint16_t nbits); +String uint64ToString(uint64_t input, uint8_t base = 10); +String int64ToString(int64_t input, uint8_t base = 10); +String typeToString(const decode_type_t protocol, + const bool isRepeat = false); +void serialPrintUint64(uint64_t input, uint8_t base = 10); +String resultToSourceCode(const decode_results * const results); +String resultToTimingInfo(const decode_results * const results); +String resultToHumanReadableBasic(const decode_results * const results); +String resultToHexidecimal(const decode_results * const result); +bool hasACState(const decode_type_t protocol); +uint16_t getCorrectedRawLength(const decode_results * const results); +uint16_t *resultToRawArray(const decode_results * const decode); +uint8_t sumBytes(const uint8_t * const start, const uint16_t length, + const uint8_t init = 0); +uint8_t xorBytes(const uint8_t * const start, const uint16_t length, + const uint8_t init = 0); +uint16_t countBits(const uint8_t * const start, const uint16_t length, + const bool ones = true, const uint16_t init = 0); +uint16_t countBits(const uint64_t data, const uint8_t length, + const bool ones = true, const uint16_t init = 0); +uint64_t invertBits(const uint64_t data, const uint16_t nbits); +decode_type_t strToDecodeType(const char *str); +float celsiusToFahrenheit(const float deg); +float fahrenheitToCelsius(const float deg); +/// Namespace for covering common functions & procedures for advancd protocol +/// handlers +namespace irutils { + String addBoolToString(const bool value, const String label, + const bool precomma = true); + String addToggleToString(const bool toggle, const String label, + const bool precomma = true); + String addIntToString(const uint16_t value, const String label, + const bool precomma = true); + String addSignedIntToString(const int16_t value, const String label, + const bool precomma = true); + String modelToStr(const decode_type_t protocol, const int16_t model); + String addModelToString(const decode_type_t protocol, const int16_t model, + const bool precomma = true); + String addLabeledString(const String value, const String label, + const bool precomma = true); + String addTempToString(const uint16_t degrees, const bool celsius = true, + const bool precomma = true, + const bool isSensorTemp = false); + String addTempFloatToString(const float degrees, const bool celsius = true, + const bool precomma = true, + const bool isSensorTemp = false); + 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 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 = 0xFF, + const uint8_t medium_high = 0xFF); + 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 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 addDayToString(const uint8_t day_of_week, const int8_t offset = 0, + const bool precomma = true); + String addTimerModeToString(const uint8_t timerType, const uint8_t noTimer, + const uint8_t delayTimer, + const uint8_t schedule1 = 0xFF, + const uint8_t schedule2 = 0xFF, + const uint8_t schedule3 = 0xFF, + const bool precomma = true); + String irCommandTypeToString(uint8_t commandType, uint8_t acControlCmd, + uint8_t iFeelReportCmd = 0xFF, + uint8_t timerCmd = 0xFF, + uint8_t configCmd = 0xFF); + String dayToString(const uint8_t day_of_week, const int8_t offset = 0); + String daysBitmaskToString(uint8_t daysBitmap, uint8_t offset = 0); + String channelToString(const uint8_t channel); + String htmlEscape(const String unescaped); + String msToString(uint32_t const msecs); + String minsToString(const uint16_t mins); + uint8_t sumNibbles(const uint8_t * const start, const uint16_t length, + const uint8_t init = 0); + uint8_t sumNibbles(const uint64_t data, const uint8_t count = 16, + const uint8_t init = 0, const bool nibbleonly = true); + uint16_t sumBytes(const uint64_t data, const uint8_t count = 8, + const uint8_t init = 0, const bool byteonly = true); + uint8_t bcdToUint8(const uint8_t bcd); + uint8_t uint8ToBcd(const uint8_t integer); + bool getBit(const uint64_t data, const uint8_t position, + const uint8_t size = 64); + bool getBit(const uint8_t data, const uint8_t position); +#define GETBIT8(a, b) ((a) & ((uint8_t)1 << (b))) +#define GETBIT16(a, b) ((a) & ((uint16_t)1 << (b))) +#define GETBIT32(a, b) ((a) & ((uint32_t)1 << (b))) +#define GETBIT64(a, b) ((a) & ((uint64_t)1 << (b))) +#define GETBITS8(data, offset, size) \ + (((data) & (((uint8_t)UINT8_MAX >> (8 - (size))) << (offset))) >> (offset)) +#define GETBITS16(data, offset, size) \ + (((data) & (((uint16_t)UINT16_MAX >> (16 - (size))) << (offset))) >> \ + (offset)) +#define GETBITS32(data, offset, size) \ + (((data) & (((uint32_t)UINT32_MAX >> (32 - (size))) << (offset))) >> \ + (offset)) +#define GETBITS64(data, offset, size) \ + (((data) & (((uint64_t)UINT64_MAX >> (64 - (size))) << (offset))) >> \ + (offset)) + uint64_t setBit(const uint64_t data, const uint8_t position, + const bool on = true, const uint8_t size = 64); + uint8_t setBit(const uint8_t data, const uint8_t position, + const bool on = true); + void setBit(uint8_t * const data, const uint8_t position, + const bool on = true); + void setBit(uint32_t * const data, const uint8_t position, + const bool on = true); + void setBit(uint64_t * const data, const uint8_t position, + const bool on = true); + void setBits(uint8_t * const dst, const uint8_t offset, const uint8_t nbits, + const uint8_t data); + void setBits(uint32_t * const dst, const uint8_t offset, const uint8_t nbits, + const uint32_t data); + void setBits(uint64_t * const dst, const uint8_t offset, const uint8_t nbits, + const uint64_t data); + uint8_t * invertBytePairs(uint8_t *ptr, const uint16_t length); + bool checkInvertedBytePairs(const uint8_t * const ptr, const uint16_t length); + uint8_t lowLevelSanityCheck(void); +} // namespace irutils +#endif // IRUTILS_H_ diff --git a/src/libraries/IRremoteESP8266/src/String.cpp b/src/libraries/IRremoteESP8266/src/String.cpp new file mode 100644 index 000000000..d35eced19 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/String.cpp @@ -0,0 +1,766 @@ +/* + String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All rights reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "String.h" +// #include "Common.h" +#include "itoa.h" +// #include "deprecated-avr-comp/avr/dtostrf.h" + +#include +#include +#include "minmax.h" + + +namespace arduino { + +static char *dtostrf (double val, signed char width, unsigned char prec, char *sout) { + char fmt[20]; + sprintf(fmt, "%%%d.%df", width, prec); + sprintf(sout, fmt, val); + return sout; +} + +/*********************************************/ +/* Static Member Initialisation */ +/*********************************************/ + +size_t const String::FLT_MAX_DECIMAL_PLACES; +size_t const String::DBL_MAX_DECIMAL_PLACES; + +/*********************************************/ +/* Constructors */ +/*********************************************/ + +String::String(const char *cstr) +{ + init(); + if (cstr) copy(cstr, strlen(cstr)); +} + +String::String(const char *cstr, unsigned int length) +{ + init(); + if (cstr) copy(cstr, length); +} + +String::String(const String &value) +{ + init(); + *this = value; +} + +// String::String(const __FlashStringHelper *pstr) +// { +// init(); +// *this = pstr; +// } + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String::String(String &&rval) + : buffer(rval.buffer) + , capacity(rval.capacity) + , len(rval.len) +{ + rval.buffer = NULL; + rval.capacity = 0; + rval.len = 0; +} +#endif + +String::String(char c) +{ + init(); + char buf[2]; + buf[0] = c; + buf[1] = 0; + *this = buf; +} + +String::String(unsigned char value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned char)]; + utoa(value, buf, base); + *this = buf; +} + +String::String(int value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(int)]; + itoa(value, buf, base); + *this = buf; +} + +String::String(unsigned int value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned int)]; + utoa(value, buf, base); + *this = buf; +} + +String::String(long value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(long)]; + ltoa(value, buf, base); + *this = buf; +} + +String::String(unsigned long value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned long)]; + ultoa(value, buf, base); + *this = buf; +} + +String::String(float value, unsigned char decimalPlaces) +{ + static size_t const FLOAT_BUF_SIZE = FLT_MAX_10_EXP + FLT_MAX_DECIMAL_PLACES + 1 /* '-' */ + 1 /* '.' */ + 1 /* '\0' */; + init(); + char buf[FLOAT_BUF_SIZE]; + decimalPlaces = ::min(decimalPlaces, FLT_MAX_DECIMAL_PLACES); + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} + +String::String(double value, unsigned char decimalPlaces) +{ + static size_t const DOUBLE_BUF_SIZE = DBL_MAX_10_EXP + DBL_MAX_DECIMAL_PLACES + 1 /* '-' */ + 1 /* '.' */ + 1 /* '\0' */; + init(); + char buf[DOUBLE_BUF_SIZE]; + decimalPlaces = ::min(decimalPlaces, DBL_MAX_DECIMAL_PLACES); + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} + +String::~String() +{ + if (buffer) free(buffer); +} + +/*********************************************/ +/* Memory Management */ +/*********************************************/ + +inline void String::init(void) +{ + buffer = NULL; + capacity = 0; + len = 0; +} + +void String::invalidate(void) +{ + if (buffer) free(buffer); + buffer = NULL; + capacity = len = 0; +} + +bool String::reserve(unsigned int size) +{ + if (buffer && capacity >= size) return 1; + if (changeBuffer(size)) { + if (len == 0) buffer[0] = 0; + return true; + } + return false; +} + +bool String::changeBuffer(unsigned int maxStrLen) +{ + char *newbuffer = (char *)realloc(buffer, maxStrLen + 1); + if (newbuffer) { + buffer = newbuffer; + capacity = maxStrLen; + return true; + } + return false; +} + +/*********************************************/ +/* Copy and Move */ +/*********************************************/ + +String & String::copy(const char *cstr, unsigned int length) +{ + if (!reserve(length)) { + invalidate(); + return *this; + } + len = length; + memcpy(buffer, cstr, length); + buffer[len] = '\0'; + return *this; +} + +// String & String::copy(const __FlashStringHelper *pstr, unsigned int length) +// { +// if (!reserve(length)) { +// invalidate(); +// return *this; +// } +// len = length; +// strcpy_P(buffer, (PGM_P)pstr); +// return *this; +// } + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +void String::move(String &rhs) +{ + if (this != &rhs) + { + free(buffer); + + buffer = rhs.buffer; + len = rhs.len; + capacity = rhs.capacity; + + rhs.buffer = NULL; + rhs.len = 0; + rhs.capacity = 0; + } +} +#endif + +String & String::operator = (const String &rhs) +{ + if (this == &rhs) return *this; + + if (rhs.buffer) copy(rhs.buffer, rhs.len); + else invalidate(); + + return *this; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String & String::operator = (String &&rval) +{ + move(rval); + return *this; +} +#endif + +String & String::operator = (const char *cstr) +{ + if (cstr) copy(cstr, strlen(cstr)); + else invalidate(); + + return *this; +} + +// String & String::operator = (const __FlashStringHelper *pstr) +// { +// if (pstr) copy(pstr, strlen_P((PGM_P)pstr)); +// else invalidate(); + +// return *this; +// } + +/*********************************************/ +/* concat */ +/*********************************************/ + +bool String::concat(const String &s) +{ + return concat(s.buffer, s.len); +} + +bool String::concat(const char *cstr, unsigned int length) +{ + unsigned int newlen = len + length; + if (!cstr) return false; + if (length == 0) return true; + if (!reserve(newlen)) return false; + memcpy(buffer + len, cstr, length); + len = newlen; + buffer[len] = '\0'; + return true; +} + +bool String::concat(const char *cstr) +{ + if (!cstr) return false; + return concat(cstr, strlen(cstr)); +} + +bool String::concat(char c) +{ + return concat(&c, 1); +} + +bool String::concat(unsigned char num) +{ + char buf[1 + 3 * sizeof(unsigned char)]; + itoa(num, buf, 10); + return concat(buf); +} + +bool String::concat(int num) +{ + char buf[2 + 3 * sizeof(int)]; + itoa(num, buf, 10); + return concat(buf); +} + +bool String::concat(unsigned int num) +{ + char buf[1 + 3 * sizeof(unsigned int)]; + utoa(num, buf, 10); + return concat(buf); +} + +bool String::concat(long num) +{ + char buf[2 + 3 * sizeof(long)]; + ltoa(num, buf, 10); + return concat(buf); +} + +bool String::concat(unsigned long num) +{ + char buf[1 + 3 * sizeof(unsigned long)]; + ultoa(num, buf, 10); + return concat(buf); +} + +bool String::concat(float num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string); +} + +bool String::concat(double num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string); +} + +// bool String::concat(const __FlashStringHelper * str) +// { +// if (!str) return false; +// int length = strlen_P((const char *) str); +// if (length == 0) return true; +// unsigned int newlen = len + length; +// if (!reserve(newlen)) return false; +// strcpy_P(buffer + len, (const char *) str); +// len = newlen; +// return true; +// } + +/*********************************************/ +/* Concatenate */ +/*********************************************/ + +StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(rhs.buffer, rhs.len)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr) +{ + StringSumHelper &a = const_cast(lhs); + if (!cstr || !a.concat(cstr)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, char c) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(c)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, int num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, long num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, float num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, double num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +// StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) +// { +// StringSumHelper &a = const_cast(lhs); +// if (!a.concat(rhs)) a.invalidate(); +// return a; +// } + +/*********************************************/ +/* Comparison */ +/*********************************************/ + +int String::compareTo(const String &s) const +{ + if (!buffer || !s.buffer) { + if (s.buffer && s.len > 0) return 0 - *(unsigned char *)s.buffer; + if (buffer && len > 0) return *(unsigned char *)buffer; + return 0; + } + return strcmp(buffer, s.buffer); +} + +int String::compareTo(const char *cstr) const +{ + if (!buffer || !cstr) { + if (cstr && *cstr) return 0 - *(unsigned char *)cstr; + if (buffer && len > 0) return *(unsigned char *)buffer; + return 0; + } + return strcmp(buffer, cstr); +} + +bool String::equals(const String &s2) const +{ + return (len == s2.len && compareTo(s2) == 0); +} + +bool String::equals(const char *cstr) const +{ + if (len == 0) return (cstr == NULL || *cstr == 0); + if (cstr == NULL) return buffer[0] == 0; + return strcmp(buffer, cstr) == 0; +} + +bool String::equalsIgnoreCase( const String &s2 ) const +{ + if (this == &s2) return true; + if (len != s2.len) return false; + if (len == 0) return true; + const char *p1 = buffer; + const char *p2 = s2.buffer; + while (*p1) { + if (tolower(*p1++) != tolower(*p2++)) return false; + } + return true; +} + +bool String::startsWith( const String &s2 ) const +{ + if (len < s2.len) return false; + return startsWith(s2, 0); +} + +bool String::startsWith( const String &s2, unsigned int offset ) const +{ + if (offset > len - s2.len || !buffer || !s2.buffer) return false; + return strncmp( &buffer[offset], s2.buffer, s2.len ) == 0; +} + +bool String::endsWith( const String &s2 ) const +{ + if ( len < s2.len || !buffer || !s2.buffer) return false; + return strcmp(&buffer[len - s2.len], s2.buffer) == 0; +} + +/*********************************************/ +/* Character Access */ +/*********************************************/ + +char String::charAt(unsigned int loc) const +{ + return operator[](loc); +} + +void String::setCharAt(unsigned int loc, char c) +{ + if (loc < len) buffer[loc] = c; +} + +char & String::operator[](unsigned int index) +{ + static char dummy_writable_char; + if (index >= len || !buffer) { + dummy_writable_char = 0; + return dummy_writable_char; + } + return buffer[index]; +} + +char String::operator[]( unsigned int index ) const +{ + if (index >= len || !buffer) return 0; + return buffer[index]; +} + +void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const +{ + if (!bufsize || !buf) return; + if (index >= len) { + buf[0] = 0; + return; + } + unsigned int n = bufsize - 1; + if (n > len - index) n = len - index; + strncpy((char *)buf, buffer + index, n); + buf[n] = 0; +} + +/*********************************************/ +/* Search */ +/*********************************************/ + +int String::indexOf(char c) const +{ + return indexOf(c, 0); +} + +int String::indexOf( char ch, unsigned int fromIndex ) const +{ + if (fromIndex >= len) return -1; + const char* temp = strchr(buffer + fromIndex, ch); + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::indexOf(const String &s2) const +{ + return indexOf(s2, 0); +} + +int String::indexOf(const String &s2, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + const char *found = strstr(buffer + fromIndex, s2.buffer); + if (found == NULL) return -1; + return found - buffer; +} + +int String::lastIndexOf( char theChar ) const +{ + return lastIndexOf(theChar, len - 1); +} + +int String::lastIndexOf(char ch, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + char tempchar = buffer[fromIndex + 1]; + buffer[fromIndex + 1] = '\0'; + char* temp = strrchr( buffer, ch ); + buffer[fromIndex + 1] = tempchar; + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::lastIndexOf(const String &s2) const +{ + return lastIndexOf(s2, len - s2.len); +} + +int String::lastIndexOf(const String &s2, unsigned int fromIndex) const +{ + if (s2.len == 0 || len == 0 || s2.len > len) return -1; + if (fromIndex >= len) fromIndex = len - 1; + int found = -1; + for (char *p = buffer; p <= buffer + fromIndex; p++) { + p = strstr(p, s2.buffer); + if (!p) break; + if ((unsigned int)(p - buffer) <= fromIndex) found = p - buffer; + } + return found; +} + +String String::substring(unsigned int left, unsigned int right) const +{ + if (left > right) { + unsigned int temp = right; + right = left; + left = temp; + } + String out; + if (left >= len) return out; + if (right > len) right = len; + out.copy(buffer + left, right - left); + return out; +} + +/*********************************************/ +/* Modification */ +/*********************************************/ + +void String::replace(char find, char replace) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + if (*p == find) *p = replace; + } +} + +void String::replace(const String& find, const String& replace) +{ + if (len == 0 || find.len == 0) return; + int diff = replace.len - find.len; + char *readFrom = buffer; + char *foundAt; + if (diff == 0) { + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + memcpy(foundAt, replace.buffer, replace.len); + readFrom = foundAt + replace.len; + } + } else if (diff < 0) { + unsigned int size = len; // compute size needed for result + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + readFrom = foundAt + find.len; + diff = 0 - diff; + size -= diff; + } + if (size == len) return; + int index = len - 1; + while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) { + readFrom = buffer + index + find.len; + memmove(readFrom - diff, readFrom, len - (readFrom - buffer)); + len -= diff; + buffer[len] = 0; + memcpy(buffer + index, replace.buffer, replace.len); + index--; + } + } else { + unsigned int size = len; // compute size needed for result + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + readFrom = foundAt + find.len; + size += diff; + } + if (size == len) return; + if (size > capacity && !changeBuffer(size)) return; // XXX: tell user! + int index = len - 1; + while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) { + readFrom = buffer + index + find.len; + memmove(readFrom + diff, readFrom, len - (readFrom - buffer)); + len += diff; + buffer[len] = 0; + memcpy(buffer + index, replace.buffer, replace.len); + index--; + } + } +} + +void String::remove(unsigned int index){ + // Pass the biggest integer as the count. The remove method + // below will take care of truncating it at the end of the + // string. + remove(index, (unsigned int)-1); +} + +void String::remove(unsigned int index, unsigned int count){ + if (index >= len) { return; } + if (count <= 0) { return; } + if (count > len - index) { count = len - index; } + char *writeTo = buffer + index; + len = len - count; + memmove(writeTo, buffer + index + count,len - index); + buffer[len] = 0; +} + +void String::toLowerCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + *p = tolower(*p); + } +} + +void String::toUpperCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + *p = toupper(*p); + } +} + +void String::trim(void) +{ + if (!buffer || len == 0) return; + char *begin = buffer; + while (isspace(*begin)) begin++; + char *end = buffer + len - 1; + while (isspace(*end) && end >= begin) end--; + len = end + 1 - begin; + if (begin > buffer) memmove(buffer, begin, len); + buffer[len] = 0; +} + +/*********************************************/ +/* Parsing / Conversion */ +/*********************************************/ + +long String::toInt(void) const +{ + if (buffer) return atol(buffer); + return 0; +} + +float String::toFloat(void) const +{ + return float(toDouble()); +} + +double String::toDouble(void) const +{ + if (buffer) return atof(buffer); + return 0; +} + +} // namespace arduino diff --git a/src/libraries/IRremoteESP8266/src/String.h b/src/libraries/IRremoteESP8266/src/String.h new file mode 100644 index 000000000..de53c0636 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/String.h @@ -0,0 +1,251 @@ +/* + String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All right reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#pragma once +#ifndef __ARDUINO_STRINGS__ +#define __ARDUINO_STRINGS__ + +#include +#include +#include +#include + +namespace arduino { + +// When compiling programs with this class, the following gcc parameters +// dramatically increase performance and memory (RAM) efficiency, typically +// with little or no increase in code size. +// -felide-constructors +// -std=c++0x + +#define F(string_literal) (string_literal) + +// An inherited class for holding the result of a concatenation. These +// result objects are assumed to be writable by subsequent concatenations. +class StringSumHelper; + +// The string class +class String +{ + friend class StringSumHelper; + // use a function pointer to allow for "if (s)" without the + // complications of an operator bool(). for more information, see: + // http://www.artima.com/cppsource/safebool.html + typedef void (String::*StringIfHelperType)() const; + void StringIfHelper() const {} + + static size_t const FLT_MAX_DECIMAL_PLACES = 10; + static size_t const DBL_MAX_DECIMAL_PLACES = FLT_MAX_DECIMAL_PLACES; + +public: + // constructors + // creates a copy of the initial value. + // if the initial value is null or invalid, or if memory allocation + // fails, the string will be marked as invalid (i.e. "if (s)" will + // be false). + String(const char *cstr = ""); + String(const char *cstr, unsigned int length); + String(const uint8_t *cstr, unsigned int length) : String((const char*)cstr, length) {} + String(const String &str); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String(String &&rval); + #endif + explicit String(char c); + explicit String(unsigned char, unsigned char base=10); + explicit String(int, unsigned char base=10); + explicit String(unsigned int, unsigned char base=10); + explicit String(long, unsigned char base=10); + explicit String(unsigned long, unsigned char base=10); + explicit String(float, unsigned char decimalPlaces=2); + explicit String(double, unsigned char decimalPlaces=2); + ~String(void); + + // memory management + // return true on success, false on failure (in which case, the string + // is left unchanged). reserve(0), if successful, will validate an + // invalid string (i.e., "if (s)" will be true afterwards) + bool reserve(unsigned int size); + inline unsigned int length(void) const {return len;} + + // creates a copy of the assigned value. if the value is null or + // invalid, or if the memory allocation fails, the string will be + // marked as invalid ("if (s)" will be false). + String & operator = (const String &rhs); + String & operator = (const char *cstr); + //String & operator = (const __FlashStringHelper *str); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String & operator = (String &&rval); + #endif + + // concatenate (works w/ built-in types) + + // returns true on success, false on failure (in which case, the string + // is left unchanged). if the argument is null or invalid, the + // concatenation is considered unsuccessful. + bool concat(const String &str); + bool concat(const char *cstr); + bool concat(const char *cstr, unsigned int length); + bool concat(const uint8_t *cstr, unsigned int length) {return concat((const char*)cstr, length);} + bool concat(char c); + bool concat(unsigned char num); + bool concat(int num); + bool concat(unsigned int num); + bool concat(long num); + bool concat(unsigned long num); + bool concat(float num); + bool concat(double num); + //bool concat(const __FlashStringHelper * str); + + // if there's not enough memory for the concatenated value, the string + // will be left unchanged (but this isn't signalled in any way) + String & operator += (const String &rhs) {concat(rhs); return (*this);} + String & operator += (const char *cstr) {concat(cstr); return (*this);} + String & operator += (char c) {concat(c); return (*this);} + String & operator += (unsigned char num) {concat(num); return (*this);} + String & operator += (int num) {concat(num); return (*this);} + String & operator += (unsigned int num) {concat(num); return (*this);} + String & operator += (long num) {concat(num); return (*this);} + String & operator += (unsigned long num) {concat(num); return (*this);} + String & operator += (float num) {concat(num); return (*this);} + String & operator += (double num) {concat(num); return (*this);} + //String & operator += (const __FlashStringHelper *str){concat(str); return (*this);} + + friend StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr); + friend StringSumHelper & operator + (const StringSumHelper &lhs, char c); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, float num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, double num); + //friend StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs); + + // comparison (only works w/ Strings and "strings") + operator StringIfHelperType() const { return buffer ? &String::StringIfHelper : 0; } + int compareTo(const String &s) const; + int compareTo(const char *cstr) const; + bool equals(const String &s) const; + bool equals(const char *cstr) const; + + friend bool operator == (const String &a, const String &b) { return a.equals(b); } + friend bool operator == (const String &a, const char *b) { return a.equals(b); } + friend bool operator == (const char *a, const String &b) { return b == a; } + friend bool operator < (const String &a, const String &b) { return a.compareTo(b) < 0; } + friend bool operator < (const String &a, const char *b) { return a.compareTo(b) < 0; } + friend bool operator < (const char *a, const String &b) { return b.compareTo(a) > 0; } + + friend bool operator != (const String &a, const String &b) { return !(a == b); } + friend bool operator != (const String &a, const char *b) { return !(a == b); } + friend bool operator != (const char *a, const String &b) { return !(a == b); } + friend bool operator > (const String &a, const String &b) { return b < a; } + friend bool operator > (const String &a, const char *b) { return b < a; } + friend bool operator > (const char *a, const String &b) { return b < a; } + friend bool operator <= (const String &a, const String &b) { return !(b < a); } + friend bool operator <= (const String &a, const char *b) { return !(b < a); } + friend bool operator <= (const char *a, const String &b) { return !(b < a); } + friend bool operator >= (const String &a, const String &b) { return !(a < b); } + friend bool operator >= (const String &a, const char *b) { return !(a < b); } + friend bool operator >= (const char *a, const String &b) { return !(a < b); } + + bool equalsIgnoreCase(const String &s) const; + bool startsWith( const String &prefix) const; + bool startsWith(const String &prefix, unsigned int offset) const; + bool endsWith(const String &suffix) const; + + // character access + char charAt(unsigned int index) const; + void setCharAt(unsigned int index, char c); + char operator [] (unsigned int index) const; + char& operator [] (unsigned int index); + void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index=0) const; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index=0) const + { getBytes((unsigned char *)buf, bufsize, index); } + const char* c_str() const { return buffer; } + char* begin() { return buffer; } + char* end() { return buffer + length(); } + const char* begin() const { return c_str(); } + const char* end() const { return c_str() + length(); } + + // search + int indexOf( char ch ) const; + int indexOf( char ch, unsigned int fromIndex ) const; + int indexOf( const String &str ) const; + int indexOf( const String &str, unsigned int fromIndex ) const; + int lastIndexOf( char ch ) const; + int lastIndexOf( char ch, unsigned int fromIndex ) const; + int lastIndexOf( const String &str ) const; + int lastIndexOf( const String &str, unsigned int fromIndex ) const; + String substring( unsigned int beginIndex ) const { return substring(beginIndex, len); }; + String substring( unsigned int beginIndex, unsigned int endIndex ) const; + + // modification + void replace(char find, char replace); + void replace(const String& find, const String& replace); + void remove(unsigned int index); + void remove(unsigned int index, unsigned int count); + void toLowerCase(void); + void toUpperCase(void); + void trim(void); + + // parsing/conversion + long toInt(void) const; + float toFloat(void) const; + double toDouble(void) const; + +protected: + char *buffer; // the actual char array + unsigned int capacity; // the array length minus one (for the '\0') + unsigned int len; // the String length (not counting the '\0') +protected: + void init(void); + void invalidate(void); + bool changeBuffer(unsigned int maxStrLen); + + // copy and move + String & copy(const char *cstr, unsigned int length); + //String & copy(const __FlashStringHelper *pstr, unsigned int length); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + void move(String &rhs); + #endif +}; + +class StringSumHelper : public String +{ +public: + StringSumHelper(const String &s) : String(s) {} + StringSumHelper(const char *p) : String(p) {} + StringSumHelper(char c) : String(c) {} + StringSumHelper(unsigned char num) : String(num) {} + StringSumHelper(int num) : String(num) {} + StringSumHelper(unsigned int num) : String(num) {} + StringSumHelper(long num) : String(num) {} + StringSumHelper(unsigned long num) : String(num) {} + StringSumHelper(float num) : String(num) {} + StringSumHelper(double num) : String(num) {} +}; + +} // namespace arduino + +//using arduino::__FlashStringHelper; +using arduino::String; + +#endif // __ARDUINO_STRINGS__ diff --git a/src/libraries/IRremoteESP8266/src/i18n.h b/src/libraries/IRremoteESP8266/src/i18n.h new file mode 100644 index 000000000..27ac4d714 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/i18n.h @@ -0,0 +1,25 @@ +// Copyright 2019 - David Conran (@crankyoldgit) + +#ifndef I18N_H_ +#define I18N_H_ + +#include "IRremoteESP8266.h" + +// Load the appropriate locale header file. +#ifndef _IR_LOCALE_ +#define _IR_LOCALE_ en-AU +#endif // _IR_LOCALE_ + +#define ENQUOTE_(x) #x +#define ENQUOTE(x) ENQUOTE_(x) + +// Load the desired/requested locale. +#ifdef _IR_LOCALE_ +#include ENQUOTE(locale/_IR_LOCALE_.h) +#endif // _IR_LOCALE_ + +// Now that any specific locale has been loaded, we can safely load the defaults +// as the defaults should not override anything that has now set. +#include "locale/defaults.h" + +#endif // I18N_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Airton.cpp b/src/libraries/IRremoteESP8266/src/ir_Airton.cpp new file mode 100644 index 000000000..2bd9c392f --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Airton.cpp @@ -0,0 +1,363 @@ +// Copyright 2021 David Conran (crankyoldgit) +/// @file +/// @brief Support for Airton protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1670 + +#include "ir_Airton.h" +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +const uint16_t kAirtonHdrMark = 6630; +const uint16_t kAirtonBitMark = 400; +const uint16_t kAirtonHdrSpace = 3350; +const uint16_t kAirtonOneSpace = 1260; +const uint16_t kAirtonZeroSpace = 430; +const uint16_t kAirtonFreq = 38000; // Hz. (Just a guess) + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::sumBytes; + +#if SEND_AIRTON +// Function should be safe up to 64 bits. +/// Send a Airton formatted message. +/// Status: STABLE / Confirmed working. +/// @param[in] data containing the IR command. +/// @param[in] nbits Nr. of bits to send. usually kAirtonBits +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendAirton(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kAirtonHdrMark, kAirtonHdrSpace, + kAirtonBitMark, kAirtonOneSpace, + kAirtonBitMark, kAirtonZeroSpace, + kAirtonBitMark, kDefaultMessageGap, + data, nbits, kAirtonFreq, false, repeat, kDutyDefault); +} +#endif // SEND_AIRTON + +#if DECODE_AIRTON +/// Decode the supplied Airton message. +/// Status: STABLE / Confirmed working. LSBF ordering confirmed via temperature. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeAirton(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - offset) + return false; // Too short a message to match. + if (strict && nbits != kAirtonBits) + return false; + + // Header + Data + Footer + if (!matchGeneric(&(results->rawbuf[offset]), &(results->value), + results->rawlen - offset, nbits, + kAirtonHdrMark, kAirtonHdrSpace, + kAirtonBitMark, kAirtonOneSpace, + kAirtonBitMark, kAirtonZeroSpace, + kAirtonBitMark, kDefaultMessageGap, + true, kUseDefTol, kMarkExcess, false)) return false; + // Compliance + if (strict && !IRAirtonAc::validChecksum(results->value)) return false; + // Success + results->decode_type = decode_type_t::AIRTON; + results->bits = nbits; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_AIRTON + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRAirtonAc::IRAirtonAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRAirtonAc::begin(void) { _irsend.begin(); } + +#if SEND_AIRTON +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRAirtonAc::send(const uint16_t repeat) { + _irsend.sendAirton(getRaw(), kAirtonBits, repeat); +} +#endif // SEND_AIRTON + +/// Calculate the checksum for the supplied state. +/// @param[in] state The source state to generate the checksum from. +/// @return The checksum value. +uint8_t IRAirtonAc::calcChecksum(const uint64_t state) { + return (uint8_t)(0x7F - sumBytes(state, 6)) ^ 0x2C; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The value to verify the checksum of. +/// @return A boolean indicating if it's checksum is valid. +bool IRAirtonAc::validChecksum(const uint64_t state) { + AirtonProtocol p; + p.raw = state; + return p.Sum == IRAirtonAc::calcChecksum(state); +} + +/// Update the checksum value for the internal state. +void IRAirtonAc::checksum(void) { _.Sum = IRAirtonAc::calcChecksum(_.raw); } + +/// Reset the internals of the object to a known good state. +void IRAirtonAc::stateReset(void) { setRaw(0x11D3); } + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A copy to the internal state. +uint64_t IRAirtonAc::getRaw(void) { + checksum(); // Ensure correct bit array before returning + return _.raw; +} + +/// Set the raw state of the object. +/// @param[in] state The raw state from the native IR message. +void IRAirtonAc::setRaw(const uint64_t state) { _.raw = state; } + + +/// Set the internal state to have the power on. +void IRAirtonAc::on(void) { setPower(true); } + +/// Set the internal state to have the power off. +void IRAirtonAc::off(void) { setPower(false); } + +/// Set the internal state to have the desired power. +/// @param[in] on The desired power state. +void IRAirtonAc::setPower(const bool on) { + _.Power = on; + setMode(getMode()); // Re-do the mode incase we need to do something special. +} + +/// Get the power setting from the internal state. +/// @return A boolean indicating the power setting. +bool IRAirtonAc::getPower(void) const { return _.Power; } + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRAirtonAc::getMode(void) const { return _.Mode; } + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRAirtonAc::setMode(const uint8_t mode) { + // Changing the mode always removes the sleep setting. + if (mode != _.Mode) setSleep(false); + // Set the actual mode. + _.Mode = (mode > kAirtonHeat) ? kAirtonAuto : mode; + // Handle special settings for each mode. + switch (_.Mode) { + case kAirtonAuto: + setTemp(25); // Auto has a fixed temp. + _.NotAutoOn = !getPower(); + break; + case kAirtonHeat: + // When powered on and in Heat mode, set a special bit. + _.HeatOn = getPower(); + // FALL-THRU + default: + _.NotAutoOn = true; + } + // Reset the economy setting if we need to. + setEcono(getEcono()); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAirtonAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kAirtonCool; + case stdAc::opmode_t::kHeat: return kAirtonHeat; + case stdAc::opmode_t::kDry: return kAirtonDry; + case stdAc::opmode_t::kFan: return kAirtonFan; + default: return kAirtonAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRAirtonAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kAirtonCool: return stdAc::opmode_t::kCool; + case kAirtonHeat: return stdAc::opmode_t::kHeat; + case kAirtonDry: return stdAc::opmode_t::kDry; + case kAirtonFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRAirtonAc::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kAirtonMinTemp, degrees); + temp = ::min(kAirtonMaxTemp, temp); + if (_.Mode == kAirtonAuto) temp = kAirtonMaxTemp; // Auto has a fixed temp. + _.Temp = temp - kAirtonMinTemp; +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRAirtonAc::getTemp(void) const { return _.Temp + kAirtonMinTemp; } + + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRAirtonAc::setFan(const uint8_t speed) { + _.Fan = (speed > kAirtonFanMax) ? kAirtonFanAuto : speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRAirtonAc::getFan(void) const { return _.Fan; } + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAirtonAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kAirtonFanMin; + case stdAc::fanspeed_t::kLow: return kAirtonFanLow; + case stdAc::fanspeed_t::kMedium: return kAirtonFanMed; + case stdAc::fanspeed_t::kHigh: return kAirtonFanHigh; + case stdAc::fanspeed_t::kMax: return kAirtonFanMax; + default: return kAirtonFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRAirtonAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kAirtonFanMax: return stdAc::fanspeed_t::kMax; + case kAirtonFanHigh: return stdAc::fanspeed_t::kHigh; + case kAirtonFanMed: return stdAc::fanspeed_t::kMedium; + case kAirtonFanLow: return stdAc::fanspeed_t::kLow; + case kAirtonFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Vertical Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setSwingV(const bool on) { _.SwingV = on; } + +/// Get the Vertical Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getSwingV(void) const { return _.SwingV; } + +/// Set the Light/LED/Display setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setLight(const bool on) { _.Light = on; } + +/// Get the Light/LED/Display setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getLight(void) const { return _.Light; } + +/// Set the Economy setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Only available in Cool mode. +void IRAirtonAc::setEcono(const bool on) { + _.Econo = on && (getMode() == kAirtonCool); +} + +/// Get the Economy setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getEcono(void) const { return _.Econo; } + +/// Set the Turbo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setTurbo(const bool on) { + _.Turbo = on; + // Pressing the turbo button sets the fan to max as well. + if (on) setFan(kAirtonFanMax); +} + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getTurbo(void) const { return _.Turbo; } + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Sleep not available in fan or auto mode. +void IRAirtonAc::setSleep(const bool on) { + switch (getMode()) { + case kAirtonAuto: + case kAirtonFan: _.Sleep = false; break; + default: _.Sleep = on; + } +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getSleep(void) const { return _.Sleep; } + +/// Set the Health/Filter setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setHealth(const bool on) { _.Health = on; } + +/// Get the Health/Filter setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getHealth(void) const { return _.Health; } + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRAirtonAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::AIRTON; + result.power = getPower(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = getSwingV() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.econo = getEcono(); + result.turbo = getTurbo(); + result.filter = getHealth(); + result.light = getLight(); + result.sleep = getSleep() ? 0 : -1; + // Not supported. + result.model = -1; + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRAirtonAc::toString(void) const { + String result = ""; + result.reserve(135); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kAirtonAuto, kAirtonCool, + kAirtonHeat, kAirtonDry, kAirtonFan); + result += addFanToString(_.Fan, kAirtonFanHigh, kAirtonFanLow, + kAirtonFanAuto, kAirtonFanMin, kAirtonFanMed, + kAirtonFanMax); + result += addTempToString(getTemp()); + result += addBoolToString(getSwingV(), kSwingVStr); + result += addBoolToString(getEcono(), kEconoStr); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getLight(), kLightStr); + result += addBoolToString(getHealth(), kHealthStr); + result += addBoolToString(getSleep(), kSleepStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Airton.h b/src/libraries/IRremoteESP8266/src/ir_Airton.h new file mode 100644 index 000000000..cd185c26b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Airton.h @@ -0,0 +1,134 @@ +// Copyright 2021 David Conran (crankyoldgit) +/// @file +/// @brief Support for Airton protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1670 + +// Supports: +// Brand: Airton, Model: SMVH09B-2A2A3NH ref. 409730 A/C +// Brand: Airton, Model: RD1A1 remote + +#ifndef IR_AIRTON_H_ +#define IR_AIRTON_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Airton 56 A/C message. +/// @see https://docs.google.com/spreadsheets/d/1Kpq7WCkh85heLnTQGlwUfCR6eeu_vfBHvhii8wtP4LU/edit?usp=sharing +union AirtonProtocol{ + uint64_t raw; ///< The state in code form. + struct { // Common + // Byte 1 & 0 (LSB) + uint16_t Header :16; // Header. (0x11D3) + // Byte 2 + uint8_t Mode :3; // Operating Mode + uint8_t Power :1; // Power Control + uint8_t Fan :3; + uint8_t Turbo :1; + // Byte 3 + uint8_t Temp :4; // Degrees Celsius (+16 offset) + uint8_t :4; // Unknown / Unused. + // Byte 4 + uint8_t SwingV :1; + uint8_t :7; // Unknown / Unused. + // Byte 5 + uint8_t Econo :1; + uint8_t Sleep :1; + uint8_t NotAutoOn :1; + uint8_t :1; // Unknown / Unused. + uint8_t HeatOn :1; + uint8_t :1; // Unknown / Unused. + uint8_t Health :1; + uint8_t Light :1; + // Byte 6 + uint8_t Sum :8; // Sepecial checksum value + }; +}; + +// Constants +const uint8_t kAirtonAuto = 0b000; // 0 +const uint8_t kAirtonCool = 0b001; // 1 +const uint8_t kAirtonDry = 0b010; // 2 +const uint8_t kAirtonFan = 0b011; // 3 +const uint8_t kAirtonHeat = 0b100; // 4 + +const uint8_t kAirtonFanAuto = 0b000; // 0 +const uint8_t kAirtonFanMin = 0b001; // 1 +const uint8_t kAirtonFanLow = 0b010; // 2 +const uint8_t kAirtonFanMed = 0b011; // 3 +const uint8_t kAirtonFanHigh = 0b100; // 4 +const uint8_t kAirtonFanMax = 0b101; // 5 + +const uint8_t kAirtonMinTemp = 16; // 16C +const uint8_t kAirtonMaxTemp = 25; // 25C + + +/// Class for handling detailed Airton 56-bit A/C messages. +class IRAirtonAc { + public: + explicit IRAirtonAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_AIRTON + void send(const uint16_t repeat = kAirtonDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_AIRTON + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + uint64_t getRaw(void); + void setRaw(const uint64_t data); + void setLight(const bool on); + bool getLight(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setHealth(const bool on); + bool getHealth(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + static bool validChecksum(const uint64_t data); + static uint8_t calcChecksum(const uint64_t data); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + AirtonProtocol _; + void checksum(void); +}; +#endif // IR_AIRTON_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Airwell.cpp b/src/libraries/IRremoteESP8266/src/ir_Airwell.cpp new file mode 100644 index 000000000..e9023a26b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Airwell.cpp @@ -0,0 +1,287 @@ +// Copyright 2020 David Conran +#include "ir_Airwell.h" +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +/// @file +/// @brief Airwell "Manchester code" based protocol. +/// Some other Airwell products use the COOLIX protocol. + +const uint8_t kAirwellOverhead = 4; +const uint16_t kAirwellHalfClockPeriod = 950; // uSeconds +const uint16_t kAirwellHdrMark = 3 * kAirwellHalfClockPeriod; // uSeconds +const uint16_t kAirwellHdrSpace = 3 * kAirwellHalfClockPeriod; // uSeconds +const uint16_t kAirwellFooterMark = 5 * kAirwellHalfClockPeriod; // uSeconds + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; + +#if SEND_AIRWELL +/// Send an Airwell Manchester Code formatted message. +/// Status: BETA / Appears to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of the message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1069 +void IRsend::sendAirwell(uint64_t data, uint16_t nbits, uint16_t repeat) { + // Header + Data + sendManchester(kAirwellHdrMark, kAirwellHdrMark, kAirwellHalfClockPeriod, + 0, 0, data, nbits, 38000, true, repeat, kDutyDefault, false); + // Footer + mark(kAirwellHdrMark + kAirwellHalfClockPeriod); + space(kDefaultMessageGap); // A guess. +} +#endif + +#if DECODE_AIRWELL +/// Decode the supplied Airwell "Manchester code" message. +/// +/// Status: BETA / Appears to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1069 +bool IRrecv::decodeAirwell(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < nbits + kAirwellOverhead - offset) + return false; // Too short a message to match. + + // Compliance + if (strict && nbits != kAirwellBits) + return false; // Doesn't match our protocol defn. + + // Header #1 + Data #1 + Footer #1 (There are total of 3 sections) + uint16_t used = matchManchester(results->rawbuf + offset, &results->value, + results->rawlen - offset, nbits, + kAirwellHdrMark, kAirwellHdrMark, + kAirwellHalfClockPeriod, + kAirwellHdrMark, kAirwellHdrSpace, + true, kUseDefTol, kMarkExcess, true, false); + if (used == 0) return false; + offset += used; + + // Success + results->decode_type = decode_type_t::AIRWELL; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRAirwellAc::IRAirwellAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRAirwellAc::begin(void) { _irsend.begin(); } + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A copy of the internal state. +uint64_t IRAirwellAc::getRaw(void) const { + return _.raw; +} + +/// Set the raw state of the object. +/// @param[in] state The raw state from the native IR message. +void IRAirwellAc::setRaw(const uint64_t state) { + _.raw = state; +} + +#if SEND_AIRWELL +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRAirwellAc::send(const uint16_t repeat) { + _irsend.sendAirwell(getRaw(), kAirwellBits, repeat); +} +#endif // SEND_AIRWELL + +/// Reset the internals of the object to a known good state. +void IRAirwellAc::stateReset(void) { + _.raw = kAirwellKnownGoodState; +} + +/// Turn on/off the Power Airwell setting. +/// @param[in] on The desired setting state. +void IRAirwellAc::setPowerToggle(const bool on) { + _.PowerToggle = on; +} + +/// Get the power toggle setting from the internal state. +/// @return A boolean indicating the setting. +bool IRAirwellAc::getPowerToggle(void) const { + return _.PowerToggle; +} + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRAirwellAc::getMode(void) const { + return _.Mode; +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRAirwellAc::setMode(const uint8_t mode) { + switch (mode) { + case kAirwellFan: + case kAirwellCool: + case kAirwellHeat: + case kAirwellDry: + case kAirwellAuto: + _.Mode = mode; + break; + default: + _.Mode = kAirwellAuto; + } + setFan(getFan()); // Ensure the fan is at the correct speed for the new mode. +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAirwellAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kAirwellCool; + case stdAc::opmode_t::kHeat: return kAirwellHeat; + case stdAc::opmode_t::kDry: return kAirwellDry; + case stdAc::opmode_t::kFan: return kAirwellFan; + default: return kAirwellAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRAirwellAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kAirwellCool: return stdAc::opmode_t::kCool; + case kAirwellHeat: return stdAc::opmode_t::kHeat; + case kAirwellDry: return stdAc::opmode_t::kDry; + case kAirwellFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @note The speed is locked to Low when in Dry mode. +void IRAirwellAc::setFan(const uint8_t speed) { + _.Fan = (_.Mode == kAirwellDry) ? kAirwellFanLow + : ::min(speed, kAirwellFanAuto); +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRAirwellAc::getFan(void) const { + return _.Fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAirwellAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kAirwellFanLow; + case stdAc::fanspeed_t::kMedium: + return kAirwellFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kAirwellFanHigh; + default: + return kAirwellFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRAirwellAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kAirwellFanHigh: return stdAc::fanspeed_t::kMax; + case kAirwellFanMedium: return stdAc::fanspeed_t::kMedium; + case kAirwellFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRAirwellAc::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kAirwellMinTemp, degrees); + temp = ::min(kAirwellMaxTemp, temp); + _.Temp = (temp - kAirwellMinTemp + 1); +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRAirwellAc::getTemp(void) const { + return _.Temp + kAirwellMinTemp - 1; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRAirwellAc::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.power = false; + } + result.protocol = decode_type_t::AIRWELL; + if (_.PowerToggle) result.power = !result.power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRAirwellAc::toString(void) const { + String result = ""; + result.reserve(70); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.PowerToggle, kPowerToggleStr, false); + result += addModeToString(_.Mode, kAirwellAuto, kAirwellCool, + kAirwellHeat, kAirwellDry, kAirwellFan); + result += addFanToString(_.Fan, kAirwellFanHigh, kAirwellFanLow, + kAirwellFanAuto, kAirwellFanAuto, + kAirwellFanMedium); + result += addTempToString(getTemp()); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Airwell.h b/src/libraries/IRremoteESP8266/src/ir_Airwell.h new file mode 100644 index 000000000..e0398d67a --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Airwell.h @@ -0,0 +1,101 @@ +// Copyright 2020 David Conran + +/// @file +/// @brief Airwell "Manchester code" based protocol. +/// Some other Airwell products use the COOLIX protocol. + +// Supports: +// Brand: Airwell, Model: RC08W remote +// Brand: Airwell, Model: RC04 remote +// Brand: Airwell, Model: DLS 21 DCI R410 AW A/C + +#ifndef IR_AIRWELL_H_ +#define IR_AIRWELL_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Airwell A/C message. +union AirwellProtocol{ + uint64_t raw; // The state of the IR remote in native IR code form. + struct { + uint64_t :19; + uint64_t Temp :4; + uint64_t :5; + uint64_t Fan :2; + uint64_t Mode :3; + uint64_t PowerToggle:1; + uint64_t :0; + }; +}; + +// Constants +const uint64_t kAirwellKnownGoodState = 0x140500002; // Mode Fan, Speed 1, 25C +// Temperature +const uint8_t kAirwellMinTemp = 16; // Celsius +const uint8_t kAirwellMaxTemp = 30; // Celsius +// Fan +const uint8_t kAirwellFanLow = 0; // 0b00 +const uint8_t kAirwellFanMedium = 1; // 0b01 +const uint8_t kAirwellFanHigh = 2; // 0b10 +const uint8_t kAirwellFanAuto = 3; // 0b11 +// Modes +const uint8_t kAirwellCool = 1; // 0b001 +const uint8_t kAirwellHeat = 2; // 0b010 +const uint8_t kAirwellAuto = 3; // 0b011 +const uint8_t kAirwellDry = 4; // 0b100 +const uint8_t kAirwellFan = 5; // 0b101 + + +// Classes +/// Class for handling detailed Airwell A/C messages. +class IRAirwellAc { + public: + explicit IRAirwellAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(); +#if SEND_AIRWELL + void send(const uint16_t repeat = kAirwellMinRepeats); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_AIRWELL + void begin(); + void setPowerToggle(const bool on); + bool getPowerToggle() const; + void setTemp(const uint8_t temp); + uint8_t getTemp() const; + void setFan(const uint8_t speed); + uint8_t getFan() const; + void setMode(const uint8_t mode); + uint8_t getMode() const; + uint64_t getRaw() const; + void setRaw(const uint64_t state); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString() const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + AirwellProtocol _; +}; +#endif // IR_AIRWELL_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Aiwa.cpp b/src/libraries/IRremoteESP8266/src/ir_Aiwa.cpp new file mode 100644 index 000000000..266fe67d6 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Aiwa.cpp @@ -0,0 +1,105 @@ +// Copyright 2017 David Conran + +#include "IRrecv.h" +#include "IRsend.h" + +/// @file +/// @brief Aiwa based protocol. +/// Based off the RC-T501 RCU +/// Inspired by IRremoteESP8266's implementation +/// @see https://github.com/z3t0/Arduino-IRremote + +// Supports: +// Brand: Aiwa, Model: RC-T501 RCU + +const uint16_t kAiwaRcT501PreBits = 26; +const uint16_t kAiwaRcT501PostBits = 1; +// NOTE: These are the compliment (inverted) of lirc values as +// lirc uses a '0' for a mark, and a '1' for a space. +const uint64_t kAiwaRcT501PreData = 0x1D8113FULL; // 26-bits +const uint64_t kAiwaRcT501PostData = 1ULL; + +#if SEND_AIWA_RC_T501 +/// Send an Aiwa RC T501 formatted message. +/// Status: BETA / Should work. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of the message to be sent. +/// Typically kAiwaRcT501Bits. Max is 37 = (64 - 27) +/// @param[in] repeat The number of times the command is to be repeated. +/// @see http://lirc.sourceforge.net/remotes/aiwa/RC-T501 +void IRsend::sendAiwaRCT501(uint64_t data, uint16_t nbits, uint16_t repeat) { + // Appears to be an extended NEC1 protocol. i.e. 42 bits instead of 32 bits. + // So use sendNEC instead, however the twist is it has a fixed 26 bit + // prefix, and a fixed postfix bit. + uint64_t new_data = ((kAiwaRcT501PreData << (nbits + kAiwaRcT501PostBits)) | + (data << kAiwaRcT501PostBits) | kAiwaRcT501PostData); + nbits += kAiwaRcT501PreBits + kAiwaRcT501PostBits; + if (nbits > sizeof(new_data) * 8) + return; // We are overflowing. Abort, and don't send. + sendNEC(new_data, nbits, repeat); +} +#endif + +#if DECODE_AIWA_RC_T501 +/// Decode the supplied Aiwa RC T501 message. +/// Status: BETA / Should work. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @note +/// Aiwa RC T501 appears to be a 42 bit variant of the NEC1 protocol. +/// However, we historically (original Arduino IRremote project) treats it as +/// a 15 bit (data) protocol. So, we expect nbits to typically be 15, and we +/// will remove the prefix and postfix from the raw data, and use that as +/// the result. +/// @see http://www.sbprojects.net/knowledge/ir/nec.php +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1069 +bool IRrecv::decodeAiwaRCT501(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance + if (strict && nbits != kAiwaRcT501Bits) + return false; // Doesn't match our protocol defn. + + // Add on the pre & post bits to our requested bit length. + uint16_t expected_nbits = nbits + kAiwaRcT501PreBits + kAiwaRcT501PostBits; + uint64_t new_data; + if (expected_nbits > sizeof(new_data) * 8) + return false; // We can't possibly match something that big. + // Decode it as a much bigger (non-standard) NEC message, so we have to turn + // off strict mode checking for NEC. + if (!decodeNEC(results, offset, expected_nbits, false)) + return false; // The NEC decode had a problem, so we should too. + uint16_t actual_bits = results->bits; + new_data = results->value; + if (actual_bits < expected_nbits) + return false; // The data we caught was undersized. Throw it back. + if ((new_data & 0x1ULL) != kAiwaRcT501PostData) + return false; // The post data doesn't match, so it can't be this protocol. + // Trim off the post data bit. + new_data >>= kAiwaRcT501PostBits; + actual_bits -= kAiwaRcT501PostBits; + + // Extract out our likely new value and put it back in the results. + actual_bits -= kAiwaRcT501PreBits; + results->value = new_data & ((1ULL << actual_bits) - 1); + + // Check the prefix data matches. + new_data >>= actual_bits; // Trim off the new data to expose the prefix. + if (new_data != kAiwaRcT501PreData) // Check the prefix. + return false; + + // Compliance + if (strict && results->bits != expected_nbits) return false; + + // Success + results->decode_type = AIWA_RC_T501; + results->bits = actual_bits; + results->address = 0; + results->command = 0; + return true; +} +#endif diff --git a/src/libraries/IRremoteESP8266/src/ir_Amcor.cpp b/src/libraries/IRremoteESP8266/src/ir_Amcor.cpp new file mode 100644 index 000000000..42fc41a70 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Amcor.cpp @@ -0,0 +1,355 @@ +// Copyright 2019 David Conran + +/// @file +/// @brief Amcor A/C protocol. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/385 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/834 + +#include "ir_Amcor.h" +// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kAmcorHdrMark = 8200; +const uint16_t kAmcorHdrSpace = 4200; +const uint16_t kAmcorOneMark = 1500; +const uint16_t kAmcorZeroMark = 600; +const uint16_t kAmcorOneSpace = kAmcorZeroMark; +const uint16_t kAmcorZeroSpace = kAmcorOneMark; +const uint16_t kAmcorFooterMark = 1900; +const uint16_t kAmcorGap = 34300; +const uint8_t kAmcorTolerance = 40; + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; + +#if SEND_AMCOR +/// Send a Amcor HVAC formatted message. +/// Status: STABLE / Reported as working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendAmcor(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + // Check if we have enough bytes to send a proper message. + if (nbytes < kAmcorStateLength) return; + sendGeneric(kAmcorHdrMark, kAmcorHdrSpace, kAmcorOneMark, kAmcorOneSpace, + kAmcorZeroMark, kAmcorZeroSpace, kAmcorFooterMark, kAmcorGap, + data, nbytes, 38, false, repeat, kDutyDefault); +} +#endif + +#if DECODE_AMCOR +/// Decode the supplied Amcor HVAC message. +/// Status: STABLE / Reported as working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeAmcor(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= 2 * nbits + kHeader - 1 + offset) + return false; // Can't possibly be a valid Amcor message. + if (strict && nbits != kAmcorBits) + return false; // We expect Amcor to be 64 bits of message. + + uint16_t used; + // Header + Data Block (64 bits) + Footer + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, 64, + kAmcorHdrMark, kAmcorHdrSpace, + kAmcorOneMark, kAmcorOneSpace, + kAmcorZeroMark, kAmcorZeroSpace, + kAmcorFooterMark, kAmcorGap, true, + kAmcorTolerance, 0, false); + if (!used) return false; + offset += used; + + if (strict) { + if (!IRAmcorAc::validChecksum(results->state)) return false; + } + + // Success + results->bits = nbits; + results->decode_type = AMCOR; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRAmcorAc::IRAmcorAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { this->stateReset(); } + +/// Set up hardware to be able to send a message. +void IRAmcorAc::begin(void) { _irsend.begin(); } + +#if SEND_AMCOR +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRAmcorAc::send(const uint16_t repeat) { + _irsend.sendAmcor(getRaw(), kAmcorStateLength, repeat); +} +#endif // SEND_AMCOR + +/// Calculate the checksum for the supplied state. +/// @param[in] state The source state to generate the checksum from. +/// @param[in] length Length of the supplied state to checksum. +/// @return The checksum value. +uint8_t IRAmcorAc::calcChecksum(const uint8_t state[], const uint16_t length) { + return irutils::sumNibbles(state, length - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The size of the state. +/// @return A boolean indicating if it's checksum is valid. +bool IRAmcorAc::validChecksum(const uint8_t state[], const uint16_t length) { + return (state[length - 1] == IRAmcorAc::calcChecksum(state, length)); +} + +/// Update the checksum value for the internal state. +void IRAmcorAc::checksum(void) { + _.Sum = IRAmcorAc::calcChecksum(_.raw, kAmcorStateLength); +} + +/// Reset the internals of the object to a known good state. +void IRAmcorAc::stateReset(void) { + for (uint8_t i = 1; i < kAmcorStateLength; i++) _.raw[i] = 0x0; + _.raw[0] = 0x01; + _.Fan = kAmcorFanAuto; + _.Mode = kAmcorAuto; + _.Temp = 25; // 25C +} + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A PTR to the internal state. +uint8_t* IRAmcorAc::getRaw(void) { + checksum(); // Ensure correct bit array before returning + return _.raw; +} + +/// Set the raw state of the object. +/// @param[in] state The raw state from the native IR message. +void IRAmcorAc::setRaw(const uint8_t state[]) { + memcpy(_.raw, state, kAmcorStateLength); +} + +/// Set the internal state to have the power on. +void IRAmcorAc::on(void) { setPower(true); } + +/// Set the internal state to have the power off. +void IRAmcorAc::off(void) { setPower(false); } + +/// Set the internal state to have the desired power. +/// @param[in] on The desired power state. +void IRAmcorAc::setPower(const bool on) { + _.Power = (on ? kAmcorPowerOn : kAmcorPowerOff); +} + +/// Get the power setting from the internal state. +/// @return A boolean indicating the power setting. +bool IRAmcorAc::getPower(void) const { + return _.Power == kAmcorPowerOn; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRAmcorAc::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kAmcorMinTemp, degrees); + temp = ::min(kAmcorMaxTemp, temp); + _.Temp = temp; +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRAmcorAc::getTemp(void) const { + return _.Temp; +} + +/// Control the current Maximum Cooling or Heating setting. (i.e. Turbo) +/// @note Only allowed in Cool or Heat mode. +/// @param[in] on The desired setting. +void IRAmcorAc::setMax(const bool on) { + if (on) { + switch (_.Mode) { + case kAmcorCool: _.Temp = kAmcorMinTemp; break; + case kAmcorHeat: _.Temp = kAmcorMaxTemp; break; + // Not allowed in all other operating modes. + default: return; + } + } + _.Max = (on ? kAmcorMax : 0); +} + +/// Is the Maximum Cooling or Heating setting (i.e. Turbo) setting on? +/// @return The current value. +bool IRAmcorAc::getMax(void) const { + return _.Max == kAmcorMax; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRAmcorAc::setFan(const uint8_t speed) { + switch (speed) { + case kAmcorFanAuto: + case kAmcorFanMin: + case kAmcorFanMed: + case kAmcorFanMax: + _.Fan = speed; + break; + default: + _.Fan = kAmcorFanAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRAmcorAc::getFan(void) const { + return _.Fan; +} + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRAmcorAc::getMode(void) const { + return _.Mode; +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRAmcorAc::setMode(const uint8_t mode) { + switch (mode) { + case kAmcorFan: + case kAmcorCool: + case kAmcorHeat: + case kAmcorDry: + case kAmcorAuto: + _.Vent = (mode == kAmcorFan) ? kAmcorVentOn : 0; + _.Mode = mode; + return; + default: + _.Vent = 0; + _.Mode = kAmcorAuto; + break; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAmcorAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kAmcorCool; + case stdAc::opmode_t::kHeat: + return kAmcorHeat; + case stdAc::opmode_t::kDry: + return kAmcorDry; + case stdAc::opmode_t::kFan: + return kAmcorFan; + default: + return kAmcorAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAmcorAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kAmcorFanMin; + case stdAc::fanspeed_t::kMedium: + return kAmcorFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kAmcorFanMax; + default: + return kAmcorFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRAmcorAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kAmcorCool: return stdAc::opmode_t::kCool; + case kAmcorHeat: return stdAc::opmode_t::kHeat; + case kAmcorDry: return stdAc::opmode_t::kDry; + case kAmcorFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRAmcorAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kAmcorFanMax: return stdAc::fanspeed_t::kMax; + case kAmcorFanMed: return stdAc::fanspeed_t::kMedium; + case kAmcorFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRAmcorAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::AMCOR; + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = toCommonFanSpeed(_.Fan); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRAmcorAc::toString(void) const { + String result = ""; + result.reserve(70); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kAmcorAuto, kAmcorCool, + kAmcorHeat, kAmcorDry, kAmcorFan); + result += addFanToString(_.Fan, kAmcorFanMax, kAmcorFanMin, + kAmcorFanAuto, kAmcorFanAuto, + kAmcorFanMed); + result += addTempToString(_.Temp); + result += addBoolToString(getMax(), kMaxStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Amcor.h b/src/libraries/IRremoteESP8266/src/ir_Amcor.h new file mode 100644 index 000000000..ec52bbf9b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Amcor.h @@ -0,0 +1,141 @@ +// Copyright 2019 David Conran + +/// @file +/// @brief Amcor A/C protocol. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/834 +/// @remark Kudos to ldellus; For the breakdown and mapping of the bit values. +// Supports: +// Brand: Amcor, Model: ADR-853H A/C +// Brand: Amcor, Model: TAC-495 remote +// Brand: Amcor, Model: TAC-444 remote + +#ifndef IR_AMCOR_H_ +#define IR_AMCOR_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + + +/// Native representation of a Amcor A/C message. +union AmcorProtocol{ + uint8_t raw[kAmcorStateLength]; // The state of the IR remote. + struct { + // Byte 0 + uint8_t :8; // Typically 0x01 + // Byte 1 + uint8_t Mode :3; + uint8_t :1; + uint8_t Fan :3; + uint8_t :1; + // Byte 2 + uint8_t :1; + uint8_t Temp :6; + uint8_t :1; + // Byte 3 + uint8_t :8; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t :4; + uint8_t Power :4; + // Byte 6 + uint8_t Max :2; + uint8_t :4; + uint8_t Vent :2; + // Byte 7 + uint8_t Sum :8; + }; +}; + +// Constants + +// Fan Control +const uint8_t kAmcorFanMin = 0b001; +const uint8_t kAmcorFanMed = 0b010; +const uint8_t kAmcorFanMax = 0b011; +const uint8_t kAmcorFanAuto = 0b100; +// Modes +const uint8_t kAmcorCool = 0b001; +const uint8_t kAmcorHeat = 0b010; +const uint8_t kAmcorFan = 0b011; // Aka "Vent" +const uint8_t kAmcorDry = 0b100; +const uint8_t kAmcorAuto = 0b101; + +// Temperature +const uint8_t kAmcorMinTemp = 12; // Celsius +const uint8_t kAmcorMaxTemp = 32; // Celsius + +// Power +const uint8_t kAmcorPowerOn = 0b0011; // 0x3 +const uint8_t kAmcorPowerOff = 0b1100; // 0xC + +// Max Mode (aka "Lo" in Cool and "Hi" in Heat) +const uint8_t kAmcorMax = 0b11; + +// "Vent" Mode +const uint8_t kAmcorVentOn = 0b11; + + +// Classes + +/// Class for handling detailed Amcor A/C messages. +class IRAmcorAc { + public: + explicit IRAmcorAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + void stateReset(); +#if SEND_AMCOR + void send(const uint16_t repeat = kAmcorDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_AMCOR + void begin(); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kAmcorStateLength); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kAmcorStateLength); + void setPower(const bool state); + bool getPower(void) const; + void on(void); + void off(void); + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setMax(const bool on); + bool getMax(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t state[]); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; +#else + /// @cond IGNORE + IRsendTest _irsend; + /// @endcond +#endif + AmcorProtocol _; + void checksum(void); +}; +#endif // IR_AMCOR_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Argo.cpp b/src/libraries/IRremoteESP8266/src/ir_Argo.cpp new file mode 100644 index 000000000..697dcd30d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Argo.cpp @@ -0,0 +1,1823 @@ +// Copyright 2017 Schmolders +// Copyright 2019 crankyoldgit +// Copyright 2022 Mateusz Bronk (mbronk) +/// @file +/// @brief Argo A/C protocol. + +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1859 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1912 + + +#include "ir_Argo.h" +// #include +#include +#include +#ifndef UNIT_TEST +#include "String.h" +#endif // UNIT_TEST +#include "IRremoteESP8266.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +// using SPACE modulation. MARK is always const 400u +const uint16_t kArgoHdrMark = 6400; +const uint16_t kArgoHdrSpace = 3300; +const uint16_t kArgoBitMark = 400; +const uint16_t kArgoOneSpace = 2200; +const uint16_t kArgoZeroSpace = 900; +const uint32_t kArgoGap = kDefaultMessageGap; // Made up value. Complete guess. +const uint8_t kArgoSensorCheck = 52; // Part of the sensor message check calc. +const uint8_t kArgoSensorFixed = 0b011; +const uint8_t kArgoWrem3Preamble = 0b1011; +const uint8_t kArgoWrem3Postfix_Timer = 0b1; +const uint8_t kArgoWrem3Postfix_ACControl = 0b110000; + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::addFanToString; +using irutils::addSwingVToString; +using irutils::minsToString; +using irutils::addDayToString; +using irutils::addModelToString; +using irutils::daysBitmaskToString; +using irutils::addTimerModeToString; + +#if SEND_ARGO +/// Send a Argo A/C formatted message. +/// Status: [WREM-2] BETA / Probably works. +/// [WREM-3] Confirmed working w/ Argo 13 ECO (WREM-3) +/// @note The "no footer" part needs re-checking for validity but retained for +/// backwards compatibility. +/// Consider using @c sendFooter=true code for WREM-2 as well +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @param[in] sendFooter Whether to send footer and add a final gap. +/// *REQUIRED* for WREM-3, UNKNOWN for WREM-2 (used to be +/// disabled in previous impl., hence retained) +/// @note Consider removing this param (default to true) if WREM-2 works w/ it +void IRsend::sendArgo(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat, bool sendFooter /*= false*/) { + if (nbytes < ::min(kArgo3AcControlStateLength, + kArgo3ConfigStateLength, + kArgo3iFeelReportStateLength, + kArgo3TimerStateLength, + kArgoStateLength, + kArgoShortStateLength)) { + return; // Not enough bytes to send a proper message. + } + + const uint16_t _footermark = (sendFooter)? kArgoBitMark : 0; + const uint32_t _gap = (sendFooter)? kArgoGap : 0; + + sendGeneric(kArgoHdrMark, kArgoHdrSpace, kArgoBitMark, kArgoOneSpace, + kArgoBitMark, kArgoZeroSpace, + _footermark, _gap, + data, nbytes, kArgoFrequency, false, repeat, kDutyDefault); +} + + +/// Send a Argo A/C formatted message. +/// Status: Confirmed working w/ Argo 13 ECO (WREM-3) +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendArgoWREM3(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + sendArgo(data, nbytes, repeat, true); +} +#endif // SEND_ARGO + + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +template +IRArgoACBase::IRArgoACBase(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRArgoAC::IRArgoAC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : IRArgoACBase(pin, inverted, use_modulation) { } + + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRArgoAC_WREM3::IRArgoAC_WREM3(const uint16_t pin, const bool inverted, + const bool use_modulation) + : IRArgoACBase(pin, inverted, use_modulation) {} + +/// Set up hardware to be able to send a message. +template +void IRArgoACBase::begin(void) { _irsend.begin(); } + + +/// @cond +/// @brief Get byte length of raw WREM-2 message based on IR cmd type +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param type The type of IR command +/// @note Not all types are supported. AC_CONTROL and TIMER are the same cmd +/// @return Byte length of state command +/// @relates IRArgoACBase\ +template<> +uint16_t IRArgoACBase::getStateLengthForIrMsgType( + argoIrMessageType_t type) { + switch (type) { + case argoIrMessageType_t::AC_CONTROL: + case argoIrMessageType_t::TIMER_COMMAND: + return kArgoStateLength; + case argoIrMessageType_t::IFEEL_TEMP_REPORT: + return kArgoShortStateLength; + case argoIrMessageType_t::CONFIG_PARAM_SET: + default: + return 0; // Not supported by WREM-2 + } +} +/// @endcond + +/// @brief Get byte length of raw WREM-3 message based on IR cmd type +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param type The type of IR command +/// @return Byte length of state command +/// @relates IRArgoACBase\ +template<> +uint16_t IRArgoACBase::getStateLengthForIrMsgType( + argoIrMessageType_t type) { + switch (type) { + case argoIrMessageType_t::AC_CONTROL: + return kArgo3AcControlStateLength; + case argoIrMessageType_t::IFEEL_TEMP_REPORT: + return kArgo3iFeelReportStateLength; + case argoIrMessageType_t::TIMER_COMMAND: + return kArgo3TimerStateLength; + case argoIrMessageType_t::CONFIG_PARAM_SET: + return kArgo3ConfigStateLength; + default: + return 0; + } +} + +/// @cond +/// @brief Get message type from raw WREM-2 data +/// 1st param ignored: WREM-2 does not carry type in payload, allegedly +/// @param length Message length: used for *heuristic* detection of message type +/// @return IR message type +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @relates IRArgoACBase\ +template<> +argoIrMessageType_t IRArgoACBase::getMessageType( + const uint8_t[], const uint16_t length) { + if (length == kArgoShortStateLength) { + return argoIrMessageType_t::IFEEL_TEMP_REPORT; + } + return argoIrMessageType_t::AC_CONTROL; +} +/// @endcond + + +/// @brief Get message type from raw WREM-3 data +/// @param state The raw IR data +/// @param length Length of @c state (in byte) +/// @return IR message type +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @relates IRArgoACBase\ +template<> +argoIrMessageType_t IRArgoACBase::getMessageType( + const uint8_t state[], const uint16_t length) { + if (length < 1) { + return static_cast(-1); + } + return static_cast(state[0] >> 6); +} + +/// @brief Get message type from raw WREM-3 data +/// @param raw Raw data +/// @return IR message type +argoIrMessageType_t IRArgoAC_WREM3::getMessageType( + const ArgoProtocolWREM3& raw) { + return static_cast(raw.IrCommandType); +} + + +/// @brief Get actual raw state byte length for the current state +/// _param 1st param ignored: WREM-2 does not caryy type in payload, allegedly +/// @param messageType Type of message the state is carrying +/// @return Actual length of state (in bytes) +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @relates IRArgoACBase\ +template<> +uint16_t IRArgoACBase::getRawByteLength(const ArgoProtocol&, + argoIrMessageType_t messageType) { + if (messageType == argoIrMessageType_t::IFEEL_TEMP_REPORT) { + return kArgoShortStateLength; + } + return kArgoStateLength; +} + + +/// @brief Get actual raw state byte length for the current state +/// @param raw The raw state +/// _param 2nd param ignored (1st byte of @c raw is sufficient to get len) +/// @return Actual length of state (in bytes) +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @relates IRArgoACBase\ +template<> +uint16_t IRArgoACBase::getRawByteLength( + const ArgoProtocolWREM3& raw, argoIrMessageType_t) { + return IRArgoAC_WREM3::getStateLengthForIrMsgType( + IRArgoAC_WREM3::getMessageType(raw)); +} + + +/// @brief Get actual raw state byte length for the current state +/// @return Actual length of state (in bytes) +template +uint16_t IRArgoACBase::getRawByteLength() const { + return getRawByteLength(_, _messageType); +} + +/// @cond +/// Calculate the checksum for a given state (WREM-2). +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @warning This does NOT calculate 'short' (iFeel) message checksums +/// @param[in] state The array to calculate the checksum for. +/// @param[in] length The size of the state. +/// @return The 8-bit calculated result. +/// @relates IRArgoACBase\ +template<> +uint8_t IRArgoACBase::calcChecksum(const uint8_t state[], + const uint16_t length) { + // Corresponds to byte 11 being constant 0b01 + // Only add up bytes to 9. byte 10 is 0b01 constant anyway. + // Assume that argo array is MSB first (left) + return sumBytes(state, length - 2, 2); +} +/// @endcond + + +/// Calculate the checksum for a given state (WREM-3). +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] state The array to calculate the checksum for. +/// @param[in] length The size of the state. +/// @return The 8-bit calculated result. +/// @relates IRArgoACBase\ +template<> +uint8_t IRArgoACBase::calcChecksum(const uint8_t state[], + const uint16_t length) { + if (length < 1) { + return -1; // Nothing to compute on + } + + uint16_t payloadSizeBits = (length - 1) * 8; // Last byte carries checksum + + argoIrMessageType_t msgType = getMessageType(state, length); + if (msgType == argoIrMessageType_t::IFEEL_TEMP_REPORT) { + payloadSizeBits += 5; // For WREM3::iFeel the checksum is 3-bit + } else if (msgType == argoIrMessageType_t::TIMER_COMMAND) { + payloadSizeBits += 3; // For WREM3::Timer the checksum is 5-bit + } // Otherwise: full 8-bit checksum + + uint8_t checksum = sumBytes(state, payloadSizeBits / 8, 0); + + // Add stray bits from last byte to the checksum (if any) + const uint8_t maskPayload = 0xFF >> (8 - (payloadSizeBits % 8)); + checksum += (state[length-1] & maskPayload); + + const uint8_t maskChecksum = 0xFF >> (payloadSizeBits % 8); + return checksum & maskChecksum; +} + + +/// Update the checksum for a given state (WREM2). +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @warning This impl does not support short message format (iFeel) +/// @param[in,out] state Pointer to a binary representation of the A/C state. +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::_checksum(ArgoProtocol *state) { + uint8_t sum = calcChecksum(state->raw, kArgoStateLength); + // Append sum to end of array + // Set const part of checksum bit 10 + state->Post = kArgoPost; + state->Sum = sum; +} + + +/// @brief Update the checksum for a given state (WREM3). +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in,out] state Pointer to a binary representation of the A/C state. +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::_checksum(ArgoProtocolWREM3 *state) { + argoIrMessageType_t msgType = IRArgoAC_WREM3::getMessageType(*state); + + uint8_t sum = calcChecksum(state->raw, getRawByteLength(*state)); + switch (msgType) { + case argoIrMessageType_t::IFEEL_TEMP_REPORT: + state->CheckHi = sum; + break; + case argoIrMessageType_t::TIMER_COMMAND: + state->timer.Checksum = sum; + break; + case argoIrMessageType_t::CONFIG_PARAM_SET: + state->config.Checksum = sum; + break; + case argoIrMessageType_t::AC_CONTROL: + default: + state->Sum = sum; + break; + } +} + + +/// Update the checksum for the internal state. +template +void IRArgoACBase::checksum(void) { _checksum(&_); } + + +/// Reset the given state to a known good state. +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in,out] state Pointer to a binary representation of the A/C state. +/// _param 2nd param unused (always resets to AC_CONTROL state) +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::_stateReset(ArgoProtocol *state, + argoIrMessageType_t) { + for (uint8_t i = 2; i < kArgoStateLength; i++) state->raw[i] = 0x0; + state->Pre1 = kArgoPreamble1; // LSB first (as sent) 0b00110101; + state->Pre2 = kArgoPreamble2; // LSB first: 0b10101111; + state->Post = kArgoPost; +} + + +/// Reset the given state to a known good state +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in,out] state Pointer to a binary representation of the A/C state. +/// @param messageType Type of message to reset the state for +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::_stateReset(ArgoProtocolWREM3 *state, + argoIrMessageType_t messageType) { + for (uint8_t i = 1; i < sizeof(state->raw) / sizeof(state->raw[0]); i++) { + state->raw[i] = 0x0; + } + state->Pre1 = kArgoWrem3Preamble; // LSB first (as sent) 0b00110101; + state->IrChannel = 0; + state->IrCommandType = static_cast(messageType); + + if (messageType == argoIrMessageType_t::TIMER_COMMAND) { + state->timer.Post1 = kArgoWrem3Postfix_Timer; // 0b1 + } else if (messageType == argoIrMessageType_t::AC_CONTROL) { + state->Post1 = kArgoWrem3Postfix_ACControl; // 0b110000 + } +} + + +/// @brief Reset the internals of the object to a known good state. +/// @param messageType Type of message to reset the state for +template +void IRArgoACBase::stateReset(argoIrMessageType_t messageType) { + _stateReset(&_, messageType); + if (messageType == argoIrMessageType_t::AC_CONTROL) { + off(); + setTemp(20); + setSensorTemp(25); + setMode(argoMode_t::AUTO); + setFan(argoFan_t::FAN_AUTO); + } + _messageType = messageType; + _length = getStateLengthForIrMsgType(_messageType); +} + + +/// @brief Retrieve the checksum value from transmitted state +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] state Raw state +/// @param length Length of @c state in bytes +/// @return Checksum value (8-bit) +/// @relates IRArgoACBase\ +template<> +uint8_t IRArgoACBase::getChecksum(const uint8_t state[], + const uint16_t length) { + if (length < 1) { + return -1; + } + return (state[length - 2] >> 2) + (state[length - 1] << 6); +} + +/// @cond +/// @brief Retrieve the checksum value from transmitted state +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] state Raw state +/// @param length Length of @c state in bytes +/// @return Checksum value (up to 8-bit) +template<> +uint8_t IRArgoACBase::getChecksum(const uint8_t state[], + const uint16_t length) { + if (length < 1) { + return -1; + } + argoIrMessageType_t msgType = getMessageType(state, length); + if (msgType == argoIrMessageType_t::IFEEL_TEMP_REPORT) { + return (state[length - 1] & 0b11100000) >> 5; + } + if (msgType == argoIrMessageType_t::TIMER_COMMAND) { + return state[length - 1] >> 3; + } + return (state[length - 1]); +} +/// @endcond + + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The size of the state. +/// @return A boolean indicating if it's checksum is valid. +template +bool IRArgoACBase::validChecksum(const uint8_t state[], + const uint16_t length) { + return (getChecksum(state, length) == calcChecksum(state, length)); +} + + +#if SEND_ARGO +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +template +void IRArgoACBase::send(const uint16_t repeat) { + _irsend.sendArgo(getRaw(), getRawByteLength(), repeat); +} + +/// @cond +/// Send the current internal state as an IR message. +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] repeat Nr. of times the message will be repeated. +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::send(const uint16_t repeat) { + _irsend.sendArgoWREM3(getRaw(), getRawByteLength(), repeat); +} +/// @endcond + + +/// Send current room temperature for the iFeel feature as a silent IR +/// message (no acknowledgement from the device) (WREM2) +/// @param[in] degrees The temperature in degrees celsius. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRArgoAC::sendSensorTemp(const uint8_t degrees, const uint16_t repeat) { + const uint8_t temp = ::max(::min(degrees, kArgoMaxRoomTemp), + kArgoTempDelta) - kArgoTempDelta; + const uint8_t check = kArgoSensorCheck + temp; + + ArgoProtocol data; + _stateReset(&data, argoIrMessageType_t::IFEEL_TEMP_REPORT); + data.SensorT = temp; + data.CheckHi = check >> 5; + data.CheckLo = check; + data.Fixed = kArgoSensorFixed; + _checksum(&data); + uint16_t msgLen = getRawByteLength(data, + argoIrMessageType_t::IFEEL_TEMP_REPORT); + + _irsend.sendArgo(data.raw, msgLen, repeat); +} + +/// Send current room temperature for the iFeel feature as a silent IR +/// message (no acknowledgement from the device) (WREM3) +/// @param[in] degrees The temperature in degrees celsius. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRArgoAC_WREM3::sendSensorTemp(const uint8_t degrees, + const uint16_t repeat) { + const uint8_t temp = ::max(::min(degrees, kArgoMaxRoomTemp), + kArgoTempDelta) - kArgoTempDelta; + ArgoProtocolWREM3 data = {}; + _stateReset(&data, argoIrMessageType_t::IFEEL_TEMP_REPORT); + data.SensorT = temp; + _checksum(&data); + uint16_t msgLen = getRawByteLength(data, + argoIrMessageType_t::IFEEL_TEMP_REPORT); + _irsend.sendArgoWREM3(data.raw, msgLen, repeat); +} +#endif + + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A PTR to the internal state. +template +uint8_t* IRArgoACBase::getRaw(void) { + checksum(); // Ensure correct bit array before returning + return _.raw; +} + + +/// Set the raw state of the object. +/// @param[in] state The raw state from the native IR message. +/// @param[in] length The length of raw state in bytes. +template +void IRArgoACBase::setRaw(const uint8_t state[], const uint16_t length) { + memcpy(_.raw, state, length); + _messageType = getMessageType(state, length); + _length = length; +} + +/// Set the internal state to have the power on. +template +void IRArgoACBase::on(void) { setPower(true); } + +/// Set the internal state to have the power off. +template +void IRArgoACBase::off(void) { setPower(false); } + +/// @cond +/// Set the internal state to have the desired power. +/// @param[in] on The desired power state. +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::setPower(const bool on) { + _.Power = on; +} +/// @endcond + +/// @brief Set the internal state to have the desired power. +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] on The desired power state. +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::setPower(const bool on) { + if (_messageType == argoIrMessageType_t::TIMER_COMMAND) { + _.timer.IsOn = on; + } else { + _.Power = on; + } +} + +/// Get the power setting from the internal state. +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @return A boolean indicating the power setting. +/// @relates IRArgoACBase\ +template<> +bool IRArgoACBase::getPower(void) const { return _.Power; } + +/// Get the power setting from the internal state. +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @return A boolean indicating the power setting. +/// @relates IRArgoACBase\ +template<> +bool IRArgoACBase::getPower(void) const { + if (_messageType == argoIrMessageType_t::TIMER_COMMAND) { + return _.timer.IsOn; + } + return _.Power; +} + +/// Control the current Max setting. (i.e. Turbo) +/// @param[in] on The desired setting. +template +void IRArgoACBase::setMax(const bool on) { + _.Max = on; +} + +/// Is the Max (i.e. Turbo) setting on? +/// @return The current value. +template +bool IRArgoACBase::getMax(void) const { return _.Max; } + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +/// @note Sending 0 equals +4 +template +void IRArgoACBase::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kArgoMinTemp, degrees); + // delta 4 degrees. "If I want 12 degrees, I need to send 8" + temp = ::min(kArgoMaxTemp, temp) - kArgoTempDelta; + // mask out bits + _.Temp = temp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +template +uint8_t IRArgoACBase::getTemp(void) const { + return _.Temp + kArgoTempDelta; +} + + +/// @brief Get the current fan mode setting as a strongly typed value (WREM2). +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @return The current fan mode. +/// @relates IRArgoACBase\ +template<> +argoFan_t IRArgoACBase::getFanEx(void) const { + switch (_.Fan) { + case kArgoFan3: + return argoFan_t::FAN_HIGHEST; + case kArgoFan2: + return argoFan_t::FAN_MEDIUM; + case kArgoFan1: + return argoFan_t::FAN_LOWEST; + case kArgoFanAuto: + return argoFan_t::FAN_AUTO; + default: + return static_cast(_.Fan); + } +} + +/// @brief Get the current fan mode setting as a strongly typed value (WREM3). +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @return The current fan mode. +/// @relates IRArgoACBase\ +template<> +argoFan_t IRArgoACBase::getFanEx(void) const { + return static_cast(_.Fan); +} + +/// @cond +/// Set the desired fan mode (WREM2). +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] fan The desired fan speed. +/// @note Only a subset of fan speeds are supported (1|2|3|Auto) +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::setFan(argoFan_t fan) { + switch (fan) { + case argoFan_t::FAN_AUTO: + _.Fan = kArgoFanAuto; + break; + case argoFan_t::FAN_HIGHEST: + case argoFan_t::FAN_HIGH: + _.Fan = kArgoFan3; + break; + case argoFan_t::FAN_MEDIUM: + case argoFan_t::FAN_LOW: + _.Fan = kArgoFan2; + break; + case argoFan_t::FAN_LOWER: + case argoFan_t::FAN_LOWEST: + _.Fan = kArgoFan1; + break; + default: + uint8_t raw_value = static_cast(fan); // 2-bit value, per def. + if ((raw_value & 0b11) == raw_value) { + // Outside of known value range, but matches field length + // Let's assume the caller knows what they're doing and pass it through + _.Fan = raw_value; + } else { + _.Fan = kArgoFanAuto; + } + break; + } +} +/// @endcond + +/// Set the desired fan mode (WREM3). +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] fan The desired fan speed. +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::setFan(argoFan_t fan) { + switch (fan) { + case argoFan_t::FAN_AUTO: + case argoFan_t::FAN_HIGHEST: + case argoFan_t::FAN_HIGH: + case argoFan_t::FAN_MEDIUM: + case argoFan_t::FAN_LOW: + case argoFan_t::FAN_LOWER: + case argoFan_t::FAN_LOWEST: + _.Fan = static_cast(fan); + break; + default: + _.Fan = static_cast(argoFan_t::FAN_AUTO); + break; + } +} + +/// Set the speed of the fan. +/// @deprecated +/// @param[in] fan The desired setting. +void IRArgoAC::setFan(const uint8_t fan) { + _.Fan = ::min(fan, kArgoFan3); +} + +/// Get the current fan speed setting. +/// @deprecated +/// @return The current fan speed. +uint8_t IRArgoAC::getFan(void) const { + return _.Fan; +} + +/// @brief Get Flap (VSwing) value as a strongly-typed value +/// @note This @c getFlapEx() method has been introduced to be able to retain +/// old implementation of @c getFlap() for @c IRArgoAc which used uint8_t +/// @return Flap setting +template +argoFlap_t IRArgoACBase::getFlapEx(void) const { + return static_cast(_.Flap); +} + +/// Set the desired flap mode +/// @param[in] flap The desired flap mode. +template +void IRArgoACBase::setFlap(argoFlap_t flap) { + uint8_t raw_value = static_cast(flap); + if ((raw_value & 0b111) == raw_value) { + // Outside of known value range, but matches field length + // Let's assume the caller knows what they're doing and pass it through + _.Flap = raw_value; + } else { + _.Flap = static_cast(argoFlap_t::FLAP_AUTO); + } +} + +/// Set the flap position. i.e. Swing. (WREM2) +/// @warning Not yet working! +/// @deprecated +/// @param[in] flap The desired setting. +void IRArgoAC::setFlap(const uint8_t flap) { + setFlap(static_cast(flap)); + // TODO(kaschmo): set correct bits for flap mode +} + +/// Get the flap position. i.e. Swing. (WREM2) +/// @warning Not yet working! +/// @deprecated +/// @return The current flap setting. +uint8_t IRArgoAC::getFlap(void) const { + return _.Flap; +} + +/// Get the current operation mode setting. +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @return The current operation mode. +/// @note This @c getModeEx() method has been introduced to be able to retain +/// old implementation of @c getMode() for @c IRArgoAc which used uint8_t +/// @relates IRArgoACBase\ +template<> +argoMode_t IRArgoACBase::getModeEx(void) const { + switch (_.Mode) { + case kArgoCool: + return argoMode_t::COOL; + case kArgoDry: + return argoMode_t::DRY; + case kArgoAuto: + return argoMode_t::AUTO; + case kArgoHeat: + return argoMode_t::HEAT; + case kArgoOff: // Modelling "FAN" as "OFF", for the lack of better constant + return argoMode_t::FAN; + case kArgoHeatAuto: + default: + return static_cast(_.Mode); + } +} + +/// Get the current operation mode setting. +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function. +/// @return The current operation mode. +/// @note This @c getModeEx() method has been introduced to be able to retain +/// old implementation of @c getMode() for @c IRArgoAc which used uint8_t +/// @relates IRArgoACBase\ +template<> +argoMode_t IRArgoACBase::getModeEx(void) const { + return static_cast(_.Mode); +} + +/// @cond +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::setMode(argoMode_t mode) { + switch (mode) { + case argoMode_t::COOL: + _.Mode = static_cast(kArgoCool); + break; + case argoMode_t::DRY: + _.Mode = static_cast(kArgoDry); + break; + case argoMode_t::HEAT: + _.Mode = static_cast(kArgoHeat); + break; + case argoMode_t::FAN: + _.Mode = static_cast(kArgoOff); + break; + case argoMode_t::AUTO: + _.Mode = static_cast(kArgoAuto); + break; + default: + uint8_t raw_value = static_cast(mode); + if ((raw_value & 0b111) == raw_value) { + // Outside of known value range, but matches field length + // Let's assume the caller knows what they're doing and pass it through + _.Mode = raw_value; + } else { + _.Mode = static_cast(kArgoAuto); + } + break; + } +} +/// @endcond + +/// @brief Set the desired operation mode. +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] mode The desired operation mode. +/// @relates IRArgoACBase\ +template<> +void IRArgoACBase::setMode(argoMode_t mode) { + switch (mode) { + case argoMode_t::COOL: + case argoMode_t::DRY: + case argoMode_t::HEAT: + case argoMode_t::FAN: + case argoMode_t::AUTO: + _.Mode = static_cast(mode); + break; + default: + _.Mode = static_cast(argoMode_t::AUTO); + break; + } +} + +/// @brief Set the desired operation mode. +/// @deprecated +/// @param mode The desired operation mode. +void IRArgoAC::setMode(uint8_t mode) { + switch (mode) { + case kArgoCool: + case kArgoDry: + case kArgoAuto: + case kArgoOff: + case kArgoHeat: + case kArgoHeatAuto: + _.Mode = mode; + break; + default: + _.Mode = kArgoAuto; + break; + } +} + +/// @brief Get the current operation mode +/// @deprecated +/// @return The current operation mode +uint8_t IRArgoAC::getMode() const { return _.Mode;} + +argoFan_t IRArgoAC_WREM3::getFan(void) const { return getFanEx(); } +argoFlap_t IRArgoAC_WREM3::getFlap(void) const { return getFlapEx(); } +argoMode_t IRArgoAC_WREM3::getMode(void) const { return getModeEx(); } + +/// Turn on/off the Night mode. i.e. Sleep. +/// @param[in] on The desired setting. +template +void IRArgoACBase::setNight(const bool on) { _.Night = on; } + +/// Get the status of Night mode. i.e. Sleep. +/// @return true if on, false if off. +template +bool IRArgoACBase::getNight(void) const { return _.Night; } + +/// @brief Turn on/off the Economy mode (lowered power mode) +/// @param[in] on The desired setting. +void IRArgoAC_WREM3::setEco(const bool on) { _.Eco = on; } + +/// @brief Get the status of Economy function +/// @return true if on, false if off. +bool IRArgoAC_WREM3::getEco(void) const { return _.Eco; } + +/// @brief Turn on/off the Filter mode (not supported by Argo Ulisse) +/// @param[in] on The desired setting. +void IRArgoAC_WREM3::setFilter(const bool on) { _.Filter = on; } + +/// @brief Get status of the filter function +/// @return true if on, false if off. +bool IRArgoAC_WREM3::getFilter(void) const { return _.Filter; } + +/// @brief Turn on/off the device Lights (LED) +/// @param[in] on The desired setting. +void IRArgoAC_WREM3::setLight(const bool on) { _.Light = on; } + +/// @brief Get status of device lights +/// @return true if on, false if off. +bool IRArgoAC_WREM3::getLight(void) const { return _.Light; } + +/// @brief Set the IR channel on which to communicate +/// @param[in] channel The desired IR channel. +void IRArgoAC_WREM3::setChannel(const uint8_t channel) { + _.IrChannel = ::min(channel, kArgoMaxChannel); +} + +/// @brief Get the currently set transmission channel +/// @return Channel number +uint8_t IRArgoAC_WREM3::getChannel(void) const { return _.IrChannel;} + +/// @brief Set the config data to send +/// Valid only for @c argoIrMessageType_t::CONFIG_PARAM_SET message +/// @param paramId The param ID +/// @param value The value of the parameter +void IRArgoAC_WREM3::setConfigEntry(const uint8_t paramId, + const uint8_t value) { + _.config.Key = paramId; + _.config.Value = value; +} + +/// @brief Get the config entry previously set +/// @return Key->value pair (paramID: value) +std::pair IRArgoAC_WREM3::getConfigEntry(void) const { + return std::make_pair(_.config.Key, _.config.Value); +} + +/// Turn on/off the iFeel mode. +/// @param[in] on The desired setting. +template +void IRArgoACBase::setiFeel(const bool on) { _.iFeel = on; } + +/// Get the status of iFeel mode. +/// @return true if on, false if off. +template +bool IRArgoACBase::getiFeel(void) const { return _.iFeel; } + +/// @brief Set the message type of the next command (setting this resets state) +/// @param msgType The message type to set +template +void IRArgoACBase::setMessageType(const argoIrMessageType_t msgType) { + stateReset(msgType); +} + +/// @brief Get the message type +/// @return Message type currently set +template +argoIrMessageType_t IRArgoACBase::getMessageType(void) const { + return _messageType; +} + +/// Set the value for the current room temperature. +/// @note Depending on message type - this will set `sensor` or `roomTemp` value +/// @param[in] degrees The temperature in degrees celsius. +template +void IRArgoACBase::setSensorTemp(const uint8_t degrees) { + uint8_t temp = ::min(degrees, kArgoMaxRoomTemp); + temp = ::max(temp, kArgoTempDelta) - kArgoTempDelta; + if (getMessageType() == argoIrMessageType_t::IFEEL_TEMP_REPORT) { + _.SensorT = temp; + } else { + _.RoomTemp = temp; + } +} + +/// Get the currently stored value for the room temperature setting. +/// @note Depending on message type - this will get `sensor` or `roomTemp` value +/// @return The current setting for the room temp. in degrees celsius. +template +uint8_t IRArgoACBase::getSensorTemp(void) const { + if (getMessageType() == argoIrMessageType_t::IFEEL_TEMP_REPORT) { + return _.SensorT + kArgoTempDelta; + } + return _.RoomTemp + kArgoTempDelta; +} + +/// @brief Convert a stdAc::ac_command_t enum into its native message type. +/// @param command The enum to be converted. +/// @return The native equivalent of the enum. +template +argoIrMessageType_t IRArgoACBase::convertCommand( + const stdAc::ac_command_t command) { + switch (command) { + case stdAc::ac_command_t::kSensorTempReport: + return argoIrMessageType_t::IFEEL_TEMP_REPORT; + case stdAc::ac_command_t::kTimerCommand: + return argoIrMessageType_t::TIMER_COMMAND; + case stdAc::ac_command_t::kConfigCommand: + return argoIrMessageType_t::CONFIG_PARAM_SET; + case stdAc::ac_command_t::kControlCommand: + default: + return argoIrMessageType_t::AC_CONTROL; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +template +argoMode_t IRArgoACBase::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return argoMode_t::COOL; + case stdAc::opmode_t::kHeat: + return argoMode_t::HEAT; + case stdAc::opmode_t::kDry: + return argoMode_t::DRY; + case stdAc::opmode_t::kFan: + return argoMode_t::FAN; + case stdAc::opmode_t::kAuto: + default: // No off mode. + return argoMode_t::AUTO; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +template +argoFan_t IRArgoACBase::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + return argoFan_t::FAN_LOWEST; + case stdAc::fanspeed_t::kLow: + return argoFan_t::FAN_LOWER; + case stdAc::fanspeed_t::kMedium: + return argoFan_t::FAN_LOW; + case stdAc::fanspeed_t::kMediumHigh: + return argoFan_t::FAN_MEDIUM; + case stdAc::fanspeed_t::kHigh: + return argoFan_t::FAN_HIGH; + case stdAc::fanspeed_t::kMax: + return argoFan_t::FAN_HIGHEST; + default: + return argoFan_t::FAN_AUTO; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +template +argoFlap_t IRArgoACBase::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + return argoFlap_t::FLAP_1; + case stdAc::swingv_t::kHigh: + return argoFlap_t::FLAP_2; + case stdAc::swingv_t::kUpperMiddle: + return argoFlap_t::FLAP_3; + case stdAc::swingv_t::kMiddle: + return argoFlap_t::FLAP_4; + case stdAc::swingv_t::kLow: + return argoFlap_t::FLAP_5; + case stdAc::swingv_t::kLowest: + return argoFlap_t::FLAP_6; + case stdAc::swingv_t::kOff: // This is abusing the semantics quite a bit + return argoFlap_t::FLAP_FULL; + case stdAc::swingv_t::kAuto: + default: + return argoFlap_t::FLAP_AUTO; + } +} + +/// @cond +/// Convert a native flap mode into its stdAc equivalent (WREM2). +/// @note This is a full specialization for @c ArgoProtocol type and while +/// it semantically belongs to @c IrArgoAC class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] position The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +/// @relates IRArgoACBase\ +template<> +stdAc::swingv_t IRArgoACBase::toCommonSwingV( + const uint8_t position) { + switch (position) { + case kArgoFlapFull: + return stdAc::swingv_t::kHighest; + case kArgoFlap5: + return stdAc::swingv_t::kHigh; + case kArgoFlap4: + return stdAc::swingv_t::kMiddle; + case kArgoFlap3: + return stdAc::swingv_t::kLow; + case kArgoFlap1: + return stdAc::swingv_t::kLowest; + default: + return stdAc::swingv_t::kAuto; + } +} +/// @endcond + +/// Convert a native flap mode into its stdAc equivalent (WREM3). +/// @note This is a full specialization for @c ArgoProtocolWREM3 type and while +/// it semantically belongs to @c IrArgoAC_WREM3 class impl., it has *not* +/// been pushed there, to avoid having to use a virtual function +/// @param[in] position The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +/// @relates IRArgoACBase\ +template<> +stdAc::swingv_t IRArgoACBase::toCommonSwingV( + const uint8_t position) { + switch (static_cast(position)) { + case argoFlap_t::FLAP_FULL: + return stdAc::swingv_t::kOff; + case argoFlap_t::FLAP_6: + return stdAc::swingv_t::kHighest; + case argoFlap_t::FLAP_5: + return stdAc::swingv_t::kHigh; + case argoFlap_t::FLAP_4: + return stdAc::swingv_t::kUpperMiddle; + case argoFlap_t::FLAP_3: + return stdAc::swingv_t::kMiddle; + case argoFlap_t::FLAP_2: + return stdAc::swingv_t::kLow; + case argoFlap_t::FLAP_1: + return stdAc::swingv_t::kLowest; + case argoFlap_t::FLAP_AUTO: + default: + return stdAc::swingv_t::kAuto; + } +} + +/// Convert a native message type into its stdAc equivalent. +/// @param[in] command The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +template +stdAc::ac_command_t IRArgoACBase::toCommonCommand( + const argoIrMessageType_t command) { + switch (command) { + case argoIrMessageType_t::AC_CONTROL: + return stdAc::ac_command_t::kControlCommand; + case argoIrMessageType_t::IFEEL_TEMP_REPORT: + return stdAc::ac_command_t::kSensorTempReport; + case argoIrMessageType_t::TIMER_COMMAND: + return stdAc::ac_command_t::kTimerCommand; + case argoIrMessageType_t::CONFIG_PARAM_SET: + return stdAc::ac_command_t::kConfigCommand; + default: + return stdAc::ac_command_t::kControlCommand; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +template +stdAc::opmode_t IRArgoACBase::toCommonMode(const argoMode_t mode) { + switch (mode) { + case argoMode_t::COOL: return stdAc::opmode_t::kCool; + case argoMode_t::DRY : return stdAc::opmode_t::kDry; + case argoMode_t::FAN : return stdAc::opmode_t::kFan; + case argoMode_t::HEAT : return stdAc::opmode_t::kHeat; + case argoMode_t::AUTO : return stdAc::opmode_t::kAuto; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +template +stdAc::fanspeed_t IRArgoACBase::toCommonFanSpeed(const argoFan_t speed) { + switch (speed) { + case argoFan_t::FAN_AUTO: return stdAc::fanspeed_t::kAuto; + case argoFan_t::FAN_HIGHEST: return stdAc::fanspeed_t::kMax; + case argoFan_t::FAN_HIGH: return stdAc::fanspeed_t::kHigh; + case argoFan_t::FAN_MEDIUM: return stdAc::fanspeed_t::kMediumHigh; + case argoFan_t::FAN_LOW: return stdAc::fanspeed_t::kMedium; + case argoFan_t::FAN_LOWER: return stdAc::fanspeed_t::kLow; + case argoFan_t::FAN_LOWEST: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRArgoAC::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::ARGO; + result.model = argo_ac_remote_model_t::SAC_WREM2; + result.command = toCommonCommand(_messageType); + result.power = _.Power; + result.mode = toCommonMode(getModeEx()); + result.celsius = true; + result.degrees = getTemp(); + result.sensorTemperature = getSensorTemp(); + result.iFeel = getiFeel(); + result.fanspeed = toCommonFanSpeed(getFanEx()); + result.turbo = _.Max; + result.sleep = _.Night ? 0 : -1; + // Not supported. + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRArgoAC_WREM3::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::ARGO; + result.model = argo_ac_remote_model_t::SAC_WREM3; + result.command = toCommonCommand(_messageType); + result.power = getPower(); + result.mode = toCommonMode(getModeEx()); + result.celsius = true; + result.degrees = getTemp(); + result.sensorTemperature = getSensorTemp(); + result.iFeel = getiFeel(); + result.fanspeed = toCommonFanSpeed(getFanEx()); + result.turbo = _.Max; + result.swingv = toCommonSwingV(_.Flap); + result.light = getLight(); + result.filter = getFilter(); + result.econo = getEco(); + result.quiet = getNight(); + result.beep = (_messageType != argoIrMessageType_t::IFEEL_TEMP_REPORT); + + result.clock = -1; + result.sleep = _.Night ? 0 : -1; + if (_messageType == argoIrMessageType_t::TIMER_COMMAND) { + result.clock = getCurrentTimeMinutes(); + result.sleep = getDelayTimerMinutes(); + } + + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.clean = false; + + + return result; +} + + +namespace { + /// @brief Short-hand for casting enum to its underlying storage type + /// @tparam E The type of enum + /// @param e Enum value + /// @return Type of underlying value + template + constexpr typename std::underlying_type::type to_underlying(E e) noexcept { + return static_cast::type>(e); + } +} + +/// Convert the current internal state into a human readable string (WREM2). +/// @return A human readable string. +String IRArgoAC::toString(void) const { + String result = ""; + result.reserve(118); // Reserve some heap for the string to reduce fragging. + // E.g.: Model: 1 (WREM2), Power: On, Mode: 0 (Cool), Fan: 0 (Auto), + // Temp: 20C, Room Temp: 21C, Max: On, IFeel: On, Night: On + result += addModelToString(decode_type_t::ARGO, + argo_ac_remote_model_t::SAC_WREM2, false); + if (_messageType == argoIrMessageType_t::IFEEL_TEMP_REPORT) { + result += addIntToString(getSensorTemp(), kSensorTempStr); + result += 'C'; + } else { + result += addBoolToString(_.Power, kPowerStr); + result += addIntToString(_.Mode, kModeStr); + result += kSpaceLBraceStr; + switch (_.Mode) { + case kArgoAuto: + result += kAutoStr; + break; + case kArgoCool: + result += kCoolStr; + break; + case kArgoHeat: + result += kHeatStr; + break; + case kArgoDry: + result += kDryStr; + break; + case kArgoHeatAuto: + result += kHeatStr; + result += ' '; + result += kAutoStr; + break; + case kArgoOff: + result += kOffStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + switch (_.Fan) { + case kArgoFanAuto: + result += kAutoStr; + break; + case kArgoFan3: + result += kMaxStr; + break; + case kArgoFan1: + result += kMinStr; + break; + case kArgoFan2: + result += kMedStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addTempToString(getTemp()); + result += addTempToString(getSensorTemp(), true, true, true); + result += addBoolToString(_.Max, kMaxStr); + result += addBoolToString(_.iFeel, kIFeelStr); + result += addBoolToString(_.Night, kNightStr); + } + return result; +} + +/// @brief Set current clock (as minutes, counted from 0:00) +/// E.g. 13:38 becomes 818 (13*60+38) +/// @param currentTimeMinutes Current time (in minutes) +void IRArgoAC_WREM3::setCurrentTimeMinutes(uint16_t currentTimeMinutes) { + uint16_t time = ::min(currentTimeMinutes, static_cast(23*60+59)); + _.timer.CurrentTimeHi = (time >> 4); + _.timer.CurrentTimeLo = (time & 0b1111); +} + +/// @brief Retrieve current time +/// @return Current time as minutes from 0:00 +uint16_t IRArgoAC_WREM3::getCurrentTimeMinutes(void) const { + return (_.timer.CurrentTimeHi << 4) + _.timer.CurrentTimeLo; +} + +/// @brief Set current day of week +/// @param dayOfWeek Current day of week +void IRArgoAC_WREM3::setCurrentDayOfWeek(argoWeekday dayOfWeek) { + uint8_t day = ::min(to_underlying(dayOfWeek), + to_underlying(argoWeekday::SATURDAY)); + _.timer.CurrentWeekdayHi = (day >> 1); + _.timer.CurrentWeekdayLo = (day & 0b1); +} + +/// @brief Get current day of week +/// @return Current day of week +argoWeekday IRArgoAC_WREM3::getCurrentDayOfWeek(void) const { + return static_cast((_.timer.CurrentWeekdayHi << 1) + + _.timer.CurrentWeekdayLo); +} + +/// @brief Set timer type +/// @param timerType Timer type to use OFF | DELAY | SCHEDULE<1|2|3> +/// @note 2 timer types supported: delay | schedule timer +/// - @c DELAY_TIMER requires setting @c setDelayTimerMinutes +/// and @c setCurrentTimeMinutes and (optionally) @c setCurrentDayOfWeek +/// - @c SCHEDULE_TIMER requires setting: +/// @c setScheduleTimerStartMinutes +/// @c setScheduleTimerStopMinutes +/// @c setScheduleTimerActiveDays +/// as well as current time *and* day +/// @c setCurrentTimeMinutes and @c setCurrentDayOfWeek +void IRArgoAC_WREM3::setTimerType(const argoTimerType_t timerType) { + if (timerType > argoTimerType_t::SCHEDULE_TIMER_3) { + _.timer.TimerType = to_underlying(argoTimerType_t::NO_TIMER); + } else { + _.timer.TimerType = to_underlying(timerType); + } +} + +/// @brief Get currently set timer type +/// @return Timer type +argoTimerType_t IRArgoAC_WREM3::getTimerType(void) const { + return static_cast(_.timer.TimerType); +} + +/// @brief Set delay timer delay in minutes (10-minute increments only) +/// Max is 1190 (19h50m) +/// @note The delay timer also accepts current device state: set by @c setPower +/// @param delayMinutes Delay minutes +void IRArgoAC_WREM3::setDelayTimerMinutes(const uint16_t delayMinutes) { + const uint16_t DELAY_TIMER_MAX = 19*60+50; + uint16_t time = ::min(delayMinutes, DELAY_TIMER_MAX); + + // only full 10 minute increments are allowed + time = static_cast((time / 10.0) + 0.5) * 10; + + _.timer.DelayTimeHi = (time >> 6); + _.timer.DelayTimeLo = (time & 0b111111); +} + +/// @brief Get current delay timer value +/// @return Delay timer value (in minutes) +uint16_t IRArgoAC_WREM3::getDelayTimerMinutes(void) const { + return (_.timer.DelayTimeHi << 6) + _.timer.DelayTimeLo; +} + +/// @brief Set schedule timer on time (time when the device should turn on) +/// (10-minute increments only) +/// @param startTimeMinutes Time when the device should turn itself on +/// expressed as # of minutes counted from 0:00 +/// The value is in 10-minute increments (rounded) +/// E.g. 13:38 becomes 820 (13:40 in minutes) +void IRArgoAC_WREM3::setScheduleTimerStartMinutes( + const uint16_t startTimeMinutes) { + const uint16_t SCHEDULE_TIMER_MAX = 23*60+50; + uint16_t time = ::min(startTimeMinutes, SCHEDULE_TIMER_MAX); + + // only full 10 minute increments are allowed + time = static_cast((time / 10.0) + 0.5) * 10; + + _.timer.TimerStartHi = (time >> 3); + _.timer.TimerStartLo = (time & 0b111); +} + +/// @brief Get schedule timer ON time +/// @return Schedule on time (as # of minutes from 0:00) +uint16_t IRArgoAC_WREM3::getScheduleTimerStartMinutes(void) const { + return (_.timer.TimerStartHi << 3) + _.timer.TimerStartLo; +} + +/// @brief Set schedule timer off time (time when the device should turn off) +/// (10-minute increments only) +/// @param stopTimeMinutes Time when the device should turn itself off +/// expressed as # of minutes counted from 0:00 +/// The value is in 10-minute increments (rounded) +/// E.g. 13:38 becomes 820 (13:40 in minutes) +void IRArgoAC_WREM3::setScheduleTimerStopMinutes( + const uint16_t stopTimeMinutes) { + const uint16_t SCHEDULE_TIMER_MAX = 23*60+50; + uint16_t time = ::min(stopTimeMinutes, SCHEDULE_TIMER_MAX); + + // only full 10 minute increments are allowed + time = static_cast((time / 10.0) + 0.5) * 10; + + _.timer.TimerEndHi = (time >> 8); + _.timer.TimerEndLo = (time & 0b11111111); +} + +/// @brief Get schedule timer OFF time +/// @return Schedule off time (as # of minutes from 0:00) +uint16_t IRArgoAC_WREM3::getScheduleTimerStopMinutes(void) const { + return (_.timer.TimerEndHi << 8) + _.timer.TimerEndLo; +} + +/// @brief Get the days when shedule timer shall be active (as bitmap) +/// @return Days when schedule timer is active, as raw bitmap type +/// where bit[0] is Sunday, bit[1] -> Monday, ... +uint8_t IRArgoAC_WREM3::getTimerActiveDaysBitmap(void) const { + return (_.timer.TimerActiveDaysHi << 5) + _.timer.TimerActiveDaysLo; +} + +/// @brief Set the days when the schedule timer shall be active +/// @param days A set of days when the timer shall run +void IRArgoAC_WREM3::setScheduleTimerActiveDays( + const std::set& days) { + uint8_t daysBitmap = 0; + for (const argoWeekday& day : days) { + daysBitmap |= (0b1 << to_underlying(day)); + } + _.timer.TimerActiveDaysHi = (daysBitmap >> 5); + _.timer.TimerActiveDaysLo = (daysBitmap & 0b11111); +} + +/// @brief Get the days when shedule timer shall be active (as set) +/// @return Days when the schedule timer runs +std::set IRArgoAC_WREM3::getScheduleTimerActiveDays(void) const { + std::set result = {}; + uint8_t daysBitmap = getTimerActiveDaysBitmap(); + for (uint8_t i = to_underlying(argoWeekday::SUNDAY); + i <= to_underlying(argoWeekday::SATURDAY); + ++i) { + if (((daysBitmap >> i) & 0b1) == 0b1) { + result.insert(static_cast(i)); + } + } + return result; +} + +/// @brief Get device model +/// @return Device model +argo_ac_remote_model_t IRArgoAC_WREM3::getModel() const { + return argo_ac_remote_model_t::SAC_WREM3; +} + +namespace { + String commandTypeToString(argoIrMessageType_t type, uint8_t channel) { + String result = irutils::irCommandTypeToString(to_underlying(type), + to_underlying(argoIrMessageType_t::AC_CONTROL), + to_underlying(argoIrMessageType_t::IFEEL_TEMP_REPORT), + to_underlying(argoIrMessageType_t::TIMER_COMMAND), + to_underlying(argoIrMessageType_t::CONFIG_PARAM_SET)); + result += irutils::channelToString(channel); + result += kColonSpaceStr; + return result; + } +} // namespace + +/// Convert the current internal state into a human readable string (WREM3). +/// @return A human readable string. +String IRArgoAC_WREM3::toString(void) const { + String result = ""; + result.reserve(190); // Reserve some heap for the string to reduce fragging. + // E.g.: Command[CH#0]: Model: 2 (WREM3), Power: On, Mode: 1 (Cool), + // Temp: 22C, Room: 26C, Fan: 0 (Auto), Swing(V): 7 (Breeze), + // IFeel: Off, Night: Off, Econo: Off, Max: Off, Filter: Off, Light: On + // Temp: 20C, Room Temp: 21C, Max: On, IFeel: On, Night: On + + argoIrMessageType_t commandType = this->getMessageType(); + argo_ac_remote_model_t model = getModel(); + + result += commandTypeToString(commandType, getChannel()); + result += addModelToString(decode_type_t::ARGO, model, false); + + switch (commandType) { + case argoIrMessageType_t::IFEEL_TEMP_REPORT: + result += addTempToString(getSensorTemp(), true, true, true); + break; + + case argoIrMessageType_t::AC_CONTROL: + result += addBoolToString(getPower(), kPowerStr); + result += addModeToString(to_underlying(getModeEx()), + to_underlying(argoMode_t::AUTO), + to_underlying(argoMode_t::COOL), + to_underlying(argoMode_t::HEAT), + to_underlying(argoMode_t::DRY), + to_underlying(argoMode_t::FAN)); + result += addTempToString(getTemp()); + result += addTempToString(getSensorTemp(), true, true, true); + result += addFanToString(to_underlying(getFanEx()), + to_underlying(argoFan_t::FAN_HIGH), + to_underlying(argoFan_t::FAN_LOWER), + to_underlying(argoFan_t::FAN_AUTO), + to_underlying(argoFan_t::FAN_LOWEST), + to_underlying(argoFan_t::FAN_LOW), + to_underlying(argoFan_t::FAN_HIGHEST), + to_underlying(argoFan_t::FAN_MEDIUM)); + result += addSwingVToString(to_underlying(getFlapEx()), + to_underlying(argoFlap_t::FLAP_AUTO), + to_underlying(argoFlap_t::FLAP_1), + to_underlying(argoFlap_t::FLAP_2), + to_underlying(argoFlap_t::FLAP_3), + to_underlying(argoFlap_t::FLAP_4), -1, + to_underlying(argoFlap_t::FLAP_5), + to_underlying(argoFlap_t::FLAP_6), -1, -1, + to_underlying(argoFlap_t::FLAP_FULL), -1); + result += addBoolToString(getiFeel(), kIFeelStr); + result += addBoolToString(getNight(), kNightStr); + result += addBoolToString(getEco(), kEconoStr); + result += addBoolToString(getMax(), kMaxStr); // Turbo + result += addBoolToString(getFilter(), kFilterStr); + result += addBoolToString(getLight(), kLightStr); + break; + + case argoIrMessageType_t::TIMER_COMMAND: + result += addBoolToString(_.timer.IsOn, kPowerStr); + result += addTimerModeToString(to_underlying(getTimerType()), + to_underlying(argoTimerType_t::NO_TIMER), + to_underlying(argoTimerType_t::DELAY_TIMER), + to_underlying(argoTimerType_t::SCHEDULE_TIMER_1), + to_underlying(argoTimerType_t::SCHEDULE_TIMER_2), + to_underlying(argoTimerType_t::SCHEDULE_TIMER_3)); + result += addLabeledString(minsToString(getCurrentTimeMinutes()), + kClockStr); + result += addDayToString(to_underlying(getCurrentDayOfWeek())); + switch (getTimerType()) { + case argoTimerType_t::NO_TIMER: + result += addLabeledString(kOffStr, kTimerStr); + break; + case argoTimerType_t::DELAY_TIMER: + result += addLabeledString(minsToString(getDelayTimerMinutes()), + kTimerStr); + break; + default: + result += addLabeledString(minsToString(getScheduleTimerStartMinutes()), + kOnTimerStr); + result += addLabeledString(minsToString(getScheduleTimerStopMinutes()), + kOffTimerStr); + + result += addLabeledString(daysBitmaskToString( + getTimerActiveDaysBitmap()), kTimerActiveDaysStr); + break; + } + break; + + case argoIrMessageType_t::CONFIG_PARAM_SET: + result += addIntToString(_.config.Key, kKeyStr); + result += addIntToString(_.config.Value, kValueStr); + break; + } + + return result; +} + +/// @brief Check if raw ARGO state starts with valid WREM3 preamble +/// @param state The state bytes +/// @param length Length of state in bytes +/// @return True if state starts wiht valid WREM3 preamble, False otherwise +bool IRArgoAC_WREM3::hasValidPreamble(const uint8_t state[], + const uint16_t length) { + if (length < 1) { + return false; + } + uint8_t preamble = state[0] & 0x0F; + return preamble == kArgoWrem3Preamble; +} + +#if DECODE_ARGO +/// Decode the supplied Argo message (WREM2). +/// Status: BETA / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @note This decoder is based soley off sendArgo(). We have no actual captures +/// to test this against. If you have one of these units, please let us know. +bool IRrecv::decodeArgo(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (strict && nbits != kArgoBits) return false; + + // Match Header + Data + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kArgoHdrMark, kArgoHdrSpace, + kArgoBitMark, kArgoOneSpace, + kArgoBitMark, kArgoZeroSpace, + 0, 0, // Footer (None, allegedly. This seems very wrong.) + true, _tolerance, 0, false)) return false; + + // Compliance + // Verify we got a valid checksum. + if (strict && !IRArgoAC::validChecksum(results->state, kArgoStateLength)) { + return false; + } + // Success + results->decode_type = decode_type_t::ARGO; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} + +/// Decode the supplied Argo message (WREM3). +/// Status: Confirmed working w/ Argo 13 ECO (WREM-3) +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @note This decoder is separate from @c decodeArgo to maintain backwards +/// compatibility. Contrary to WREM2, this expects a footer and gap! +bool IRrecv::decodeArgoWREM3(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (strict + && nbits != kArgo3AcControlStateLength * 8 + && nbits != kArgo3ConfigStateLength * 8 + && nbits != kArgo3iFeelReportStateLength * 8 + && nbits != kArgo3TimerStateLength * 8) { + return false; + } + + uint16_t bytesRead = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kArgoHdrMark, kArgoHdrSpace, + kArgoBitMark, kArgoOneSpace, + kArgoBitMark, kArgoZeroSpace, + kArgoBitMark, kArgoGap, // difference vs decodeArgo + true, _tolerance, 0, + false); + if (!bytesRead) { + return false; + } + + // If 'strict', assert it is a valid WREM-3 'model' protocolar message + // vs. just 'any ARGO' + if (strict && + !IRArgoAC_WREM3::isValidWrem3Message(results->state, nbits, true)) { + return false; + } + + // Success: Matched ARGO protocol and WREM3-model + // Note that unfortunately decode_type does not allow to persist model... + // so we will be re-detecting it later :) + results->decode_type = decode_type_t::ARGO; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} + +/// @brief Detects if an ARGO protocol message is a WREM-3 sub-type (model) +/// @param state The raw IR decore state +/// @param nbits The length of @c state **IN BITS** +/// @param verifyChecksum Whether to perform checksum verification +/// @return True if the message is a WREM-3 one +bool IRArgoAC_WREM3::isValidWrem3Message(const uint8_t state[], + const uint16_t nbits, + bool verifyChecksum) { + if ((nbits % 8) != 0) { + return false; // WREM-3 protocol always has a full byte length commands + } + uint16_t stateLengthBytes = ::min(static_cast(nbits / 8), + kStateSizeMax); + if (!IRArgoAC_WREM3::hasValidPreamble(state, stateLengthBytes)) { + return false; + } + + argoIrMessageType_t messageType = IRArgoACBase + ::getMessageType(state, stateLengthBytes); + + switch (messageType) { + case argoIrMessageType_t::AC_CONTROL : + if (stateLengthBytes != kArgo3AcControlStateLength) { return false; } + break; + case argoIrMessageType_t::CONFIG_PARAM_SET: + if (stateLengthBytes != kArgo3ConfigStateLength) { return false; } + break; + case argoIrMessageType_t::TIMER_COMMAND: + if (stateLengthBytes != kArgo3TimerStateLength) { return false; } + break; + case argoIrMessageType_t::IFEEL_TEMP_REPORT: + if (stateLengthBytes != kArgo3iFeelReportStateLength) { return false; } + break; + default: + return false; + } + + // Compliance: Verify we got a valid checksum. + if (verifyChecksum && + !IRArgoAC_WREM3::validChecksum(state, stateLengthBytes)) { + return false; + } + return true; +} + +#endif // DECODE_ARGO + + +// force template instantiation +template class IRArgoACBase; +template class IRArgoACBase; diff --git a/src/libraries/IRremoteESP8266/src/ir_Argo.h b/src/libraries/IRremoteESP8266/src/ir_Argo.h new file mode 100644 index 000000000..772b2d038 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Argo.h @@ -0,0 +1,521 @@ +// Copyright 2017 Schmolders +// Copyright 2022 crankyoldgit +// Copyright 2022 Mateusz Bronk (mbronk) +/// @file +/// @brief Support for Argo Ulisse 13 DCI Mobile Split ACs. + +// Supports: +// Brand: Argo, Model: Ulisse 13 DCI Mobile Split A/C [WREM2 remote] +// Brand: Argo, Model: Ulisse Eco Mobile Split A/C (Wifi) [WREM3 remote] + +#ifndef IR_ARGO_H_ +#define IR_ARGO_H_ + +#include +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + + +// ARGO Ulisse DCI + +/// Native representation of a Argo A/C message for WREM-2 remote. +union ArgoProtocol { + uint8_t raw[kArgoStateLength]; ///< The state in native IR code form + struct { + // Byte 0 + uint64_t Pre1 :8; // Typically 0b00110101 + // Byte 1 + uint64_t Pre2 :8; // Typically 0b10101111 + // Byte 2~4 + uint64_t :3; + uint64_t Mode :3; + uint64_t Temp :5; // straddle byte 2 and 3 + uint64_t Fan :2; + uint64_t RoomTemp :5; // straddle byte 3 and 4 + uint64_t Flap :3; // SwingV + uint64_t :3; // OnTimer, maybe hours + // Byte 5 + uint64_t :8; // OnTimer, maybe minutes + // Byte 6 + uint64_t :8; // OffTimer, maybe minutes + // Byte 7 + uint64_t :3; // OffTimer, maybe hours + uint64_t :5; // Time + // Byte 8 + uint32_t :6; // Time + uint32_t :1; // Timer On/Off + uint32_t :1; // Timer Program + // Byte 9 + uint32_t :1; // Timer Program + uint32_t :1; // Timer 1h + uint32_t Night :1; + uint32_t Max :1; + uint32_t :1; // Filter + uint32_t Power :1; + uint32_t :1; // const 0 + uint32_t iFeel :1; + // Byte 10~11 + uint32_t Post :2; + uint32_t Sum :8; // straddle byte 10 and 11 + uint32_t :6; + }; + struct { + // Byte 0-1 + uint8_t :8; + uint8_t :8; + // Byte 2-3 + uint8_t CheckHi :3; + uint8_t SensorT :5; + uint8_t Fixed :3; // Typically 0b011 + uint8_t CheckLo :5; + }; +}; + +/// Native representation of A/C IR message for WREM-3 remote +/// @note The remote sends 4 different IR command types, varying in length +/// and methods of checksum calculation +/// - [0b00] Regular A/C command (change operation mode) - 6-byte +/// - [0b01] iFeel Temperature report - 2-byte +/// - [0b10] Timer command - 9-byte +/// - [0b11] Config command - 4-byte +/// @note The 1st 2 structures are unnamed for compat. with @c ArgoProtocol +/// 1st byte definition is a header common across all commands though +union ArgoProtocolWREM3 { + uint8_t raw[kArgoStateLength]; ///< The state in native IR code form + struct { + // Byte 0 (same definition across the union) + uint8_t Pre1 :4; /// Preamble: 0b1011 @ref kArgoWrem3Preamble + uint8_t IrChannel :2; /// 0..3 range + uint8_t IrCommandType :2; /// @ref argoIrMessageType_t + // Byte 1 + uint8_t RoomTemp :5; // in Celsius, range: 4..35 (offset by -4[*C]) + uint8_t Mode :3; /// @ref argoMode_t + // Byte 2 + uint8_t Temp :5; // in Celsius, range: 10..32 (offset by -4[*C]) + uint8_t Fan :3; /// @ref argoFan_t + // Byte3 + uint8_t Flap :3; /// SwingV @ref argoFlap_t + uint8_t Power :1; + uint8_t iFeel :1; + uint8_t Night :1; + uint8_t Eco :1; + uint8_t Max :1; ///< a.k.a. Turbo + // Byte4 + uint8_t Filter :1; + uint8_t Light :1; + uint8_t Post1 :6; /// Unknown, always 0b110000 (TempScale?) + // Byte5 + uint8_t Sum :8; /// Checksum + }; + struct { + // Byte 0 (same definition across the union) + uint8_t :8; // {Pre1 | IrChannel | IrCommandType} + // Byte 1 + uint8_t SensorT :5; // in Celsius, range: 4..35 (offset by -4[*C]) + uint8_t CheckHi :3; // Checksum (short) + }; + struct Timer { + // Byte 0 (same definition across the union) + uint8_t : 8; // {Pre1 | IrChannel | IrCommandType} + // Byte 1 + uint8_t IsOn : 1; + uint8_t TimerType : 3; + uint8_t CurrentTimeLo : 4; + // Byte 2 + uint8_t CurrentTimeHi : 7; + uint8_t CurrentWeekdayLo : 1; + // Byte 3 + uint8_t CurrentWeekdayHi : 2; + uint8_t DelayTimeLo : 6; + // Byte 4 + uint8_t DelayTimeHi : 5; + uint8_t TimerStartLo : 3; + // Byte 5 + uint8_t TimerStartHi : 8; + // Byte 6 + uint8_t TimerEndLo : 8; + // Byte 7 + uint8_t TimerEndHi : 3; + uint8_t TimerActiveDaysLo : 5; // Bitmap (LSBit is Sunday) + // Byte 8 + uint8_t TimerActiveDaysHi : 2; // Bitmap (LSBit is Sunday) + uint8_t Post1 : 1; // Unknown, always 1 + uint8_t Checksum : 5; + } timer; + struct Config { + uint8_t :8; // Byte 0 {Pre1 | IrChannel | IrCommandType} + uint8_t Key :8; // Byte 1 + uint8_t Value :8; // Byte 2 + uint8_t Checksum :8; // Byte 3 + } config; +}; + +// Constants (WREM-2). Store MSB left. +const uint8_t kArgoHeatBit = 0b00100000; +const uint8_t kArgoPreamble1 = 0b10101100; +const uint8_t kArgoPreamble2 = 0b11110101; +const uint8_t kArgoPost = 0b00000010; + +// Constants (generic) +const uint16_t kArgoFrequency = 38000; // Hz +// Temp +const uint8_t kArgoTempDelta = 4; +const uint8_t kArgoMaxRoomTemp = 35; // Celsius +const uint8_t kArgoMinTemp = 10; // Celsius delta +4 +const uint8_t kArgoMaxTemp = 32; // Celsius +const uint8_t kArgoMaxChannel = 3; + + +/// @brief IR message type (determines the payload part of IR command) +/// @note Raw values match WREM-3 protocol, but the enum is used in generic +/// context +/// @note WREM-3 remote supports all commands separately, whereas +/// WREM-2 (allegedly) only has the @c AC_CONTROL and @c IFEEL_TEMP_REPORT +/// (timers are part of @c AC_CONTROL command), and there's no config. +enum class argoIrMessageType_t : uint8_t { + AC_CONTROL = 0b00, + IFEEL_TEMP_REPORT = 0b01, + TIMER_COMMAND = 0b10, // WREM-3 only (WREM-2 has it under AC_CONTROL) + CONFIG_PARAM_SET = 0b11 // WREM-3 only +}; + +/// @brief A/C operation mode +/// @note Raw values match WREM-3 protocol, but the enum is used in generic +/// context +enum class argoMode_t : uint8_t { + COOL = 0b001, + DRY = 0b010, + HEAT = 0b011, + FAN = 0b100, + AUTO = 0b101 +}; + +// Raw mode definitions for WREM-2 remote +// (not wraped into a ns nor enum for backwards-compat.) +const uint8_t kArgoCool = 0b000; +const uint8_t kArgoDry = 0b001; +const uint8_t kArgoAuto = 0b010; +const uint8_t kArgoOff = 0b011; +const uint8_t kArgoHeat = 0b100; +const uint8_t kArgoHeatAuto = 0b101; +// ?no idea what mode that is +const uint8_t kArgoHeatBlink = 0b110; + +/// @brief Fan speed +/// @note Raw values match WREM-3 protocol, but the enum is used in generic +/// context +enum class argoFan_t : uint8_t { + FAN_AUTO = 0b000, + FAN_LOWEST = 0b001, + FAN_LOWER = 0b010, + FAN_LOW = 0b011, + FAN_MEDIUM = 0b100, + FAN_HIGH = 0b101, + FAN_HIGHEST = 0b110 +}; + +// Raw fan speed definitions for WREM-2 remote +// (not wraped into a ns nor enum for backwards-compat.) +const uint8_t kArgoFanAuto = 0; // 0b00 +const uint8_t kArgoFan1 = 1; // 0b01 +const uint8_t kArgoFan2 = 2; // 0b10 +const uint8_t kArgoFan3 = 3; // 0b11 + +/// @brief Flap position (swing-V) +/// @note Raw values match WREM-3 protocol, but the enum is used in generic +/// context +enum class argoFlap_t : uint8_t { + FLAP_AUTO = 0, + FLAP_1 = 1, // Highest + FLAP_2 = 2, + FLAP_3 = 3, + FLAP_4 = 4, + FLAP_5 = 5, + FLAP_6 = 6, // Lowest + FLAP_FULL = 7 +}; + +// Raw Flap/SwingV definitions for WREM-2 remote +// (not wraped into a ns nor enum for backwards-compat.) +const uint8_t kArgoFlapAuto = 0; +const uint8_t kArgoFlap1 = 1; +const uint8_t kArgoFlap2 = 2; +const uint8_t kArgoFlap3 = 3; +const uint8_t kArgoFlap4 = 4; +const uint8_t kArgoFlap5 = 5; +const uint8_t kArgoFlap6 = 6; +const uint8_t kArgoFlapFull = 7; + +// Legacy defines. (Deprecated) +#define ARGO_COOL_ON kArgoCoolOn +#define ARGO_COOL_OFF kArgoCoolOff +#define ARGO_COOL_AUTO kArgoCoolAuto +#define ARGO_COOL_HUM kArgoCoolHum +#define ARGO_HEAT_ON kArgoHeatOn +#define ARGO_HEAT_AUTO kArgoHeatAuto +#define ARGO_HEAT_BLINK kArgoHeatBlink +#define ARGO_MIN_TEMP kArgoMinTemp +#define ARGO_MAX_TEMP kArgoMaxTemp +#define ARGO_FAN_AUTO kArgoFanAuto +#define ARGO_FAN_3 kArgoFan3 +#define ARGO_FAN_2 kArgoFan2 +#define ARGO_FAN_1 kArgoFan1 +#define ARGO_FLAP_AUTO kArgoFlapAuto +#define ARGO_FLAP_1 kArgoFlap1 +#define ARGO_FLAP_2 kArgoFlap2 +#define ARGO_FLAP_3 kArgoFlap3 +#define ARGO_FLAP_4 kArgoFlap4 +#define ARGO_FLAP_5 kArgoFlap5 +#define ARGO_FLAP_6 kArgoFlap6 +#define ARGO_FLAP_FULL kArgoFlapFull + + +/// @brief Timer type to set (for @c argoIrMessageType_t::TIMER_COMMAND) +/// @note Raw values match WREM-3 protocol +enum class argoTimerType_t : uint8_t { + NO_TIMER = 0b000, + DELAY_TIMER = 0b001, + SCHEDULE_TIMER_1 = 0b010, + SCHEDULE_TIMER_2 = 0b011, + SCHEDULE_TIMER_3 = 0b100 +}; + +/// @brief Day type to set (for @c argoIrMessageType_t::TIMER_COMMAND) +/// @note Raw values match WREM-3 protocol +enum class argoWeekday : uint8_t { + SUNDAY = 0b000, + MONDAY = 0b001, + TUESDAY = 0b010, + WEDNESDAY = 0b011, + THURSDAY = 0b100, + FRIDAY = 0b101, + SATURDAY = 0b110 +}; + + + +/// @brief Base class for handling *common* support for Argo remote protocols +/// (functionality is shared across WREM-2 and WREM-3 IR protocols) +/// @note This class uses static polymorphism and full template specializations +/// when required, to avoid a performance penalty of doing v-table lookup. +/// 2 instantiations are forced in impl. file: for @c ArgoProtocol and +/// @c ArgoProtocolWREM3 +/// @note This class is abstract (though does not declare a pure-virtual fn. +/// for abovementioned reasons), and instead declares protected c-tor +/// @tparam ARGO_PROTOCOL_T The Raw device protocol/message used +template +class IRArgoACBase { +#ifndef UNIT_TEST // A less cloggy way of expressing FRIEND_TEST(...) + + protected: +#else + + public: +#endif + explicit IRArgoACBase(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + public: +#if SEND_ARGO + void send(const uint16_t repeat = kArgoDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_ARGO + + void begin(void); + void on(void); + void off(void); + + void setPower(const bool on); + bool getPower(void) const; + + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + + void setSensorTemp(const uint8_t degrees); + uint8_t getSensorTemp(void) const; + + void setFan(const argoFan_t fan); + void setFanEx(const argoFan_t fan) { setFan(fan); } + argoFan_t getFanEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC + + void setFlap(const argoFlap_t flap); + void setFlapEx(const argoFlap_t flap) { setFlap(flap); } + argoFlap_t getFlapEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC + + void setMode(const argoMode_t mode); + void setModeEx(const argoMode_t mode) { setMode(mode); } + argoMode_t getModeEx(void) const; ///< `-Ex` for backw. compat w/ @c IRArgoAC + + void setMax(const bool on); + bool getMax(void) const; + + void setNight(const bool on); + bool getNight(void) const; + + void setiFeel(const bool on); + bool getiFeel(void) const; + + void setMessageType(const argoIrMessageType_t msgType); + argoIrMessageType_t getMessageType(void) const; + static argoIrMessageType_t getMessageType(const uint8_t state[], + const uint16_t length); + + uint8_t* getRaw(void); + uint16_t getRawByteLength() const; + static uint16_t getStateLengthForIrMsgType(argoIrMessageType_t type); + void setRaw(const uint8_t state[], const uint16_t length); + + static bool validChecksum(const uint8_t state[], const uint16_t length); + + static argoMode_t convertMode(const stdAc::opmode_t mode); + static argoFan_t convertFan(const stdAc::fanspeed_t speed); + static argoFlap_t convertSwingV(const stdAc::swingv_t position); + static argoIrMessageType_t convertCommand(const stdAc::ac_command_t command); + + protected: + void _stateReset(ARGO_PROTOCOL_T *state, argoIrMessageType_t messageType + = argoIrMessageType_t::AC_CONTROL); + void stateReset(argoIrMessageType_t messageType + = argoIrMessageType_t::AC_CONTROL); + void _checksum(ARGO_PROTOCOL_T *state); + void checksum(void); + static uint16_t getRawByteLength(const ARGO_PROTOCOL_T& raw, + argoIrMessageType_t messageTypeHint = argoIrMessageType_t::AC_CONTROL); + static uint8_t calcChecksum(const uint8_t state[], const uint16_t length); + static uint8_t getChecksum(const uint8_t state[], const uint16_t length); + + static stdAc::opmode_t toCommonMode(const argoMode_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const argoFan_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t position); + static stdAc::ac_command_t toCommonCommand(const argoIrMessageType_t command); + + // Attributes + ARGO_PROTOCOL_T _; ///< The raw protocol data + uint16_t _length = kArgoStateLength; + argoIrMessageType_t _messageType = argoIrMessageType_t::AC_CONTROL; + +#ifndef UNIT_TEST + + protected: + IRsend _irsend; ///< instance of the IR send class +#else + + public: + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif +}; + +/// @brief Supports Argo A/C SAC-WREM2 IR remote protocol +class IRArgoAC : public IRArgoACBase { + public: + explicit IRArgoAC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + #if SEND_ARGO + void sendSensorTemp(const uint8_t degrees, + const uint16_t repeat = kArgoDefaultRepeat); + #endif // SEND_ARGO + + String toString(void) const; + stdAc::state_t toCommon(void) const; + + using IRArgoACBase::setMode; + void setMode(const uint8_t mode); /// @deprecated, for backwards-compat. + uint8_t getMode(void) const; /// @deprecated, for backwards-compat. + + using IRArgoACBase::setFan; + void setFan(const uint8_t fan); /// @deprecated, for backwards-compat. + uint8_t getFan(void) const; /// @deprecated, for backwards-compat. + + using IRArgoACBase::setFlap; + void setFlap(const uint8_t flap); /// @deprecated, for backwards-compat. + uint8_t getFlap(void) const; /// @deprecated, for backwards-compat. +}; + +/// @brief Supports Argo A/C SAC-WREM3 IR remote protocol +class IRArgoAC_WREM3 : public IRArgoACBase { + public: + explicit IRArgoAC_WREM3(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + #if SEND_ARGO + void sendSensorTemp(const uint8_t degrees, + const uint16_t repeat = kArgoDefaultRepeat); + #endif // SEND_ARGO + + argo_ac_remote_model_t getModel(void) const; + + + argoFan_t getFan(void) const; + argoFlap_t getFlap(void) const; + argoMode_t getMode(void) const; + + void setEco(const bool on); + bool getEco(void) const; + + void setFilter(const bool on); + bool getFilter(void) const; + + void setLight(const bool on); + bool getLight(void) const; + + void setChannel(const uint8_t channel); + uint8_t getChannel(void) const; + + void setConfigEntry(const uint8_t paramId, const uint8_t value); + std::pair getConfigEntry(void) const; + + void setCurrentTimeMinutes(uint16_t currentTimeMinutes); + uint16_t getCurrentTimeMinutes(void) const; + + void setCurrentDayOfWeek(argoWeekday dayOfWeek); + argoWeekday getCurrentDayOfWeek(void) const; + + void setTimerType(const argoTimerType_t timerType); + argoTimerType_t getTimerType(void) const; + + void setDelayTimerMinutes(const uint16_t delayMinutes); + uint16_t getDelayTimerMinutes(void) const; + + void setScheduleTimerStartMinutes(const uint16_t startTimeMinutes); + uint16_t getScheduleTimerStartMinutes(void) const; + // uint16_t getTimerXStartMinutes(void) const + + void setScheduleTimerStopMinutes(const uint16_t stopTimeMinutes); + uint16_t getScheduleTimerStopMinutes(void) const; + // uint16_t getTimerXStopMinutes(void) const; + + + void setScheduleTimerActiveDays(const std::set& days); + std::set getScheduleTimerActiveDays(void) const; + uint8_t getTimerActiveDaysBitmap(void) const; + + using IRArgoACBase::getMessageType; + static argoIrMessageType_t getMessageType(const ArgoProtocolWREM3& raw); + + String toString(void) const; + stdAc::state_t toCommon(void) const; + + static bool hasValidPreamble(const uint8_t state[], const uint16_t length); + + public: +#if DECODE_ARGO + static bool isValidWrem3Message(const uint8_t state[], const uint16_t nbits, + bool verifyChecksum = true); +#endif +}; + +#endif // IR_ARGO_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Arris.cpp b/src/libraries/IRremoteESP8266/src/ir_Arris.cpp new file mode 100644 index 000000000..5b39808c8 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Arris.cpp @@ -0,0 +1,123 @@ +// Copyright 2021 David Conran +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +/// @file +/// @brief Arris "Manchester code" based protocol. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595 + +// Supports: +// Brand: Arris, Model: VIP1113M Set-top box +// Brand: Arris, Model: 120A V1.0 A18 remote + +const uint8_t kArrisOverhead = 2; +const uint16_t kArrisHalfClockPeriod = 320; // uSeconds +const uint16_t kArrisHdrMark = 8 * kArrisHalfClockPeriod; // uSeconds +const uint16_t kArrisHdrSpace = 6 * kArrisHalfClockPeriod; // uSeconds +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595#issuecomment-913755841 +// aka. 77184 uSeconds. +const uint32_t kArrisGapSpace = 102144 - ((8 + 6 + kArrisBits * 2) * + kArrisHalfClockPeriod); // uSeconds +const uint32_t kArrisReleaseToggle = 0x800008; +const uint8_t kArrisChecksumSize = 4; +const uint8_t kArrisCommandSize = 19; +const uint8_t kArrisReleaseBit = kArrisChecksumSize + kArrisCommandSize; + +using irutils::sumNibbles; + +#if SEND_ARRIS +/// Send an Arris Manchester Code formatted message. +/// Status: STABLE / Confirmed working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of the message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595 +void IRsend::sendArris(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + enableIROut(38); + for (uint16_t r = 0; r <= repeat; r++) { + // Header (part 1) + mark(kArrisHdrMark); + space(kArrisHdrSpace); + // Header (part 2) + Data + Footer + sendManchester(kArrisHalfClockPeriod * 2, 0, kArrisHalfClockPeriod, + 0, kArrisGapSpace, data, nbits); + } +} + +/// Flip the toggle button release bits of an Arris message. +/// Used to indicate a change of remote button's state. e.g. Press vs. Release. +/// @param[in] data The existing Arris message. +/// @return A data message suitable for use in sendArris() with the release bits +/// flipped. +uint32_t IRsend::toggleArrisRelease(const uint32_t data) { + return data ^ kArrisReleaseToggle; +} + +/// Construct a raw 32-bit Arris message code from the supplied command & +/// release setting. +/// @param[in] command The command code. +/// @param[in] release The button/command action: press (false), release (true) +/// @return A raw 32-bit Arris message code suitable for sendArris() etc. +/// @note Sequence of bits = header + release + command + checksum. +uint32_t IRsend::encodeArris(const uint32_t command, const bool release) { + uint32_t result = 0x10000000; + irutils::setBits(&result, kArrisChecksumSize, kArrisCommandSize, command); + irutils::setBit(&result, kArrisReleaseBit, release); + return result + sumNibbles(result); +} +#endif // SEND_ARRIS + +#if DECODE_ARRIS +/// Decode the supplied Arris "Manchester code" message. +/// Status: STABLE / Confirmed working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595 +bool IRrecv::decodeArris(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < nbits + kArrisOverhead - offset) + return false; // Too short a message to match. + + // Compliance + if (strict && nbits != kArrisBits) + return false; // Doesn't match our protocol defn. + + // Header (part 1) + if (!matchMark(results->rawbuf[offset++], kArrisHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kArrisHdrSpace)) return false; + + // Header (part 2) + Data + uint64_t data = 0; + if (!matchManchester(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kArrisHalfClockPeriod * 2, 0, + kArrisHalfClockPeriod, 0, 0, + false, kUseDefTol, kMarkExcess, true, false)) + return false; + + // Compliance + if (strict) + // Validate the checksum. + if (GETBITS32(data, 0, kArrisChecksumSize) != + sumNibbles(data >> kArrisChecksumSize)) + return false; + + // Success + results->decode_type = decode_type_t::ARRIS; + results->bits = nbits; + results->value = data; + // Set the address as the Release Bit for something useful. + results->address = static_cast(GETBIT32(data, kArrisReleaseBit)); + // The last 4 bits are likely a checksum value, so skip those. Everything else + // after the release bit. e.g. Bits 10-28 + results->command = GETBITS32(data, kArrisChecksumSize, kArrisCommandSize); + return true; +} +#endif // DECODE_ARRIS diff --git a/src/libraries/IRremoteESP8266/src/ir_Bosch.cpp b/src/libraries/IRremoteESP8266/src/ir_Bosch.cpp new file mode 100644 index 000000000..dd0041a05 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Bosch.cpp @@ -0,0 +1,332 @@ +// Copyright 2022 David Conran +// Copyright 2022 Nico Thien +/// @file +/// @brief Support for the Bosch A/C / heatpump protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787 + +#include "ir_Bosch.h" +#include "minmax.h" + +#if SEND_BOSCH144 +/// Send a Bosch 144-bit / 18-byte message (96-bit message are also possible) +/// Status: BETA / Probably Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendBosch144(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + // nbytes is required to be a multiple of kBosch144BytesPerSection. + if (nbytes % kBosch144BytesPerSection != 0)return; + // Set IR carrier frequency + enableIROut(kBoschFreq); + + for (uint16_t r = 0; r <= repeat; r++) { + for (uint16_t offset=0; offset < nbytes; offset += kBosch144BytesPerSection) + // Section Header + Data + Footer + sendGeneric(kBoschHdrMark, kBoschHdrSpace, + kBoschBitMark, kBoschOneSpace, + kBoschBitMark, kBoschZeroSpace, + kBoschBitMark, kBoschFooterSpace, + data + offset, kBosch144BytesPerSection, + kBoschFreq, true, 0, kDutyDefault); + space(kDefaultMessageGap); // Complete guess + } +} + +#endif // SEND_BOSCH144 + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRBosch144AC::IRBosch144AC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IRBosch144AC::stateReset(void) { + setRaw(kBosch144DefaultState, kBosch144StateLength); + setPower(true); +} + +/// Set up hardware to be able to send a message. +void IRBosch144AC::begin(void) { _irsend.begin(); } + +#if SEND_BOSCH144 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRBosch144AC::send(const uint16_t repeat) { + if (!powerFlag) { // "Off" is a 96bit message + _irsend.sendBosch144(kBosch144Off, sizeof(kBosch144Off), repeat); + } else { + _irsend.sendBosch144(getRaw(), kBosch144StateLength, repeat); + } // other 96bit messages are not yet supported +} +#endif // SEND_BOSCH144 + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +unsigned char* IRBosch144AC::getRaw(void) { + setInvertBytes(); + setCheckSumS3(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length Size of the array being passed in in bytes. +void IRBosch144AC::setRaw(const uint8_t new_code[], const uint16_t length) { + const uint16_t len = min(length, kBosch144StateLength); + const uint16_t lenOff = sizeof(kBosch144Off); +// Is it an off message? + if (memcmp(kBosch144Off, new_code, min(lenOff, len)) == 0) + setPower(false); // It is. + else + setPower(true); + memcpy(_.raw, new_code, len); +} + +void IRBosch144AC::setPower(const bool on) { + powerFlag = on; +} + +bool IRBosch144AC::getPower(void) const { + return powerFlag; +} + +void IRBosch144AC::setTempRaw(const uint8_t code) { + _.TempS1 = _.TempS2 = code >> 1; // save 4 bits in S1 and S2 + _.TempS3 = code & 1; // save 1 bit in Section3 +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRBosch144AC::setTemp(const uint8_t degrees) { + uint8_t temp = max(kBosch144TempMin, degrees); + temp = min(kBosch144TempMax, temp); + setTempRaw(kBosch144TempMap[temp - kBosch144TempMin]); +} + +uint8_t IRBosch144AC::getTemp(void) const { + uint8_t temp = (_.TempS1 << 1) + _.TempS3; + uint8_t retemp = 25; + for (uint8_t i = 0; i < kBosch144TempRange; i++) { + if (temp == kBosch144TempMap[i]) { + retemp = kBosch144TempMin + i; + } + } + return retemp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRBosch144AC::setFan(const uint16_t speed) { + _.FanS1 = _.FanS2 = speed >> 6; // save 3 bits in S1 and S2 + _.FanS3 = speed & 0b111111; // save 6 bits in Section3 +} + +uint16_t IRBosch144AC::getFan(void) const { + return (_.FanS1 << 6) + _.FanS3; +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRBosch144AC::setMode(const uint8_t mode) { + _.ModeS1 = _.ModeS2 = mode >> 1; // save 2 bits in S1 and S2 + _.ModeS3 = mode & 0b1; // save 1 bit in Section3 + if (mode == kBosch144Auto || mode == kBosch144Dry) { + _.FanS1 = _.FanS2 = 0b000; // save 3 bits in S1 and S2 + _.FanS3 = kBosch144FanAuto0; // save 6 bits in Section3 + } +} + +uint8_t IRBosch144AC::getMode(void) const { + return (_.ModeS1 << 1) + _.ModeS3; +} + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRBosch144AC::setQuiet(const bool on) { + _.Quiet = on; // save 1 bit in Section3 + setFan(kBosch144FanAuto); // set Fan -> Auto +} + +/// Get the Quiet mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRBosch144AC::getQuiet(void) const { return _.Quiet; } + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRBosch144AC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kBosch144Cool; + case stdAc::opmode_t::kHeat: + return kBosch144Heat; + case stdAc::opmode_t::kDry: + return kBosch144Dry; + case stdAc::opmode_t::kFan: + return kBosch144Fan; + default: + return kBosch144Auto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint16_t IRBosch144AC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + return kBosch144Fan20; + case stdAc::fanspeed_t::kLow: + return kBosch144Fan40; + case stdAc::fanspeed_t::kMedium: + return kBosch144Fan60; + case stdAc::fanspeed_t::kHigh: + return kBosch144Fan80; + case stdAc::fanspeed_t::kMax: + return kBosch144Fan100; + default: + return kBosch144FanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRBosch144AC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kBosch144Cool: return stdAc::opmode_t::kCool; + case kBosch144Heat: return stdAc::opmode_t::kHeat; + case kBosch144Dry: return stdAc::opmode_t::kDry; + case kBosch144Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRBosch144AC::toCommonFanSpeed(const uint16_t speed) { + switch (speed) { + case kBosch144Fan100: return stdAc::fanspeed_t::kMax; + case kBosch144Fan80: return stdAc::fanspeed_t::kHigh; + case kBosch144Fan60: return stdAc::fanspeed_t::kMedium; + case kBosch144Fan40: return stdAc::fanspeed_t::kLow; + case kBosch144Fan20: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRBosch144AC::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::BOSCH144; + result.power = getPower(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.quiet = getQuiet(); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.clean = false; + result.beep = false; + result.clock = -1; + result.sleep = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRBosch144AC::toString(void) const { + uint8_t mode = getMode(); + uint8_t fan = static_cast(toCommonFanSpeed(getFan())); + String result = ""; + result.reserve(70); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(mode, kBosch144Auto, kBosch144Cool, + kBosch144Heat, kBosch144Dry, kBosch144Fan); + result += addFanToString(fan, static_cast(stdAc::fanspeed_t::kMax), + static_cast(stdAc::fanspeed_t::kMin), + static_cast(stdAc::fanspeed_t::kAuto), + static_cast(stdAc::fanspeed_t::kAuto), + static_cast(stdAc::fanspeed_t::kMedium)); + result += addTempToString(getTemp()); + result += addBoolToString(_.Quiet, kQuietStr); + return result; +} + +void IRBosch144AC::setInvertBytes() { + for (uint8_t i = 0; i <= 10; i += 2) { + _.raw[i + 1] = ~_.raw[i]; + } +} + +void IRBosch144AC::setCheckSumS3() { + _.ChecksumS3 = sumBytes(&(_.raw[12]), 5); +} + +#if DECODE_BOSCH144 +/// Decode the supplied Bosch 144-bit / 18-byte A/C message. +/// Status: STABLE / Confirmed Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeBosch144(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + + kBosch144NrOfSections * (kHeader + kFooter) - + 1 + offset) + return false; // Can't possibly be a valid BOSCH144 message. + if (strict && nbits != kBosch144Bits) + return false; // Not strictly a BOSCH144 message. + if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte. + return false; + if (nbits % kBosch144NrOfSections != 0) + return false; // nbits has to be a multiple of kBosch144NrOfSections. + const uint16_t kSectionBits = nbits / kBosch144NrOfSections; + const uint16_t kSectionBytes = kSectionBits / 8; + const uint16_t kNBytes = kSectionBytes * kBosch144NrOfSections; + // Capture each section individually + for (uint16_t pos = 0, section = 0; + pos < kNBytes; + pos += kSectionBytes, section++) { + uint16_t used = 0; + // Section Header + Section Data + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, kSectionBits, + kBoschHdrMark, kBoschHdrSpace, + kBoschBitMark, kBoschOneSpace, + kBoschBitMark, kBoschZeroSpace, + kBoschBitMark, kBoschFooterSpace, + section >= kBosch144NrOfSections - 1, + _tolerance, kMarkExcess, true); + if (!used) return false; // Didn't match. + offset += used; + } + + // Compliance + + // Success + results->decode_type = decode_type_t::BOSCH144; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_BOSCH144 diff --git a/src/libraries/IRremoteESP8266/src/ir_Bosch.h b/src/libraries/IRremoteESP8266/src/ir_Bosch.h new file mode 100644 index 000000000..e9073dd64 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Bosch.h @@ -0,0 +1,189 @@ +// Copyright 2022 Nico Thien +/// @file +/// @brief Support for Bosch A/C protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787 + +// Supports: +// Brand: Bosch, Model: CL3000i-Set 26 E A/C +// Brand: Bosch, Model: RG10A(G2S)BGEF remote + + +#ifndef IR_BOSCH_H_ +#define IR_BOSCH_H_ + +#define __STDC_LIMIT_MACROS +#include +//// #include +#include +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRrecv.h" +#include "IRtext.h" +#include "IRutils.h" +#ifndef UNIT_TEST +#include "String.h" +#endif +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +// Constants +const uint16_t kBoschHdrMark = 4366; +const uint16_t kBoschBitMark = 502; +const uint16_t kBoschHdrSpace = 4415; +const uint16_t kBoschOneSpace = 1645; +const uint16_t kBoschZeroSpace = 571; +const uint16_t kBoschFooterSpace = 5235; +const uint16_t kBoschFreq = 38000; // Hz. (Guessing the most common frequency.) +const uint16_t kBosch144NrOfSections = 3; +const uint16_t kBosch144BytesPerSection = 6; + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; + +// Modes Bit[0] to Section 3 Bit[1-2] to Section 1 +// ModeS3 ModeS1 +const uint8_t kBosch144Cool = 0b000; +const uint8_t kBosch144Dry = 0b011; +const uint8_t kBosch144Auto = 0b101; +const uint8_t kBosch144Heat = 0b110; +const uint8_t kBosch144Fan = 0b010; + +// Fan Control Bit[0-5] to Section 3 Bit[6-8] to Section 1 +// FanS3 FanS1 +const uint16_t kBosch144Fan20 = 0b111001010; +const uint16_t kBosch144Fan40 = 0b100010100; +const uint16_t kBosch144Fan60 = 0b010011110; +const uint16_t kBosch144Fan80 = 0b001101000; +const uint16_t kBosch144Fan100 = 0b001110010; +const uint16_t kBosch144FanAuto = 0b101110011; +const uint16_t kBosch144FanAuto0 = 0b000110011; + +// Temperature +const uint8_t kBosch144TempMin = 16; // Celsius +const uint8_t kBosch144TempMax = 30; // Celsius +const uint8_t kBosch144TempRange = kBosch144TempMax - kBosch144TempMin + 1; +const uint8_t kBosch144TempMap[kBosch144TempRange] = { + 0b00001, // 16C // Bit[0] to Section 3 Bit[1-4] to Section 1 + 0b00000, // 17C // TempS3 TempS1 + 0b00010, // 18c + 0b00110, // 19C + 0b00100, // 20C + 0b01100, // 21C + 0b01110, // 22C + 0b01010, // 23C + 0b01000, // 24C + 0b11000, // 25C + 0b11010, // 26C + 0b10010, // 27C + 0b10000, // 28C + 0b10100, // 29C + 0b10110 // 30C +}; + +// "OFF" is a 96bit-message the same as Coolix protocol +const uint8_t kBosch144Off[] = {0xB2, 0x4D, 0x7B, 0x84, 0xE0, 0x1F, + 0xB2, 0x4D, 0x7B, 0x84, 0xE0, 0x1F}; + +// On, 25C, Mode: Auto +const uint8_t kBosch144DefaultState[kBosch144StateLength] = { + 0xB2, 0x4D, 0x1F, 0xE0, 0xC8, 0x37, + 0xB2, 0x4D, 0x1F, 0xE0, 0xC8, 0x37, + 0xD5, 0x65, 0x00, 0x00, 0x00, 0x3A}; + +union Bosch144Protocol { + uint8_t raw[kBosch144StateLength]; ///< The state in IR code form. + struct { + uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############ + uint8_t InnvertS1_1:8; // Invert byte 0b01001101 / 0x4D # + uint8_t :5; // not used (without timer use) # + uint8_t FanS1 :3; // Fan speed bits in Section 1 # + uint8_t InnvertS1_2:8; // Invert byte # Section 1 = + uint8_t :2; // not used (without timer use) # Sektion 2 + uint8_t ModeS1 :2; // Operation mode bits S1 # + uint8_t TempS1 :4; // Desired temperature (Celsius) S2 # + uint8_t InnvertS1_3:8; // Invert byte (without timer use) ############ + + uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############ + uint8_t InnvertS2_1:8; // Invert byte 0b01001101 / 0x4D # + uint8_t :5; // not used (without timer use) # + uint8_t FanS2 :3; // Fan speed bits in Section 2 # + uint8_t InnvertS2_2:8; // Invert byte # Section 2 = + uint8_t :2; // not used (without timer use) # Sektion 1 + uint8_t ModeS2 :2; // Operation mode bits S2 # + uint8_t TempS2 :4; // Desired temperature (Celsius) S2 # + uint8_t InnvertS2_3:8; // Invert byte (without timer use) ########### + + uint8_t :8; // Fixed value 0b11010101 / 0xD5 ########### + uint8_t ModeS3 :1; // ModeBit in Section 3 # + uint8_t FanS3 :6; // Fan speed bits in Section 3 # + uint8_t :1; // Unknown # + uint8_t :7; // Unknown # + uint8_t Quiet :1; // Silent-Mode # Section 3 + uint8_t :4; // Unknown # + uint8_t TempS3 :1; // Desired temp. Bit in Section3 # + uint8_t :3; // Unknown # + uint8_t :8; // Unknown # + uint8_t ChecksumS3 :8; // Checksum from byte 13-17 ########### + }; +}; + +// Classes + +/// Class for handling detailed Bosch144 A/C messages. +class IRBosch144AC { + public: + explicit IRBosch144AC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_BOSCH144 + void send(const uint16_t repeat = 0); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_BOSCH144 + void begin(); + void setPower(const bool state); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint16_t speed); + uint16_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kBosch144StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint16_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint16_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + Bosch144Protocol _; ///< The state of the IR remote in IR code form. + + // Internal State settings + bool powerFlag; + + void setInvertBytes(); + void setCheckSumS3(); + void setTempRaw(const uint8_t code); + uint8_t getTempRaw(void) const; +}; + +#endif // IR_BOSCH_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Bose.cpp b/src/libraries/IRremoteESP8266/src/ir_Bose.cpp new file mode 100644 index 000000000..a57d125b3 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Bose.cpp @@ -0,0 +1,69 @@ +// Copyright 2021 parsnip42 +// Copyright 2021 David Conran + +/// @file +/// @brief Support for Bose protocols. +/// @note Currently only tested against Bose TV Speaker. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1579 + +// Supports: +// Brand: Bose, Model: Bose TV Speaker + +#include "IRrecv.h" +#include "IRsend.h" + +const uint16_t kBoseHdrMark = 1100; +const uint16_t kBoseHdrSpace = 1350; +const uint16_t kBoseBitMark = 555; +const uint16_t kBoseOneSpace = 1435; +const uint16_t kBoseZeroSpace = 500; +const uint32_t kBoseGap = kDefaultMessageGap; +const uint16_t kBoseFreq = 38; + +#if SEND_BOSE +/// Send a Bose formatted message. +/// Status: STABLE / Known working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendBose(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kBoseHdrMark, kBoseHdrSpace, + kBoseBitMark, kBoseOneSpace, + kBoseBitMark, kBoseZeroSpace, + kBoseBitMark, kBoseGap, + data, nbits, kBoseFreq, false, + repeat, kDutyDefault); +} +#endif // SEND_BOSE + +#if DECODE_BOSE +/// Decode the supplied Bose formatted message. +/// Status: STABLE / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +bool IRrecv::decodeBose(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kBoseBits) return false; + + if (!matchGeneric(results->rawbuf + offset, &(results->value), + results->rawlen - offset, nbits, + kBoseHdrMark, kBoseHdrSpace, + kBoseBitMark, kBoseOneSpace, + kBoseBitMark, kBoseZeroSpace, + kBoseBitMark, kBoseGap, true, + kUseDefTol, 0, false)) { + return false; + } + + // + results->decode_type = decode_type_t::BOSE; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_BOSE diff --git a/src/libraries/IRremoteESP8266/src/ir_Carrier.cpp b/src/libraries/IRremoteESP8266/src/ir_Carrier.cpp new file mode 100644 index 000000000..28b85169d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Carrier.cpp @@ -0,0 +1,748 @@ +// Copyright 2018-2022 David Conran +/// @file +/// @brief Carrier protocols. +/// @see CarrierAc https://github.com/crankyoldgit/IRremoteESP8266/issues/385 +/// @see CarrierAc64 https://github.com/crankyoldgit/IRremoteESP8266/issues/1127 +/// @see CarrierAc128 https://github.com/crankyoldgit/IRremoteESP8266/issues/1797 + +#include "ir_Carrier.h" +// #include +#include "IRac.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::addFanToString; +using irutils::minsToString; +using irutils::sumNibbles; + +// Constants +const uint16_t kCarrierAcHdrMark = 8532; +const uint16_t kCarrierAcHdrSpace = 4228; +const uint16_t kCarrierAcBitMark = 628; +const uint16_t kCarrierAcOneSpace = 1320; +const uint16_t kCarrierAcZeroSpace = 532; +const uint16_t kCarrierAcGap = 20000; +const uint16_t kCarrierAcFreq = 38; // kHz. (An educated guess) + +const uint16_t kCarrierAc40HdrMark = 8402; +const uint16_t kCarrierAc40HdrSpace = 4166; +const uint16_t kCarrierAc40BitMark = 547; +const uint16_t kCarrierAc40OneSpace = 1540; +const uint16_t kCarrierAc40ZeroSpace = 497; +const uint32_t kCarrierAc40Gap = 150000; ///< +///< @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1190#issuecomment-643380155 + +const uint16_t kCarrierAc64HdrMark = 8940; +const uint16_t kCarrierAc64HdrSpace = 4556; +const uint16_t kCarrierAc64BitMark = 503; +const uint16_t kCarrierAc64OneSpace = 1736; +const uint16_t kCarrierAc64ZeroSpace = 615; +const uint32_t kCarrierAc64Gap = kDefaultMessageGap; // A guess. + +//< @see: https://github.com/crankyoldgit/IRremoteESP8266/issues/1943#issue-1519570772 +const uint16_t kCarrierAc84HdrMark = 5850; +const uint16_t kCarrierAc84Zero = 1175; +const uint16_t kCarrierAc84One = 430; +const uint16_t kCarrierAc84HdrSpace = kCarrierAc84Zero; +const uint32_t kCarrierAc84Gap = kDefaultMessageGap; // A guess. +const uint8_t kCarrierAc84ExtraBits = 4; +const uint8_t kCarrierAc84ExtraTolerance = 5; + +const uint16_t kCarrierAc128HdrMark = 4600; +const uint16_t kCarrierAc128HdrSpace = 2600; +const uint16_t kCarrierAc128Hdr2Mark = 9300; +const uint16_t kCarrierAc128Hdr2Space = 5000; +const uint16_t kCarrierAc128BitMark = 340; +const uint16_t kCarrierAc128OneSpace = 1000; +const uint16_t kCarrierAc128ZeroSpace = 400; +const uint16_t kCarrierAc128SectionGap = 20600; +const uint16_t kCarrierAc128InterSpace = 6700; +const uint16_t kCarrierAc128SectionBits = kCarrierAc128Bits / 2; + +#if SEND_CARRIER_AC +/// Send a Carrier HVAC formatted message. +/// Status: STABLE / Works on real devices. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendCarrierAC(uint64_t data, uint16_t nbits, uint16_t repeat) { + for (uint16_t r = 0; r <= repeat; r++) { + uint64_t temp_data = data; + // Carrier sends the data block three times. normal + inverted + normal. + for (uint16_t i = 0; i < 3; i++) { + sendGeneric(kCarrierAcHdrMark, kCarrierAcHdrSpace, kCarrierAcBitMark, + kCarrierAcOneSpace, kCarrierAcBitMark, kCarrierAcZeroSpace, + kCarrierAcBitMark, kCarrierAcGap, temp_data, nbits, 38, true, + 0, kDutyDefault); + temp_data = invertBits(temp_data, nbits); + } + } +} +#endif + +#if DECODE_CARRIER_AC +/// Decode the supplied Carrier HVAC message. +/// @note Carrier HVAC messages contain only 32 bits, but it is sent three(3) +/// times. i.e. normal + inverted + normal +/// Status: BETA / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeCarrierAC(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < ((2 * nbits + kHeader + kFooter) * 3) - 1 + offset) + return false; // Can't possibly be a valid Carrier message. + if (strict && nbits != kCarrierAcBits) + return false; // We expect Carrier to be 32 bits of message. + + uint64_t data = 0; + uint64_t prev_data = 0; + + for (uint8_t i = 0; i < 3; i++) { + prev_data = data; + // Match Header + Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kCarrierAcHdrMark, kCarrierAcHdrSpace, + kCarrierAcBitMark, kCarrierAcOneSpace, + kCarrierAcBitMark, kCarrierAcZeroSpace, + kCarrierAcBitMark, kCarrierAcGap, true); + if (!used) return false; + offset += used; + // Compliance. + if (strict) { + // Check if the data is an inverted copy of the previous data. + if (i > 0 && prev_data != invertBits(data, nbits)) return false; + } + } + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = CARRIER_AC; + results->address = data >> 16; + results->command = data & 0xFFFF; + return true; +} +#endif // DECODE_CARRIER_AC + +#if SEND_CARRIER_AC40 +/// Send a Carrier 40bit HVAC formatted message. +/// Status: STABLE / Tested against a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The bit size of the message being sent. +/// @param[in] repeat The number of times the message is to be repeated. +void IRsend::sendCarrierAC40(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kCarrierAc40HdrMark, kCarrierAc40HdrSpace, kCarrierAc40BitMark, + kCarrierAc40OneSpace, kCarrierAc40BitMark, kCarrierAc40ZeroSpace, + kCarrierAc40BitMark, kCarrierAc40Gap, + data, nbits, kCarrierAcFreq, true, repeat, kDutyDefault); +} +#endif // SEND_CARRIER_AC40 + +#if DECODE_CARRIER_AC40 +/// Decode the supplied Carrier 40-bit HVAC message. +/// Carrier HVAC messages contain only 40 bits, but it is sent three(3) times. +/// Status: STABLE / Tested against a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeCarrierAC40(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset) + return false; // Can't possibly be a valid Carrier message. + if (strict && nbits != kCarrierAc40Bits) + return false; // We expect Carrier to be 40 bits of message. + + if (!matchGeneric(results->rawbuf + offset, &(results->value), + results->rawlen - offset, nbits, + kCarrierAc40HdrMark, kCarrierAc40HdrSpace, + kCarrierAc40BitMark, kCarrierAc40OneSpace, + kCarrierAc40BitMark, kCarrierAc40ZeroSpace, + kCarrierAc40BitMark, kCarrierAc40Gap, true)) return false; + + // Success + results->bits = nbits; + results->decode_type = CARRIER_AC40; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_CARRIER_AC40 + +#if SEND_CARRIER_AC64 +/// Send a Carrier 64bit HVAC formatted message. +/// Status: STABLE / Known to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The bit size of the message being sent. +/// @param[in] repeat The number of times the message is to be repeated. +void IRsend::sendCarrierAC64(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kCarrierAc64HdrMark, kCarrierAc64HdrSpace, kCarrierAc64BitMark, + kCarrierAc64OneSpace, kCarrierAc64BitMark, kCarrierAc64ZeroSpace, + kCarrierAc64BitMark, kCarrierAc64Gap, + data, nbits, kCarrierAcFreq, false, repeat, kDutyDefault); +} +#endif // SEND_CARRIER_AC64 + +#if DECODE_CARRIER_AC64 +/// Decode the supplied Carrier 64-bit HVAC message. +/// Status: STABLE / Known to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeCarrierAC64(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset) + return false; // Can't possibly be a valid Carrier message. + if (strict && nbits != kCarrierAc64Bits) + return false; // We expect Carrier to be 64 bits of message. + + if (!matchGeneric(results->rawbuf + offset, &(results->value), + results->rawlen - offset, nbits, + kCarrierAc64HdrMark, kCarrierAc64HdrSpace, + kCarrierAc64BitMark, kCarrierAc64OneSpace, + kCarrierAc64BitMark, kCarrierAc64ZeroSpace, + kCarrierAc64BitMark, kCarrierAc64Gap, true, + kUseDefTol, kMarkExcess, false)) return false; + + // Compliance + if (strict && !IRCarrierAc64::validChecksum(results->value)) return false; + + // Success + results->bits = nbits; + results->decode_type = CARRIER_AC64; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_CARRIER_AC64 + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRCarrierAc64::IRCarrierAc64(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +/// @note The state is powered off. +void IRCarrierAc64::stateReset(void) { _.raw = 0x109000002C2A5584; } + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @return The 4-bit checksum stored in a uint_8. +uint8_t IRCarrierAc64::calcChecksum(const uint64_t state) { + uint64_t data = GETBITS64(state, + kCarrierAc64ChecksumOffset + kCarrierAc64ChecksumSize, kCarrierAc64Bits - + (kCarrierAc64ChecksumOffset + kCarrierAc64ChecksumSize)); + uint8_t result = 0; + for (; data; data >>= 4) // Add each nibble together. + result += GETBITS64(data, 0, 4); + return result & 0xF; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRCarrierAc64::validChecksum(const uint64_t state) { + // Validate the checksum of the given state. + return (GETBITS64(state, kCarrierAc64ChecksumOffset, + kCarrierAc64ChecksumSize) == calcChecksum(state)); +} + +/// Calculate and set the checksum values for the internal state. +void IRCarrierAc64::checksum(void) { + _.Sum = calcChecksum(_.raw); +} + +/// Set up hardware to be able to send a message. +void IRCarrierAc64::begin(void) { _irsend.begin(); } + +#if SEND_CARRIER_AC64 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRCarrierAc64::send(const uint16_t repeat) { + _irsend.sendCarrierAC64(getRaw(), kCarrierAc64Bits, repeat); +} +#endif // SEND_CARRIER_AC64 + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +uint64_t IRCarrierAc64::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRCarrierAc64::setRaw(const uint64_t state) { _.raw = state; } + +/// Set the temp in deg C. +/// @param[in] temp The desired temperature in Celsius. +void IRCarrierAc64::setTemp(const uint8_t temp) { + uint8_t degrees = ::max(temp, kCarrierAc64MinTemp); + degrees = ::min(degrees, kCarrierAc64MaxTemp); + _.Temp = degrees - kCarrierAc64MinTemp; +} + +/// Get the current temperature from the internal state. +/// @return The current temperature in Celsius. +uint8_t IRCarrierAc64::getTemp(void) const { + return _.Temp + kCarrierAc64MinTemp; +} + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRCarrierAc64::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRCarrierAc64::getPower(void) const { + return _.Power; +} + +/// Change the power setting to On. +void IRCarrierAc64::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRCarrierAc64::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRCarrierAc64::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRCarrierAc64::setMode(const uint8_t mode) { + switch (mode) { + case kCarrierAc64Heat: + case kCarrierAc64Cool: + case kCarrierAc64Fan: + _.Mode = mode; + return; + default: + _.Mode = kCarrierAc64Cool; + } +} + +/// Convert a standard A/C mode into its native mode. +/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent. +/// @return The corresponding native mode. +uint8_t IRCarrierAc64::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kHeat: return kCarrierAc64Heat; + case stdAc::opmode_t::kFan: return kCarrierAc64Fan; + default: return kCarrierAc64Cool; + } +} + +/// Convert a native mode to it's common stdAc::opmode_t equivalent. +/// @param[in] mode A native operation mode to be converted. +/// @return The corresponding common stdAc::opmode_t mode. +stdAc::opmode_t IRCarrierAc64::toCommonMode(const uint8_t mode) { + switch (mode) { + case kCarrierAc64Heat: return stdAc::opmode_t::kHeat; + case kCarrierAc64Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRCarrierAc64::getFan(void) const { + return _.Fan; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRCarrierAc64::setFan(const uint8_t speed) { + if (speed > kCarrierAc64FanHigh) + _.Fan = kCarrierAc64FanAuto; + else + _.Fan = speed; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRCarrierAc64::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kCarrierAc64FanLow; + case stdAc::fanspeed_t::kMedium: return kCarrierAc64FanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kCarrierAc64FanHigh; + default: return kCarrierAc64FanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRCarrierAc64::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kCarrierAc64FanHigh: return stdAc::fanspeed_t::kHigh; + case kCarrierAc64FanMedium: return stdAc::fanspeed_t::kMedium; + case kCarrierAc64FanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRCarrierAc64::setSwingV(const bool on) { + _.SwingV = on; +} + +/// Get the Vertical Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCarrierAc64::getSwingV(void) const { + return _.SwingV; +} + +/// Set the Sleep mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRCarrierAc64::setSleep(const bool on) { + if (on) { + // Sleep sets a default value in the Off timer, and disables both timers. + setOffTimer(2 * 60); + // Clear the enable bits for each timer. + _cancelOnTimer(); + _cancelOffTimer(); + } + _.Sleep = on; +} + +/// Get the Sleep mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCarrierAc64::getSleep(void) const { + return _.Sleep; +} + +/// Clear the On Timer enable bit. +void IRCarrierAc64::_cancelOnTimer(void) { + _.OnTimerEnable = false; +} + +/// Get the current On Timer time. +/// @return The number of minutes it is set for. 0 means it's off. +/// @note The A/C protocol only supports one hour increments. +uint16_t IRCarrierAc64::getOnTimer(void) const { + if (_.OnTimerEnable) + return _.OnTimer * 60; + else + return 0; +} + +/// Set the On Timer time. +/// @param[in] nr_of_mins Number of minutes to set the timer to. +/// (< 60 is disable). +/// @note The A/C protocol only supports one hour increments. +void IRCarrierAc64::setOnTimer(const uint16_t nr_of_mins) { + uint8_t hours = ::min((uint8_t)(nr_of_mins / 60), kCarrierAc64TimerMax); + _.OnTimerEnable = static_cast(hours); // Enable + _.OnTimer = ::max(kCarrierAc64TimerMin, hours); // Hours + if (hours) { // If enabled, disable the Off Timer & Sleep mode. + _cancelOffTimer(); + setSleep(false); + } +} + +/// Clear the Off Timer enable bit. +void IRCarrierAc64::_cancelOffTimer(void) { + _.OffTimerEnable = false; +} + +/// Get the current Off Timer time. +/// @return The number of minutes it is set for. 0 means it's off. +/// @note The A/C protocol only supports one hour increments. +uint16_t IRCarrierAc64::getOffTimer(void) const { + if (_.OffTimerEnable) + return _.OffTimer * 60; + else + return 0; +} + +/// Set the Off Timer time. +/// @param[in] nr_of_mins Number of minutes to set the timer to. +/// (< 60 is disable). +/// @note The A/C protocol only supports one hour increments. +void IRCarrierAc64::setOffTimer(const uint16_t nr_of_mins) { + uint8_t hours = ::min((uint8_t)(nr_of_mins / 60), kCarrierAc64TimerMax); + // The time can be changed in sleep mode, but doesn't set the flag. + _.OffTimerEnable = (hours && !_.Sleep); + _.OffTimer = ::max(kCarrierAc64TimerMin, hours); // Hours + if (hours) { // If enabled, disable the On Timer & Sleep mode. + _cancelOnTimer(); + setSleep(false); + } +} + +/// Convert the internal state into a human readable string. +/// @return The current internal state expressed as a human readable String. +String IRCarrierAc64::toString(void) const { + String result = ""; + result.reserve(120); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, 0xFF, kCarrierAc64Cool, + kCarrierAc64Heat, 0xFF, kCarrierAc64Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kCarrierAc64FanHigh, kCarrierAc64FanLow, + kCarrierAc64FanAuto, kCarrierAc64FanAuto, + kCarrierAc64FanMedium); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(getOnTimer() + ? minsToString(getOnTimer()) : kOffStr, + kOnTimerStr); + result += addLabeledString(getOffTimer() + ? minsToString(getOffTimer()) : kOffStr, + kOffTimerStr); + return result; +} + +/// Convert the A/C state to it's common stdAc::state_t equivalent. +/// @return A stdAc::state_t state. +stdAc::state_t IRCarrierAc64::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::CARRIER_AC64; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.quiet = false; + result.clean = false; + result.filter = false; + result.beep = false; + result.econo = false; + result.light = false; + result.clock = -1; + return result; +} + +#if SEND_CARRIER_AC128 +/// Send a Carrier 128bit HVAC formatted message. +/// Status: BETA / Seems to work with tests. Needs testing agaisnt real devices. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The byte size of the message being sent. +/// @param[in] repeat The number of times the message is to be repeated. +void IRsend::sendCarrierAC128(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // Min length check. + if (nbytes <= kCarrierAc128StateLength / 2) return; + + enableIROut(kCarrierAcFreq); + // Handle repeats. + for (uint16_t r = 0; r <= repeat; r++) { + // First part of the message. + // Headers + Data + SectionGap + sendGeneric(kCarrierAc128HdrMark, kCarrierAc128HdrSpace, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, + data, nbytes / 2, kCarrierAcFreq, false, 0, kDutyDefault); + // Inter-message markers + mark(kCarrierAc128HdrMark); + space(kCarrierAc128InterSpace); + // Second part of the message + // Headers + Data + SectionGap + sendGeneric(kCarrierAc128Hdr2Mark, kCarrierAc128Hdr2Space, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, + data + (nbytes / 2), nbytes / 2, kCarrierAcFreq, + false, 0, kDutyDefault); + // Footer + mark(kCarrierAc128HdrMark); + space(kDefaultMessageGap); + } +} +#endif // SEND_CARRIER_AC128 + +#if DECODE_CARRIER_AC128 +/// Decode the supplied Carrier 128-bit HVAC message. +/// Status: STABLE / Expected to work. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeCarrierAC128(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + 2 * kHeader + kFooter) - 1 + offset) + return false; // Can't possibly be a valid Carrier message. + if (strict && nbits != kCarrierAc128Bits) + return false; // We expect Carrier to be 128 bits of message. + + uint16_t used; + uint16_t pos = 0; + const uint16_t sectionbits = nbits / 2; + // Match the first section. + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, sectionbits, + kCarrierAc128HdrMark, kCarrierAc128HdrSpace, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, true, + kUseDefTol, kMarkExcess, false); + if (used == 0) return false; // No match. + offset += used; + pos += sectionbits / 8; + // Look for the inter-message markers. + if (!matchMark(results->rawbuf[offset++], kCarrierAc128HdrMark)) + return false; + if (!matchSpace(results->rawbuf[offset++], kCarrierAc128InterSpace)) + return false; + // Now look for the second section. + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, sectionbits, + kCarrierAc128Hdr2Mark, kCarrierAc128Hdr2Space, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, true, + kUseDefTol, kMarkExcess, false); + if (used == 0) return false; // No match. + offset += used; + // Now check for the Footer. + if (!matchMark(results->rawbuf[offset++], kCarrierAc128HdrMark)) return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kDefaultMessageGap)) return false; + + + // Compliance + // if (strict && !IRCarrierAc128::validChecksum(results->value)) return false; + + // Success + results->bits = nbits; + results->decode_type = CARRIER_AC128; + return true; +} +#endif // DECODE_CARRIER_AC128 + +#if SEND_CARRIER_AC84 +/// Send a Carroer A/C 84 Bit formatted message. +/// Status: BETA / Untested but probably works. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The byte size of the message being sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendCarrierAC84(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // Protocol uses a constant bit time encoding. + for (uint16_t r = 0; r <= repeat; r++) { + if (nbytes) { + // The least significant `kCarrierAc84ExtraBits` bits of the first byte + sendGeneric(kCarrierAc84HdrMark, kCarrierAc84HdrSpace, // Header + kCarrierAc84Zero, kCarrierAc84One, // Data + kCarrierAc84One, kCarrierAc84Zero, + 0, 0, // No footer + GETBITS64(data[0], 0, kCarrierAc84ExtraBits), + kCarrierAc84ExtraBits, + 38000, false, 0, 33); + // The rest of the data. + sendGeneric(0, 0, // No Header + kCarrierAc84Zero, kCarrierAc84One, // Data + kCarrierAc84One, kCarrierAc84Zero, + kCarrierAc84Zero, kDefaultMessageGap, // Footer + data + 1, nbytes - 1, 38000, false, 0, 33); + } + } +} +#endif // SEND_CARRIER_AC84 + +#if DECODE_CARRIER_AC84 +/// Decode the supplied Carroer A/C 84 Bit formatted message. +/// Status: STABLE / Confirmed Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeCarrierAC84(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Check if we have enough data to even possibly match. + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset) + return false; // Can't possibly be a valid Carrier message. + // Compliance check. + if (strict && nbits != kCarrierAc84Bits) return false; + + // This decoder expects to decode an unusual number of bits. Check before we + // start. + if (nbits % 8 != kCarrierAc84ExtraBits) return false; + + uint64_t data = 0; + uint16_t used = 0; + + // Header + Data (kCarrierAc84ExtraBits only) + used = matchGenericConstBitTime(results->rawbuf + offset, &data, + results->rawlen - offset, + kCarrierAc84ExtraBits, + // Header (None) + kCarrierAc84HdrMark, kCarrierAc84HdrSpace, + // Data + kCarrierAc84Zero, kCarrierAc84One, + // No Footer + 0, 0, + false, + _tolerance + kCarrierAc84ExtraTolerance, + kMarkExcess, false); + if (!used) return false; + // Stuff the captured data so far into the first byte of the state. + *results->state = data; + offset += used; + // Capture the rest of the data as normal as we should be on a byte boundary. + // Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state + 1, + results->rawlen - offset, nbits - kCarrierAc84ExtraBits, + 0, 0, // No Header expected. + kCarrierAc84Zero, kCarrierAc84One, // Data + kCarrierAc84One, kCarrierAc84Zero, + kCarrierAc84Zero, kDefaultMessageGap, true, + _tolerance + kCarrierAc84ExtraTolerance, + kMarkExcess, false)) return false; + + // Success + results->decode_type = decode_type_t::CARRIER_AC84; + results->bits = nbits; + results->repeat = false; + return true; +} +#endif // DECODE_CARRIER_AC84 diff --git a/src/libraries/IRremoteESP8266/src/ir_Carrier.h b/src/libraries/IRremoteESP8266/src/ir_Carrier.h new file mode 100644 index 000000000..1bfab1c13 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Carrier.h @@ -0,0 +1,146 @@ +// Copyright 2020-2022 David Conran +/// @file +/// @brief Carrier A/C +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1127 +/// @see https://docs.google.com/spreadsheets/d/1EZy78L0cn1KDIX1aKq2biptejFqCjD5HO3tLiRvXf48/edit#gid=0 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1797 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1943 + +// Supports: +// Brand: Carrier/Surrey, Model: 42QG5A55970 remote +// Brand: Carrier/Surrey, Model: 619EGX0090E0 A/C +// Brand: Carrier/Surrey, Model: 619EGX0120E0 A/C +// Brand: Carrier/Surrey, Model: 619EGX0180E0 A/C +// Brand: Carrier/Surrey, Model: 619EGX0220E0 A/C +// Brand: Carrier/Surrey, Model: 53NGK009/012 Inverter +// Brand: Carrier, Model: 40GKX0E2006 remote (CARRIER_AC128) +// Brand: Carrier, Model: 3021203 RR03-S-Remote (CARRIER_AC84) +// Brand: Carrier, Model: 342WM100CT A/C (CARRIER_AC84) + +#ifndef IR_CARRIER_H_ +#define IR_CARRIER_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Carrier A/C message. +union CarrierProtocol { + uint64_t raw; ///< The state of the IR remote. + struct { + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t :8; + // Byte 2 + uint8_t Sum:4; + uint8_t Mode:2; + uint8_t Fan:2; + // Byte 3 + uint8_t Temp:4; + uint8_t :1; + uint8_t SwingV:1; + uint8_t :2; + // Byte 4 + uint8_t :4; + uint8_t Power:1; + uint8_t OffTimerEnable:1; + uint8_t OnTimerEnable:1; + uint8_t Sleep:1; + // Byte 5 + uint8_t :8; + // Byte 6 + uint8_t :4; + uint8_t OnTimer:4; + // Byte 7 + uint8_t :4; + uint8_t OffTimer:4; + }; +}; + +// Constants + +// CARRIER_AC64 +const uint8_t kCarrierAc64ChecksumOffset = 16; +const uint8_t kCarrierAc64ChecksumSize = 4; +const uint8_t kCarrierAc64Heat = 0b01; // 1 +const uint8_t kCarrierAc64Cool = 0b10; // 2 +const uint8_t kCarrierAc64Fan = 0b11; // 3 +const uint8_t kCarrierAc64FanAuto = 0b00; // 0 +const uint8_t kCarrierAc64FanLow = 0b01; // 1 +const uint8_t kCarrierAc64FanMedium = 0b10; // 2 +const uint8_t kCarrierAc64FanHigh = 0b11; // 3 +const uint8_t kCarrierAc64MinTemp = 16; // Celsius +const uint8_t kCarrierAc64MaxTemp = 30; // Celsius +const uint8_t kCarrierAc64TimerMax = 9; // Hours. +const uint8_t kCarrierAc64TimerMin = 1; // Hours. + + +// Classes + +/// Class for handling detailed Carrier 64 bit A/C messages. +class IRCarrierAc64 { + public: + explicit IRCarrierAc64(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + void stateReset(); +#if SEND_CARRIER_AC64 + void send(const uint16_t repeat = kCarrierAc64MinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_CARRIER_AC64 + void begin(); + static uint8_t calcChecksum(const uint64_t state); + static bool validChecksum(const uint64_t state); + void setPower(const bool on); + bool getPower(void) const; + void on(void); + void off(void); + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setOnTimer(const uint16_t nr_of_mins); + uint16_t getOnTimer(void) const; + void setOffTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void) const; + uint64_t getRaw(void); + void setRaw(const uint64_t state); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + CarrierProtocol _; + void checksum(void); + void _cancelOnTimer(void); + void _cancelOffTimer(void); +}; +#endif // IR_CARRIER_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_ClimaButler.cpp b/src/libraries/IRremoteESP8266/src/ir_ClimaButler.cpp new file mode 100644 index 000000000..d8b4f5a98 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_ClimaButler.cpp @@ -0,0 +1,86 @@ +// Copyright 2022 benjy3gg +// Copyright 2022 David Conran (crankyoldgit) +/// @file +/// @brief Support for Clima-Butler protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1812 + +// Supports: +// Brand: Clima-Butler, Model: AR-715 remote +// Brand: Clima-Butler, Model: RCS-SD43UWI A/C + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + + +const uint16_t kClimaButlerBitMark = 511; // uSeconds +const uint16_t kClimaButlerHdrMark = kClimaButlerBitMark; +const uint16_t kClimaButlerHdrSpace = 3492; // uSeconds +const uint16_t kClimaButlerOneSpace = 1540; // uSeconds +const uint16_t kClimaButlerZeroSpace = 548; // uSeconds +const uint32_t kClimaButlerGap = kDefaultMessageGap; // uSeconds (A guess.) +const uint16_t kClimaButlerFreq = 38000; // Hz. (Guess.) + +#if SEND_CLIMABUTLER +/// Send a ClimaButler formatted message. +/// Status: STABLE / Confirmed working. +/// @param[in] data containing the IR command. +/// @param[in] nbits Nr. of bits to send. usually kClimaButlerBits +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendClimaButler(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + enableIROut(kClimaButlerFreq); + for (uint16_t r = 0; r <= repeat; r++) { + // Header + Data + sendGeneric(kClimaButlerHdrMark, kClimaButlerHdrSpace, + kClimaButlerBitMark, kClimaButlerOneSpace, + kClimaButlerBitMark, kClimaButlerZeroSpace, + kClimaButlerBitMark, kClimaButlerHdrSpace, + data, nbits, kClimaButlerFreq, true, 0, kDutyDefault); + // Footer + mark(kClimaButlerBitMark); + space(kClimaButlerGap); + } +} +#endif // SEND_CLIMABUTLER + +#if DECODE_CLIMABUTLER +/// Decode the supplied ClimaButler message. +/// Status: STABLE / Confirmed working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeClimaButler(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + 2 * kFooter - offset) + return false; // Too short a message to match. + if (strict && nbits != kClimaButlerBits) + return false; + + // Header + Data + uint16_t used = matchGeneric(results->rawbuf + offset, &(results->value), + results->rawlen - offset, nbits, + kClimaButlerHdrMark, kClimaButlerHdrSpace, + kClimaButlerBitMark, kClimaButlerOneSpace, + kClimaButlerBitMark, kClimaButlerZeroSpace, + kClimaButlerBitMark, kClimaButlerHdrSpace); + if (!used) return false; // Didn't matched. + offset += used; + // Footer + if (!matchMark(results->rawbuf[offset++], kClimaButlerBitMark)) + return false; + if (results->rawlen <= offset && !matchAtLeast(results->rawbuf[offset], + kClimaButlerGap)) + return false; + + // Success + results->decode_type = decode_type_t::CLIMABUTLER; + results->bits = nbits; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_CLIMABUTLER diff --git a/src/libraries/IRremoteESP8266/src/ir_Coolix.cpp b/src/libraries/IRremoteESP8266/src/ir_Coolix.cpp new file mode 100644 index 000000000..28c156aea --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Coolix.cpp @@ -0,0 +1,758 @@ +// Copyright bakrus +// Copyright 2017,2019 David Conran +// added by (send) bakrus & (decode) crankyoldgit +/// @file +/// @brief Coolix A/C / heatpump +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/484 + +#include "ir_Coolix.h" +// #include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +// Pulse parms are *50-100 for the Mark and *50+100 for the space +// First MARK is the one after the long gap +// pulse parameters in usec +const uint16_t kCoolixTick = 276; // Approximately 10.5 cycles at 38kHz +const uint16_t kCoolixBitMarkTicks = 2; +const uint16_t kCoolixBitMark = kCoolixBitMarkTicks * kCoolixTick; // 552us +const uint16_t kCoolixOneSpaceTicks = 6; +const uint16_t kCoolixOneSpace = kCoolixOneSpaceTicks * kCoolixTick; // 1656us +const uint16_t kCoolixZeroSpaceTicks = 2; +const uint16_t kCoolixZeroSpace = kCoolixZeroSpaceTicks * kCoolixTick; // 552us +const uint16_t kCoolixHdrMarkTicks = 17; +const uint16_t kCoolixHdrMark = kCoolixHdrMarkTicks * kCoolixTick; // 4692us +const uint16_t kCoolixHdrSpaceTicks = 16; +const uint16_t kCoolixHdrSpace = kCoolixHdrSpaceTicks * kCoolixTick; // 4416us +const uint16_t kCoolixMinGapTicks = kCoolixHdrMarkTicks + kCoolixZeroSpaceTicks; +const uint16_t kCoolixMinGap = kCoolixMinGapTicks * kCoolixTick; // 5244us +const uint8_t kCoolixExtraTolerance = 5; // Percent + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; + +#if SEND_COOLIX +/// Send a Coolix 24-bit message +/// Status: STABLE / Confirmed Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_COOLIX.cpp +void IRsend::sendCOOLIX(uint64_t data, uint16_t nbits, uint16_t repeat) { + if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8. + + // Set IR carrier frequency + enableIROut(38); + + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kCoolixHdrMark); + space(kCoolixHdrSpace); + + // Data + // Break data into byte segments, starting at the Most Significant + // Byte. Each byte then being sent normal, then followed inverted. + for (uint16_t i = 8; i <= nbits; i += 8) { + // Grab a bytes worth of data. + uint8_t segment = (data >> (nbits - i)) & 0xFF; + // Normal + sendData(kCoolixBitMark, kCoolixOneSpace, kCoolixBitMark, + kCoolixZeroSpace, segment, 8, true); + // Inverted. + sendData(kCoolixBitMark, kCoolixOneSpace, kCoolixBitMark, + kCoolixZeroSpace, segment ^ 0xFF, 8, true); + } + + // Footer + mark(kCoolixBitMark); + space(kCoolixMinGap); // Pause before repeating + } + space(kDefaultMessageGap); +} +#endif // SEND_COOLIX + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRCoolixAC::IRCoolixAC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IRCoolixAC::stateReset(void) { + setRaw(kCoolixDefaultState); + savedFan = getFan(); + clearSensorTemp(); + powerFlag = false; + turboFlag = false; + ledFlag = false; + cleanFlag = false; + sleepFlag = false; + swingFlag = false; +} + +/// Set up hardware to be able to send a message. +void IRCoolixAC::begin(void) { _irsend.begin(); } + +#if SEND_COOLIX +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRCoolixAC::send(const uint16_t repeat) { + // SwingVStep (aka. Direct / Vane step) needs to be sent with `0` repeats. + // Typically repeat is `kCoolixDefaultRepeat` which is `1`, so this allows + // it to be 0 normally for this command, and allows additional repeats if + // requested rather always 0 for that command. + _irsend.sendCOOLIX(getRaw(), kCoolixBits, repeat - (getSwingVStep() && + repeat > 0) ? 1 : 0); + // make sure to remove special state from the internal state + // after command has being transmitted. + recoverSavedState(); +} +#endif // SEND_COOLIX + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +uint32_t IRCoolixAC::getRaw(void) const { return _.raw; } + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRCoolixAC::setRaw(const uint32_t new_code) { + powerFlag = true; // Everything that is not the special power off mesg is On. + if (!handleSpecialState(new_code)) { + // it isn`t special so might affect Temp|mode|Fan + if (new_code == kCoolixCmdFan) { + setMode(kCoolixFan); + return; + } + } + // must be a command changing Temp|Mode|Fan + // it is safe to just copy to remote var + _.raw = new_code; +} + +/// Is the current state is a special state? +/// @return true, if it is. false if it isn't. +bool IRCoolixAC::isSpecialState(void) const { + switch (_.raw) { + case kCoolixClean: + case kCoolixLed: + case kCoolixOff: + case kCoolixSwing: + case kCoolixSwingV: + case kCoolixSleep: + case kCoolixTurbo: return true; + default: return false; + } +} + +/// Adjust any internal settings based on the type of special state we are +/// supplied. Does nothing if it isn't a special state. +/// @param[in] data The state we need to act upon. +/// @note Special state means commands that are not affecting +/// Temperature/Mode/Fan, and they toggle a setting. +/// e.g. Swing Step is not a special state by this definition. +/// @return true, if it is a special state. false if it isn't. +bool IRCoolixAC::handleSpecialState(const uint32_t data) { + switch (data) { + case kCoolixClean: + cleanFlag = !cleanFlag; + break; + case kCoolixLed: + ledFlag = !ledFlag; + break; + case kCoolixOff: + powerFlag = false; + break; + case kCoolixSwing: + swingFlag = !swingFlag; + break; + case kCoolixSleep: + sleepFlag = !sleepFlag; + break; + case kCoolixTurbo: + turboFlag = !turboFlag; + break; + default: + return false; + } + return true; +} + +/// Backup the current internal state as long as it isn't a special state and +/// set the new state. +/// @note: Must be called before every special state to make sure the +/// internal state is safe. +/// @param[in] raw_state A valid raw state/code for this protocol. +void IRCoolixAC::updateAndSaveState(const uint32_t raw_state) { + if (!isSpecialState()) _saved = _; + _.raw = raw_state; +} + +/// Restore the current internal state from backup as long as it isn't a +/// special state. +void IRCoolixAC::recoverSavedState(void) { + // If the current state is a special one, last known normal one. + if (isSpecialState()) _ = _saved; + // If the saved state was also a special state, reset as we expect a normal + // state out of all this. + if (isSpecialState()) stateReset(); +} + +/// Set the raw (native) temperature value. +/// @note Bypasses any checks. +/// @param[in] code The desired native temperature. +void IRCoolixAC::setTempRaw(const uint8_t code) { _.Temp = code; } + +/// Get the raw (native) temperature value. +/// @return The native temperature value. +uint8_t IRCoolixAC::getTempRaw(void) const { return _.Temp; } + +/// Set the temperature. +/// @param[in] desired The temperature in degrees celsius. +void IRCoolixAC::setTemp(const uint8_t desired) { + // Range check. + uint8_t temp = ::min(desired, kCoolixTempMax); + temp = ::max(temp, kCoolixTempMin); + setTempRaw(kCoolixTempMap[temp - kCoolixTempMin]); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRCoolixAC::getTemp(void) const { + const uint8_t code = getTempRaw(); + for (uint8_t i = 0; i < kCoolixTempRange; i++) + if (kCoolixTempMap[i] == code) return kCoolixTempMin + i; + return kCoolixTempMax; // Not a temp we expected. +} + +/// Set the raw (native) sensor temperature value. +/// @note Bypasses any checks or additional actions. +/// @param[in] code The desired native sensor temperature. +void IRCoolixAC::setSensorTempRaw(const uint8_t code) { _.SensorTemp = code; } + +/// Set the sensor temperature. +/// @param[in] temp The temperature in degrees celsius. +/// @warning Do not send messages with a Sensor Temp more frequently than once +/// per minute, otherwise the A/C unit will ignore them. +void IRCoolixAC::setSensorTemp(const uint8_t temp) { + setSensorTempRaw(::min(temp, kCoolixSensorTempMax)); + setZoneFollow(true); // Setting a Sensor temp means you want to Zone Follow. +} + +/// Get the sensor temperature setting. +/// @return The current setting for sensor temp. in degrees celsius. +uint8_t IRCoolixAC::getSensorTemp(void) const { return _.SensorTemp; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +/// @note There is only an "off" state. Everything else is "on". +bool IRCoolixAC::getPower(void) const { return powerFlag; } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRCoolixAC::setPower(const bool on) { + if (!on) + updateAndSaveState(kCoolixOff); + else if (!powerFlag) + // at this point state must be ready + // to be transmitted + recoverSavedState(); + powerFlag = on; +} + +/// Change the power setting to On. +void IRCoolixAC::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRCoolixAC::off(void) { setPower(false); } + +/// Get the Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCoolixAC::getSwing(void) const { return swingFlag; } + +/// Toggle the Swing mode of the A/C. +void IRCoolixAC::setSwing(void) { + // Assumes that repeated sending "swing" toggles the action on the device. + updateAndSaveState(kCoolixSwing); + swingFlag = !swingFlag; +} + +/// Get the Vertical Swing Step setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCoolixAC::getSwingVStep(void) const { return _.raw == kCoolixSwingV; } + +/// Set the Vertical Swing Step setting of the A/C. +void IRCoolixAC::setSwingVStep(void) { + updateAndSaveState(kCoolixSwingV); +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCoolixAC::getSleep(void) const { return sleepFlag; } + +/// Toggle the Sleep mode of the A/C. +void IRCoolixAC::setSleep(void) { + updateAndSaveState(kCoolixSleep); + sleepFlag = !sleepFlag; +} + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCoolixAC::getTurbo(void) const { return turboFlag; } + +/// Toggle the Turbo mode of the A/C. +void IRCoolixAC::setTurbo(void) { + // Assumes that repeated sending "turbo" toggles the action on the device. + updateAndSaveState(kCoolixTurbo); + turboFlag = !turboFlag; +} + +/// Get the Led (light) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCoolixAC::getLed(void) const { return ledFlag; } + +/// Toggle the Led (light) mode of the A/C. +void IRCoolixAC::setLed(void) { + // Assumes that repeated sending "Led" toggles the action on the device. + updateAndSaveState(kCoolixLed); + ledFlag = !ledFlag; +} + +/// Get the Clean setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCoolixAC::getClean(void) const { return cleanFlag; } + +/// Toggle the Clean mode of the A/C. +void IRCoolixAC::setClean(void) { + updateAndSaveState(kCoolixClean); + cleanFlag = !cleanFlag; +} + +/// Get the Zone Follow setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRCoolixAC::getZoneFollow(void) const { + return _.ZoneFollow1 && _.ZoneFollow2; +} + +/// Change the Zone Follow setting. +/// @note Internal use only. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRCoolixAC::setZoneFollow(const bool on) { + _.ZoneFollow1 = on; + _.ZoneFollow2 = on; + setFan(on ? kCoolixFanZoneFollow : savedFan); +} + +/// Clear the Sensor Temperature setting.. +void IRCoolixAC::clearSensorTemp(void) { + setZoneFollow(false); + setSensorTempRaw(kCoolixSensorTempIgnoreCode); +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRCoolixAC::setMode(const uint8_t mode) { + uint32_t actualmode = mode; + switch (actualmode) { + case kCoolixAuto: + case kCoolixDry: + setFan(kCoolixFanAuto0, false); + break; + case kCoolixCool: + case kCoolixHeat: + case kCoolixFan: + setFan(kCoolixFanAuto, false); + break; + default: // Anything else, go with Auto mode. + setMode(kCoolixAuto); + setFan(kCoolixFanAuto0, false); + return; + } + setTemp(getTemp()); + // Fan mode is a special case of Dry. + if (mode == kCoolixFan) { + actualmode = kCoolixDry; + setTempRaw(kCoolixFanTempCode); + } + _.Mode = actualmode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRCoolixAC::getMode(void) const { + const uint8_t mode = _.Mode; + if (mode == kCoolixDry) + if (getTempRaw() == kCoolixFanTempCode) return kCoolixFan; + return mode; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRCoolixAC::getFan(void) const { return _.Fan; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @param[in] modecheck Do we enforce any mode limitations before setting? +void IRCoolixAC::setFan(const uint8_t speed, const bool modecheck) { + uint8_t newspeed = speed; + switch (speed) { + case kCoolixFanAuto: // Dry & Auto mode can't have this speed. + if (modecheck) { + switch (getMode()) { + case kCoolixAuto: + case kCoolixDry: + newspeed = kCoolixFanAuto0; + break; + } + } + break; + case kCoolixFanAuto0: // Only Dry & Auto mode can have this speed. + if (modecheck) { + switch (getMode()) { + case kCoolixAuto: + case kCoolixDry: break; + default: newspeed = kCoolixFanAuto; + } + } + break; + case kCoolixFanMin: + case kCoolixFanMed: + case kCoolixFanMax: + case kCoolixFanZoneFollow: + case kCoolixFanFixed: + break; + default: // Unknown speed requested. + newspeed = kCoolixFanAuto; + break; + } + // Keep a copy of the last non-ZoneFollow fan setting. + savedFan = (_.Fan == kCoolixFanZoneFollow) ? savedFan : _.Fan; + _.Fan = newspeed; +} + +/// Convert a standard A/C mode into its native mode. +/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent. +/// @return The corresponding native mode. +uint8_t IRCoolixAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kCoolixCool; + case stdAc::opmode_t::kHeat: return kCoolixHeat; + case stdAc::opmode_t::kDry: return kCoolixDry; + case stdAc::opmode_t::kFan: return kCoolixFan; + default: return kCoolixAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRCoolixAC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kCoolixFanMin; + case stdAc::fanspeed_t::kMedium: return kCoolixFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kCoolixFanMax; + default: return kCoolixFanAuto; + } +} + +/// Convert a native mode to it's common stdAc::opmode_t equivalent. +/// @param[in] mode A native operation mode to be converted. +/// @return The corresponding common stdAc::opmode_t mode. +stdAc::opmode_t IRCoolixAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kCoolixCool: return stdAc::opmode_t::kCool; + case kCoolixHeat: return stdAc::opmode_t::kHeat; + case kCoolixDry: return stdAc::opmode_t::kDry; + case kCoolixFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRCoolixAC::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kCoolixFanMax: return stdAc::fanspeed_t::kMax; + case kCoolixFanMed: return stdAc::fanspeed_t::kMedium; + case kCoolixFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the A/C state to it's common stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return A stdAc::state_t state. +stdAc::state_t IRCoolixAC::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.swingv = stdAc::swingv_t::kOff; + result.turbo = false; + result.clean = false; + result.light = false; + result.sleep = -1; + } + // Not supported. + result.model = -1; // No models used. + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.econo = false; + result.filter = false; + result.beep = false; + result.clock = -1; + + // Supported. + result.protocol = decode_type_t::COOLIX; + result.celsius = true; + result.power = getPower(); + // Power off state no other state info. Use the previous state if we have it. + if (!result.power) return result; + // Handle the special single command (Swing/Turbo/Light/Clean/Sleep) toggle + // messages. These have no other state info so use the rest of the previous + // state if we have it for them. + if (getSwing()) { + result.swingv = result.swingv != stdAc::swingv_t::kOff ? + stdAc::swingv_t::kOff : stdAc::swingv_t::kAuto; // Invert swing. + return result; + } else if (getTurbo()) { + result.turbo = !result.turbo; + return result; + } else if (getLed()) { + result.light = !result.light; + return result; + } else if (getClean()) { + result.clean = !result.clean; + return result; + } else if (getSleep()) { + result.sleep = result.sleep >= 0 ? -1 : 0; // Invert sleep. + return result; + } + // Back to "normal" stateful messages. + result.mode = toCommonMode(getMode()); + result.degrees = getTemp(); + result.sensorTemperature = getSensorTemp(); + result.iFeel = getZoneFollow(); + result.fanspeed = toCommonFanSpeed(getFan()); + return result; +} + +/// Convert the internal state into a human readable string. +/// @return The current internal state expressed as a human readable String. +String IRCoolixAC::toString(void) const { + String result = ""; + result.reserve(100); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + if (!getPower()) return result; // If it's off, there is no other info. + if (isSpecialState()) { + // Special modes. + result += kCommaSpaceStr; + if (getSwing()) result += kSwingStr; + else if (getSwingVStep()) result += kSwingVStr; + else if (getSleep()) result += kSleepStr; + else if (getTurbo()) result += kTurboStr; + else if (getLed()) result += kLightStr; + else if (getClean()) result += kCleanStr; + + result += kColonSpaceStr; + if (getSwingVStep()) + result += kStepStr; + else + result += kToggleStr; + return result; + } + result += addModeToString(getMode(), kCoolixAuto, kCoolixCool, kCoolixHeat, + kCoolixDry, kCoolixFan); + result += addIntToString(getFan(), kFanStr); + result += kSpaceLBraceStr; + switch (getFan()) { + case kCoolixFanAuto: + result += kAutoStr; + break; + case kCoolixFanAuto0: + result += kAutoStr; + result += '0'; + break; + case kCoolixFanMax: + result += kMaxStr; + break; + case kCoolixFanMin: + result += kMinStr; + break; + case kCoolixFanMed: + result += kMedStr; + break; + case kCoolixFanZoneFollow: + result += kZoneFollowStr; + break; + case kCoolixFanFixed: + result += kFixedStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + // Fan mode doesn't have a temperature. + if (getMode() != kCoolixFan) result += addTempToString(getTemp()); + result += addBoolToString(getZoneFollow(), kZoneFollowStr); + result += addLabeledString( + (getSensorTemp() == kCoolixSensorTempIgnoreCode) + // Encasing with String(blah) to keep compatible with old arduino + // frameworks. Not needed with 3.0.2. + ///> @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1639#issuecomment-944906016 + ? kOffStr : String(uint64ToString(getSensorTemp()) + 'C'), + kSensorTempStr); + return result; +} + +#if DECODE_COOLIX +/// Decode the supplied Coolix 24-bit A/C message. +/// Status: STABLE / Known Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeCOOLIX(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // The protocol sends the data normal + inverted, alternating on + // each byte. Hence twice the number of expected data bits. + if (results->rawlen < 2 * 2 * nbits + kHeader + kFooter - 1 + offset) + return false; // Can't possibly be a valid COOLIX message. + if (strict && nbits != kCoolixBits) + return false; // Not strictly a COOLIX message. + if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte. + return false; + + uint64_t data = 0; + uint64_t inverted = 0; + + if (nbits > sizeof(data) * 8) + return false; // We can't possibly capture a Coolix packet that big. + + // Header + if (!matchMark(results->rawbuf[offset++], kCoolixHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kCoolixHdrSpace)) return false; + + // Data + // Twice as many bits as there are normal plus inverted bits. + for (uint16_t i = 0; i < nbits * 2; i += 8) { + const bool flip = (i / 8) % 2; + uint64_t result = 0; + // Read the next byte of data. + const uint16_t used = matchGeneric(results->rawbuf + offset, &result, + results->rawlen - offset, 8, + 0, 0, // No Header + kCoolixBitMark, kCoolixOneSpace, // Data + kCoolixBitMark, kCoolixZeroSpace, + 0, 0, // No Footer + false, + _tolerance + kCoolixExtraTolerance, + 0, true); + if (!used) return false; // Didn't match a bytes worth of data. + offset += used; + if (flip) { // The inverted byte. + inverted <<= 8; + inverted |= result; + } else { + data <<= 8; + data |= result; + } + } + + // Footer + if (!matchMark(results->rawbuf[offset++], kCoolixBitMark)) return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kCoolixMinGap)) return false; + + // Compliance + uint64_t orig = data; // Save a copy of the data. + if (strict) { + for (uint16_t i = 0; i < nbits; i += 8, data >>= 8, inverted >>= 8) + if ((data & 0xFF) != ((inverted & 0xFF) ^ 0xFF)) return false; + } + + // Success + results->decode_type = COOLIX; + results->bits = nbits; + results->value = orig; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_COOLIX + +#if SEND_COOLIX48 +/// Send a Coolix 48-bit message. +/// Status: ALPHA / Untested. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694 +/// @note This is effectively the same as `sendCOOLIX()` except requiring the +/// bit flipping be done prior to the call. +void IRsend::sendCoolix48(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + // Header + Data + Footer + sendGeneric(kCoolixHdrMark, kCoolixHdrSpace, + kCoolixBitMark, kCoolixOneSpace, + kCoolixBitMark, kCoolixZeroSpace, + kCoolixBitMark, kCoolixMinGap, + data, nbits, 38000, true, repeat, 33); +} +#endif // SEND_COOLIX48 + +#if DECODE_COOLIX48 +/// Decode the supplied Coolix 48-bit A/C message. +/// Status: BETA / Probably Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694 +bool IRrecv::decodeCoolix48(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kCoolix48Bits) + return false; // Not strictly a COOLIX48 message. + + // Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &(results->value), + results->rawlen - offset, nbits, + kCoolixHdrMark, kCoolixHdrSpace, + kCoolixBitMark, kCoolixOneSpace, + kCoolixBitMark, kCoolixZeroSpace, + kCoolixBitMark, kCoolixMinGap, + true, _tolerance + kCoolixExtraTolerance, 0, true)) + return false; + + // Success + results->decode_type = COOLIX48; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_COOLIX48 diff --git a/src/libraries/IRremoteESP8266/src/ir_Coolix.h b/src/libraries/IRremoteESP8266/src/ir_Coolix.h new file mode 100644 index 000000000..ffe112e2d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Coolix.h @@ -0,0 +1,200 @@ +// Copyright 2018 David Conran +/// @file +/// @brief Support for Coolix A/C protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/484 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1318 +/// @note Kudos: +/// Hamper: For the breakdown and mapping of the bit values. +/// fraschizzato: For additional ZoneFollow & SwingVStep analysis. +/// @note Timers seem to use the `COOLIX48` protocol. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694 + +// Supports: +// Brand: Beko, Model: RG57K7(B)/BGEF Remote +// Brand: Beko, Model: BINR 070/071 split-type A/C +// Brand: Midea, Model: RG52D/BGE Remote +// Brand: Midea, Model: MS12FU-10HRDN1-QRD0GW(B) A/C +// Brand: Midea, Model: MSABAU-07HRFN1-QRD0GW A/C (circa 2016) +// Brand: Tokio, Model: AATOEMF17-12CHR1SW split-type RG51|50/BGE Remote +// Brand: Airwell, Model: RC08B remote +// Brand: Kastron, Model: RG57A7/BGEF Inverter remote +// Brand: Kaysun, Model: Casual CF A/C +// Brand: Toshiba, Model: RAS-M10YKV-E A/C +// Brand: Toshiba, Model: RAS-M13YKV-E A/C +// Brand: Toshiba, Model: RAS-4M27YAV-E A/C +// Brand: Toshiba, Model: WH-E1YE remote +// Brand: Bosch, Model: RG36B4/BGE remote +// Brand: Bosch, Model: B1ZAI2441W/B1ZAO2441W A/C + +#ifndef IR_COOLIX_H_ +#define IR_COOLIX_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +// Constants +// Modes +const uint8_t kCoolixCool = 0b000; +const uint8_t kCoolixDry = 0b001; +const uint8_t kCoolixAuto = 0b010; +const uint8_t kCoolixHeat = 0b011; +const uint8_t kCoolixFan = 0b100; // Synthetic. +// const uint32_t kCoolixModeMask = 0b000000000000000000001100; // 0xC +// const uint32_t kCoolixZoneFollowMask = 0b000010000000000000000010 0x80002 +// Fan Control +const uint8_t kCoolixFanMin = 0b100; +const uint8_t kCoolixFanMed = 0b010; +const uint8_t kCoolixFanMax = 0b001; +const uint8_t kCoolixFanAuto = 0b101; +const uint8_t kCoolixFanAuto0 = 0b000; +const uint8_t kCoolixFanZoneFollow = 0b110; +const uint8_t kCoolixFanFixed = 0b111; +// Temperature +const uint8_t kCoolixTempMin = 17; // Celsius +const uint8_t kCoolixTempMax = 30; // Celsius +const uint8_t kCoolixTempRange = kCoolixTempMax - kCoolixTempMin + 1; +const uint8_t kCoolixFanTempCode = 0b1110; // Part of Fan Mode. +const uint8_t kCoolixTempMap[kCoolixTempRange] = { + 0b0000, // 17C + 0b0001, // 18c + 0b0011, // 19C + 0b0010, // 20C + 0b0110, // 21C + 0b0111, // 22C + 0b0101, // 23C + 0b0100, // 24C + 0b1100, // 25C + 0b1101, // 26C + 0b1001, // 27C + 0b1000, // 28C + 0b1010, // 29C + 0b1011 // 30C +}; +const uint8_t kCoolixSensorTempMax = 30; // Celsius +const uint8_t kCoolixSensorTempIgnoreCode = 0b11111; // 0x1F / 31 (DEC) +// kCoolixSensorTempMask = 0b000000000000111100000000; // 0xF00 +// Fixed states/messages. +const uint32_t kCoolixOff = 0b101100100111101111100000; // 0xB27BE0 +const uint32_t kCoolixSwing = 0b101100100110101111100000; // 0xB26BE0 +const uint32_t kCoolixSwingH = 0b101100101111010110100010; // 0xB5F5A2 +const uint32_t kCoolixSwingV = 0b101100100000111111100000; // 0xB20FE0 +const uint32_t kCoolixSleep = 0b101100101110000000000011; // 0xB2E003 +const uint32_t kCoolixTurbo = 0b101101011111010110100010; // 0xB5F5A2 +const uint32_t kCoolixLed = 0b101101011111010110100101; // 0xB5F5A5 +const uint32_t kCoolixClean = 0b101101011111010110101010; // 0xB5F5AA +const uint32_t kCoolixCmdFan = 0b101100101011111111100100; // 0xB2BFE4 +// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. +const uint32_t kCoolixDefaultState = 0b101100100001111111001000; // 0xB21FC8 + +/// Native representation of a Coolix A/C message. +union CoolixProtocol { + uint32_t raw; ///< The state in IR code form. + struct { // Only 24 bits are used. + // Byte + uint32_t :1; // Unknown + uint32_t ZoneFollow1:1; ///< Control bit for Zone Follow mode. + uint32_t Mode :2; ///< Operation mode. + uint32_t Temp :4; ///< Desired temperature (Celsius) + // Byte + uint32_t SensorTemp :5; ///< The temperature sensor in the IR remote. + uint32_t Fan :3; ///< Fan speed + // Byte + uint32_t :3; // Unknown + uint32_t ZoneFollow2:1; ///< Additional control bit for Zone Follow mode. + uint32_t :4; ///< Fixed value 0b1011 / 0xB. + }; +}; + +// Classes + +/// Class for handling detailed Coolix A/C messages. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/484 +class IRCoolixAC { + public: + explicit IRCoolixAC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_COOLIX + void send(const uint16_t repeat = kCoolixDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_COOLIX + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setSensorTemp(const uint8_t temp); + uint8_t getSensorTemp(void) const; + void clearSensorTemp(void); + void setFan(const uint8_t speed, const bool modecheck = true); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwing(void); + bool getSwing(void) const; + void setSwingVStep(void); + bool getSwingVStep(void) const; + void setSleep(void); + bool getSleep(void) const; + void setTurbo(void); + bool getTurbo(void) const; + void setLed(void); + bool getLed(void) const; + void setClean(void); + bool getClean(void) const; + bool getZoneFollow(void) const; + uint32_t getRaw(void) const; + void setRaw(const uint32_t new_code); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; + void setZoneFollow(const bool on); +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + CoolixProtocol _; ///< The state of the IR remote in IR code form. + CoolixProtocol _saved; ///< Copy of the state if we required a special mode. + + // Internal State settings + bool powerFlag; + bool turboFlag; + bool ledFlag; + bool cleanFlag; + bool sleepFlag; + bool swingFlag; + uint8_t savedFan; + + void setTempRaw(const uint8_t code); + uint8_t getTempRaw(void) const; + void setSensorTempRaw(const uint8_t code); + bool isSpecialState(void) const; + bool handleSpecialState(const uint32_t data); + void updateAndSaveState(const uint32_t raw_state); + void recoverSavedState(void); + uint32_t getNormalState(void); +}; + +#endif // IR_COOLIX_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Corona.cpp b/src/libraries/IRremoteESP8266/src/ir_Corona.cpp new file mode 100644 index 000000000..c0e369702 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Corona.cpp @@ -0,0 +1,576 @@ +// Copyright 2020 Christian Nilsson +// +/// @file +/// @brief Corona A/C protocol +/// @note Unsupported: +/// - Auto/Max button press (special format) + +#include "ir_Corona.h" +// #include +#include +#include "IRac.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::addFanToString; +using irutils::minsToString; +using irutils::setBits; + +// Constants +const uint16_t kCoronaAcHdrMark = 3500; +const uint16_t kCoronaAcHdrSpace = 1680; +const uint16_t kCoronaAcBitMark = 450; +const uint16_t kCoronaAcOneSpace = 1270; +const uint16_t kCoronaAcZeroSpace = 420; +const uint16_t kCoronaAcSpaceGap = 10800; +const uint16_t kCoronaAcFreq = 38000; // Hz. +const uint16_t kCoronaAcOverheadShort = 3; +const uint16_t kCoronaAcOverhead = 11; // full message +const uint8_t kCoronaTolerance = 5; // +5% + +#if SEND_CORONA_AC +/// Send a CoronaAc formatted message. +/// Status: STABLE / Working on real device. +/// @param[in] data An array of bytes containing the IR command. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// e.g. +/// @code +/// uint8_t data[kCoronaAcStateLength] = { +/// 0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8, +/// 0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00, +/// 0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00}; +/// @endcode +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendCoronaAc(const uint8_t data[], + const uint16_t nbytes, const uint16_t repeat) { + if (nbytes < kCoronaAcSectionBytes) return; + if (kCoronaAcSectionBytes < nbytes && + nbytes < kCoronaAcStateLength) return; + for (uint16_t r = 0; r <= repeat; r++) { + uint16_t pos = 0; + // Data Section #1 - 3 loop + // e.g. + // bits = 56; bytes = 7; + // #1 *(data + pos) = {0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8}; + // #2 *(data + pos) = {0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00}; + // #3 *(data + pos) = {0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00}; + for (uint8_t section = 0; section < kCoronaAcSections; section++) { + sendGeneric(kCoronaAcHdrMark, kCoronaAcHdrSpace, + kCoronaAcBitMark, kCoronaAcOneSpace, + kCoronaAcBitMark, kCoronaAcZeroSpace, + kCoronaAcBitMark, kCoronaAcSpaceGap, + data + pos, kCoronaAcSectionBytes, + kCoronaAcFreq, false, kNoRepeat, kDutyDefault); + pos += kCoronaAcSectionBytes; // Adjust by how many bytes was sent + // don't send more data then what we have + if (nbytes <= pos) + break; + } + } +} +#endif // SEND_CORONA_AC + +#if DECODE_CORONA_AC +/// Decode the supplied CoronaAc message. +/// Status: STABLE / Appears to be working. +/// @param[in,out] results Ptr to the data to decode & where to store it +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeCoronaAc(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + bool isLong = results->rawlen >= kCoronaAcBits * 2; + if (results->rawlen < 2 * nbits + + (isLong ? kCoronaAcOverhead : kCoronaAcOverheadShort) + - offset) + return false; // Too short a message to match. + if (strict && nbits != kCoronaAcBits && nbits != kCoronaAcBitsShort) + return false; + + uint16_t pos = 0; + uint16_t used = 0; + + // Data Section #1 - 3 loop + // e.g. + // bits = 56; bytes = 7; + // #1 *(results->state + pos) = {0x28, 0x61, 0x3D, 0x19, 0xE6, 0x37, 0xC8}; + // #2 *(results->state + pos) = {0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00}; + // #3 *(results->state + pos) = {0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00}; + for (uint8_t section = 0; section < kCoronaAcSections; section++) { + DPRINT(uint64ToString(section)); + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, kCoronaAcBitsShort, + kCoronaAcHdrMark, kCoronaAcHdrSpace, + kCoronaAcBitMark, kCoronaAcOneSpace, + kCoronaAcBitMark, kCoronaAcZeroSpace, + kCoronaAcBitMark, kCoronaAcSpaceGap, true, + _tolerance + kCoronaTolerance, kMarkExcess, false); + if (used == 0) return false; // We failed to find any data. + // short versions section 0 is special + if (strict && !IRCoronaAc::validSection(results->state, pos, + isLong ? section : 3)) + return false; + offset += used; // Adjust for how much of the message we read. + pos += kCoronaAcSectionBytes; // Adjust by how many bytes of data was read + // don't read more data then what we have + if (results->rawlen <= offset) + break; + } + + // Re-check we got the correct size/length due to the way we read the data. + if (strict && pos * 8 != kCoronaAcBits && pos * 8 != kCoronaAcBitsShort) { + DPRINTLN("strict bit match fail"); + return false; + } + + // Success + results->decode_type = decode_type_t::CORONA_AC; + results->bits = pos * 8; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_CORONA_AC + +/// Class constructor for handling detailed Corona A/C messages. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRCoronaAc::IRCoronaAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +/// @note The state is powered off. +void IRCoronaAc::stateReset(void) { + // known good state + _.sections[kCoronaAcSettingsSection].Data0 = kCoronaAcSectionData0Base; + _.sections[kCoronaAcSettingsSection].Data1 = 0x00; // ensure no unset mem + setPowerButton(true); // we default to this on, any timer removes it + setTemp(kCoronaAcMinTemp); + setMode(kCoronaAcModeCool); + setFan(kCoronaAcFanAuto); + setOnTimer(kCoronaAcTimerOff); + setOffTimer(kCoronaAcTimerOff); + // headers and checks are fixed in getRaw by checksum(_.raw) +} + +/// Get the byte that identifies the section +/// @param[in] section Index of the section 0-2, +/// 3 and above is used as the special case for short message +/// @return The byte used for the section +uint8_t IRCoronaAc::getSectionByte(const uint8_t section) { + // base byte + uint8_t b = kCoronaAcSectionLabelBase; + // 2 enabled bits shifted 0-2 bits depending on section + if (section >= 3) + return 0b10010000 | b; + setBits(&b, kHighNibble, kNibbleSize, 0b11 << section); + return b; +} + +/// Check that a CoronaAc Section part is valid with section byte and inverted +/// @param[in] state An array of bytes containing the section +/// @param[in] pos Where to start in the state array +/// @param[in] section Which section to work with +/// Used to get the section byte, and is validated against pos +/// @return true if section is valid, otherwise false +bool IRCoronaAc::validSection(const uint8_t state[], const uint16_t pos, + const uint8_t section) { + // sanity check, pos must match section, section 4 is at pos 0 + if ((section % kCoronaAcSections) * kCoronaAcSectionBytes != pos) + return false; + // all individual sections has the same prefix + const CoronaSection *p = reinterpret_cast(state + pos); + if (p->Header0 != kCoronaAcSectionHeader0) { + DPRINT("State "); + DPRINT(&(p->Header0) - state); + DPRINT(" expected 0x28 was "); + DPRINTLN(uint64ToString(p->Header0, 16)); + return false; + } + if (p->Header1 != kCoronaAcSectionHeader1) { + DPRINT("State "); + DPRINT(&(p->Header1) - state); + DPRINT(" expected 0x61 was "); + DPRINTLN(uint64ToString(p->Header1, 16)); + return false; + } + + // checking section byte + if (p->Label != getSectionByte(section)) { + DPRINT("check 2 not matching, got "); + DPRINT(uint64ToString(p->Label, 16)); + DPRINT(" expected "); + DPRINTLN(uint64ToString(getSectionByte(section), 16)); + return false; + } + + // checking inverts + uint8_t d0invinv = ~p->Data0Inv; + if (p->Data0 != d0invinv) { + DPRINT("inverted 3 - 4 not matching, got "); + DPRINT(uint64ToString(p->Data0, 16)); + DPRINT(" vs "); + DPRINTLN(uint64ToString(p->Data0Inv, 16)); + return false; + } + uint8_t d1invinv = ~p->Data1Inv; + if (p->Data1 != d1invinv) { + DPRINT("inverted 5 - 6 not matching, got "); + DPRINT(uint64ToString(p->Data1, 16)); + DPRINT(" vs "); + DPRINTLN(uint64ToString(p->Data1Inv, 16)); + return false; + } + return true; +} + +/// Calculate and set the check values for the internal state. +/// @param[in,out] data The array to be modified +void IRCoronaAc::checksum(uint8_t* data) { + CoronaProtocol *p = reinterpret_cast(data); + for (uint8_t i = 0; i < kCoronaAcSections; i++) { + p->sections[i].Header0 = kCoronaAcSectionHeader0; + p->sections[i].Header1 = kCoronaAcSectionHeader1; + p->sections[i].Label = getSectionByte(i); + p->sections[i].Data0Inv = ~p->sections[i].Data0; + p->sections[i].Data1Inv = ~p->sections[i].Data1; + } +} + +/// Set up hardware to be able to send a message. +void IRCoronaAc::begin(void) { _irsend.begin(); } + +#if SEND_CORONA_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRCoronaAc::send(const uint16_t repeat) { + // if no timer, always send once without power press + if (!getOnTimer() && !getOffTimer()) { + setPowerButton(false); + _irsend.sendCoronaAc(getRaw(), kCoronaAcStateLength, repeat); + // and then with power press + setPowerButton(true); + } + _irsend.sendCoronaAc(getRaw(), kCoronaAcStateLength, repeat); +} +#endif // SEND_CORONA_AC + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A Ptr to a valid code for this protocol based on the current +/// internal state. +/// @note To get stable AC state, if no timers, send once +/// without PowerButton set, and once with +uint8_t* IRCoronaAc::getRaw(void) { + checksum(_.raw); // Ensure correct check bits before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid state for this protocol. +/// @param[in] length of the new_code array. +void IRCoronaAc::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kCoronaAcStateLength)); +} + +/// Set the temp in deg C. +/// @param[in] temp The desired temperature in Celsius. +void IRCoronaAc::setTemp(const uint8_t temp) { + uint8_t degrees = ::max(temp, kCoronaAcMinTemp); + degrees = ::min(degrees, kCoronaAcMaxTemp); + _.Temp = degrees - kCoronaAcMinTemp + 1; +} + +/// Get the current temperature from the internal state. +/// @return The current temperature in Celsius. +uint8_t IRCoronaAc::getTemp(void) const { + return _.Temp + kCoronaAcMinTemp - 1; +} + +/// Change the power setting. (in practice Standby, remote power) +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note If changed, setPowerButton is also needed, +/// unless timer is or was active +void IRCoronaAc::setPower(const bool on) { + _.Power = on; + // setting power state resets timers that would cause the state + if (on) + setOnTimer(kCoronaAcTimerOff); + else + setOffTimer(kCoronaAcTimerOff); +} + +/// Get the current power setting. (in practice Standby, remote power) +/// @return true, the setting is on. false, the setting is off. +bool IRCoronaAc::getPower(void) const { + return _.Power; +} + +/// Change the power button setting. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note this sets that the AC should set power, +/// use setPower to define if the AC should end up as on or off +/// When no timer is active, the below is a truth table +/// With AC On, a command with setPower and setPowerButton gives nothing +/// With AC On, a command with setPower but not setPowerButton is ok +/// With AC Off, a command with setPower but not setPowerButton gives nothing +/// With AC Off, a command with setPower and setPowerButton is ok +void IRCoronaAc::setPowerButton(const bool on) { + _.PowerButton = on; +} + +/// Get the value of the current power button setting. +/// @return true, the setting is on. false, the setting is off. +bool IRCoronaAc::getPowerButton(void) const { + return _.PowerButton; +} + +/// Change the power setting to On. +void IRCoronaAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRCoronaAc::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRCoronaAc::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRCoronaAc::setMode(const uint8_t mode) { + switch (mode) { + case kCoronaAcModeCool: + case kCoronaAcModeDry: + case kCoronaAcModeFan: + case kCoronaAcModeHeat: + _.Mode = mode; + return; + default: + _.Mode = kCoronaAcModeCool; + } +} + +/// Convert a standard A/C mode into its native mode. +/// @param[in] mode A stdAc::opmode_t mode to be +/// converted to it's native equivalent +/// @return The corresponding native mode. +uint8_t IRCoronaAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kFan: return kCoronaAcModeFan; + case stdAc::opmode_t::kDry: return kCoronaAcModeDry; + case stdAc::opmode_t::kHeat: return kCoronaAcModeHeat; + default: return kCoronaAcModeCool; + } +} + +/// Convert a native mode to it's common stdAc::opmode_t equivalent. +/// @param[in] mode A native operation mode to be converted. +/// @return The corresponding common stdAc::opmode_t mode. +stdAc::opmode_t IRCoronaAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kCoronaAcModeFan: return stdAc::opmode_t::kFan; + case kCoronaAcModeDry: return stdAc::opmode_t::kDry; + case kCoronaAcModeHeat: return stdAc::opmode_t::kHeat; + default: return stdAc::opmode_t::kCool; + } +} + +/// Get the operating speed of the A/C Fan +/// @return The current operating fan speed setting +uint8_t IRCoronaAc::getFan(void) const { + return _.Fan; +} + +/// Set the operating speed of the A/C Fan +/// @param[in] speed The desired fan speed +void IRCoronaAc::setFan(const uint8_t speed) { + if (speed > kCoronaAcFanHigh) + _.Fan = kCoronaAcFanAuto; + else + _.Fan = speed; +} + +/// Change the powersave setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRCoronaAc::setEcono(const bool on) { + _.Econo = on; +} + +/// Get the value of the current powersave setting. +/// @return true, the setting is on. false, the setting is off. +bool IRCoronaAc::getEcono(void) const { + return _.Econo; +} + +/// Convert a standard A/C Fan speed into its native fan speed. +/// @param[in] speed The desired stdAc::fanspeed_t fan speed +/// @return The given fan speed in native format +uint8_t IRCoronaAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kCoronaAcFanLow; + case stdAc::fanspeed_t::kMedium: return kCoronaAcFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kCoronaAcFanHigh; + default: return kCoronaAcFanAuto; + } +} + +/// Convert a native fan speed to it's common equivalent. +/// @param[in] speed The desired native fan speed +/// @return The given fan speed in stdAc::fanspeed_t format +stdAc::fanspeed_t IRCoronaAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kCoronaAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kCoronaAcFanMedium: return stdAc::fanspeed_t::kMedium; + case kCoronaAcFanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Vertical Swing toggle setting +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note This is a button press, and not a state +/// after sending it once you should turn it off +void IRCoronaAc::setSwingVToggle(const bool on) { + _.SwingVToggle = on; +} + +/// Get the Vertical Swing toggle setting +/// @return true, the setting is on. false, the setting is off. +bool IRCoronaAc::getSwingVToggle(void) const { + return _.SwingVToggle; +} + +/// Set the Timer time +/// @param[in] section index of section, used for offset. +/// @param[in] nr_of_mins Number of minutes to set the timer to. +/// (non in range value is disable). +/// Valid is from 1 minute to 12 hours +void IRCoronaAc::_setTimer(const uint8_t section, const uint16_t nr_of_mins) { + // default to off + uint16_t hsecs = kCoronaAcTimerOff; + if (1 <= nr_of_mins && nr_of_mins <= kCoronaAcTimerMax) + hsecs = nr_of_mins * kCoronaAcTimerUnitsPerMin; + + // convert 16 bit value to separate 8 bit parts + _.sections[section].Data1 = hsecs >> 8; + _.sections[section].Data0 = hsecs; + + // if any timer is enabled, then (remote) ac must be on (Standby) + if (hsecs != kCoronaAcTimerOff) { + _.Power = true; + setPowerButton(false); + } +} + +/// Get the current Timer time +/// @return The number of minutes it is set for. 0 means it's off. +/// @note The A/C protocol supports 2 second increments +uint16_t IRCoronaAc::_getTimer(const uint8_t section) const { + // combine separate 8 bit parts to 16 bit value + uint16_t hsecs = _.sections[section].Data1 << 8 | + _.sections[section].Data0; + + if (hsecs == kCoronaAcTimerOff) + return 0; + + return hsecs / kCoronaAcTimerUnitsPerMin; +} + +/// Get the current On Timer time +/// @return The number of minutes it is set for. 0 means it's off. +uint16_t IRCoronaAc::getOnTimer(void) const { + return _getTimer(kCoronaAcOnTimerSection); +} + +/// Set the On Timer time +/// @param[in] nr_of_mins Number of minutes to set the timer to. +/// (0 or kCoronaAcTimerOff is disable). +void IRCoronaAc::setOnTimer(const uint16_t nr_of_mins) { + _setTimer(kCoronaAcOnTimerSection, nr_of_mins); + // if we set a timer value, clear the other timer + if (getOnTimer()) + setOffTimer(kCoronaAcTimerOff); +} + +/// Get the current Off Timer time +/// @return The number of minutes it is set for. 0 means it's off. +uint16_t IRCoronaAc::getOffTimer(void) const { + return _getTimer(kCoronaAcOffTimerSection); +} + +/// Set the Off Timer time +/// @param[in] nr_of_mins Number of minutes to set the timer to. +/// (0 or kCoronaAcTimerOff is disable). +void IRCoronaAc::setOffTimer(const uint16_t nr_of_mins) { + _setTimer(kCoronaAcOffTimerSection, nr_of_mins); + // if we set a timer value, clear the other timer + if (getOffTimer()) + setOnTimer(kCoronaAcTimerOff); +} + +/// Convert the internal state into a human readable string. +/// @return The current internal state expressed as a human readable String. +String IRCoronaAc::toString(void) const { + String result = ""; + result.reserve(140); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addBoolToString(_.PowerButton, kPowerButtonStr); + result += addModeToString(_.Mode, 0xFF, kCoronaAcModeCool, + kCoronaAcModeHeat, kCoronaAcModeDry, + kCoronaAcModeFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kCoronaAcFanHigh, kCoronaAcFanLow, + kCoronaAcFanAuto, kCoronaAcFanAuto, + kCoronaAcFanMedium); + result += addBoolToString(_.SwingVToggle, kSwingVToggleStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addLabeledString(getOnTimer() + ? minsToString(getOnTimer()) : kOffStr, + kOnTimerStr); + result += addLabeledString(getOffTimer() + ? minsToString(getOffTimer()) : kOffStr, + kOffTimerStr); + return result; +} + +/// Convert the A/C state to it's common stdAc::state_t equivalent. +/// @return A stdAc::state_t state. +stdAc::state_t IRCoronaAc::toCommon() const { + stdAc::state_t result{}; + result.protocol = decode_type_t::CORONA_AC; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingVToggle ? + stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.econo = _.Econo; + // Not supported. + result.sleep = -1; + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.quiet = false; + result.clean = false; + result.filter = false; + result.beep = false; + result.light = false; + result.clock = -1; + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Corona.h b/src/libraries/IRremoteESP8266/src/ir_Corona.h new file mode 100644 index 000000000..a04a5b6dd --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Corona.h @@ -0,0 +1,168 @@ +// Corona A/C +// +// Copyright 2020 Christian Nilsson + +// Supports: +// Brand: Corona, Model: CSH-N2211 A/C +// Brand: Corona, Model: CSH-N2511 A/C +// Brand: Corona, Model: CSH-N2811 A/C +// Brand: Corona, Model: CSH-N4011 A/C +// Brand: Corona, Model: AR-01 remote +// +// Ref: https://docs.google.com/spreadsheets/d/1zzDEUQ52y7MZ7_xCU3pdjdqbRXOwZLsbTGvKWcicqCI/ +// Ref: https://www.corona.co.jp/box/download.php?id=145060636229 + +#ifndef IR_CORONA_H_ +#define IR_CORONA_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a section of a Corona A/C message. +struct CoronaSection { + uint8_t Header0; + uint8_t Header1; + uint8_t Label; + uint8_t Data0; + uint8_t Data0Inv; + uint8_t Data1; + uint8_t Data1Inv; +}; + +const uint8_t kCoronaAcSections = 3; + +/// Native representation of a Corona A/C message. +union CoronaProtocol { + uint8_t raw[kCoronaAcStateLength]; ///< The state of the IR remote. + CoronaSection sections[kCoronaAcSections]; + struct { + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t :8; + // Byte 2 + uint8_t :8; + // Byte 3 + uint8_t Fan :2; + uint8_t :1; + uint8_t Econo :1; + uint8_t :1; // always on + uint8_t :1; + uint8_t SwingVToggle :1; + uint8_t :1; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t Temp :4; + uint8_t Power :1; + uint8_t PowerButton :1; + uint8_t Mode :2; + }; +}; + +// Constants + +// CORONA_AC +const uint8_t kCoronaAcSectionBytes = 7; // kCoronaAcStateLengthShort +const uint8_t kCoronaAcSectionHeader0 = 0x28; +const uint8_t kCoronaAcSectionHeader1 = 0x61; +const uint8_t kCoronaAcSectionLabelBase = 0x0D; // 0b1101 +const uint8_t kCoronaAcSectionData0Base = 0x10; // D0 Pos 4 always on + +const uint8_t kCoronaAcFanAuto = 0b00; // 0 +const uint8_t kCoronaAcFanLow = 0b01; // 1 +const uint8_t kCoronaAcFanMedium = 0b10; // 2 +const uint8_t kCoronaAcFanHigh = 0b11; // 3 + +/* full auto mode not supported by this code yet +const uint8_t kCoronaAcAutoD0 = 0b00010100; // only combined with power save +const uint8_t kCoronaAcAutoD1 = 0b10000011; // only combined with power +*/ +const uint8_t kCoronaAcMinTemp = 17; // Celsius = 0b0001 +const uint8_t kCoronaAcMaxTemp = 30; // Celsius = 0b1110 +const uint8_t kCoronaAcModeHeat = 0b00; // 0 +const uint8_t kCoronaAcModeDry = 0b01; // 1 +const uint8_t kCoronaAcModeCool = 0b10; // 2 +const uint8_t kCoronaAcModeFan = 0b11; // 3 + +const uint8_t kCoronaAcSettingsSection = 0; +const uint8_t kCoronaAcOnTimerSection = 1; +const uint8_t kCoronaAcOffTimerSection = 2; +const uint16_t kCoronaAcTimerMax = 12 * 60; // 12H in Minutes +// Min value on remote is 1 hour, actual sent value can be 2 secs +const uint16_t kCoronaAcTimerOff = 0xffff; +const uint16_t kCoronaAcTimerUnitsPerMin = 30; // 30 units = 1 minute + +// Classes + +/// Class for handling detailed Corona A/C messages. +class IRCoronaAc { + public: + explicit IRCoronaAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + void stateReset(); +#if SEND_CORONA_AC + void send(const uint16_t repeat = kNoRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_CORONA_AC + void begin(); + static bool validSection(const uint8_t state[], const uint16_t pos, + const uint8_t section); + void setPower(const bool on); + bool getPower(void) const; + bool getPowerButton(void) const; + void on(void); + void off(void); + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setSwingVToggle(const bool on); + bool getSwingVToggle(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setOnTimer(const uint16_t nr_of_mins); + uint16_t getOnTimer(void) const; + void setOffTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void) const; + uint8_t* getRaw(); + void setRaw(const uint8_t new_code[], + const uint16_t length = kCoronaAcStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + CoronaProtocol _; + static uint8_t getSectionByte(const uint8_t section); + static void checksum(uint8_t* data); + void setPowerButton(const bool on); + void _setTimer(const uint8_t section, const uint16_t nr_of_mins); + uint16_t _getTimer(const uint8_t section) const; +}; +#endif // IR_CORONA_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Daikin.cpp b/src/libraries/IRremoteESP8266/src/ir_Daikin.cpp new file mode 100644 index 000000000..58df30095 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Daikin.cpp @@ -0,0 +1,3927 @@ +// Copyright 2016 sillyfrog +// Copyright 2017 sillyfrog, crankyoldgit +// Copyright 2018-2022 crankyoldgit +// Copyright 2019 pasna (IRDaikin160 class / Daikin176 class) + +/// @file +/// @brief Support for Daikin A/C protocols. +/// @see Daikin http://harizanov.com/2012/02/control-daikin-air-conditioner-over-the-internet/ +/// @see Daikin https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote +/// @see Daikin http://rdlab.cdmt.vn/project-2013/daikin-ir-protocol +/// @see Daikin https://github.com/blafois/Daikin-IR-Reverse +/// @see Daikin128 https://github.com/crankyoldgit/IRremoteESP8266/issues/827 +/// @see Daikin152 https://github.com/crankyoldgit/IRremoteESP8266/issues/873 +/// @see Daikin152 https://github.com/ToniA/arduino-heatpumpir/blob/master/DaikinHeatpumpARC480A14IR.cpp +/// @see Daikin152 https://github.com/ToniA/arduino-heatpumpir/blob/master/DaikinHeatpumpARC480A14IR.h +/// @see Daikin160 https://github.com/crankyoldgit/IRremoteESP8266/issues/731 +/// @see Daikin2 https://docs.google.com/spreadsheets/d/1f8EGfIbBUo2B-CzUFdrgKQprWakoYNKM80IKZN4KXQE/edit#gid=236366525&range=B25:D32 +/// @see Daikin2 https://github.com/crankyoldgit/IRremoteESP8266/issues/582 +/// @see Daikin2 https://github.com/crankyoldgit/IRremoteESP8266/issues/1535 +/// @see Daikin2 https://www.daikin.co.nz/sites/default/files/daikin-split-system-US7-FTXZ25-50NV1B.pdf +/// @see Daikin216 https://github.com/crankyoldgit/IRremoteESP8266/issues/689 +/// @see Daikin216 https://github.com/danny-source/Arduino_DY_IRDaikin +/// @see Daikin64 https://github.com/crankyoldgit/IRremoteESP8266/issues/1064 +/// @see Daikin200 https://github.com/crankyoldgit/IRremoteESP8266/issues/1802 + +#include "ir_Daikin.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addDayToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addSwingHToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::addFanToString; +using irutils::bcdToUint8; +using irutils::minsToString; +using irutils::setBit; +using irutils::setBits; +using irutils::sumNibbles; +using irutils::uint8ToBcd; + +#if SEND_DAIKIN +/// Send a Daikin 280-bit A/C formatted message. +/// Status: STABLE +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote +/// @see https://github.com/blafois/Daikin-IR-Reverse +void IRsend::sendDaikin(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikinStateLengthShort) + return; // Not enough bytes to send a proper message. + + for (uint16_t r = 0; r <= repeat; r++) { + uint16_t offset = 0; + // Send the header, 0b00000 + sendGeneric(0, 0, // No header for the header + kDaikinBitMark, kDaikinOneSpace, kDaikinBitMark, + kDaikinZeroSpace, kDaikinBitMark, kDaikinZeroSpace + kDaikinGap, + (uint64_t)0b00000, kDaikinHeaderLength, 38, false, 0, 50); + // Data #1 + if (nbytes < kDaikinStateLength) { // Are we using the legacy size? + // Do this as a constant to save RAM and keep in flash memory + sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark, + kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace, + kDaikinBitMark, kDaikinZeroSpace + kDaikinGap, + kDaikinFirstHeader64, 64, 38, false, 0, 50); + } else { // We are using the newer/more correct size. + sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark, + kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace, + kDaikinBitMark, kDaikinZeroSpace + kDaikinGap, + data, kDaikinSection1Length, 38, false, 0, 50); + offset += kDaikinSection1Length; + } + // Data #2 + sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark, + kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace, + kDaikinBitMark, kDaikinZeroSpace + kDaikinGap, + data + offset, kDaikinSection2Length, 38, false, 0, 50); + offset += kDaikinSection2Length; + // Data #3 + sendGeneric(kDaikinHdrMark, kDaikinHdrSpace, kDaikinBitMark, + kDaikinOneSpace, kDaikinBitMark, kDaikinZeroSpace, + kDaikinBitMark, kDaikinZeroSpace + kDaikinGap, + data + offset, nbytes - offset, 38, false, 0, 50); + } +} +#endif // SEND_DAIKIN + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikinESP::IRDaikinESP(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikinESP::begin(void) { _irsend.begin(); } + +#if SEND_DAIKIN +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikinESP::send(const uint16_t repeat) { + _irsend.sendDaikin(getRaw(), kDaikinStateLength, repeat); +} +#endif // SEND_DAIKIN + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikinESP::validChecksum(uint8_t state[], const uint16_t length) { + // Data #1 + if (length < kDaikinSection1Length || + state[kDaikinByteChecksum1] != sumBytes(state, kDaikinSection1Length - 1)) + return false; + // Data #2 + if (length < kDaikinSection1Length + kDaikinSection2Length || + state[kDaikinByteChecksum2] != sumBytes(state + kDaikinSection1Length, + kDaikinSection2Length - 1)) + return false; + // Data #3 + if (length < kDaikinSection1Length + kDaikinSection2Length + 2 || + state[length - 1] != sumBytes(state + kDaikinSection1Length + + kDaikinSection2Length, + length - (kDaikinSection1Length + + kDaikinSection2Length) - 1)) + return false; + return true; +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikinESP::checksum(void) { + _.Sum1 = sumBytes(_.raw, kDaikinSection1Length - 1); + _.Sum2 = sumBytes(_.raw + kDaikinSection1Length, kDaikinSection2Length - 1); + _.Sum3 = sumBytes(_.raw + kDaikinSection1Length + kDaikinSection2Length, + kDaikinSection3Length - 1); +} + +/// Reset the internal state to a fixed known good state. +void IRDaikinESP::stateReset(void) { + for (uint8_t i = 0; i < kDaikinStateLength; i++) _.raw[i] = 0x0; + + _.raw[0] = 0x11; + _.raw[1] = 0xDA; + _.raw[2] = 0x27; + _.raw[4] = 0xC5; + // _.raw[7] is a checksum byte, it will be set by checksum(). + _.raw[8] = 0x11; + _.raw[9] = 0xDA; + _.raw[10] = 0x27; + _.raw[12] = 0x42; + // _.raw[15] is a checksum byte, it will be set by checksum(). + _.raw[16] = 0x11; + _.raw[17] = 0xDA; + _.raw[18] = 0x27; + _.raw[21] = 0x49; + _.raw[22] = 0x1E; + _.raw[24] = 0xB0; + _.raw[27] = 0x06; + _.raw[28] = 0x60; + _.raw[31] = 0xC0; + // _.raw[34] is a checksum byte, it will be set by checksum(). + checksum(); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRDaikinESP::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length Length of the code in bytes. +void IRDaikinESP::setRaw(const uint8_t new_code[], const uint16_t length) { + uint8_t offset = 0; + if (length == kDaikinStateLengthShort) { // Handle the "short" length case. + offset = kDaikinStateLength - kDaikinStateLengthShort; + stateReset(); + } + for (uint8_t i = 0; i < length && i < kDaikinStateLength; i++) + _.raw[i + offset] = new_code[i]; +} + +/// Change the power setting to On. +void IRDaikinESP::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRDaikinESP::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRDaikinESP::setTemp(const uint8_t temp) { + uint8_t degrees = ::max(temp, kDaikinMinTemp); + degrees = ::min(degrees, kDaikinMaxTemp); + _.Temp = degrees; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikinESP::getTemp(void) const { return _.Temp; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +/// @note 1-5 or kDaikinFanAuto or kDaikinFanQuiet +void IRDaikinESP::setFan(const uint8_t fan) { + // Set the fan speed bits, leave low 4 bits alone + uint8_t fanset; + if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto) + fanset = fan; + else if (fan < kDaikinFanMin || fan > kDaikinFanMax) + fanset = kDaikinFanAuto; + else + fanset = 2 + fan; + _.Fan = fanset; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikinESP::getFan(void) const { + uint8_t fan = _.Fan; + if (fan != kDaikinFanQuiet && fan != kDaikinFanAuto) fan -= 2; + return fan; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikinESP::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRDaikinESP::setMode(const uint8_t mode) { + switch (mode) { + case kDaikinAuto: + case kDaikinCool: + case kDaikinHeat: + case kDaikinFan: + case kDaikinDry: + _.Mode = mode; + break; + default: + _.Mode = kDaikinAuto; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setSwingVertical(const bool on) { + _.SwingV = (on ? kDaikinSwingOn : kDaikinSwingOff); +} + +/// Get the Vertical Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getSwingVertical(void) const { + return _.SwingV; +} + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setSwingHorizontal(const bool on) { + _.SwingH = (on ? kDaikinSwingOn : kDaikinSwingOff); +} + +/// Get the Horizontal Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getSwingHorizontal(void) const { + return _.SwingH; +} + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setQuiet(const bool on) { + _.Quiet = on; + // Powerful & Quiet mode being on are mutually exclusive. + if (on) setPowerful(false); +} + +/// Get the Quiet mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getQuiet(void) const { + return _.Quiet; +} + +/// Set the Powerful (Turbo) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setPowerful(const bool on) { + _.Powerful = on; + if (on) { + // Powerful, Quiet, & Econo mode being on are mutually exclusive. + setQuiet(false); + setEcono(false); + } +} + +/// Get the Powerful (Turbo) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getPowerful(void) const { + return _.Powerful; +} + +/// Set the Sensor mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setSensor(const bool on) { + _.Sensor = on; +} + +/// Get the Sensor mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getSensor(void) const { + return _.Sensor; +} + +/// Set the Economy mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setEcono(const bool on) { + _.Econo = on; + // Powerful & Econo mode being on are mutually exclusive. + if (on) setPowerful(false); +} + +/// Get the Economical mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getEcono(void) const { + return _.Econo; +} + +/// Set the Mould mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setMold(const bool on) { + _.Mold = on; +} + +/// Get the Mould mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getMold(void) const { + return _.Mold; +} + +/// Set the Comfort mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setComfort(const bool on) { + _.Comfort = on; +} + +/// Get the Comfort mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getComfort(void) const { + return _.Comfort; +} + +/// Set the enable status & time of the On Timer. +/// @param[in] starttime The number of minutes past midnight. +void IRDaikinESP::enableOnTimer(const uint16_t starttime) { + _.OnTimer = true; + _.OnTime = starttime; +} + +/// Clear and disable the On timer. +void IRDaikinESP::disableOnTimer(void) { + _.OnTimer = false; + _.OnTime = kDaikinUnusedTime; +} + +/// Get the On Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikinESP::getOnTime(void) const { + return _.OnTime; +} + +/// Get the enable status of the On Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getOnTimerEnabled(void) const { + return _.OnTimer; +} + +/// Set the enable status & time of the Off Timer. +/// @param[in] endtime The number of minutes past midnight. +void IRDaikinESP::enableOffTimer(const uint16_t endtime) { + _.OffTimer = true; + _.OffTime = endtime; +} + +/// Clear and disable the Off timer. +void IRDaikinESP::disableOffTimer(void) { + _.OffTimer = false; + _.OffTime = kDaikinUnusedTime; +} + +/// Get the Off Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikinESP::getOffTime(void) const { + return _.OffTime; +} + +/// Get the enable status of the Off Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getOffTimerEnabled(void) const { + return _.OffTimer; +} + +/// Set the clock on the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRDaikinESP::setCurrentTime(const uint16_t mins_since_midnight) { + uint16_t mins = mins_since_midnight; + if (mins > 24 * 60) mins = 0; // If > 23:59, set to 00:00 + _.CurrentTime = mins; +} + +/// Get the clock time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikinESP::getCurrentTime(void) const { + return _.CurrentTime; +} + +/// Set the current day of the week to be sent to the A/C unit. +/// @param[in] day_of_week The numerical representation of the day of the week. +/// @note 1 is SUN, 2 is MON, ..., 7 is SAT +void IRDaikinESP::setCurrentDay(const uint8_t day_of_week) { + _.CurrentDay = day_of_week; +} + +/// Get the current day of the week to be sent to the A/C unit. +/// @return The numerical representation of the day of the week. +/// @note 1 is SUN, 2 is MON, ..., 7 is SAT +uint8_t IRDaikinESP::getCurrentDay(void) const { + return _.CurrentDay; +} + +/// Set the enable status of the Weekly Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikinESP::setWeeklyTimerEnable(const bool on) { + // Bit is cleared for `on`. + _.WeeklyTimer = !on; +} + +/// Get the enable status of the Weekly Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikinESP::getWeeklyTimerEnable(void) const { + return !_.WeeklyTimer; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikinESP::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kDaikinCool; + case stdAc::opmode_t::kHeat: return kDaikinHeat; + case stdAc::opmode_t::kDry: return kDaikinDry; + case stdAc::opmode_t::kFan: return kDaikinFan; + default: return kDaikinAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikinESP::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kDaikinFanQuiet; + case stdAc::fanspeed_t::kLow: return kDaikinFanMin; + case stdAc::fanspeed_t::kMedium: return kDaikinFanMed; + case stdAc::fanspeed_t::kHigh: return kDaikinFanMax - 1; + case stdAc::fanspeed_t::kMax: return kDaikinFanMax; + default: return kDaikinFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRDaikinESP::toCommonMode(const uint8_t mode) { + switch (mode) { + case kDaikinCool: return stdAc::opmode_t::kCool; + case kDaikinHeat: return stdAc::opmode_t::kHeat; + case kDaikinDry: return stdAc::opmode_t::kDry; + case kDaikinFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRDaikinESP::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kDaikinFanMax: return stdAc::fanspeed_t::kMax; + case kDaikinFanMax - 1: return stdAc::fanspeed_t::kHigh; + case kDaikinFanMed: + case kDaikinFanMin + 1: return stdAc::fanspeed_t::kMedium; + case kDaikinFanMin: return stdAc::fanspeed_t::kLow; + case kDaikinFanQuiet: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikinESP::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DAIKIN; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : + stdAc::swingv_t::kOff; + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : + stdAc::swingh_t::kOff; + result.quiet = _.Quiet; + result.turbo = _.Powerful; + result.clean = _.Mold; + result.econo = _.Econo; + // Not supported. + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikinESP::toString(void) const { + String result = ""; + result.reserve(230); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kDaikinAuto, kDaikinCool, kDaikinHeat, + kDaikinDry, kDaikinFan); + result += addTempToString(_.Temp); + result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin, + kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed); + result += addBoolToString(_.Powerful, kPowerfulStr); + result += addBoolToString(_.Quiet, kQuietStr); + result += addBoolToString(getSensor(), kSensorStr); + result += addBoolToString(_.Mold, kMouldStr); + result += addBoolToString(_.Comfort, kComfortStr); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addLabeledString(minsToString(_.CurrentTime), kClockStr); + result += addDayToString(_.CurrentDay, -1); + result += addLabeledString(_.OnTimer + ? minsToString(_.OnTime) : kOffStr, + kOnTimerStr); + result += addLabeledString(_.OffTimer + ? minsToString(_.OffTime) : kOffStr, + kOffTimerStr); + result += addBoolToString(getWeeklyTimerEnable(), kWeeklyTimerStr); + return result; +} + +#if DECODE_DAIKIN +/// Decode the supplied Daikin 280-bit message. (DAIKIN) +/// Status: STABLE / Reported as working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote +bool IRrecv::decodeDaikin(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Is there enough data to match successfully? + if (results->rawlen < (2 * (nbits + kDaikinHeaderLength) + + kDaikinSections * (kHeader + kFooter) + kFooter - 1) + + offset) + return false; + + // Compliance + if (strict && nbits != kDaikinBits) return false; + + match_result_t data_result; + + // Header #1 - Doesn't count as data. + data_result = matchData(&(results->rawbuf[offset]), kDaikinHeaderLength, + kDaikinBitMark, kDaikinOneSpace, + kDaikinBitMark, kDaikinZeroSpace, + kDaikinTolerance, kDaikinMarkExcess, false); + offset += data_result.used; + if (data_result.success == false) return false; // Fail + if (data_result.data) return false; // The header bits should be zero. + // Footer + if (!matchMark(results->rawbuf[offset++], kDaikinBitMark, + kDaikinTolerance, kDaikinMarkExcess)) return false; + if (!matchSpace(results->rawbuf[offset++], kDaikinZeroSpace + kDaikinGap, + kDaikinTolerance, kDaikinMarkExcess)) return false; + // Sections + const uint8_t ksectionSize[kDaikinSections] = { + kDaikinSection1Length, kDaikinSection2Length, kDaikinSection3Length}; + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikinSections; section++) { + uint16_t used; + // Section Header + Section Data (7 bytes) + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + kDaikinHdrMark, kDaikinHdrSpace, + kDaikinBitMark, kDaikinOneSpace, + kDaikinBitMark, kDaikinZeroSpace, + kDaikinBitMark, kDaikinZeroSpace + kDaikinGap, + section >= kDaikinSections - 1, + kDaikinTolerance, kDaikinMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + // Re-check we got the correct size/length due to the way we read the data. + if (pos * 8 != kDaikinBits) return false; + // Validate the checksum. + if (!IRDaikinESP::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = DAIKIN; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN + +#if SEND_DAIKIN2 +/// Send a Daikin2 (312-bit) A/C formatted message. +/// Status: STABLE / Expected to work. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/582 +void IRsend::sendDaikin2(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikin2Section1Length) + return; // Not enough bytes to send a partial message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Leader + sendGeneric(kDaikin2LeaderMark, kDaikin2LeaderSpace, + 0, 0, 0, 0, 0, 0, (uint64_t) 0, // No data payload. + 0, kDaikin2Freq, false, 0, 50); + // Section #1 + sendGeneric(kDaikin2HdrMark, kDaikin2HdrSpace, kDaikin2BitMark, + kDaikin2OneSpace, kDaikin2BitMark, kDaikin2ZeroSpace, + kDaikin2BitMark, kDaikin2Gap, data, kDaikin2Section1Length, + kDaikin2Freq, false, 0, 50); + // Section #2 + sendGeneric(kDaikin2HdrMark, kDaikin2HdrSpace, kDaikin2BitMark, + kDaikin2OneSpace, kDaikin2BitMark, kDaikin2ZeroSpace, + kDaikin2BitMark, kDaikin2Gap, data + kDaikin2Section1Length, + nbytes - kDaikin2Section1Length, + kDaikin2Freq, false, 0, 50); + } +} +#endif // SEND_DAIKIN2 + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikin2::IRDaikin2(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikin2::begin(void) { _irsend.begin(); } + +#if SEND_DAIKIN2 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikin2::send(const uint16_t repeat) { + _irsend.sendDaikin2(getRaw(), kDaikin2StateLength, repeat); +} +#endif // SEND_DAIKIN2 + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikin2::validChecksum(uint8_t state[], const uint16_t length) { + // Validate the checksum of section #1. + if (length <= kDaikin2Section1Length - 1 || + state[kDaikin2Section1Length - 1] != sumBytes(state, + kDaikin2Section1Length - 1)) + return false; + // Validate the checksum of section #2 (a.k.a. the rest) + if (length <= kDaikin2Section1Length + 1 || + state[length - 1] != sumBytes(state + kDaikin2Section1Length, + length - kDaikin2Section1Length - 1)) + return false; + return true; +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikin2::checksum(void) { + _.Sum1 = sumBytes(_.raw, kDaikin2Section1Length - 1); + _.Sum2 = sumBytes(_.raw + kDaikin2Section1Length, kDaikin2Section2Length - 1); +} + +/// Reset the internal state to a fixed known good state. +void IRDaikin2::stateReset(void) { + for (uint8_t i = 0; i < kDaikin2StateLength; i++) _.raw[i] = 0x0; + + _.raw[0] = 0x11; + _.raw[1] = 0xDA; + _.raw[2] = 0x27; + _.raw[4] = 0x01; + _.raw[6] = 0xC0; + _.raw[7] = 0x70; + _.raw[8] = 0x08; + _.raw[9] = 0x0C; + _.raw[10] = 0x80; + _.raw[11] = 0x04; + _.raw[12] = 0xB0; + _.raw[13] = 0x16; + _.raw[14] = 0x24; + _.raw[17] = 0xBE; + _.raw[18] = 0xD0; + // _.raw[19] is a checksum byte, it will be set by checksum(). + _.raw[20] = 0x11; + _.raw[21] = 0xDA; + _.raw[22] = 0x27; + _.raw[25] = 0x08; + _.raw[28] = 0xA0; + _.raw[35] = 0xC1; + _.raw[36] = 0x80; + _.raw[37] = 0x60; + // _.raw[38] is a checksum byte, it will be set by checksum(). + disableOnTimer(); + disableOffTimer(); + disableSleepTimer(); + checksum(); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRDaikin2::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRDaikin2::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kDaikin2StateLength); +} + +/// Change the power setting to On. +void IRDaikin2::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRDaikin2::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setPower(const bool on) { + _.Power = on; + _.Power2 = !on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getPower(void) const { return _.Power && !_.Power2; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin2::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] desired_mode The desired operating mode. +void IRDaikin2::setMode(const uint8_t desired_mode) { + uint8_t mode = desired_mode; + switch (mode) { + case kDaikinCool: + case kDaikinHeat: + case kDaikinFan: + case kDaikinDry: break; + default: mode = kDaikinAuto; + } + _.Mode = mode; + // Redo the temp setting as Cool mode has a different min temp. + if (mode == kDaikinCool) setTemp(getTemp()); + setHumidity(getHumidity()); // Make sure the humidity is okay for this mode. +} + +/// Set the temperature. +/// @param[in] desired The temperature in degrees celsius. +void IRDaikin2::setTemp(const uint8_t desired) { + // The A/C has a different min temp if in cool mode. + uint8_t temp = ::max( + (_.Mode == kDaikinCool) ? kDaikin2MinCoolTemp : kDaikinMinTemp, + desired); + _.Temp = ::min(kDaikinMaxTemp, temp); + // If the humidity setting is in use, the temp is a fixed value. + if (_.HumidOn) _.Temp = kDaikinMaxTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikin2::getTemp(void) const { return _.Temp; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +/// @note 1-5 or kDaikinFanAuto or kDaikinFanQuiet +void IRDaikin2::setFan(const uint8_t fan) { + uint8_t fanset; + if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto) + fanset = fan; + else if (fan < kDaikinFanMin || fan > kDaikinFanMax) + fanset = kDaikinFanAuto; + else + fanset = 2 + fan; + _.Fan = fanset; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikin2::getFan(void) const { + const uint8_t fan = _.Fan; + switch (fan) { + case kDaikinFanAuto: + case kDaikinFanQuiet: return fan; + default: return fan - 2; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] position The position/mode to set the swing to. +void IRDaikin2::setSwingVertical(const uint8_t position) { + switch (position) { + case kDaikin2SwingVHighest: + case kDaikin2SwingVHigh: + case kDaikin2SwingVUpperMiddle: + case kDaikin2SwingVLowerMiddle: + case kDaikin2SwingVLow: + case kDaikin2SwingVLowest: + case kDaikin2SwingVOff: + case kDaikin2SwingVBreeze: + case kDaikin2SwingVCirculate: + case kDaikin2SwingVAuto: + _.SwingV = position; + } +} + +/// Get the Vertical Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRDaikin2::getSwingVertical(void) const { return _.SwingV; } + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin2::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: + case stdAc::swingv_t::kLow: + case stdAc::swingv_t::kLowest: + return (uint8_t)position + kDaikin2SwingVHighest; + case stdAc::swingv_t::kOff: + return kDaikin2SwingVOff; + default: + return kDaikin2SwingVAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] setting A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRDaikin2::toCommonSwingV(const uint8_t setting) { + switch (setting) { + case kDaikin2SwingVHighest: return stdAc::swingv_t::kHighest; + case kDaikin2SwingVHigh: return stdAc::swingv_t::kHigh; + case kDaikin2SwingVUpperMiddle: + case kDaikin2SwingVLowerMiddle: return stdAc::swingv_t::kMiddle; + case kDaikin2SwingVLow: return stdAc::swingv_t::kLow; + case kDaikin2SwingVLowest: return stdAc::swingv_t::kLowest; + case kDaikin2SwingVOff: return stdAc::swingv_t::kOff; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] position The position/mode to set the swing to. +void IRDaikin2::setSwingHorizontal(const uint8_t position) { + _.SwingH = position; +} + +/// Get the Horizontal Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRDaikin2::getSwingHorizontal(void) const { return _.SwingH; } + +/// Set the clock on the A/C unit. +/// @param[in] numMins Nr. of minutes past midnight. +void IRDaikin2::setCurrentTime(const uint16_t numMins) { + uint16_t mins = numMins; + if (numMins > 24 * 60) mins = 0; // If > 23:59, set to 00:00 + _.CurrentTime = mins; +} + +/// Get the clock time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin2::getCurrentTime(void) const { return _.CurrentTime; } + +/// Set the enable status & time of the On Timer. +/// @param[in] starttime The number of minutes past midnight. +/// @note Timer location is shared with sleep timer. +void IRDaikin2::enableOnTimer(const uint16_t starttime) { + clearSleepTimerFlag(); + _.OnTimer = true; + _.OnTime = starttime; +} + +/// Clear the On Timer flag. +void IRDaikin2::clearOnTimerFlag(void) { _.OnTimer = false; } + +/// Disable the On timer. +void IRDaikin2::disableOnTimer(void) { + _.OnTime = kDaikinUnusedTime; + clearOnTimerFlag(); + clearSleepTimerFlag(); +} + +/// Get the On Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin2::getOnTime(void) const { return _.OnTime; } + +/// Get the enable status of the On Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getOnTimerEnabled(void) const { return _.OnTimer; } + +/// Set the enable status & time of the Off Timer. +/// @param[in] endtime The number of minutes past midnight. +void IRDaikin2::enableOffTimer(const uint16_t endtime) { + // Set the Off Timer flag. + _.OffTimer = true; + _.OffTime = endtime; +} + +/// Disable the Off timer. +void IRDaikin2::disableOffTimer(void) { + _.OffTime = kDaikinUnusedTime; + // Clear the Off Timer flag. + _.OffTimer = false; +} + +/// Get the Off Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin2::getOffTime(void) const { return _.OffTime; } + +/// Get the enable status of the Off Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getOffTimerEnabled(void) const { return _.OffTimer; } + +/// Get the Beep status of the A/C. +/// @return true, the setting is on. false, the setting is off. +uint8_t IRDaikin2::getBeep(void) const { return _.Beep; } + +/// Set the Beep mode of the A/C. +/// @param[in] beep true, the setting is on. false, the setting is off. +void IRDaikin2::setBeep(const uint8_t beep) { _.Beep = beep; } + +/// Get the Light status of the A/C. +/// @return true, the setting is on. false, the setting is off. +uint8_t IRDaikin2::getLight(void) const { return _.Light; } + +/// Set the Light (LED) mode of the A/C. +/// @param[in] light true, the setting is on. false, the setting is off. +void IRDaikin2::setLight(const uint8_t light) { _.Light = light; } + +/// Set the Mould (filter) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setMold(const bool on) { _.Mold = on; } + +/// Get the Mould (filter) mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getMold(void) const { return _.Mold; } + +/// Set the Auto clean mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setClean(const bool on) { _.Clean = on; } + +/// Get the Auto Clean mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getClean(void) const { return _.Clean; } + +/// Set the Fresh Air mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setFreshAir(const bool on) { _.FreshAir = on; } + +/// Get the Fresh Air mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getFreshAir(void) const { return _.FreshAir; } + +/// Set the (High) Fresh Air mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setFreshAirHigh(const bool on) { _.FreshAirHigh = on; } + +/// Get the (High) Fresh Air mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getFreshAirHigh(void) const { return _.FreshAirHigh; } + +/// Set the Automatic Eye (Sensor) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setEyeAuto(bool on) { _.EyeAuto = on; } + +/// Get the Automaitc Eye (Sensor) mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getEyeAuto(void) const { return _.EyeAuto; } + +/// Set the Eye (Sensor) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setEye(bool on) { _.Eye = on; } + +/// Get the Eye (Sensor) mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getEye(void) const { return _.Eye; } + +/// Set the Economy mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setEcono(bool on) { _.Econo = on; } + +/// Get the Economical mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getEcono(void) const { return _.Econo; } + +/// Set the enable status & time of the Sleep Timer. +/// @param[in] sleeptime The number of minutes past midnight. +/// @note The Timer location is shared with On Timer. +void IRDaikin2::enableSleepTimer(const uint16_t sleeptime) { + enableOnTimer(sleeptime); + clearOnTimerFlag(); + _.SleepTimer = true; +} + +/// Clear the sleep timer flag. +void IRDaikin2::clearSleepTimerFlag(void) { _.SleepTimer = false; } + +/// Disable the sleep timer. +void IRDaikin2::disableSleepTimer(void) { disableOnTimer(); } + +/// Get the Sleep Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin2::getSleepTime(void) const { return getOnTime(); } + +/// Get the Sleep timer enabled status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getSleepTimerEnabled(void) const { return _.SleepTimer; } + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setQuiet(const bool on) { + _.Quiet = on; + // Powerful & Quiet mode being on are mutually exclusive. + if (on) setPowerful(false); +} + +/// Get the Quiet mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getQuiet(void) const { return _.Quiet; } + +/// Set the Powerful (Turbo) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setPowerful(const bool on) { + _.Powerful = on; + // Powerful & Quiet mode being on are mutually exclusive. + if (on) setQuiet(false); +} + +/// Get the Powerful (Turbo) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getPowerful(void) const { return _.Powerful; } + +/// Set the Purify (Filter) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin2::setPurify(const bool on) { _.Purify = on; } + +/// Get the Purify (Filter) mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin2::getPurify(void) const { return _.Purify; } + +/// Get the Humidity percentage setting of the A/C. +/// @return The setting percentage. 255 is Automatic. 0 is Off. +uint8_t IRDaikin2::getHumidity(void) const { return _.Humidity; } + +/// Set the Humidity percentage setting of the A/C. +/// @param[in] percent Percentage humidty. 255 is Auto. 0 is Off. +/// @note Only available in Dry & Heat modes, otherwise it is Off. +void IRDaikin2::setHumidity(const uint8_t percent) { + _.Humidity = kDaikin2HumidityOff; // Default to off. + switch (getMode()) { + case kDaikinHeat: + switch (percent) { + case kDaikin2HumidityOff: + case kDaikin2HumidityHeatLow: + case kDaikin2HumidityHeatMedium: + case kDaikin2HumidityHeatHigh: + case kDaikin2HumidityAuto: + _.Humidity = percent; + } + break; + case kDaikinDry: + switch (percent) { + case kDaikin2HumidityOff: + case kDaikin2HumidityDryLow: + case kDaikin2HumidityDryMedium: + case kDaikin2HumidityDryHigh: + case kDaikin2HumidityAuto: + _.Humidity = percent; + } + break; + } + _.HumidOn = (_.Humidity != kDaikin2HumidityOff); // Enabled? + setTemp(getTemp()); // Adjust the temperature if we need to. +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin2::convertMode(const stdAc::opmode_t mode) { + return IRDaikinESP::convertMode(mode); +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin2::convertFan(const stdAc::fanspeed_t speed) { + return IRDaikinESP::convertFan(speed); +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin2::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kAuto: return kDaikin2SwingHSwing; + case stdAc::swingh_t::kLeftMax: return kDaikin2SwingHLeftMax; + case stdAc::swingh_t::kLeft: return kDaikin2SwingHLeft; + case stdAc::swingh_t::kMiddle: return kDaikin2SwingHMiddle; + case stdAc::swingh_t::kRight: return kDaikin2SwingHRight; + case stdAc::swingh_t::kRightMax: return kDaikin2SwingHRightMax; + case stdAc::swingh_t::kWide: return kDaikin2SwingHWide; + default: return kDaikin2SwingHAuto; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] setting A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRDaikin2::toCommonSwingH(const uint8_t setting) { + switch (setting) { + case kDaikin2SwingHSwing: return stdAc::swingh_t::kAuto; + case kDaikin2SwingHLeftMax: return stdAc::swingh_t::kLeftMax; + case kDaikin2SwingHLeft: return stdAc::swingh_t::kLeft; + case kDaikin2SwingHMiddle: return stdAc::swingh_t::kMiddle; + case kDaikin2SwingHRight: return stdAc::swingh_t::kRight; + case kDaikin2SwingHRightMax: return stdAc::swingh_t::kRightMax; + case kDaikin2SwingHWide: return stdAc::swingh_t::kWide; + default: return stdAc::swingh_t::kOff; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikin2::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DAIKIN2; + result.model = -1; // No models used. + result.power = getPower(); + result.mode = IRDaikinESP::toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = IRDaikinESP::toCommonFanSpeed(getFan()); + result.swingv = toCommonSwingV(_.SwingV); + result.swingh = toCommonSwingH(_.SwingH); + result.quiet = _.Quiet; + result.light = _.Light != 3; // 3 is Off, everything else is On. + result.turbo = _.Powerful; + result.clean = _.Mold; + result.econo = _.Econo; + result.filter = _.Purify; + result.beep = _.Beep != 3; // 3 is Off, everything else is On. + result.sleep = _.SleepTimer ? getSleepTime() : -1; + // Not supported. + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikin2::toString(void) const { + String result = ""; + result.reserve(330); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kDaikinAuto, kDaikinCool, kDaikinHeat, + kDaikinDry, kDaikinFan); + result += addTempToString(_.Temp); + result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin, + kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed); + result += addSwingVToString(_.SwingV, kDaikin2SwingVAuto, + kDaikin2SwingVHighest, kDaikin2SwingVHigh, + kDaikin2SwingVUpperMiddle, + kDaikin2SwingVAuto, // Middle is unused. + kDaikin2SwingVLowerMiddle, + kDaikin2SwingVLow, kDaikin2SwingVLowest, + kDaikin2SwingVOff, // Off is unused + kDaikin2SwingVSwing, kDaikin2SwingVBreeze, + kDaikin2SwingVCirculate); + result += addSwingHToString(_.SwingH, kDaikin2SwingHAuto, + kDaikin2SwingHLeftMax, + kDaikin2SwingHLeft, + kDaikin2SwingHMiddle, + kDaikin2SwingHRight, + kDaikin2SwingHRightMax, + kDaikin2SwingHOff, + kDaikin2SwingHAuto, // Unused + kDaikin2SwingHAuto, // Unused + kDaikin2SwingHAuto, // Unused + kDaikin2SwingHWide); + result += addLabeledString(minsToString(_.CurrentTime), kClockStr); + result += addLabeledString( + _.OnTimer ? minsToString(_.OnTime) : kOffStr, kOnTimerStr); + result += addLabeledString( + _.OffTimer ? minsToString(_.OffTime) : kOffStr, + kOffTimerStr); + result += addLabeledString( + _.SleepTimer ? minsToString(getSleepTime()) : kOffStr, + kSleepTimerStr); + result += addIntToString(_.Beep, kBeepStr); + result += kSpaceLBraceStr; + switch (_.Beep) { + case kDaikinBeepLoud: + result += kLoudStr; + break; + case kDaikinBeepQuiet: + result += kQuietStr; + break; + case kDaikinBeepOff: + result += kOffStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addIntToString(_.Light, kLightStr); + result += kSpaceLBraceStr; + switch (_.Light) { + case kDaikinLightBright: + result += kHighStr; + break; + case kDaikinLightDim: + result += kLowStr; + break; + case kDaikinLightOff: + result += kOffStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addBoolToString(_.Mold, kMouldStr); + result += addBoolToString(_.Clean, kCleanStr); + result += addLabeledString( + _.FreshAir ? (_.FreshAirHigh ? kHighStr : kOnStr) : kOffStr, + kFreshStr); + result += addBoolToString(_.Eye, kEyeStr); + result += addBoolToString(_.EyeAuto, kEyeAutoStr); + result += addBoolToString(_.Quiet, kQuietStr); + result += addBoolToString(_.Powerful, kPowerfulStr); + result += addBoolToString(_.Purify, kPurifyStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addIntToString(_.Humidity, kHumidStr); + switch (_.Humidity) { + case kDaikin2HumidityOff: + case kDaikin2HumidityAuto: + result += kSpaceLBraceStr; + result += _.Humidity ? kAutoStr : kOffStr; + result += ')'; + break; + default: + result += '%'; + } + return result; +} + +#if DECODE_DAIKIN2 +/// Decode the supplied Daikin 312-bit message. (DAIKIN2) +/// Status: STABLE / Works as expected. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeDaikin2(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + kHeader + kFooter) + kHeader - 1 + offset) + return false; + + // Compliance + if (strict && nbits != kDaikin2Bits) return false; + + const uint8_t ksectionSize[kDaikin2Sections] = {kDaikin2Section1Length, + kDaikin2Section2Length}; + + // Leader + if (!matchMark(results->rawbuf[offset++], kDaikin2LeaderMark, + _tolerance + kDaikin2Tolerance)) return false; + if (!matchSpace(results->rawbuf[offset++], kDaikin2LeaderSpace, + _tolerance + kDaikin2Tolerance)) return false; + + // Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikin2Sections; section++) { + uint16_t used; + // Section Header + Section Data + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + kDaikin2HdrMark, kDaikin2HdrSpace, + kDaikin2BitMark, kDaikin2OneSpace, + kDaikin2BitMark, kDaikin2ZeroSpace, + kDaikin2BitMark, kDaikin2Gap, + section >= kDaikin2Sections - 1, + _tolerance + kDaikin2Tolerance, kDaikinMarkExcess, + false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + // Re-check we got the correct size/length due to the way we read the data. + if (pos * 8 != kDaikin2Bits) return false; + // Validate the checksum. + if (!IRDaikin2::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = DAIKIN2; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN2 + +#if SEND_DAIKIN216 +/// Send a Daikin216 (216-bit) A/C formatted message. +/// Status: Alpha / Untested on a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/689 +/// @see https://github.com/danny-source/Arduino_DY_IRDaikin +void IRsend::sendDaikin216(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikin216Section1Length) + return; // Not enough bytes to send a partial message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Section #1 + sendGeneric(kDaikin216HdrMark, kDaikin216HdrSpace, kDaikin216BitMark, + kDaikin216OneSpace, kDaikin216BitMark, kDaikin216ZeroSpace, + kDaikin216BitMark, kDaikin216Gap, data, + kDaikin216Section1Length, + kDaikin216Freq, false, 0, kDutyDefault); + // Section #2 + sendGeneric(kDaikin216HdrMark, kDaikin216HdrSpace, kDaikin216BitMark, + kDaikin216OneSpace, kDaikin216BitMark, kDaikin216ZeroSpace, + kDaikin216BitMark, kDaikin216Gap, + data + kDaikin216Section1Length, + nbytes - kDaikin216Section1Length, + kDaikin216Freq, false, 0, kDutyDefault); + } +} +#endif // SEND_DAIKIN216 + +/// Class Constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikin216::IRDaikin216(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikin216::begin(void) { _irsend.begin(); } + +#if SEND_DAIKIN216 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikin216::send(const uint16_t repeat) { + _irsend.sendDaikin216(getRaw(), kDaikin216StateLength, repeat); +} +#endif // SEND_DAIKIN216 + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikin216::validChecksum(uint8_t state[], const uint16_t length) { + // Validate the checksum of section #1. + if (length <= kDaikin216Section1Length - 1 || + state[kDaikin216Section1Length - 1] != sumBytes( + state, kDaikin216Section1Length - 1)) + return false; + // Validate the checksum of section #2 (a.k.a. the rest) + if (length <= kDaikin216Section1Length + 1 || + state[length - 1] != sumBytes(state + kDaikin216Section1Length, + length - kDaikin216Section1Length - 1)) + return false; + return true; +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikin216::checksum(void) { + _.Sum1 = sumBytes(_.raw, kDaikin216Section1Length - 1); + _.Sum2 = sumBytes(_.raw + kDaikin216Section1Length, + kDaikin216Section2Length - 1); +} + +/// Reset the internal state to a fixed known good state. +void IRDaikin216::stateReset(void) { + for (uint8_t i = 0; i < kDaikin216StateLength; i++) _.raw[i] = 0x00; + _.raw[0] = 0x11; + _.raw[1] = 0xDA; + _.raw[2] = 0x27; + _.raw[3] = 0xF0; + // _.raw[7] is a checksum byte, it will be set by checksum(). + _.raw[8] = 0x11; + _.raw[9] = 0xDA; + _.raw[10] = 0x27; + _.raw[23] = 0xC0; + // _.raw[26] is a checksum byte, it will be set by checksum(). +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRDaikin216::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRDaikin216::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kDaikin216StateLength); +} + +/// Change the power setting to On. +void IRDaikin216::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRDaikin216::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin216::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin216::getPower(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin216::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRDaikin216::setMode(const uint8_t mode) { + switch (mode) { + case kDaikinAuto: + case kDaikinCool: + case kDaikinHeat: + case kDaikinFan: + case kDaikinDry: + _.Mode = mode; + break; + default: + _.Mode = kDaikinAuto; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin216::convertMode(const stdAc::opmode_t mode) { + return IRDaikinESP::convertMode(mode); +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRDaikin216::setTemp(const uint8_t temp) { + uint8_t degrees = ::max(temp, kDaikinMinTemp); + degrees = ::min(degrees, kDaikinMaxTemp); + _.Temp = degrees; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikin216::getTemp(void) const { return _.Temp; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +/// @note 1-5 or kDaikinFanAuto or kDaikinFanQuiet +void IRDaikin216::setFan(const uint8_t fan) { + // Set the fan speed bits, leave low 4 bits alone + uint8_t fanset; + if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto) + fanset = fan; + else if (fan < kDaikinFanMin || fan > kDaikinFanMax) + fanset = kDaikinFanAuto; + else + fanset = 2 + fan; + _.Fan = fanset; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikin216::getFan(void) const { + uint8_t fan = _.Fan; + if (fan != kDaikinFanQuiet && fan != kDaikinFanAuto) fan -= 2; + return fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin216::convertFan(const stdAc::fanspeed_t speed) { + return IRDaikinESP::convertFan(speed); +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin216::setSwingVertical(const bool on) { + _.SwingV = (on ? kDaikin216SwingOn : kDaikin216SwingOff); +} + +/// Get the Vertical Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin216::getSwingVertical(void) const { return _.SwingV; } + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin216::setSwingHorizontal(const bool on) { + _.SwingH = (on ? kDaikin216SwingOn : kDaikin216SwingOff); +} + +/// Get the Horizontal Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin216::getSwingHorizontal(void) const { return _.SwingH; } + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note This is a horrible hack till someone works out the quiet mode bit. +void IRDaikin216::setQuiet(const bool on) { + if (on) { + setFan(kDaikinFanQuiet); + // Powerful & Quiet mode being on are mutually exclusive. + setPowerful(false); + } else if (getFan() == kDaikinFanQuiet) { + setFan(kDaikinFanAuto); + } +} + +/// Get the Quiet mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +/// @note This is a horrible hack till someone works out the quiet mode bit. +bool IRDaikin216::getQuiet(void) const { return getFan() == kDaikinFanQuiet; } + +/// Set the Powerful (Turbo) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin216::setPowerful(const bool on) { + _.Powerful = on; + // Powerful & Quiet mode being on are mutually exclusive. + if (on) setQuiet(false); +} + +/// Get the Powerful (Turbo) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin216::getPowerful(void) const { return _.Powerful; } + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikin216::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DAIKIN216; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = IRDaikinESP::toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = IRDaikinESP::toCommonFanSpeed(getFan()); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : + stdAc::swingv_t::kOff; + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : + stdAc::swingh_t::kOff; + result.quiet = getQuiet(); + result.turbo = _.Powerful; + // Not supported. + result.light = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikin216::toString(void) const { + String result = ""; + result.reserve(120); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kDaikinAuto, kDaikinCool, kDaikinHeat, + kDaikinDry, kDaikinFan); + result += addTempToString(_.Temp); + result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin, + kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(getQuiet(), kQuietStr); + result += addBoolToString(_.Powerful, kPowerfulStr); + return result; +} + +#if DECODE_DAIKIN216 +/// Decode the supplied Daikin 216-bit message. (DAIKIN216) +/// Status: STABLE / Should be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/689 +/// @see https://github.com/danny-source/Arduino_DY_IRDaikin +bool IRrecv::decodeDaikin216(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1 + offset) + return false; + + // Compliance + if (strict && nbits != kDaikin216Bits) return false; + + const uint8_t ksectionSize[kDaikin216Sections] = {kDaikin216Section1Length, + kDaikin216Section2Length}; + // Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikin216Sections; section++) { + uint16_t used; + // Section Header + Section Data + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + kDaikin216HdrMark, kDaikin216HdrSpace, + kDaikin216BitMark, kDaikin216OneSpace, + kDaikin216BitMark, kDaikin216ZeroSpace, + kDaikin216BitMark, kDaikin216Gap, + section >= kDaikin216Sections - 1, + kDaikinTolerance, kDaikinMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + if (pos * 8 != kDaikin216Bits) return false; + // Validate the checksum. + if (!IRDaikin216::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = decode_type_t::DAIKIN216; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN216 + +#if SEND_DAIKIN160 +/// Send a Daikin160 (160-bit) A/C formatted message. +/// Status: STABLE / Confirmed working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/731 +void IRsend::sendDaikin160(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikin160Section1Length) + return; // Not enough bytes to send a partial message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Section #1 + sendGeneric(kDaikin160HdrMark, kDaikin160HdrSpace, kDaikin160BitMark, + kDaikin160OneSpace, kDaikin160BitMark, kDaikin160ZeroSpace, + kDaikin160BitMark, kDaikin160Gap, data, + kDaikin160Section1Length, + kDaikin160Freq, false, 0, kDutyDefault); + // Section #2 + sendGeneric(kDaikin160HdrMark, kDaikin160HdrSpace, kDaikin160BitMark, + kDaikin160OneSpace, kDaikin160BitMark, kDaikin160ZeroSpace, + kDaikin160BitMark, kDaikin160Gap, + data + kDaikin160Section1Length, + nbytes - kDaikin160Section1Length, + kDaikin160Freq, false, 0, kDutyDefault); + } +} +#endif // SEND_DAIKIN160 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikin160::IRDaikin160(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikin160::begin(void) { _irsend.begin(); } + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikin160::validChecksum(uint8_t state[], const uint16_t length) { + // Validate the checksum of section #1. + if (length <= kDaikin160Section1Length - 1 || + state[kDaikin160Section1Length - 1] != sumBytes( + state, kDaikin160Section1Length - 1)) + return false; + // Validate the checksum of section #2 (a.k.a. the rest) + if (length <= kDaikin160Section1Length + 1 || + state[length - 1] != sumBytes(state + kDaikin160Section1Length, + length - kDaikin160Section1Length - 1)) + return false; + return true; +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikin160::checksum(void) { + _.Sum1 = sumBytes(_.raw, kDaikin160Section1Length - 1); + _.Sum2 = sumBytes(_.raw + kDaikin160Section1Length, + kDaikin160Section2Length - 1); +} + +/// Reset the internal state to a fixed known good state. +void IRDaikin160::stateReset(void) { + for (uint8_t i = 0; i < kDaikin160StateLength; i++) _.raw[i] = 0x00; + _.raw[0] = 0x11; + _.raw[1] = 0xDA; + _.raw[2] = 0x27; + _.raw[3] = 0xF0; + _.raw[4] = 0x0D; + // _.raw[6] is a checksum byte, it will be set by checksum(). + _.raw[7] = 0x11; + _.raw[8] = 0xDA; + _.raw[9] = 0x27; + _.raw[11] = 0xD3; + _.raw[12] = 0x30; + _.raw[13] = 0x11; + _.raw[16] = 0x1E; + _.raw[17] = 0x0A; + _.raw[18] = 0x08; + // _.raw[19] is a checksum byte, it will be set by checksum(). +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRDaikin160::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRDaikin160::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kDaikin160StateLength); +} + +#if SEND_DAIKIN160 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikin160::send(const uint16_t repeat) { + _irsend.sendDaikin160(getRaw(), kDaikin160StateLength, repeat); +} +#endif // SEND_DAIKIN160 + +/// Change the power setting to On. +void IRDaikin160::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRDaikin160::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin160::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin160::getPower(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin160::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRDaikin160::setMode(const uint8_t mode) { + switch (mode) { + case kDaikinAuto: + case kDaikinCool: + case kDaikinHeat: + case kDaikinFan: + case kDaikinDry: + _.Mode = mode; + break; + default: _.Mode = kDaikinAuto; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin160::convertMode(const stdAc::opmode_t mode) { + return IRDaikinESP::convertMode(mode); +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRDaikin160::setTemp(const uint8_t temp) { + uint8_t degrees = ::max(temp, kDaikinMinTemp); + degrees = ::min(degrees, kDaikinMaxTemp) - 10; + _.Temp = degrees; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikin160::getTemp(void) const { return _.Temp + 10; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +/// @note 1-5 or kDaikinFanAuto or kDaikinFanQuiet +void IRDaikin160::setFan(const uint8_t fan) { + uint8_t fanset; + if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto) + fanset = fan; + else if (fan < kDaikinFanMin || fan > kDaikinFanMax) + fanset = kDaikinFanAuto; + else + fanset = 2 + fan; + _.Fan = fanset; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikin160::getFan(void) const { + uint8_t fan = _.Fan; + if (fan != kDaikinFanQuiet && fan != kDaikinFanAuto) fan -= 2; + return fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin160::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kDaikinFanMin; + case stdAc::fanspeed_t::kLow: return kDaikinFanMin + 1; + case stdAc::fanspeed_t::kMedium: return kDaikinFanMin + 2; + case stdAc::fanspeed_t::kHigh: return kDaikinFanMax - 1; + case stdAc::fanspeed_t::kMax: return kDaikinFanMax; + default: + return kDaikinFanAuto; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] position The position/mode to set the swing to. +void IRDaikin160::setSwingVertical(const uint8_t position) { + switch (position) { + case kDaikin160SwingVLowest: + case kDaikin160SwingVLow: + case kDaikin160SwingVMiddle: + case kDaikin160SwingVHigh: + case kDaikin160SwingVHighest: + case kDaikin160SwingVAuto: + _.SwingV = position; + break; + default: _.SwingV = kDaikin160SwingVAuto; + } +} + +/// Get the Vertical Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRDaikin160::getSwingVertical(void) const { return _.SwingV; } + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin160::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: + case stdAc::swingv_t::kLow: + case stdAc::swingv_t::kLowest: + return kDaikin160SwingVHighest + 1 - (uint8_t)position; + default: + return kDaikin160SwingVAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] setting A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRDaikin160::toCommonSwingV(const uint8_t setting) { + switch (setting) { + case kDaikin160SwingVHighest: return stdAc::swingv_t::kHighest; + case kDaikin160SwingVHigh: return stdAc::swingv_t::kHigh; + case kDaikin160SwingVMiddle: return stdAc::swingv_t::kMiddle; + case kDaikin160SwingVLow: return stdAc::swingv_t::kLow; + case kDaikin160SwingVLowest: return stdAc::swingv_t::kLowest; + default: + return stdAc::swingv_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikin160::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DAIKIN160; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = IRDaikinESP::toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = IRDaikinESP::toCommonFanSpeed(getFan()); + result.swingv = toCommonSwingV(_.SwingV); + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.turbo = false; + result.light = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikin160::toString(void) const { + String result = ""; + result.reserve(150); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kDaikinAuto, kDaikinCool, kDaikinHeat, + kDaikinDry, kDaikinFan); + result += addTempToString(getTemp()); + result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin, + kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed); + result += addIntToString(_.SwingV, kSwingVStr); + result += kSpaceLBraceStr; + switch (_.SwingV) { + case kDaikin160SwingVHighest: result += kHighestStr; break; + case kDaikin160SwingVHigh: result += kHighStr; break; + case kDaikin160SwingVMiddle: result += kMiddleStr; break; + case kDaikin160SwingVLow: result += kLowStr; break; + case kDaikin160SwingVLowest: result += kLowestStr; break; + case kDaikin160SwingVAuto: result += kAutoStr; break; + default: result += kUnknownStr; + } + result += ')'; + return result; +} + +#if DECODE_DAIKIN160 +/// Decode the supplied Daikin 160-bit message. (DAIKIN160) +/// Status: STABLE / Confirmed working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/731 +bool IRrecv::decodeDaikin160(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1 + offset) + return false; + + // Compliance + if (strict && nbits != kDaikin160Bits) return false; + + const uint8_t ksectionSize[kDaikin160Sections] = {kDaikin160Section1Length, + kDaikin160Section2Length}; + + // Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikin160Sections; section++) { + uint16_t used; + // Section Header + Section Data (7 bytes) + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + kDaikin160HdrMark, kDaikin160HdrSpace, + kDaikin160BitMark, kDaikin160OneSpace, + kDaikin160BitMark, kDaikin160ZeroSpace, + kDaikin160BitMark, kDaikin160Gap, + section >= kDaikin160Sections - 1, + kDaikinTolerance, kDaikinMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + // Validate the checksum. + if (!IRDaikin160::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = decode_type_t::DAIKIN160; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN160 + +#if SEND_DAIKIN176 +/// Send a Daikin176 (176-bit) A/C formatted message. +/// Status: STABLE / Working on a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendDaikin176(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikin176Section1Length) + return; // Not enough bytes to send a partial message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Section #1 + sendGeneric(kDaikin176HdrMark, kDaikin176HdrSpace, kDaikin176BitMark, + kDaikin176OneSpace, kDaikin176BitMark, kDaikin176ZeroSpace, + kDaikin176BitMark, kDaikin176Gap, data, + kDaikin176Section1Length, + kDaikin176Freq, false, 0, kDutyDefault); + // Section #2 + sendGeneric(kDaikin176HdrMark, kDaikin176HdrSpace, kDaikin176BitMark, + kDaikin176OneSpace, kDaikin176BitMark, kDaikin176ZeroSpace, + kDaikin176BitMark, kDaikin176Gap, + data + kDaikin176Section1Length, + nbytes - kDaikin176Section1Length, + kDaikin176Freq, false, 0, kDutyDefault); + } +} +#endif // SEND_DAIKIN176 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikin176::IRDaikin176(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikin176::begin(void) { _irsend.begin(); } + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikin176::validChecksum(uint8_t state[], const uint16_t length) { + // Validate the checksum of section #1. + if (length <= kDaikin176Section1Length - 1 || + state[kDaikin176Section1Length - 1] != sumBytes( + state, kDaikin176Section1Length - 1)) + return false; + // Validate the checksum of section #2 (a.k.a. the rest) + if (length <= kDaikin176Section1Length + 1 || + state[length - 1] != sumBytes(state + kDaikin176Section1Length, + length - kDaikin176Section1Length - 1)) + return false; + return true; +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikin176::checksum(void) { + _.Sum1 = sumBytes(_.raw, kDaikin176Section1Length - 1); + _.Sum2 = sumBytes(_.raw + kDaikin176Section1Length, + kDaikin176Section2Length - 1); +} + +/// Reset the internal state to a fixed known good state. +void IRDaikin176::stateReset(void) { + for (uint8_t i = 0; i < kDaikin176StateLength; i++) _.raw[i] = 0x00; + _.raw[0] = 0x11; + _.raw[1] = 0xDA; + _.raw[2] = 0x17; + _.raw[3] = 0x18; + _.raw[4] = 0x04; + // _.raw[6] is a checksum byte, it will be set by checksum(). + _.raw[7] = 0x11; + _.raw[8] = 0xDA; + _.raw[9] = 0x17; + _.raw[10] = 0x18; + _.raw[12] = 0x73; + _.raw[14] = 0x20; + _.raw[18] = 0x16; // Fan speed and swing + _.raw[20] = 0x20; + // _.raw[21] is a checksum byte, it will be set by checksum(). + _saved_temp = getTemp(); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRDaikin176::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRDaikin176::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kDaikin176StateLength); + _saved_temp = getTemp(); +} + +#if SEND_DAIKIN176 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikin176::send(const uint16_t repeat) { + _irsend.sendDaikin176(getRaw(), kDaikin176StateLength, repeat); +} +#endif // SEND_DAIKIN176 + +/// Change the power setting to On. +void IRDaikin176::on(void) { setPower(true); } + +/// Change the power setting to Off.. +void IRDaikin176::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin176::setPower(const bool on) { + _.ModeButton = 0; + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin176::getPower(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin176::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRDaikin176::setMode(const uint8_t mode) { + uint8_t altmode = 0; + // Set the mode bits. + _.Mode = mode; + // Daikin172 has some alternate/additional mode bits that need to be changed + // in line with the operating mode. The following few lines match up these + // bits with the corresponding operating bits. + switch (mode) { + case kDaikin176Dry: altmode = 2; break; + case kDaikin176Fan: altmode = 6; break; + case kDaikin176Auto: + case kDaikin176Cool: + case kDaikin176Heat: altmode = 7; break; + default: _.Mode = kDaikin176Cool; altmode = 7; break; + } + // Set the additional mode bits. + _.AltMode = altmode; + setTemp(_saved_temp); + // Needs to happen after setTemp() as it will clear it. + _.ModeButton = kDaikin176ModeButton; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin176::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kDry: return kDaikin176Dry; + case stdAc::opmode_t::kHeat: return kDaikin176Heat; + case stdAc::opmode_t::kFan: return kDaikin176Fan; + case stdAc::opmode_t::kAuto: return kDaikin176Auto; + default: return kDaikin176Cool; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRDaikin176::toCommonMode(const uint8_t mode) { + switch (mode) { + case kDaikin176Dry: return stdAc::opmode_t::kDry; + case kDaikin176Heat: return stdAc::opmode_t::kHeat; + case kDaikin176Fan: return stdAc::opmode_t::kFan; + case kDaikin176Auto: return stdAc::opmode_t::kAuto; + default: return stdAc::opmode_t::kCool; + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRDaikin176::setTemp(const uint8_t temp) { + uint8_t degrees = ::min(kDaikinMaxTemp, ::max(temp, kDaikinMinTemp)); + _saved_temp = degrees; + switch (_.Mode) { + case kDaikin176Dry: + case kDaikin176Fan: + degrees = kDaikin176DryFanTemp; break; + } + _.Temp = degrees - 9; + _.ModeButton = 0; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikin176::getTemp(void) const { return _.Temp + 9; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +/// @note 1 for Min or 3 for Max +void IRDaikin176::setFan(const uint8_t fan) { + switch (fan) { + case kDaikinFanMin: + case kDaikin176FanMax: + _.Fan = fan; + break; + default: + _.Fan = kDaikin176FanMax; + break; + } + _.ModeButton = 0; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikin176::getFan(void) const { return _.Fan; } + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin176::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kDaikinFanMin; + default: return kDaikin176FanMax; + } +} + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] position The position/mode to set the swing to. +void IRDaikin176::setSwingHorizontal(const uint8_t position) { + switch (position) { + case kDaikin176SwingHOff: + case kDaikin176SwingHAuto: + _.SwingH = position; + break; + default: _.SwingH = kDaikin176SwingHAuto; + } +} + +/// Get the Horizontal Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRDaikin176::getSwingHorizontal(void) const { return _.SwingH; } + +/// Get the Unit Id of the A/C. +/// @return The Unit Id the A/C is to use. +uint8_t IRDaikin176::getId(void) const { return _.Id1; } + +/// Set the Unit Id of the A/C. +/// @param[in] num The Unit Id the A/C is to use. +/// @note 0 for Unit A; 1 for Unit B +void IRDaikin176::setId(const uint8_t num) { _.Id1 = _.Id2 = num; } + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin176::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kOff: return kDaikin176SwingHOff; + case stdAc::swingh_t::kAuto: return kDaikin176SwingHAuto; + default: return kDaikin176SwingHAuto; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] setting A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRDaikin176::toCommonSwingH(const uint8_t setting) { + switch (setting) { + case kDaikin176SwingHOff: return stdAc::swingh_t::kOff; + case kDaikin176SwingHAuto: return stdAc::swingh_t::kAuto; + default: + return stdAc::swingh_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRDaikin176::toCommonFanSpeed(const uint8_t speed) { + return (speed == kDaikinFanMin) ? stdAc::fanspeed_t::kMin + : stdAc::fanspeed_t::kMax; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikin176::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DAIKIN176; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = IRDaikin176::toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingh = toCommonSwingH(_.SwingH); + + // Not supported. + result.swingv = stdAc::swingv_t::kOff; + result.quiet = false; + result.turbo = false; + result.light = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikin176::toString(void) const { + String result = ""; + result.reserve(90); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kDaikin176Auto, kDaikin176Cool, + kDaikin176Heat, kDaikin176Dry, kDaikin176Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kDaikin176FanMax, kDaikinFanMin, + kDaikinFanMin, kDaikinFanMin, kDaikinFanMin); + result += addSwingHToString(_.SwingH, kDaikin176SwingHAuto, + kDaikin176SwingHAuto, // maxleft Unused + kDaikin176SwingHAuto, // left Unused + kDaikin176SwingHAuto, // middle Unused + kDaikin176SwingHAuto, // right Unused + kDaikin176SwingHAuto, // maxright Unused + kDaikin176SwingHOff, + // Below are unused. + kDaikin176SwingHAuto, + kDaikin176SwingHAuto, + kDaikin176SwingHAuto, + kDaikin176SwingHAuto); + result += addIntToString(_.Id1, kIdStr); + return result; +} + +#if DECODE_DAIKIN176 +/// Decode the supplied Daikin 176-bit message. (DAIKIN176) +/// Status: STABLE / Expected to work. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeDaikin176(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1 + offset) + return false; + + // Compliance + if (strict && nbits != kDaikin176Bits) return false; + + const uint8_t ksectionSize[kDaikin176Sections] = {kDaikin176Section1Length, + kDaikin176Section2Length}; + + // Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikin176Sections; section++) { + uint16_t used; + // Section Header + Section Data (7 bytes) + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + kDaikin176HdrMark, kDaikin176HdrSpace, + kDaikin176BitMark, kDaikin176OneSpace, + kDaikin176BitMark, kDaikin176ZeroSpace, + kDaikin176BitMark, kDaikin176Gap, + section >= kDaikin176Sections - 1, + kDaikinTolerance, kDaikinMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + // Validate the checksum. + if (!IRDaikin176::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = decode_type_t::DAIKIN176; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN176 + +#if SEND_DAIKIN128 +/// Send a Daikin128 (128-bit) A/C formatted message. +/// Status: STABLE / Known Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/827 +void IRsend::sendDaikin128(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikin128SectionLength) + return; // Not enough bytes to send a partial message. + + for (uint16_t r = 0; r <= repeat; r++) { + enableIROut(kDaikin128Freq); + // Leader + for (uint8_t i = 0; i < 2; i++) { + mark(kDaikin128LeaderMark); + space(kDaikin128LeaderSpace); + } + // Section #1 (Header + Data) + sendGeneric(kDaikin128HdrMark, kDaikin128HdrSpace, kDaikin128BitMark, + kDaikin128OneSpace, kDaikin128BitMark, kDaikin128ZeroSpace, + kDaikin128BitMark, kDaikin128Gap, data, + kDaikin128SectionLength, + kDaikin128Freq, false, 0, kDutyDefault); + // Section #2 (Data + Footer) + sendGeneric(0, 0, kDaikin128BitMark, + kDaikin128OneSpace, kDaikin128BitMark, kDaikin128ZeroSpace, + kDaikin128FooterMark, kDaikin128Gap, + data + kDaikin128SectionLength, + nbytes - kDaikin128SectionLength, + kDaikin128Freq, false, 0, kDutyDefault); + } +} +#endif // SEND_DAIKIN128 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikin128::IRDaikin128(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikin128::begin(void) { _irsend.begin(); } + +uint8_t IRDaikin128::calcFirstChecksum(const uint8_t state[]) { + return sumNibbles(state, kDaikin128SectionLength - 1, + state[kDaikin128SectionLength - 1] & 0x0F) & 0x0F; +} + +uint8_t IRDaikin128::calcSecondChecksum(const uint8_t state[]) { + return sumNibbles(state + kDaikin128SectionLength, + kDaikin128SectionLength - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikin128::validChecksum(uint8_t state[]) { + // Validate the checksum of section #1. + if (state[kDaikin128SectionLength - 1] >> 4 != calcFirstChecksum(state)) + return false; + // Validate the checksum of section #2 + if (state[kDaikin128StateLength - 1] != calcSecondChecksum(state)) + return false; + return true; +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikin128::checksum(void) { + _.Sum1 = calcFirstChecksum(_.raw); + _.Sum2 = calcSecondChecksum(_.raw); +} + +/// Reset the internal state to a fixed known good state. +void IRDaikin128::stateReset(void) { + for (uint8_t i = 0; i < kDaikin128StateLength; i++) _.raw[i] = 0x00; + _.raw[0] = 0x16; + _.raw[7] = 0x04; // Most significant nibble is a checksum. + _.raw[8] = 0xA1; + // _.raw[15] is a checksum byte, it will be set by checksum(). +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRDaikin128::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRDaikin128::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kDaikin128StateLength); +} + +#if SEND_DAIKIN128 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikin128::send(const uint16_t repeat) { + _irsend.sendDaikin128(getRaw(), kDaikin128StateLength, repeat); +} +#endif // SEND_DAIKIN128 + +/// Set the Power toggle setting of the A/C. +/// @param[in] toggle true, the setting is on. false, the setting is off. +void IRDaikin128::setPowerToggle(const bool toggle) { _.Power = toggle; } + +/// Get the Power toggle setting of the A/C. +/// @return The current operating mode setting. +bool IRDaikin128::getPowerToggle(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin128::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRDaikin128::setMode(const uint8_t mode) { + switch (mode) { + case kDaikin128Auto: + case kDaikin128Cool: + case kDaikin128Heat: + case kDaikin128Fan: + case kDaikin128Dry: + _.Mode = mode; + break; + default: + _.Mode = kDaikin128Auto; + break; + } + // Force a reset of mode dependant things. + setFan(getFan()); // Covers Quiet & Powerful too. + setEcono(getEcono()); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin128::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kDaikin128Cool; + case stdAc::opmode_t::kHeat: return kDaikin128Heat; + case stdAc::opmode_t::kDry: return kDaikinDry; + case stdAc::opmode_t::kFan: return kDaikin128Fan; + default: return kDaikin128Auto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRDaikin128::toCommonMode(const uint8_t mode) { + switch (mode) { + case kDaikin128Cool: return stdAc::opmode_t::kCool; + case kDaikin128Heat: return stdAc::opmode_t::kHeat; + case kDaikin128Dry: return stdAc::opmode_t::kDry; + case kDaikin128Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRDaikin128::setTemp(const uint8_t temp) { + _.Temp = uint8ToBcd(::min(kDaikin128MaxTemp, + ::max(temp, kDaikin128MinTemp))); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikin128::getTemp(void) const { return bcdToUint8(_.Temp); } + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikin128::getFan(void) const { return _.Fan; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRDaikin128::setFan(const uint8_t speed) { + uint8_t new_speed = speed; + uint8_t mode = _.Mode; + switch (speed) { + case kDaikin128FanQuiet: + case kDaikin128FanPowerful: + if (mode == kDaikin128Auto) new_speed = kDaikin128FanAuto; + // FALL-THRU + case kDaikin128FanAuto: + case kDaikin128FanHigh: + case kDaikin128FanMed: + case kDaikin128FanLow: + _.Fan = new_speed; + break; + default: + _.Fan = kDaikin128FanAuto; + return; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin128::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kDaikinFanQuiet; + case stdAc::fanspeed_t::kLow: return kDaikin128FanLow; + case stdAc::fanspeed_t::kMedium: return kDaikin128FanMed; + case stdAc::fanspeed_t::kHigh: return kDaikin128FanHigh; + case stdAc::fanspeed_t::kMax: return kDaikin128FanPowerful; + default: return kDaikin128FanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRDaikin128::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kDaikin128FanPowerful: return stdAc::fanspeed_t::kMax; + case kDaikin128FanHigh: return stdAc::fanspeed_t::kHigh; + case kDaikin128FanMed: return stdAc::fanspeed_t::kMedium; + case kDaikin128FanLow: return stdAc::fanspeed_t::kLow; + case kDaikinFanQuiet: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin128::setSwingVertical(const bool on) { _.SwingV = on; } + +/// Get the Vertical Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin128::getSwingVertical(void) const { return _.SwingV; } + +/// Set the Sleep mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin128::setSleep(const bool on) { _.Sleep = on; } + +/// Get the Sleep mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin128::getSleep(void) const { return _.Sleep; } + +/// Set the Economy mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin128::setEcono(const bool on) { + uint8_t mode = _.Mode; + _.Econo = (on && (mode == kDaikin128Cool || mode == kDaikin128Heat)); +} + +/// Get the Economical mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin128::getEcono(void) const { return _.Econo; } + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin128::setQuiet(const bool on) { + uint8_t mode = _.Mode; + if (on && (mode == kDaikin128Cool || mode == kDaikin128Heat)) + setFan(kDaikin128FanQuiet); + else if (_.Fan == kDaikin128FanQuiet) + setFan(kDaikin128FanAuto); +} + +/// Get the Quiet mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin128::getQuiet(void) const { return _.Fan == kDaikin128FanQuiet; } + +/// Set the Powerful (Turbo) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin128::setPowerful(const bool on) { + uint8_t mode = _.Mode; + if (on && (mode == kDaikin128Cool || mode == kDaikin128Heat)) + setFan(kDaikin128FanPowerful); + else if (_.Fan == kDaikin128FanPowerful) + setFan(kDaikin128FanAuto); +} + +/// Get the Powerful (Turbo) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin128::getPowerful(void) const { + return _.Fan == kDaikin128FanPowerful; +} + +/// Set the clock on the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRDaikin128::setClock(const uint16_t mins_since_midnight) { + uint16_t mins = mins_since_midnight; + if (mins_since_midnight >= 24 * 60) mins = 0; // Bounds check. + // Hours. + _.ClockHours = uint8ToBcd(mins / 60); + // Minutes. + _.ClockMins = uint8ToBcd(mins % 60); +} + +/// Get the clock time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin128::getClock(void) const { + return bcdToUint8(_.ClockHours) * 60 + bcdToUint8(_.ClockMins); +} + +/// Set the enable status of the On Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin128::setOnTimerEnabled(const bool on) { _.OnTimer = on; } + +/// Get the enable status of the On Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin128::getOnTimerEnabled(void) const { return _.OnTimer; } + +#define SETTIME(x, n) do { \ + uint16_t mins = n;\ + if (n >= 24 * 60) mins = 0;\ + _.x##HalfHour = (mins % 60) >= 30;\ + _.x##Hours = uint8ToBcd(mins / 60);\ +} while (0) + +#define GETTIME(x) bcdToUint8(_.x##Hours) * 60 + (_.x##HalfHour ? 30 : 0) + +/// Set the On Timer time for the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRDaikin128::setOnTimer(const uint16_t mins_since_midnight) { + SETTIME(On, mins_since_midnight); +} + +/// Get the On Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin128::getOnTimer(void) const { return GETTIME(On); } + +/// Set the enable status of the Off Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin128::setOffTimerEnabled(const bool on) { _.OffTimer = on; } + +/// Get the enable status of the Off Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin128::getOffTimerEnabled(void) const { return _.OffTimer; } + +/// Set the Off Timer time for the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRDaikin128::setOffTimer(const uint16_t mins_since_midnight) { + SETTIME(Off, mins_since_midnight); +} + +/// Get the Off Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin128::getOffTimer(void) const { return GETTIME(Off); } + +/// Set the Light toggle setting of the A/C. +/// @param[in] unit Device to show the LED (Light) Display info about. +/// @note 0 is off. +void IRDaikin128::setLightToggle(const uint8_t unit) { + _.Ceiling = 0; + _.Wall = 0; + switch (unit) { + case kDaikin128BitCeiling: + _.Ceiling = 1; + break; + case kDaikin128BitWall: + _.Wall = 1; + break; + } +} + +/// Get the Light toggle setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin128::getLightToggle(void) const { + uint8_t code = 0; + if (_.Ceiling) { + code = kDaikin128BitCeiling; + } else if (_.Wall) { + code = kDaikin128BitWall; + } + + return code; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikin128::toString(void) const { + String result = ""; + result.reserve(240); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerToggleStr, false); + result += addModeToString(_.Mode, kDaikin128Auto, kDaikin128Cool, + kDaikin128Heat, kDaikin128Dry, kDaikin128Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kDaikin128FanHigh, kDaikin128FanLow, + kDaikin128FanAuto, kDaikin128FanQuiet, + kDaikin128FanMed); + result += addBoolToString(getPowerful(), kPowerfulStr); + result += addBoolToString(getQuiet(), kQuietStr); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addLabeledString(minsToString(getClock()), kClockStr); + result += addBoolToString(_.OnTimer, kOnTimerStr); + result += addLabeledString(minsToString(getOnTimer()), kOnTimerStr); + result += addBoolToString(_.OffTimer, kOffTimerStr); + result += addLabeledString(minsToString(getOffTimer()), kOffTimerStr); + result += addIntToString(getLightToggle(), kLightToggleStr); + result += kSpaceLBraceStr; + switch (getLightToggle()) { + case kDaikin128BitCeiling: result += kCeilingStr; break; + case kDaikin128BitWall: result += kWallStr; break; + case 0: result += kOffStr; break; + default: result += kUnknownStr; + } + result += ')'; + return result; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to a previous state. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikin128::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + if (prev != NULL) result = *prev; + result.protocol = decode_type_t::DAIKIN128; + result.model = -1; // No models used. + result.power ^= _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.quiet = getQuiet(); + result.turbo = getPowerful(); + result.econo = _.Econo; + result.light ^= (getLightToggle() != 0); + result.sleep = _.Sleep ? 0 : -1; + result.clock = getClock(); + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.clean = false; + result.filter = false; + result.beep = false; + return result; +} + +#if DECODE_DAIKIN128 +/// Decode the supplied Daikin 128-bit message. (DAIKIN128) +/// Status: STABLE / Known Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/827 +bool IRrecv::decodeDaikin128(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + kHeader) + kFooter - 1 + offset) + return false; + if (nbits / 8 <= kDaikin128SectionLength) return false; + + // Compliance + if (strict && nbits != kDaikin128Bits) return false; + + // Leader + for (uint8_t i = 0; i < 2; i++) { + if (!matchMark(results->rawbuf[offset++], kDaikin128LeaderMark, + kDaikinTolerance, kDaikinMarkExcess)) return false; + if (!matchSpace(results->rawbuf[offset++], kDaikin128LeaderSpace, + kDaikinTolerance, kDaikinMarkExcess)) return false; + } + const uint16_t ksectionSize[kDaikin128Sections] = { + kDaikin128SectionLength, (uint16_t)(nbits / 8 - kDaikin128SectionLength)}; + // Data Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikin128Sections; section++) { + uint16_t used; + // Section Header (first section only) + Section Data (8 bytes) + + // Section Footer (Not for first section) + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + section == 0 ? kDaikin128HdrMark : 0, + section == 0 ? kDaikin128HdrSpace : 0, + kDaikin128BitMark, kDaikin128OneSpace, + kDaikin128BitMark, kDaikin128ZeroSpace, + section > 0 ? kDaikin128FooterMark : kDaikin128BitMark, + kDaikin128Gap, + section > 0, + kDaikinTolerance, kDaikinMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + if (!IRDaikin128::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = decode_type_t::DAIKIN128; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN128 + +#if SEND_DAIKIN152 +/// Send a Daikin152 (152-bit) A/C formatted message. +/// Status: STABLE / Known Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/873 +void IRsend::sendDaikin152(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + for (uint16_t r = 0; r <= repeat; r++) { + // Leader + sendGeneric(0, 0, kDaikin152BitMark, kDaikin152OneSpace, + kDaikin152BitMark, kDaikin152ZeroSpace, + kDaikin152BitMark, kDaikin152Gap, + (uint64_t)0, kDaikin152LeaderBits, + kDaikin152Freq, false, 0, kDutyDefault); + // Header + Data + Footer + sendGeneric(kDaikin152HdrMark, kDaikin152HdrSpace, kDaikin152BitMark, + kDaikin152OneSpace, kDaikin152BitMark, kDaikin152ZeroSpace, + kDaikin152BitMark, kDaikin152Gap, data, + nbytes, kDaikin152Freq, false, 0, kDutyDefault); + } +} +#endif // SEND_DAIKIN152 + +#if DECODE_DAIKIN152 +/// Decode the supplied Daikin 152-bit message. (DAIKIN152) +/// Status: STABLE / Known Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/873 +bool IRrecv::decodeDaikin152(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (5 + nbits + kFooter) + kHeader - 1 + offset) + return false; + if (nbits / 8 < kDaikin152StateLength) return false; + + // Compliance + if (strict && nbits != kDaikin152Bits) return false; + + uint16_t used; + + // Leader + uint64_t leader = 0; + used = matchGeneric(results->rawbuf + offset, &leader, + results->rawlen - offset, kDaikin152LeaderBits, + 0, 0, // No Header + kDaikin152BitMark, kDaikin152OneSpace, + kDaikin152BitMark, kDaikin152ZeroSpace, + kDaikin152BitMark, kDaikin152Gap, // Footer gap + false, _tolerance, kMarkExcess, false); + if (used == 0 || leader != 0) return false; + offset += used; + + // Header + Data + Footer + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kDaikin152HdrMark, kDaikin152HdrSpace, + kDaikin152BitMark, kDaikin152OneSpace, + kDaikin152BitMark, kDaikin152ZeroSpace, + kDaikin152BitMark, kDaikin152Gap, + true, _tolerance, kMarkExcess, false); + if (used == 0) return false; + + // Compliance + if (strict) { + if (!IRDaikin152::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = decode_type_t::DAIKIN152; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN152 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikin152::IRDaikin152(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikin152::begin(void) { _irsend.begin(); } + +#if SEND_DAIKIN152 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikin152::send(const uint16_t repeat) { + _irsend.sendDaikin152(getRaw(), kDaikin152StateLength, repeat); +} +#endif // SEND_DAIKIN152 + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikin152::validChecksum(uint8_t state[], const uint16_t length) { + // Validate the checksum of the given state. + if (length <= 1 || state[length - 1] != sumBytes(state, length - 1)) + return false; + else + return true; +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikin152::checksum(void) { + _.Sum = sumBytes(_.raw, kDaikin152StateLength - 1); +} + +/// Reset the internal state to a fixed known good state. +void IRDaikin152::stateReset(void) { + for (uint8_t i = 3; i < kDaikin152StateLength; i++) _.raw[i] = 0x00; + _.raw[0] = 0x11; + _.raw[1] = 0xDA; + _.raw[2] = 0x27; + _.raw[15] = 0xC5; + // _.raw[19] is a checksum byte, it will be set by checksum(). +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRDaikin152::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRDaikin152::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kDaikin152StateLength); +} + +/// Change the power setting to On. +void IRDaikin152::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRDaikin152::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin152::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin152::getPower(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin152::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRDaikin152::setMode(const uint8_t mode) { + switch (mode) { + case kDaikinFan: + setTemp(kDaikin152FanTemp); // Handle special temp for fan mode. + break; + case kDaikinDry: + setTemp(kDaikin152DryTemp); // Handle special temp for dry mode. + break; + case kDaikinAuto: + case kDaikinCool: + case kDaikinHeat: + break; + default: + _.Mode = kDaikinAuto; + return; + } + _.Mode = mode; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin152::convertMode(const stdAc::opmode_t mode) { + return IRDaikinESP::convertMode(mode); +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRDaikin152::setTemp(const uint8_t temp) { + uint8_t degrees = ::max( + temp, (_.Mode == kDaikinHeat) ? kDaikinMinTemp : kDaikin2MinCoolTemp); + degrees = ::min(degrees, kDaikinMaxTemp); + if (temp == kDaikin152FanTemp) degrees = temp; // Handle fan only temp. + _.Temp = degrees; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikin152::getTemp(void) const { return _.Temp; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +/// @note 1-5 or kDaikinFanAuto or kDaikinFanQuiet +void IRDaikin152::setFan(const uint8_t fan) { + // Set the fan speed bits, leave low 4 bits alone + uint8_t fanset; + if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto) + fanset = fan; + else if (fan < kDaikinFanMin || fan > kDaikinFanMax) + fanset = kDaikinFanAuto; + else + fanset = 2 + fan; + _.Fan = fanset; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikin152::getFan(void) const { + const uint8_t fan = _.Fan; + switch (fan) { + case kDaikinFanAuto: + case kDaikinFanQuiet: return fan; + default: return fan - 2; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin152::convertFan(const stdAc::fanspeed_t speed) { + return IRDaikinESP::convertFan(speed); +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin152::setSwingV(const bool on) { + _.SwingV = (on ? kDaikinSwingOn : kDaikinSwingOff); +} + +/// Get the Vertical Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin152::getSwingV(void) const { return _.SwingV; } + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin152::setQuiet(const bool on) { + _.Quiet = on; + // Powerful & Quiet mode being on are mutually exclusive. + if (on) setPowerful(false); +} + +/// Get the Quiet mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin152::getQuiet(void) const { return _.Quiet; } + +/// Set the Powerful (Turbo) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin152::setPowerful(const bool on) { + _.Powerful = on; + if (on) { + // Powerful, Quiet, Comfort & Econo mode being on are mutually exclusive. + setQuiet(false); + setComfort(false); + setEcono(false); + } +} + +/// Get the Powerful (Turbo) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin152::getPowerful(void) const { return _.Powerful; } + +/// Set the Economy mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin152::setEcono(const bool on) { + _.Econo = on; + // Powerful & Econo mode being on are mutually exclusive. + if (on) setPowerful(false); +} + +/// Get the Economical mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin152::getEcono(void) const { return _.Econo; } + +/// Set the Sensor mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin152::setSensor(const bool on) { _.Sensor = on; } + +/// Get the Sensor mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin152::getSensor(void) const { return _.Sensor; } + +/// Set the Comfort mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin152::setComfort(const bool on) { + _.Comfort = on; + if (on) { + // Comfort mode is incompatible with Powerful mode. + setPowerful(false); + // It also sets the fan to auto and turns off swingv. + setFan(kDaikinFanAuto); + setSwingV(false); + } +} + +/// Get the Comfort mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin152::getComfort(void) const { return _.Comfort; } + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikin152::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DAIKIN152; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = IRDaikinESP::toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = IRDaikinESP::toCommonFanSpeed(getFan()); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.quiet = _.Quiet; + result.turbo = _.Powerful; + result.econo = _.Econo; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.clean = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikin152::toString(void) const { + String result = ""; + result.reserve(180); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kDaikinAuto, kDaikinCool, kDaikinHeat, + kDaikinDry, kDaikinFan); + result += addTempToString(_.Temp); + result += addFanToString(getFan(), kDaikinFanMax, kDaikinFanMin, + kDaikinFanAuto, kDaikinFanQuiet, kDaikinFanMed); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.Powerful, kPowerfulStr); + result += addBoolToString(_.Quiet, kQuietStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addBoolToString(_.Sensor, kSensorStr); + result += addBoolToString(_.Comfort, kComfortStr); + return result; +} + +#if SEND_DAIKIN64 +/// Send a Daikin64 (64-bit) A/C formatted message. +/// Status: Beta / Probably Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1064 +void IRsend::sendDaikin64(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + enableIROut(kDaikin64Freq); + for (uint16_t r = 0; r <= repeat; r++) { + for (uint8_t i = 0; i < 2; i++) { + // Leader + mark(kDaikin64LdrMark); + space(kDaikin64LdrSpace); + } + // Header + Data + Footer #1 + sendGeneric(kDaikin64HdrMark, kDaikin64HdrSpace, + kDaikin64BitMark, kDaikin64OneSpace, + kDaikin64BitMark, kDaikin64ZeroSpace, + kDaikin64BitMark, kDaikin64Gap, + data, nbits, kDaikin64Freq, false, 0, 50); + // Footer #2 + mark(kDaikin64HdrMark); + space(kDefaultMessageGap); // A guess of the gap between messages. + } +} +#endif // SEND_DAIKIN64 + +#if DECODE_DAIKIN64 +/// Decode the supplied Daikin 64-bit message. (DAIKIN64) +/// Status: Beta / Probably Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1064 +bool IRrecv::decodeDaikin64(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kDaikin64Overhead - offset) + return false; // Too short a message to match. + // Compliance + if (strict && nbits != kDaikin64Bits) + return false; + + // Leader + for (uint8_t i = 0; i < 2; i++) { + if (!matchMark(results->rawbuf[offset++], kDaikin64LdrMark)) + return false; + if (!matchSpace(results->rawbuf[offset++], kDaikin64LdrSpace)) + return false; + } + // Header + Data + Footer #1 + uint16_t used = matchGeneric(results->rawbuf + offset, &results->value, + results->rawlen - offset, nbits, + kDaikin64HdrMark, kDaikin64HdrSpace, + kDaikin64BitMark, kDaikin64OneSpace, + kDaikin64BitMark, kDaikin64ZeroSpace, + kDaikin64BitMark, kDaikin64Gap, + false, _tolerance + kDaikin64ToleranceDelta, + kMarkExcess, false); + if (used == 0) return false; + offset += used; + // Footer #2 + if (!matchMark(results->rawbuf[offset++], kDaikin64HdrMark)) + return false; + + // Compliance + if (strict && !IRDaikin64::validChecksum(results->value)) return false; + // Success + results->decode_type = decode_type_t::DAIKIN64; + results->bits = nbits; + results->command = 0; + results->address = 0; + return true; +} +#endif // DAIKIN64 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDaikin64::IRDaikin64(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDaikin64::begin(void) { _irsend.begin(); } + +#if SEND_DAIKIN64 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDaikin64::send(const uint16_t repeat) { + _irsend.sendDaikin64(getRaw(), kDaikin64Bits, repeat); +} +#endif // SEND_DAIKIN64 + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @return The 4-bit checksum stored in a uint_8. +uint8_t IRDaikin64::calcChecksum(const uint64_t state) { + uint64_t data = GETBITS64(state, 0, kDaikin64ChecksumOffset); + uint8_t result = 0; + for (; data; data >>= 4) // Add each nibble together. + result += GETBITS64(data, 0, 4); + return result & 0xF; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The state to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDaikin64::validChecksum(const uint64_t state) { + // Validate the checksum of the given state. + return (GETBITS64(state, kDaikin64ChecksumOffset, + kDaikin64ChecksumSize) == calcChecksum(state)); +} + +/// Calculate and set the checksum values for the internal state. +void IRDaikin64::checksum(void) { _.Sum = calcChecksum(_.raw); } + +/// Reset the internal state to a fixed known good state. +void IRDaikin64::stateReset(void) { _.raw = kDaikin64KnownGoodState; } + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +uint64_t IRDaikin64::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_state A valid code for this protocol. +void IRDaikin64::setRaw(const uint64_t new_state) { _.raw = new_state; } + +/// Set the Power toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin64::setPowerToggle(const bool on) { _.Power = on; } + +/// Get the Power toggle setting of the A/C. +/// @return The current operating mode setting. +bool IRDaikin64::getPowerToggle(void) const { return _.Power; } + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRDaikin64::setTemp(const uint8_t temp) { + uint8_t degrees = ::max(temp, kDaikin64MinTemp); + degrees = ::min(degrees, kDaikin64MaxTemp); + _.Temp = uint8ToBcd(degrees); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRDaikin64::getTemp(void) const { return bcdToUint8(_.Temp); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDaikin64::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRDaikin64::setMode(const uint8_t mode) { + switch (mode) { + case kDaikin64Fan: + case kDaikin64Dry: + case kDaikin64Cool: + case kDaikin64Heat: + _.Mode = mode; + break; + default: + _.Mode = kDaikin64Cool; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin64::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kDry: return kDaikin64Dry; + case stdAc::opmode_t::kFan: return kDaikin64Fan; + case stdAc::opmode_t::kHeat: return kDaikin64Heat; + default: return kDaikin64Cool; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRDaikin64::toCommonMode(const uint8_t mode) { + switch (mode) { + case kDaikin64Cool: return stdAc::opmode_t::kCool; + case kDaikin64Heat: return stdAc::opmode_t::kHeat; + case kDaikin64Dry: return stdAc::opmode_t::kDry; + case kDaikin64Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRDaikin64::getFan(void) const { return _.Fan; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRDaikin64::setFan(const uint8_t speed) { + switch (speed) { + case kDaikin64FanQuiet: + case kDaikin64FanTurbo: + case kDaikin64FanAuto: + case kDaikin64FanHigh: + case kDaikin64FanMed: + case kDaikin64FanLow: + _.Fan = speed; + break; + default: + _.Fan = kDaikin64FanAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDaikin64::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kDaikin64FanQuiet; + case stdAc::fanspeed_t::kLow: return kDaikin64FanLow; + case stdAc::fanspeed_t::kMedium: return kDaikin64FanMed; + case stdAc::fanspeed_t::kHigh: return kDaikin64FanHigh; + case stdAc::fanspeed_t::kMax: return kDaikin64FanTurbo; + default: return kDaikin64FanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRDaikin64::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kDaikin64FanTurbo: return stdAc::fanspeed_t::kMax; + case kDaikin64FanHigh: return stdAc::fanspeed_t::kHigh; + case kDaikin64FanMed: return stdAc::fanspeed_t::kMedium; + case kDaikin64FanLow: return stdAc::fanspeed_t::kLow; + case kDaikinFanQuiet: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Get the Turbo (Powerful) mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin64::getTurbo(void) const { return _.Fan == kDaikin64FanTurbo; } + +/// Set the Turbo (Powerful) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin64::setTurbo(const bool on) { + if (on) { + setFan(kDaikin64FanTurbo); + } else if (_.Fan == kDaikin64FanTurbo) { + setFan(kDaikin64FanAuto); + } +} + +/// Get the Quiet mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin64::getQuiet(void) const { return _.Fan == kDaikin64FanQuiet; } + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin64::setQuiet(const bool on) { + if (on) { + setFan(kDaikin64FanQuiet); + } else if (_.Fan == kDaikin64FanQuiet) { + setFan(kDaikin64FanAuto); + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin64::setSwingVertical(const bool on) { _.SwingV = on; } + +/// Get the Vertical Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin64::getSwingVertical(void) const { return _.SwingV; } + +/// Set the Sleep mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin64::setSleep(const bool on) { _.Sleep = on; } + +/// Get the Sleep mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin64::getSleep(void) const { return _.Sleep; } + +/// Set the clock on the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRDaikin64::setClock(const uint16_t mins_since_midnight) { + uint16_t mins = mins_since_midnight; + if (mins_since_midnight >= 24 * 60) mins = 0; // Bounds check. + _.ClockMins = uint8ToBcd(mins % 60); + _.ClockHours = uint8ToBcd(mins / 60); +} + +/// Get the clock time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin64::getClock(void) const { + return bcdToUint8(_.ClockHours) * 60 + bcdToUint8(_.ClockMins); +} + +/// Set the enable status of the On Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin64::setOnTimeEnabled(const bool on) { _.OnTimer = on; } + +/// Get the enable status of the On Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin64::getOnTimeEnabled(void) const { return _.OnTimer; } + +/// Get the On Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin64::getOnTime(void) const { return GETTIME(On); } + +/// Set the On Timer time for the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRDaikin64::setOnTime(const uint16_t mins_since_midnight) { + SETTIME(On, mins_since_midnight); +} + +/// Set the enable status of the Off Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDaikin64::setOffTimeEnabled(const bool on) { _.OffTimer = on; } + +/// Get the enable status of the Off Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDaikin64::getOffTimeEnabled(void) const { return _.OffTimer; } + +/// Get the Off Timer time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRDaikin64::getOffTime(void) const { return GETTIME(Off); } + +/// Set the Off Timer time for the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRDaikin64::setOffTime(const uint16_t mins_since_midnight) { + SETTIME(Off, mins_since_midnight); +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDaikin64::toString(void) const { + String result = ""; + result.reserve(120); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerToggleStr, false); + result += addModeToString(_.Mode, 0xFF, kDaikin64Cool, + kDaikin64Heat, kDaikin64Dry, kDaikin64Fan); + result += addTempToString(getTemp()); + if (!getTurbo()) { + result += addFanToString(_.Fan, kDaikin64FanHigh, kDaikin64FanLow, + kDaikin64FanAuto, kDaikin64FanQuiet, + kDaikin64FanMed); + } else { + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + result += kTurboStr; + result += ')'; + } + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getQuiet(), kQuietStr); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(minsToString(getClock()), kClockStr); + result += addLabeledString(_.OnTimer + ? minsToString(getOnTime()) : kOffStr, + kOnTimerStr); + result += addLabeledString(_.OffTimer + ? minsToString(getOffTime()) : kOffStr, + kOffTimerStr); + return result; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to a previous state. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDaikin64::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + if (prev != NULL) result = *prev; + result.protocol = decode_type_t::DAIKIN64; + result.model = -1; // No models used. + result.power ^= _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.turbo = getTurbo(); + result.quiet = getQuiet(); + result.sleep = _.Sleep ? 0 : -1; + result.clock = getClock(); + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.clean = false; + result.filter = false; + result.beep = false; + result.econo = false; + result.light = false; + return result; +} + +#if SEND_DAIKIN200 +/// Send a Daikin200 (200-bit) A/C formatted message. +/// Status: BETA / Untested on a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1802 +void IRsend::sendDaikin200(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikin200Section1Length) + return; // Not enough bytes to send a partial message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Section #1 + sendGeneric(kDaikin200HdrMark, kDaikin200HdrSpace, kDaikin200BitMark, + kDaikin200OneSpace, kDaikin200BitMark, kDaikin200ZeroSpace, + kDaikin200BitMark, kDaikin200Gap, data, + kDaikin200Section1Length, + kDaikin200Freq, false, 0, kDutyDefault); + // Section #2 + sendGeneric(kDaikin200HdrMark, kDaikin200HdrSpace, kDaikin200BitMark, + kDaikin200OneSpace, kDaikin200BitMark, kDaikin200ZeroSpace, + kDaikin200BitMark, kDaikin200Gap, + data + kDaikin200Section1Length, + nbytes - kDaikin200Section1Length, + kDaikin200Freq, false, 0, kDutyDefault); + } +} +#endif // SEND_DAIKIN200 + +#if DECODE_DAIKIN200 +/// Decode the supplied Daikin 200-bit message. (DAIKIN200) +/// Status: STABLE / Known to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1802 +bool IRrecv::decodeDaikin200(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1 + offset) + return false; + + // Compliance + if (strict && nbits != kDaikin200Bits) return false; + + const uint8_t ksectionSize[kDaikin200Sections] = {kDaikin200Section1Length, + kDaikin200Section2Length}; + // Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikin200Sections; section++) { + uint16_t used; + // Section Header + Section Data + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + kDaikin200HdrMark, kDaikin200HdrSpace, + kDaikin200BitMark, kDaikin200OneSpace, + kDaikin200BitMark, kDaikin200ZeroSpace, + kDaikin200BitMark, kDaikin200Gap, + section >= kDaikin200Sections - 1, + kDaikinTolerance, 0, false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + if (pos * 8 != kDaikin200Bits) return false; + // Validate the checksum. + if (!IRDaikin176::validChecksum(results->state, pos)) return false; + } + + // Success + results->decode_type = decode_type_t::DAIKIN200; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN200 + +#if SEND_DAIKIN312 +/// Send a Daikin312 (312-bit / 39 byte) A/C formatted message. +/// Status: BETA / Untested on a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1829 +void IRsend::sendDaikin312(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kDaikin312Section1Length) + return; // Not enough bytes to send a partial message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Send the header, 0b00000 + sendGeneric(0, 0, // No header for the header + kDaikin312BitMark, kDaikin312OneSpace, + kDaikin312BitMark, kDaikin312ZeroSpace, + kDaikin312BitMark, kDaikin312HdrGap, + (uint64_t)0b00000, kDaikinHeaderLength, + kDaikin2Freq, false, 0, kDutyDefault); + // Section #1 + sendGeneric(kDaikin312HdrMark, kDaikin312HdrSpace, kDaikin312BitMark, + kDaikin312OneSpace, kDaikin312BitMark, kDaikin312ZeroSpace, + kDaikin312BitMark, kDaikin312SectionGap, data, + kDaikin312Section1Length, + kDaikin2Freq, false, 0, kDutyDefault); + // Section #2 + sendGeneric(kDaikin312HdrMark, kDaikin312HdrSpace, kDaikin312BitMark, + kDaikin312OneSpace, kDaikin312BitMark, kDaikin312ZeroSpace, + kDaikin312BitMark, kDaikin312SectionGap, + data + kDaikin312Section1Length, + nbytes - kDaikin312Section1Length, + kDaikin2Freq, false, 0, kDutyDefault); + } +} +#endif // SEND_DAIKIN312 + +#if DECODE_DAIKIN312 +/// Decode the supplied Daikin 312-bit / 39-byte message. (DAIKIN312) +/// Status: STABLE / Confirmed working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1829 +bool IRrecv::decodeDaikin312(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Is there enough data to match successfully? + if (results->rawlen < 2 * (nbits + kDaikinHeaderLength + kHeader + kFooter) + + kFooter - 1 + offset) + return false; + + // Compliance + if (strict && nbits != kDaikin312Bits) return false; + + const uint8_t ksectionSize[kDaikin312Sections] = {kDaikin312Section1Length, + kDaikin312Section2Length}; + // Header/Leader Section + uint64_t leaderdata = 0; + uint16_t used = matchGeneric(results->rawbuf + offset, &leaderdata, + results->rawlen - offset, kDaikinHeaderLength, + 0, 0, // No Header Mark or Space for the "header" + kDaikin312BitMark, kDaikin312OneSpace, + kDaikin312BitMark, kDaikin312ZeroSpace, + kDaikin312BitMark, kDaikin312HdrGap, + false, kDaikinTolerance, 0, false); + if (!used) return false; // Failed to match. + if (leaderdata) return false; // The header bits should all be zero. + + offset += used; + + // Data Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kDaikin312Sections; section++) { + // Section Header + Section Data + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, ksectionSize[section] * 8, + kDaikin312HdrMark, kDaikin312HdrSpace, + kDaikin312BitMark, kDaikin312OneSpace, + kDaikin312BitMark, kDaikin312ZeroSpace, + kDaikin312BitMark, kDaikin312SectionGap, + section >= kDaikin312Sections - 1, + kDaikinTolerance, 0, false); + if (used == 0) return false; + offset += used; + pos += ksectionSize[section]; + } + // Compliance + if (strict) { + if (pos * 8 != kDaikin312Bits) return false; + } + + // Success + results->decode_type = decode_type_t::DAIKIN312; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_DAIKIN312 diff --git a/src/libraries/IRremoteESP8266/src/ir_Daikin.h b/src/libraries/IRremoteESP8266/src/ir_Daikin.h new file mode 100644 index 000000000..fae9400af --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Daikin.h @@ -0,0 +1,1263 @@ +// Copyright 2016 sillyfrog +// Copyright 2017 sillyfrog, crankyoldgit +// Copyright 2018-2022 crankyoldgit +// Copyright 2019 pasna (IRDaikin160 class / Daikin176 class) + +/// @file +/// @brief Support for Daikin A/C protocols. +/// @see Daikin http://harizanov.com/2012/02/control-daikin-air-conditioner-over-the-internet/ +/// @see Daikin https://github.com/mharizanov/Daikin-AC-remote-control-over-the-Internet/tree/master/IRremote +/// @see Daikin http://rdlab.cdmt.vn/project-2013/daikin-ir-protocol +/// @see Daikin https://github.com/blafois/Daikin-IR-Reverse +/// @see Daikin128 https://github.com/crankyoldgit/IRremoteESP8266/issues/827 +/// @see Daikin152 https://github.com/crankyoldgit/IRremoteESP8266/issues/873 +/// @see Daikin152 https://github.com/ToniA/arduino-heatpumpir/blob/master/DaikinHeatpumpARC480A14IR.cpp +/// @see Daikin152 https://github.com/ToniA/arduino-heatpumpir/blob/master/DaikinHeatpumpARC480A14IR.h +/// @see Daikin160 https://github.com/crankyoldgit/IRremoteESP8266/issues/731 +/// @see Daikin2 https://docs.google.com/spreadsheets/d/1f8EGfIbBUo2B-CzUFdrgKQprWakoYNKM80IKZN4KXQE/edit#gid=236366525&range=B25:D32 +/// @see Daikin2 https://github.com/crankyoldgit/IRremoteESP8266/issues/582 +/// @see Daikin2 https://github.com/crankyoldgit/IRremoteESP8266/issues/1535 +/// @see Daikin2 https://www.daikin.co.nz/sites/default/files/daikin-split-system-US7-FTXZ25-50NV1B.pdf +/// @see Daikin216 https://github.com/crankyoldgit/IRremoteESP8266/issues/689 +/// @see Daikin216 https://github.com/danny-source/Arduino_DY_IRDaikin +/// @see Daikin64 https://github.com/crankyoldgit/IRremoteESP8266/issues/1064 +/// @see Daikin200 https://github.com/crankyoldgit/IRremoteESP8266/issues/1802 +/// @see Daikin312 https://github.com/crankyoldgit/IRremoteESP8266/issues/1829 + +// Supports: +// Brand: Daikin, Model: ARC433** remote (DAIKIN) +// Brand: Daikin, Model: ARC477A1 remote (DAIKIN2) +// Brand: Daikin, Model: FTXZ25NV1B A/C (DAIKIN2) +// Brand: Daikin, Model: FTXZ35NV1B A/C (DAIKIN2) +// Brand: Daikin, Model: FTXZ50NV1B A/C (DAIKIN2) +// Brand: Daikin, Model: ARC433B69 remote (DAIKIN216) +// Brand: Daikin, Model: ARC423A5 remote (DAIKIN160) +// Brand: Daikin, Model: FTE12HV2S A/C +// Brand: Daikin, Model: BRC4C153 remote (DAIKIN176) +// Brand: Daikin, Model: FFQ35B8V1B A/C (DAIKIN176) +// Brand: Daikin, Model: BRC4C151 remote (DAIKIN176) +// Brand: Daikin, Model: 17 Series FTXB09AXVJU A/C (DAIKIN128) +// Brand: Daikin, Model: 17 Series FTXB12AXVJU A/C (DAIKIN128) +// Brand: Daikin, Model: 17 Series FTXB24AXVJU A/C (DAIKIN128) +// Brand: Daikin, Model: BRC52B63 remote (DAIKIN128) +// Brand: Daikin, Model: ARC480A5 remote (DAIKIN152) +// Brand: Daikin, Model: FFN-C/FCN-F Series A/C (DAIKIN64) +// Brand: Daikin, Model: DGS01 remote (DAIKIN64) +// Brand: Daikin, Model: M Series A/C (DAIKIN) +// Brand: Daikin, Model: FTXM-M A/C (DAIKIN) +// Brand: Daikin, Model: ARC466A12 remote (DAIKIN) +// Brand: Daikin, Model: ARC466A33 remote (DAIKIN) +// Brand: Daikin, Model: FTWX35AXV1 A/C (DAIKIN64) +// Brand: Daikin, Model: ARC484A4 remote (DAIKIN216) +// Brand: Daikin, Model: FTQ60TV16U2 A/C (DAIKIN216) +// Brand: Daikin, Model: BRC4M150W16 remote (DAIKIN200) +// Brand: Daikin, Model: FTXM20R5V1B A/C (DAIKIN312) +// Brand: Daikin, Model: ARC466A67 remote (DAIKIN312) + +#ifndef IR_DAIKIN_H_ +#define IR_DAIKIN_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Daikin A/C message. +union DaikinESPProtocol{ + uint8_t raw[kDaikinStateLength]; ///< The state of the IR remote. + struct { + // Byte 0~5 + uint64_t :48; + // Byte 6 + uint64_t :4; + uint64_t Comfort :1; + uint64_t :3; + // Byte 7 + uint64_t Sum1 :8; // checksum of the first part + + // Byte 8~12 + uint64_t :40; + // Byte 13~14 + uint64_t CurrentTime :11; // Current time, mins past midnight + uint64_t CurrentDay :3; // Day of the week (SUN=1, MON=2, ..., SAT=7) + uint64_t :2; + // Byte 15 + uint64_t Sum2 :8; // checksum of the second part + + // Byte 16~20 + uint64_t :40; + // Byte 21 + uint64_t Power :1; + uint64_t OnTimer :1; + uint64_t OffTimer :1; + uint64_t :1; // always 1 + uint64_t Mode :3; + uint64_t :1; + // Byte 22 + uint64_t :1; + uint64_t Temp :7; // Temp should be between 10 - 32 + // Byte 23 + uint64_t :8; + + // Byte 24 + uint64_t SwingV :4; // 0000 = off, 1111 = on + uint64_t Fan :4; + // Byte 25 + uint64_t SwingH :4; // 0000 = off, 1111 = on + uint64_t :4; + // Byte 26~28 + uint64_t OnTime :12; // timer mins past midnight + uint64_t OffTime :12; // timer mins past midnight + // Byte 29 + uint64_t Powerful :1; + uint64_t :4; + uint64_t Quiet :1; + uint64_t :2; + // Byte 30~31 + uint64_t :0; + + // Byte 32 + uint8_t :1; + uint8_t Sensor :1; + uint8_t Econo :1; + uint8_t :4; + uint8_t WeeklyTimer :1; + // Byte 33 + uint8_t :1; + uint8_t Mold :1; + uint8_t :6; + // Byte 34 + uint8_t Sum3 :8; // checksum of the third part + }; +}; + +// Constants +const uint8_t kDaikinAuto = 0b000; // temp 25 +const uint8_t kDaikinDry = 0b010; // temp 0xc0 = 96 degrees c +const uint8_t kDaikinCool = 0b011; +const uint8_t kDaikinHeat = 0b100; // temp 23 +const uint8_t kDaikinFan = 0b110; // temp not shown, but 25 +const uint8_t kDaikinMinTemp = 10; // Celsius +const uint8_t kDaikinMaxTemp = 32; // Celsius +const uint8_t kDaikinFanMin = 1; +const uint8_t kDaikinFanMed = 3; +const uint8_t kDaikinFanMax = 5; +const uint8_t kDaikinFanAuto = 0b1010; // 10 / 0xA +const uint8_t kDaikinFanQuiet = 0b1011; // 11 / 0xB +const uint8_t kDaikinSwingOn = 0b1111; +const uint8_t kDaikinSwingOff = 0b0000; +const uint16_t kDaikinHeaderLength = 5; +const uint8_t kDaikinSections = 3; +const uint8_t kDaikinSection1Length = 8; +const uint8_t kDaikinSection2Length = 8; +const uint8_t kDaikinSection3Length = + kDaikinStateLength - kDaikinSection1Length - kDaikinSection2Length; +const uint8_t kDaikinByteChecksum1 = 7; +const uint8_t kDaikinByteChecksum2 = 15; +// const uint8_t kDaikinBitEye = 0b10000000; +const uint16_t kDaikinUnusedTime = 0x600; +const uint8_t kDaikinBeepQuiet = 1; +const uint8_t kDaikinBeepLoud = 2; +const uint8_t kDaikinBeepOff = 3; +const uint8_t kDaikinLightBright = 1; +const uint8_t kDaikinLightDim = 2; +const uint8_t kDaikinLightOff = 3; +const uint8_t kDaikinCurBit = kDaikinStateLength; +const uint8_t kDaikinCurIndex = kDaikinStateLength + 1; +const uint8_t kDaikinTolerance = 35; +const uint16_t kDaikinMarkExcess = kMarkExcess; +const uint16_t kDaikinHdrMark = 3650; // kDaikinBitMark * 8 +const uint16_t kDaikinHdrSpace = 1623; // kDaikinBitMark * 4 +const uint16_t kDaikinBitMark = 428; +const uint16_t kDaikinZeroSpace = 428; +const uint16_t kDaikinOneSpace = 1280; +const uint16_t kDaikinGap = 29000; +// Note bits in each octet swapped so can be sent as a single value +const uint64_t kDaikinFirstHeader64 = + 0b1101011100000000000000001100010100000000001001111101101000010001; + +/// Native representation of a Daikin2 A/C message. +union Daikin2Protocol{ + struct{ + uint8_t pad[3]; + uint8_t raw[kDaikin2StateLength]; ///< The state of the IR remote. + }; + struct { + // Byte -3~4 + uint64_t :64; + + // Byte 5~6 + uint64_t CurrentTime :12; + uint64_t :3; + uint64_t Power2 :1; + // Byte 7 + uint64_t :4; + uint64_t Light :2; + uint64_t Beep :2; + // Byte 8 + uint64_t FreshAir :1; + uint64_t :2; + uint64_t Mold :1; + uint64_t :1; + uint64_t Clean :1; + uint64_t :1; + uint64_t FreshAirHigh :1; + // Byte 9~12 + uint64_t :32; + + // Byte 13 + uint64_t :7; + uint64_t EyeAuto :1; + // Byte 14~16 + uint64_t :24; + // Byte 17 + uint64_t SwingH :8; + // Byte 18 + uint64_t SwingV :4; + uint64_t :4; + // Byte 19 + uint64_t Sum1 :8; + // Byte 20 + uint64_t :8; + + // Byte 21~24 + uint64_t :32; + // Byte 25 + uint64_t Power :1; + uint64_t OnTimer :1; + uint64_t OffTimer :1; + uint64_t :1; + uint64_t Mode :3; + uint64_t :1; + // Byte 26 + uint64_t :1; + uint64_t Temp :6; + uint64_t HumidOn :1; + // Byte 27 + uint64_t Humidity :8; + // Byte 28 + uint64_t :4; + uint64_t Fan :4; + + // Byte 29 + uint64_t :8; + // Byte 30~32 + /// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1264 + uint64_t OnTime :12; + uint64_t OffTime :12; + // Byte 33 + uint64_t Powerful :1; + uint64_t :4; + uint64_t Quiet :1; + uint64_t :2; + // Byte 34~35 + uint64_t :16; + // Byte 36 + uint64_t :1; + uint64_t Eye :1; + uint64_t Econo :1; + uint64_t :1; + uint64_t Purify :1; + uint64_t SleepTimer :1; + uint64_t :2; + + // Byte 37 + uint8_t :8; + // Byte 38 + uint8_t Sum2 :8; + }; +}; + +const uint16_t kDaikin2Freq = 36700; // Modulation Frequency in Hz. +const uint16_t kDaikin2LeaderMark = 10024; +const uint16_t kDaikin2LeaderSpace = 25180; +const uint16_t kDaikin2Gap = kDaikin2LeaderMark + kDaikin2LeaderSpace; +const uint16_t kDaikin2HdrMark = 3500; +const uint16_t kDaikin2HdrSpace = 1728; +const uint16_t kDaikin2BitMark = 460; +const uint16_t kDaikin2OneSpace = 1270; +const uint16_t kDaikin2ZeroSpace = 420; +const uint16_t kDaikin2Sections = 2; +const uint16_t kDaikin2Section1Length = 20; +const uint16_t kDaikin2Section2Length = 19; +const uint8_t kDaikin2Tolerance = 5; // Extra percentage tolerance +const uint8_t kDaikin2SwingVHighest = 0x1; +const uint8_t kDaikin2SwingVHigh = 0x2; +const uint8_t kDaikin2SwingVUpperMiddle = 0x3; +const uint8_t kDaikin2SwingVLowerMiddle = 0x4; +const uint8_t kDaikin2SwingVLow = 0x5; +const uint8_t kDaikin2SwingVLowest = 0x6; +const uint8_t kDaikin2SwingVBreeze = 0xC; +const uint8_t kDaikin2SwingVCirculate = 0xD; +const uint8_t kDaikin2SwingVOff = 0xE; +const uint8_t kDaikin2SwingVAuto = 0xF; // A.k.a "Swing" +const uint8_t kDaikin2SwingVSwing = kDaikin2SwingVAuto; + + +const uint8_t kDaikin2SwingHWide = 0xA3; +const uint8_t kDaikin2SwingHLeftMax = 0xA8; +const uint8_t kDaikin2SwingHLeft = 0xA9; +const uint8_t kDaikin2SwingHMiddle = 0xAA; +const uint8_t kDaikin2SwingHRight = 0xAB; +const uint8_t kDaikin2SwingHRightMax = 0xAC; +const uint8_t kDaikin2SwingHAuto = 0xBE; // A.k.a "Swing" +const uint8_t kDaikin2SwingHOff = 0xBF; +const uint8_t kDaikin2SwingHSwing = kDaikin2SwingHAuto; + +// Ref: +// https://github.com/crankyoldgit/IRremoteESP8266/issues/1535#issuecomment-882092486 +// https://docs.google.com/spreadsheets/d/1kxHgFqiUB9ETXYEkszAIN5gE-t2ykvnPCnOV-sPUE0A/edit?usp=sharing +const uint8_t kDaikin2HumidityOff = 0x00; +const uint8_t kDaikin2HumidityHeatLow = 0x28; // Humidify (Heat) only (40%?) +const uint8_t kDaikin2HumidityHeatMedium = 0x2D; // Humidify (Heat) only (45%?) +const uint8_t kDaikin2HumidityHeatHigh = 0x32; // Humidify (Heat) only (50%?) +const uint8_t kDaikin2HumidityDryLow = 0x32; // Dry only (50%?) +const uint8_t kDaikin2HumidityDryMedium = 0x37; // Dry only (55%?) +const uint8_t kDaikin2HumidityDryHigh = 0x3C; // Dry only (60%?) +const uint8_t kDaikin2HumidityAuto = 0xFF; + + +const uint8_t kDaikin2MinCoolTemp = 18; // Min temp (in C) when in Cool mode. + +/// Native representation of a Daikin216 A/C message. +union Daikin216Protocol{ + uint8_t raw[kDaikin216StateLength]; ///< The state of the IR remote. + struct { + // Byte 0~6 + uint8_t pad0[7]; + // Byte 7 + uint8_t Sum1 :8; + // Byte 8~12 + uint8_t pad1[5]; + // Byte 13 + uint8_t Power :1; + uint8_t :3; + uint8_t Mode :3; + uint8_t :1; + // Byte 14 + uint8_t :1; + uint8_t Temp :6; + uint8_t :1; + // Byte 15 + uint8_t :8; + // Byte 16 + uint8_t SwingV :4; + uint8_t Fan :4; + // Byte 17 + uint8_t SwingH :4; + uint8_t :4; + // Byte 18~20 + uint8_t pad2[3]; + // Byte 21 + uint8_t Powerful :1; + uint8_t :0; + // Byte 22~25 + uint8_t pad3[4]; + // Byte 26 + uint8_t Sum2 :8; + }; +}; + +const uint16_t kDaikin216Freq = 38000; // Modulation Frequency in Hz. +const uint16_t kDaikin216HdrMark = 3440; +const uint16_t kDaikin216HdrSpace = 1750; +const uint16_t kDaikin216BitMark = 420; +const uint16_t kDaikin216OneSpace = 1300; +const uint16_t kDaikin216ZeroSpace = 450; +const uint16_t kDaikin216Gap = 29650; +const uint16_t kDaikin216Sections = 2; +const uint16_t kDaikin216Section1Length = 8; +const uint16_t kDaikin216Section2Length = kDaikin216StateLength - + kDaikin216Section1Length; + +const uint8_t kDaikin216SwingOn = 0b1111; +const uint8_t kDaikin216SwingOff = 0b0000; + +/// Native representation of a Daikin160 A/C message. +union Daikin160Protocol{ + uint8_t raw[kDaikin160StateLength]; ///< The state of the IR remote. + struct { + // Byte 0~5 + uint8_t pad0[6]; + // Byte 6 + uint8_t Sum1 :8; + // Byte 7~11 + uint8_t pad1[5]; + // Byte 12 + uint8_t Power :1; + uint8_t :3; + uint8_t Mode :3; + uint8_t :1; + // Byte 13 + uint8_t :4; + uint8_t SwingV :4; + // Byte 14~15 + uint8_t pad2[2]; + // Byte 16 + uint8_t :1; + uint8_t Temp :6; + uint8_t :1; + // Byte 17 + uint8_t Fan :4; + uint8_t :4; + // Byte 18 + uint8_t :8; + // Byte 19 + uint8_t Sum2 :8; + }; +}; + +const uint16_t kDaikin160Freq = 38000; // Modulation Frequency in Hz. +const uint16_t kDaikin160HdrMark = 5000; +const uint16_t kDaikin160HdrSpace = 2145; +const uint16_t kDaikin160BitMark = 342; +const uint16_t kDaikin160OneSpace = 1786; +const uint16_t kDaikin160ZeroSpace = 700; +const uint16_t kDaikin160Gap = 29650; +const uint16_t kDaikin160Sections = 2; +const uint16_t kDaikin160Section1Length = 7; +const uint16_t kDaikin160Section2Length = kDaikin160StateLength - + kDaikin160Section1Length; +const uint8_t kDaikin160SwingVLowest = 0x1; +const uint8_t kDaikin160SwingVLow = 0x2; +const uint8_t kDaikin160SwingVMiddle = 0x3; +const uint8_t kDaikin160SwingVHigh = 0x4; +const uint8_t kDaikin160SwingVHighest = 0x5; +const uint8_t kDaikin160SwingVAuto = 0xF; + +/// Native representation of a Daikin176 A/C message. +union Daikin176Protocol{ + uint8_t raw[kDaikin176StateLength]; ///< The state of the IR remote. + struct { + // Byte 0~2 + uint8_t :8; + uint8_t :8; + uint8_t :8; + // Byte 3 + uint8_t Id1 :1; + uint8_t :7; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t :8; + // Byte 6 + uint8_t Sum1 :8; + // Byte 7-9 + uint8_t :8; + uint8_t :8; + uint8_t :8; + // Byte 10 + uint8_t Id2 :1; + uint8_t :7; + // Byte 11 + uint8_t :8; + // Byte 12 + uint8_t :4; + uint8_t AltMode :3; + uint8_t :1; + // Byte 13 + uint8_t ModeButton :8; + // Byte 14 + uint8_t Power :1; + uint8_t :3; + uint8_t Mode :3; + uint8_t :1; + // Byte 15~16 + uint8_t pad2[2]; + // Byte 17 + uint8_t :1; + uint8_t Temp :6; + uint8_t :1; + // Byte 18 + uint8_t SwingH :4; + uint8_t Fan :4; + // Byte 19~20 + uint8_t pad3[2]; + // Byte 21 + uint8_t Sum2 :8; + }; +}; + +const uint16_t kDaikin176Freq = 38000; // Modulation Frequency in Hz. +const uint16_t kDaikin176HdrMark = 5070; +const uint16_t kDaikin176HdrSpace = 2140; +const uint16_t kDaikin176BitMark = 370; +const uint16_t kDaikin176OneSpace = 1780; +const uint16_t kDaikin176ZeroSpace = 710; +const uint16_t kDaikin176Gap = 29410; +const uint16_t kDaikin176Sections = 2; +const uint16_t kDaikin176Section1Length = 7; +const uint16_t kDaikin176Section2Length = kDaikin176StateLength - + kDaikin176Section1Length; +const uint8_t kDaikin176Fan = 0b000; // 0 +const uint8_t kDaikin176Heat = 0b001; // 1 +const uint8_t kDaikin176Cool = 0b010; // 2 +const uint8_t kDaikin176Auto = 0b011; // 3 +const uint8_t kDaikin176Dry = 0b111; // 7 +const uint8_t kDaikin176ModeButton = 0b00000100; +const uint8_t kDaikin176DryFanTemp = 17; // Dry/Fan mode is always 17 Celsius. +const uint8_t kDaikin176FanMax = 3; +const uint8_t kDaikin176SwingHAuto = 0x5; +const uint8_t kDaikin176SwingHOff = 0x6; + +/// Native representation of a Daikin128 A/C message. +union Daikin128Protocol{ + uint8_t raw[kDaikin128StateLength]; ///< The state of the IR remote. + struct { + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t Mode :4; + uint8_t Fan :4; + // Byte 2 + uint8_t ClockMins :8; + // Byte 3 + uint8_t ClockHours :8; + // Byte 4 + uint8_t OnHours :6; + uint8_t OnHalfHour :1; + uint8_t OnTimer :1; + // Byte 5 + uint8_t OffHours :6; + uint8_t OffHalfHour :1; + uint8_t OffTimer :1; + // Byte 6 + uint8_t Temp :8; + // Byte 7 + uint8_t SwingV :1; + uint8_t Sleep :1; + uint8_t :1; // always 1 + uint8_t Power :1; + uint8_t Sum1 :4; + // Byte 8 + uint8_t :8; + // Byte 9 + uint8_t Ceiling :1; + uint8_t :1; + uint8_t Econo :1; + uint8_t Wall :1; + uint8_t :4; + // Byte 10~14 + uint8_t pad[5]; + // Byte 15 + uint8_t Sum2 :8; + }; +}; + +const uint16_t kDaikin128Freq = 38000; // Modulation Frequency in Hz. +const uint16_t kDaikin128LeaderMark = 9800; +const uint16_t kDaikin128LeaderSpace = 9800; +const uint16_t kDaikin128HdrMark = 4600; +const uint16_t kDaikin128HdrSpace = 2500; +const uint16_t kDaikin128BitMark = 350; +const uint16_t kDaikin128OneSpace = 954; +const uint16_t kDaikin128ZeroSpace = 382; +const uint16_t kDaikin128Gap = 20300; +const uint16_t kDaikin128FooterMark = kDaikin128HdrMark; +const uint16_t kDaikin128Sections = 2; +const uint16_t kDaikin128SectionLength = 8; +const uint8_t kDaikin128Dry = 0b00000001; +const uint8_t kDaikin128Cool = 0b00000010; +const uint8_t kDaikin128Fan = 0b00000100; +const uint8_t kDaikin128Heat = 0b00001000; +const uint8_t kDaikin128Auto = 0b00001010; +const uint8_t kDaikin128FanAuto = 0b0001; +const uint8_t kDaikin128FanHigh = 0b0010; +const uint8_t kDaikin128FanMed = 0b0100; +const uint8_t kDaikin128FanLow = 0b1000; +const uint8_t kDaikin128FanPowerful = 0b0011; +const uint8_t kDaikin128FanQuiet = 0b1001; +const uint8_t kDaikin128MinTemp = 16; // C +const uint8_t kDaikin128MaxTemp = 30; // C +const uint8_t kDaikin128BitWall = 0b00001000; +const uint8_t kDaikin128BitCeiling = 0b00000001; + +/// Native representation of a Daikin152 A/C message. +union Daikin152Protocol{ + uint8_t raw[kDaikin152StateLength]; ///< The state of the IR remote. + struct { + // Byte 0~4 + uint8_t pad0[5]; + // Byte 5 + uint8_t Power :1; + uint8_t :3; + uint8_t Mode :3; + uint8_t :1; + // Byte 6 + uint8_t :1; + uint8_t Temp :7; + // Byte 7 + uint8_t :8; + // Byte 8 + uint8_t SwingV :4; + uint8_t Fan :4; + // Byte 9~12 + uint8_t pad1[4]; + // Byte 13 + uint8_t Powerful :1; + uint8_t :4; + uint8_t Quiet :1; + uint8_t :2; + // Byte 14~15 + uint8_t pad2[2]; + // Byte 16 + uint8_t :1; + uint8_t Comfort :1; + uint8_t Econo :1; + uint8_t Sensor :1; + uint8_t :4; + // Byte 17 + uint8_t :8; + // Byte 18 + uint8_t Sum :8; + }; +}; + +const uint16_t kDaikin152Freq = 38000; // Modulation Frequency in Hz. +const uint8_t kDaikin152LeaderBits = 5; +const uint16_t kDaikin152HdrMark = 3492; +const uint16_t kDaikin152HdrSpace = 1718; +const uint16_t kDaikin152BitMark = 433; +const uint16_t kDaikin152OneSpace = 1529; +const uint16_t kDaikin152ZeroSpace = kDaikin152BitMark; +const uint16_t kDaikin152Gap = 25182; + +const uint8_t kDaikin152DryTemp = kDaikin2MinCoolTemp; // Celsius +const uint8_t kDaikin152FanTemp = 0x60; // 96 Celsius + +/// Native representation of a Daikin64 A/C message. +union Daikin64Protocol{ + uint64_t raw; ///< The state of the IR remote. + struct { + uint8_t :8; + uint8_t Mode :4; + uint8_t Fan :4; + uint8_t ClockMins :8; + uint8_t ClockHours :8; + uint8_t OnHours :6; + uint8_t OnHalfHour :1; + uint8_t OnTimer :1; + uint8_t OffHours :6; + uint8_t OffHalfHour :1; + uint8_t OffTimer :1; + uint8_t Temp :8; + uint8_t SwingV :1; + uint8_t Sleep :1; + uint8_t :1; + uint8_t Power :1; + uint8_t Sum :4; + }; +}; + +const uint16_t kDaikin64HdrMark = kDaikin128HdrMark; +const uint16_t kDaikin64BitMark = kDaikin128BitMark; +const uint16_t kDaikin64HdrSpace = kDaikin128HdrSpace; +const uint16_t kDaikin64OneSpace = kDaikin128OneSpace; +const uint16_t kDaikin64ZeroSpace = kDaikin128ZeroSpace; +const uint16_t kDaikin64LdrMark = kDaikin128LeaderMark; +const uint16_t kDaikin64Gap = kDaikin128Gap; +const uint16_t kDaikin64LdrSpace = kDaikin128LeaderSpace; +const uint16_t kDaikin64Freq = kDaikin128Freq; // Hz. +const uint8_t kDaikin64Overhead = 9; +const int8_t kDaikin64ToleranceDelta = 5; // +5% + +const uint64_t kDaikin64KnownGoodState = 0x7C16161607204216; +const uint8_t kDaikin64Dry = 0b0001; +const uint8_t kDaikin64Cool = 0b0010; +const uint8_t kDaikin64Fan = 0b0100; +const uint8_t kDaikin64Heat = 0b1000; +const uint8_t kDaikin64FanAuto = 0b0001; +const uint8_t kDaikin64FanLow = 0b1000; +const uint8_t kDaikin64FanMed = 0b0100; +const uint8_t kDaikin64FanHigh = 0b0010; +const uint8_t kDaikin64FanQuiet = 0b1001; +const uint8_t kDaikin64FanTurbo = 0b0011; +const uint8_t kDaikin64MinTemp = 16; // Celsius +const uint8_t kDaikin64MaxTemp = 30; // Celsius +const uint8_t kDaikin64ChecksumOffset = 60; +const uint8_t kDaikin64ChecksumSize = 4; // Mask 0b1111 << 59 + +const uint16_t kDaikin200Freq = 38000; // Modulation Frequency in Hz. +const uint16_t kDaikin200HdrMark = 4920; +const uint16_t kDaikin200HdrSpace = 2230; +const uint16_t kDaikin200BitMark = 290; +const uint16_t kDaikin200OneSpace = 1850; +const uint16_t kDaikin200ZeroSpace = 780; +const uint16_t kDaikin200Gap = 29400; +const uint16_t kDaikin200Sections = 2; +const uint16_t kDaikin200Section1Length = 7; +const uint16_t kDaikin200Section2Length = kDaikin200StateLength - + kDaikin200Section1Length; + +const uint16_t kDaikin312HdrMark = 3518; +const uint16_t kDaikin312HdrSpace = 1688; +const uint16_t kDaikin312BitMark = 453; +const uint16_t kDaikin312ZeroSpace = 414; +const uint16_t kDaikin312OneSpace = 1275; +const uint16_t kDaikin312HdrGap = 25100; +const uint16_t kDaikin312SectionGap = 35512; +const uint16_t kDaikin312Sections = 2; +const uint16_t kDaikin312Section1Length = 20; +const uint16_t kDaikin312Section2Length = kDaikin312StateLength - + kDaikin312Section1Length; + +// Legacy defines. +#define DAIKIN_COOL kDaikinCool +#define DAIKIN_HEAT kDaikinHeat +#define DAIKIN_FAN kDaikinFan +#define DAIKIN_AUTO kDaikinAuto +#define DAIKIN_DRY kDaikinDry +#define DAIKIN_MIN_TEMP kDaikinMinTemp +#define DAIKIN_MAX_TEMP kDaikinMaxTemp +#define DAIKIN_FAN_MIN kDaikinFanMin +#define DAIKIN_FAN_MAX kDaikinFanMax +#define DAIKIN_FAN_AUTO kDaikinFanAuto +#define DAIKIN_FAN_QUIET kDaikinFanQuiet + +/// Class for handling detailed Daikin 280-bit A/C messages. +class IRDaikinESP { + public: + explicit IRDaikinESP(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + +#if SEND_DAIKIN + void send(const uint16_t repeat = kDaikinDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingVertical(const bool on); + bool getSwingVertical(void) const; + void setSwingHorizontal(const bool on); + bool getSwingHorizontal(void) const; + bool getQuiet(void) const; + void setQuiet(const bool on); + bool getPowerful(void) const; + void setPowerful(const bool on); + void setSensor(const bool on); + bool getSensor(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setMold(const bool on); + bool getMold(void) const; + void setComfort(const bool on); + bool getComfort(void) const; + void enableOnTimer(const uint16_t starttime); + void disableOnTimer(void); + uint16_t getOnTime(void) const; + bool getOnTimerEnabled(void) const; + void enableOffTimer(const uint16_t endtime); + void disableOffTimer(void); + uint16_t getOffTime(void) const; + bool getOffTimerEnabled(void) const; + void setCurrentTime(const uint16_t mins_since_midnight); + uint16_t getCurrentTime(void) const; + void setCurrentDay(const uint8_t day_of_week); + uint8_t getCurrentDay(void) const; + void setWeeklyTimerEnable(const bool on); + bool getWeeklyTimerEnable(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kDaikinStateLength); + static bool validChecksum(uint8_t state[], + const uint16_t length = kDaikinStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + // # of bytes per command + DaikinESPProtocol _; + void stateReset(void); + void checksum(void); +}; + +/// Class for handling detailed Daikin 312-bit A/C messages. +/// @note Code by crankyoldgit, Reverse engineering analysis by sheppy99 +class IRDaikin2 { + public: + explicit IRDaikin2(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + +#if SEND_DAIKIN2 + void send(const uint16_t repeat = kDaikin2DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif + void begin(void); + void on(void); + void off(void); + void setPower(const bool state); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + void setSwingVertical(const uint8_t position); + uint8_t getSwingVertical(void) const; + void setSwingHorizontal(const uint8_t position); + uint8_t getSwingHorizontal(void) const; + bool getQuiet(void) const; + void setQuiet(const bool on); + bool getPowerful(void) const; + void setPowerful(const bool on); + void setEcono(const bool on); + bool getEcono(void) const; + void setEye(const bool on); + bool getEye(void) const; + void setEyeAuto(const bool on); + bool getEyeAuto(void) const; + void setPurify(const bool on); + bool getPurify(void) const; + void setMold(const bool on); + bool getMold(void) const; + void enableOnTimer(const uint16_t starttime); + void disableOnTimer(void); + uint16_t getOnTime(void) const; + bool getOnTimerEnabled(void) const; + void enableSleepTimer(const uint16_t sleeptime); + void disableSleepTimer(void); + uint16_t getSleepTime(void) const; + bool getSleepTimerEnabled(void) const; + void enableOffTimer(const uint16_t endtime); + void disableOffTimer(void); + uint16_t getOffTime(void) const; + bool getOffTimerEnabled(void) const; + void setCurrentTime(const uint16_t time); + uint16_t getCurrentTime(void) const; + void setBeep(const uint8_t beep); + uint8_t getBeep(void) const; + void setLight(const uint8_t light); + uint8_t getLight(void) const; + void setClean(const bool on); + bool getClean(void) const; + void setFreshAir(const bool on); + bool getFreshAir(void) const; + void setFreshAirHigh(const bool on); + bool getFreshAirHigh(void) const; + uint8_t getHumidity(void) const; + void setHumidity(const uint8_t percent); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(uint8_t state[], + const uint16_t length = kDaikin2StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::swingv_t toCommonSwingV(const uint8_t setting); + static stdAc::swingh_t toCommonSwingH(const uint8_t setting); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + // # of bytes per command + Daikin2Protocol _; + void stateReset(void); + void checksum(void); + void clearOnTimerFlag(void); + void clearSleepTimerFlag(void); +}; + +/// Class for handling detailed Daikin 216-bit A/C messages. +class IRDaikin216 { + public: + explicit IRDaikin216(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + +#if SEND_DAIKIN216 + void send(const uint16_t repeat = kDaikin216DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif + void begin(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(uint8_t state[], + const uint16_t length = kDaikin216StateLength); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + static uint8_t convertFan(const stdAc::fanspeed_t speed); + void setSwingVertical(const bool on); + bool getSwingVertical(void) const; + void setSwingHorizontal(const bool on); + bool getSwingHorizontal(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + void setPowerful(const bool on); + bool getPowerful(void) const; + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + // # of bytes per command + Daikin216Protocol _; + void stateReset(void); + void checksum(void); +}; + +/// Class for handling detailed Daikin 160-bit A/C messages. +class IRDaikin160 { + public: + explicit IRDaikin160(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + +#if SEND_DAIKIN160 + void send(const uint16_t repeat = kDaikin160DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif + void begin(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(uint8_t state[], + const uint16_t length = kDaikin160StateLength); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + static uint8_t convertFan(const stdAc::fanspeed_t speed); + void setSwingVertical(const uint8_t position); + uint8_t getSwingVertical(void) const; + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::swingv_t toCommonSwingV(const uint8_t setting); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + // # of bytes per command + Daikin160Protocol _; + void stateReset(void); + void checksum(void); +}; + +/// Class for handling detailed Daikin 176-bit A/C messages. +class IRDaikin176 { + public: + explicit IRDaikin176(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + +#if SEND_DAIKIN176 + void send(const uint16_t repeat = kDaikin176DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif + void begin(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(uint8_t state[], + const uint16_t length = kDaikin176StateLength); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + static uint8_t convertFan(const stdAc::fanspeed_t speed); + void setSwingHorizontal(const uint8_t position); + uint8_t getSwingHorizontal(void) const; + uint8_t getId(void) const; + void setId(const uint8_t num); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::swingh_t toCommonSwingH(const uint8_t setting); + stdAc::state_t toCommon(void) const; + String toString(void) const; + +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + // # of bytes per command + Daikin176Protocol _; + uint8_t _saved_temp; ///< The previously user requested temp value. + void stateReset(void); + void checksum(void); +}; + +/// Class for handling detailed Daikin 128-bit A/C messages. +/// @note Code by crankyoldgit. Analysis by Daniel Vena +class IRDaikin128 { + public: + explicit IRDaikin128(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_DAIKIN128 + void send(const uint16_t repeat = kDaikin128DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_DAIKIN128 + void begin(void); + void setPowerToggle(const bool toggle); + bool getPowerToggle(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + void setSwingVertical(const bool on); + bool getSwingVertical(void) const; + bool getSleep(void) const; + void setSleep(const bool on); + bool getQuiet(void) const; + void setQuiet(const bool on); + bool getPowerful(void) const; + void setPowerful(const bool on); + void setEcono(const bool on); + bool getEcono(void) const; + void setOnTimer(const uint16_t mins_since_midnight); + uint16_t getOnTimer(void) const; + bool getOnTimerEnabled(void) const; + void setOnTimerEnabled(const bool on); + void setOffTimer(const uint16_t mins_since_midnight); + uint16_t getOffTimer(void) const; + bool getOffTimerEnabled(void) const; + void setOffTimerEnabled(const bool on); + void setClock(const uint16_t mins_since_midnight); + uint16_t getClock(void) const; + void setLightToggle(const uint8_t unit_type); + uint8_t getLightToggle(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(uint8_t state[]); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + // # of bytes per command + Daikin128Protocol _; + void stateReset(void); + static uint8_t calcFirstChecksum(const uint8_t state[]); + static uint8_t calcSecondChecksum(const uint8_t state[]); + void checksum(void); +}; + +/// Class for handling detailed Daikin 152-bit A/C messages. +class IRDaikin152 { + public: + explicit IRDaikin152(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + +#if SEND_DAIKIN152 + void send(const uint16_t repeat = kDaikin152DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif + void begin(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(uint8_t state[], + const uint16_t length = kDaikin152StateLength); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + bool getQuiet(void) const; + void setQuiet(const bool on); + bool getPowerful(void) const; + void setPowerful(const bool on); + void setSensor(const bool on); + bool getSensor(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setComfort(const bool on); + bool getComfort(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + // # of bytes per command + Daikin152Protocol _; + void stateReset(void); + void checksum(void); +}; + +/// Class for handling detailed Daikin 64-bit A/C messages. +class IRDaikin64 { + public: + explicit IRDaikin64(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + +#if SEND_DAIKIN64 + void send(const uint16_t repeat = kDaikin64DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_DAIKIN64 + void begin(void); + uint64_t getRaw(void); + void setRaw(const uint64_t new_state); + static uint8_t calcChecksum(const uint64_t state); + static bool validChecksum(const uint64_t state); + void setPowerToggle(const bool on); + bool getPowerToggle(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingVertical(const bool on); + bool getSwingVertical(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + bool getQuiet(void) const; + void setQuiet(const bool on); + bool getTurbo(void) const; + void setTurbo(const bool on); + void setClock(const uint16_t mins_since_midnight); + uint16_t getClock(void) const; + void setOnTimeEnabled(const bool on); + bool getOnTimeEnabled(void) const; + void setOnTime(const uint16_t mins_since_midnight); + uint16_t getOnTime(void) const; + void setOffTimeEnabled(const bool on); + bool getOffTimeEnabled(void) const; + void setOffTime(const uint16_t mins_since_midnight); + uint16_t getOffTime(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + Daikin64Protocol _; + void stateReset(void); + void checksum(void); +}; +#endif // IR_DAIKIN_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Delonghi.cpp b/src/libraries/IRremoteESP8266/src/ir_Delonghi.cpp new file mode 100644 index 000000000..ce9c3f5a4 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Delonghi.cpp @@ -0,0 +1,470 @@ +// Copyright 2020 David Conran +/// @file +/// @brief Delonghi based protocol. + +#include "ir_Delonghi.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addLabeledString; +using irutils::addTempToString; +using irutils::minsToString; + +const uint16_t kDelonghiAcHdrMark = 8984; +const uint16_t kDelonghiAcBitMark = 572; +const uint16_t kDelonghiAcHdrSpace = 4200; +const uint16_t kDelonghiAcOneSpace = 1558; +const uint16_t kDelonghiAcZeroSpace = 510; +const uint32_t kDelonghiAcGap = kDefaultMessageGap; // A totally made-up guess. +const uint16_t kDelonghiAcFreq = 38000; // Hz. (Guess: most common frequency.) +const uint16_t kDelonghiAcOverhead = 3; + + +#if SEND_DELONGHI_AC +/// Send a Delonghi A/C formatted message. +/// Status: STABLE / Reported as working on a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1096 +void IRsend::sendDelonghiAc(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kDelonghiAcHdrMark, kDelonghiAcHdrSpace, + kDelonghiAcBitMark, kDelonghiAcOneSpace, + kDelonghiAcBitMark, kDelonghiAcZeroSpace, + kDelonghiAcBitMark, kDelonghiAcGap, + data, nbits, kDelonghiAcFreq, false, // LSB First. + repeat, kDutyDefault); +} +#endif // SEND_DELONGHI_AC + +#if DECODE_DELONGHI_AC +/// Decode the supplied Delonghi A/C message. +/// Status: STABLE / Expected to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1096 +bool IRrecv::decodeDelonghiAc(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kDelonghiAcOverhead - offset) + return false; // Too short a message to match. + if (strict && nbits != kDelonghiAcBits) + return false; + + uint64_t data = 0; + + // Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kDelonghiAcHdrMark, kDelonghiAcHdrSpace, + kDelonghiAcBitMark, kDelonghiAcOneSpace, + kDelonghiAcBitMark, kDelonghiAcZeroSpace, + kDelonghiAcBitMark, kDelonghiAcGap, true, + _tolerance, kMarkExcess, false)) return false; + + // Compliance + if (strict && !IRDelonghiAc::validChecksum(data)) return false; + + // Success + results->decode_type = decode_type_t::DELONGHI_AC; + results->bits = nbits; + results->value = data; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_DELONGHI_AC + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRDelonghiAc::IRDelonghiAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDelonghiAc::begin(void) { _irsend.begin(); } + +#if SEND_DELONGHI_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDelonghiAc::send(const uint16_t repeat) { + _irsend.sendDelonghiAc(getRaw(), kDelonghiAcBits, repeat); +} +#endif // SEND_DELONGHI_AC + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @return A valid checksum value. +uint8_t IRDelonghiAc::calcChecksum(const uint64_t state) { + uint8_t sum = 0; + // Add up all the 8 bit chunks except for Most-significant 8 bits. + for (uint8_t offset = 0; offset < kDelonghiAcChecksumOffset; offset += 8) { + sum += GETBITS64(state, offset, 8); + } + return sum; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The state to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRDelonghiAc::validChecksum(const uint64_t state) { + DelonghiProtocol dp; + dp.raw = state; + return (dp.Sum == IRDelonghiAc::calcChecksum(state)); +} + +/// Calculate and set the checksum values for the internal state. +void IRDelonghiAc::checksum(void) { + _.Sum = calcChecksum(_.raw); +} + +/// Reset the internal state to a fixed known good state. +void IRDelonghiAc::stateReset(void) { + _.raw = 0x5400000000000153; + _saved_temp = 23; // DegC (Random reasonable default value) + _saved_temp_units = 0; // Celsius +} + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +uint64_t IRDelonghiAc::getRaw(void) { + checksum(); // Ensure correct bit array before returning + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRDelonghiAc::setRaw(const uint64_t state) { _.raw = state; } + +/// Change the power setting to On. +void IRDelonghiAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRDelonghiAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghiAc::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghiAc::getPower(void) const { + return _.Power; +} + +/// Change the temperature scale units. +/// @param[in] fahrenheit true, use Fahrenheit. false, use Celsius. +void IRDelonghiAc::setTempUnit(const bool fahrenheit) { + _.Fahrenheit = fahrenheit; +} + +/// Get the temperature scale unit of measure currently in use. +/// @return true, is Fahrenheit. false, is Celsius. +bool IRDelonghiAc::getTempUnit(void) const { + return _.Fahrenheit; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees. +/// @param[in] fahrenheit Use Fahrenheit as the temperature scale. +/// @param[in] force Do we ignore any sanity checks? +void IRDelonghiAc::setTemp(const uint8_t degrees, const bool fahrenheit, + const bool force) { + uint8_t temp; + if (force) { + temp = degrees; // We've been asked to force set this value. + } else { + uint8_t temp_min = kDelonghiAcTempMinC; + uint8_t temp_max = kDelonghiAcTempMaxC; + setTempUnit(fahrenheit); + if (fahrenheit) { + temp_min = kDelonghiAcTempMinF; + temp_max = kDelonghiAcTempMaxF; + } + temp = ::max(temp_min, degrees); + temp = ::min(temp_max, temp); + _saved_temp = temp; + _saved_temp_units = fahrenheit; + temp = temp - temp_min + 1; + } + _.Temp = temp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in currently configured units/scale. +uint8_t IRDelonghiAc::getTemp(void) const { + return _.Temp + (_.Fahrenheit ? kDelonghiAcTempMinF + : kDelonghiAcTempMinC) - 1; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired native setting. +void IRDelonghiAc::setFan(const uint8_t speed) { + // Mode fan speed rules. + switch (_.Mode) { + case kDelonghiAcFan: + // Fan mode can't have auto fan speed. + if (speed == kDelonghiAcFanAuto) { + if (_.Fan == kDelonghiAcFanAuto) _.Fan = kDelonghiAcFanHigh; + return; + } + break; + case kDelonghiAcAuto: + case kDelonghiAcDry: + // Auto & Dry modes only allows auto fan speed. + if (speed != kDelonghiAcFanAuto) { + _.Fan = kDelonghiAcFanAuto; + return; + } + break; + } + // Bounds check enforcement + if (speed > kDelonghiAcFanLow) + _.Fan = kDelonghiAcFanAuto; + else + _.Fan = speed; +} + +/// Get the current native fan speed setting. +/// @return The current fan speed. +uint8_t IRDelonghiAc::getFan(void) const { + return _.Fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDelonghiAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kDelonghiAcFanLow; + case stdAc::fanspeed_t::kMedium: + return kDelonghiAcFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kDelonghiAcFanHigh; + default: + return kDelonghiAcFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRDelonghiAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kDelonghiAcFanHigh: return stdAc::fanspeed_t::kMax; + case kDelonghiAcFanMedium: return stdAc::fanspeed_t::kMedium; + case kDelonghiAcFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDelonghiAc::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired native operating mode. +void IRDelonghiAc::setMode(const uint8_t mode) { + _.Mode = mode; + switch (mode) { + case kDelonghiAcAuto: + case kDelonghiAcDry: + // Set special temp for these modes. + setTemp(kDelonghiAcTempAutoDryMode, _.Fahrenheit, true); + break; + case kDelonghiAcFan: + // Set special temp for this mode. + setTemp(kDelonghiAcTempFanMode, _.Fahrenheit, true); + break; + case kDelonghiAcCool: + // Restore previous temp settings for cool mode. + setTemp(_saved_temp, _saved_temp_units); + break; + default: + _.Mode = kDelonghiAcAuto; + setTemp(kDelonghiAcTempAutoDryMode, _.Fahrenheit, true); + break; + } + setFan(_.Fan); // Re-force any fan speed constraints. +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDelonghiAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kDelonghiAcCool; + case stdAc::opmode_t::kDry: + return kDelonghiAcDry; + case stdAc::opmode_t::kFan: + return kDelonghiAcFan; + default: + return kDelonghiAcAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRDelonghiAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kDelonghiAcCool: return stdAc::opmode_t::kCool; + case kDelonghiAcDry: return stdAc::opmode_t::kDry; + case kDelonghiAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the Boost (Turbo) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghiAc::setBoost(const bool on) { + _.Boost = on; +} + +/// Get the Boost (Turbo) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghiAc::getBoost(void) const { + return _.Boost; +} + +/// Set the Sleep mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghiAc::setSleep(const bool on) { + _.Sleep = on; +} + +/// Get the Sleep mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghiAc::getSleep(void) const { + return _.Sleep; +} + +/// Set the enable status of the On Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghiAc::setOnTimerEnabled(const bool on) { + _.OnTimer = on; +} + +/// Get the enable status of the On Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghiAc::getOnTimerEnabled(void) const { + return _.OnTimer; +} + +/// Set the On timer to activate in nr of minutes. +/// @param[in] nr_of_mins Total nr of mins to wait before waking the device. +/// @note Max 23 hrs and 59 minutes. i.e. 1439 mins. +void IRDelonghiAc::setOnTimer(const uint16_t nr_of_mins) { + uint16_t value = ::min(kDelonghiAcTimerMax, nr_of_mins); + _.OnMins = value % 60; + _.OnHours = value / 60; + // Enable or not? + setOnTimerEnabled(value > 0); +} + +/// Get the On timer time. +/// @return Total nr of mins before the device turns on. +uint16_t IRDelonghiAc::getOnTimer(void) const { + return _.OnHours * 60 + _.OnMins; +} + +/// Set the enable status of the Off Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghiAc::setOffTimerEnabled(const bool on) { + _.OffTimer = on; +} + +/// Get the enable status of the Off Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghiAc::getOffTimerEnabled(void) const { + return _.OffTimer; +} + +/// Set the Off timer to activate in nr of minutes. +/// @param[in] nr_of_mins Total nr of mins to wait before turning off the device +/// @note Max 23 hrs and 59 minutes. i.e. 1439 mins. +void IRDelonghiAc::setOffTimer(const uint16_t nr_of_mins) { + uint16_t value = ::min(kDelonghiAcTimerMax, nr_of_mins); + _.OffMins = value % 60; + _.OffHours = value / 60; + // Enable or not? + setOffTimerEnabled(value > 0); +} + +/// Get the Off timer time. +/// @return Total nr of mins before the device turns off. +uint16_t IRDelonghiAc::getOffTimer(void) const { + return _.OffHours * 60 + _.OffMins; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDelonghiAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DELONGHI_AC; + result.power = _.Power; + // result.mode = toCommonMode(getMode()); + result.celsius = !_.Fahrenheit; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.turbo = _.Boost; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.model = -1; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDelonghiAc::toString(void) const { + String result = ""; + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kDelonghiAcAuto, kDelonghiAcCool, + kDelonghiAcAuto, kDelonghiAcDry, kDelonghiAcFan); + result += addFanToString(_.Fan, kDelonghiAcFanHigh, kDelonghiAcFanLow, + kDelonghiAcFanAuto, kDelonghiAcFanAuto, + kDelonghiAcFanMedium); + result += addTempToString(getTemp(), !_.Fahrenheit); + result += addBoolToString(_.Boost, kTurboStr); + result += addBoolToString(_.Sleep, kSleepStr); + uint16_t mins = getOnTimer(); + result += addLabeledString((mins && _.OnTimer) ? minsToString(mins) + : kOffStr, + kOnTimerStr); + mins = getOffTimer(); + result += addLabeledString((mins && _.OffTimer) ? minsToString(mins) + : kOffStr, + kOffTimerStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Delonghi.h b/src/libraries/IRremoteESP8266/src/ir_Delonghi.h new file mode 100644 index 000000000..bc6fe19a0 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Delonghi.h @@ -0,0 +1,136 @@ +// Copyright 2020 David Conran + +/// @file +/// @brief Delonghi A/C +/// @note Kudos to TheMaxxz For the breakdown and mapping of the bit values. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1096 + +// Supports: +// Brand: Delonghi, Model: PAC A95 + +#ifndef IR_DELONGHI_H_ +#define IR_DELONGHI_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Delonghi A/C message. +union DelonghiProtocol{ + uint64_t raw; ///< The state of the IR remote. + struct { + uint8_t :8; // Header + uint8_t Temp :5; + uint8_t Fan :2; + uint8_t Fahrenheit:1; + uint8_t Power :1; + uint8_t Mode :3; + uint8_t Boost :1; + uint8_t Sleep :1; + uint8_t :2; + uint8_t OnTimer :1; + uint8_t OnHours :5; + uint8_t :2; + uint8_t OnMins :6; + uint8_t :2; + uint8_t OffTimer :1; + uint8_t OffHours :5; + uint8_t :2; + uint8_t OffMins :6; + uint8_t :2; + uint8_t Sum :8; + }; +}; + +// Constants +const uint8_t kDelonghiAcTempMinC = 18; // Deg C +const uint8_t kDelonghiAcTempMaxC = 32; // Deg C +const uint8_t kDelonghiAcTempMinF = 64; // Deg F +const uint8_t kDelonghiAcTempMaxF = 90; // Deg F +const uint8_t kDelonghiAcTempAutoDryMode = 0; +const uint8_t kDelonghiAcTempFanMode = 0b00110; +const uint8_t kDelonghiAcFanAuto = 0b00; +const uint8_t kDelonghiAcFanHigh = 0b01; +const uint8_t kDelonghiAcFanMedium = 0b10; +const uint8_t kDelonghiAcFanLow = 0b11; +const uint8_t kDelonghiAcCool = 0b000; +const uint8_t kDelonghiAcDry = 0b001; +const uint8_t kDelonghiAcFan = 0b010; +const uint8_t kDelonghiAcAuto = 0b100; +const uint16_t kDelonghiAcTimerMax = 23 * 60 + 59; +const uint8_t kDelonghiAcChecksumOffset = 56; + +// Classes + +/// Class for handling detailed Delonghi A/C messages. +class IRDelonghiAc { + public: + explicit IRDelonghiAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_DELONGHI_AC + void send(const uint16_t repeat = kDelonghiAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_DELONGHI_AC + void begin(void); + static uint8_t calcChecksum(const uint64_t state); + static bool validChecksum(const uint64_t state); + void setPower(const bool on); + bool getPower(void) const; + void on(void); + void off(void); + void setTempUnit(const bool celsius); + bool getTempUnit(void) const; + void setTemp(const uint8_t temp, const bool fahrenheit = false, + const bool force = false); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setBoost(const bool on); // Aka Turbo + bool getBoost(void) const; // Aka Turbo + void setSleep(const bool on); + bool getSleep(void) const; + void setOnTimerEnabled(const bool on); + bool getOnTimerEnabled(void) const; + void setOnTimer(const uint16_t nr_of_mins); + uint16_t getOnTimer(void) const; + void setOffTimerEnabled(const bool on); + bool getOffTimerEnabled(void) const; + void setOffTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void) const; + uint64_t getRaw(void); + void setRaw(const uint64_t state); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + DelonghiProtocol _; + uint8_t _saved_temp; ///< The previously user requested temp value. + uint8_t _saved_temp_units; ///< The previously user requested temp units. + void checksum(void); +}; +#endif // IR_DELONGHI_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Denon.cpp b/src/libraries/IRremoteESP8266/src/ir_Denon.cpp new file mode 100644 index 000000000..d9c0ce520 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Denon.cpp @@ -0,0 +1,122 @@ +// Copyright 2016 Massimiliano Pinto +// Copyright 2017 David Conran +/// @file +/// @brief Denon support +/// Original Denon support added by https://github.com/csBlueChip +/// Ported over by Massimiliano Pinto +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp +/// @see http://assets.denon.com/documentmaster/us/denon%20master%20ir%20hex.xls + +// Supports: +// Brand: Denon, Model: AVR-3801 A/V Receiver (probably) + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + + +// Constants +const uint16_t kDenonTick = 263; +const uint16_t kDenonHdrMarkTicks = 1; +const uint16_t kDenonHdrMark = kDenonHdrMarkTicks * kDenonTick; +const uint16_t kDenonHdrSpaceTicks = 3; +const uint16_t kDenonHdrSpace = kDenonHdrSpaceTicks * kDenonTick; +const uint16_t kDenonBitMarkTicks = 1; +const uint16_t kDenonBitMark = kDenonBitMarkTicks * kDenonTick; +const uint16_t kDenonOneSpaceTicks = 7; +const uint16_t kDenonOneSpace = kDenonOneSpaceTicks * kDenonTick; +const uint16_t kDenonZeroSpaceTicks = 3; +const uint16_t kDenonZeroSpace = kDenonZeroSpaceTicks * kDenonTick; +const uint16_t kDenonMinCommandLengthTicks = 510; +const uint16_t kDenonMinGapTicks = + kDenonMinCommandLengthTicks - + (kDenonHdrMarkTicks + kDenonHdrSpaceTicks + + kDenonBits * (kDenonBitMarkTicks + kDenonOneSpaceTicks) + + kDenonBitMarkTicks); +const uint32_t kDenonMinGap = kDenonMinGapTicks * kDenonTick; +const uint64_t kDenonManufacturer = 0x2A4CULL; + +#if SEND_DENON +/// Send a Denon formatted message. +/// Status: STABLE / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Some Denon devices use a Kaseikyo/Panasonic 48-bit format +/// Others use the Sharp protocol. +void IRsend::sendDenon(uint64_t data, uint16_t nbits, uint16_t repeat) { + if (nbits >= kPanasonicBits) // Is this really Panasonic? + sendPanasonic64(data, nbits, repeat); + else if (nbits == kDenonLegacyBits) + // Support legacy (broken) calls of sendDenon(). + sendSharpRaw(data & (~0x2000ULL), nbits + 1, repeat); + else + sendSharpRaw(data, nbits, repeat); +} +#endif + +#if DECODE_DENON +/// Decode the supplied Delonghi A/C message. +/// Status: STABLE / Should work fine. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Denon.cpp +bool IRrecv::decodeDenon(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance + if (strict) { + switch (nbits) { + case kDenonBits: + case kDenon48Bits: + case kDenonLegacyBits: + break; + default: + return false; + } + } + + // Denon uses the Sharp & Panasonic(Kaseikyo) protocol for some + // devices, so check for those first. + // It is not exactly like Sharp's protocols, but close enough. + // e.g. The expansion bit is not set for Denon vs. set for Sharp. + // Ditto for Panasonic, it's the same except for a different + // manufacturer code. + + if (!decodeSharp(results, offset, nbits, true, false) && + !decodePanasonic(results, offset, nbits, true, kDenonManufacturer)) { + // We couldn't decode it as expected, so try the old legacy method. + // NOTE: I don't think this following protocol actually exists. + // Looks like a partial version of the Sharp protocol. + if (strict && nbits != kDenonLegacyBits) return false; + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kDenonHdrMark, kDenonHdrSpace, + kDenonBitMark, kDenonOneSpace, + kDenonBitMark, kDenonZeroSpace, + kDenonBitMark, 0, false)) return false; + + // Success + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + } // Legacy decode. + + // Compliance + if (strict && nbits != results->bits) return false; + + // Success + results->decode_type = DENON; + return true; +} +#endif diff --git a/src/libraries/IRremoteESP8266/src/ir_Dish.cpp b/src/libraries/IRremoteESP8266/src/ir_Dish.cpp new file mode 100644 index 000000000..94f5450b8 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Dish.cpp @@ -0,0 +1,103 @@ +// Copyright Todd Treece +// Copyright 2017 David Conran +/// @file +/// @brief DISH Network protocol support +/// DISH support originally by Todd Treece +/// @see http://unionbridge.org/design/ircommand +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Dish.cpp +/// @see http://www.hifi-remote.com/wiki/index.php?title=Dish + +// Supports: +// Brand: DISH NETWORK, Model: echostar 301 + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + + +// Constants +const uint16_t kDishTick = 100; +const uint16_t kDishHdrMarkTicks = 4; +const uint16_t kDishHdrMark = kDishHdrMarkTicks * kDishTick; +const uint16_t kDishHdrSpaceTicks = 61; +const uint16_t kDishHdrSpace = kDishHdrSpaceTicks * kDishTick; +const uint16_t kDishBitMarkTicks = 4; +const uint16_t kDishBitMark = kDishBitMarkTicks * kDishTick; +const uint16_t kDishOneSpaceTicks = 17; +const uint16_t kDishOneSpace = kDishOneSpaceTicks * kDishTick; +const uint16_t kDishZeroSpaceTicks = 28; +const uint16_t kDishZeroSpace = kDishZeroSpaceTicks * kDishTick; +const uint16_t kDishRptSpaceTicks = kDishHdrSpaceTicks; +const uint16_t kDishRptSpace = kDishRptSpaceTicks * kDishTick; + +#if SEND_DISH +/// Send a DISH NETWORK formatted message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Dishplayer is a different protocol. +/// Typically a DISH device needs to get a command a total of at least 4 +/// times to accept it. e.g. repeat=3 +/// +/// Here is the LIRC file I found that seems to match the remote codes from the +/// oscilloscope: +/// DISH NETWORK (echostar 301): +/// @see http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx +/// @see http://www.hifi-remote.com/wiki/index.php?title=Dish +void IRsend::sendDISH(uint64_t data, uint16_t nbits, uint16_t repeat) { + enableIROut(57600); // Set modulation freq. to 57.6kHz. + // Header is only ever sent once. + mark(kDishHdrMark); + space(kDishHdrSpace); + + sendGeneric(0, 0, // No headers from here on in. + kDishBitMark, kDishOneSpace, kDishBitMark, kDishZeroSpace, + kDishBitMark, kDishRptSpace, data, nbits, 57600, true, repeat, + 50); +} +#endif + +#if DECODE_DISH +/// Decode the supplied DISH NETWORK message. +/// Status: ALPHA (untested and unconfirmed.) +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @note Dishplayer is a different protocol. +/// Typically a DISH device needs to get a command a total of at least 4 +/// times to accept it. +/// @see http://www.hifi-remote.com/wiki/index.php?title=Dish +/// @see http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Dish.cpp +bool IRrecv::decodeDISH(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kDishBits) return false; // Not strictly compliant. + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kDishHdrMark, kDishHdrSpace, + kDishBitMark, kDishOneSpace, + kDishBitMark, kDishZeroSpace, + kDishBitMark, + // The DISH protocol calls for a repeated message, so + // strictly speaking there should be a code following this. + // Only require it if we are set to strict matching. + strict ? kDishRptSpace : 0, false)) return false; + + // Success + results->decode_type = DISH; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +#endif diff --git a/src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp b/src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp new file mode 100644 index 000000000..0a5fadedb --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp @@ -0,0 +1,124 @@ +// Copyright 2020 Christian (nikize) +/// @file +/// @brief Doshisha protocol support +/// @see https://www.doshisha-led.com/ + +// Supports: +// Brand: Doshisha, Model: CZ-S32D LED Light +// Brand: Doshisha, Model: CZ-S38D LED Light +// Brand: Doshisha, Model: CZ-S50D LED Light +// Brand: Doshisha, Model: RCZ01 remote + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + + +const uint16_t kDoshishaHdrMark = 3412; +const uint16_t kDoshishaHdrSpace = 1722; +const uint16_t kDoshishaBitMark = 420; +const uint16_t kDoshishaOneSpace = 1310; +const uint16_t kDoshishaZeroSpace = 452; + +// basic structure of bits, and mask +const uint64_t kRcz01SignatureMask = 0xffffffff00; +const uint64_t kRcz01Signature = 0x800B304800; +const uint8_t kRcz01CommandMask = 0xFE; +const uint8_t kRcz01ChannelMask = 0x01; + +// Known commands - Here for documentation rather than actual usage +const uint8_t kRcz01CommandSwitchChannel = 0xD2; +const uint8_t kRcz01CommandTimmer60 = 0x52; +const uint8_t kRcz01CommandTimmer30 = 0x92; +const uint8_t kRcz01CommandOff = 0xA0; + +const uint8_t kRcz01CommandLevelDown = 0x2C; +const uint8_t kRcz01CommandLevelUp = 0xCC; +// below are the only ones that turns it on +const uint8_t kRcz01CommandLevel1 = 0xA4; +const uint8_t kRcz01CommandLevel2 = 0x24; +const uint8_t kRcz01CommandLevel3 = 0xC4; +const uint8_t kRcz01CommandLevel4 = 0xD0; + +const uint8_t kRcz01CommandOn = 0xC0; +const uint8_t kRcz01CommandNightLight = 0xC8; +// end Known commands + +#if SEND_DOSHISHA +/// Send a Doshisha formatted message. +/// Status: STABLE / Works on real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendDoshisha(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kDoshishaHdrMark, kDoshishaHdrSpace, + kDoshishaBitMark, kDoshishaOneSpace, + kDoshishaBitMark, kDoshishaZeroSpace, + kDoshishaBitMark, kDefaultMessageGap, + data, nbits, 38, true, repeat, kDutyDefault); +} + +/// Encode Doshisha combining constant values with command and channel. +/// Status: STABLE / Working. +/// @param[in] command The command code to be sent. +/// @param[in] channel The one bit channel 0 for CH1 and 1 for CH2 +/// @return The corresponding Doshisha code. +uint64_t IRsend::encodeDoshisha(const uint8_t command, const uint8_t channel) { + uint64_t data = kRcz01Signature | + (command & kRcz01CommandMask) | + (channel & kRcz01ChannelMask); + return data; +} +#endif // SEND_DOSHISHA + +#if DECODE_DOSHISHA +/// Decode the supplied Doshisha message. +/// Status: STABLE / Works on real device. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeDoshisha(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset) + return false; // Can't possibly be a valid message. + if (strict && nbits != kDoshishaBits) + return false; + + uint64_t data = 0; + // Match Header + Data + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kDoshishaHdrMark, kDoshishaHdrSpace, + kDoshishaBitMark, kDoshishaOneSpace, + kDoshishaBitMark, kDoshishaZeroSpace, + kDoshishaBitMark, 0, + true, kTolerance, kMarkExcess, true)) return false; + + // e.g. data = 0x800B3048C0, nbits = 40 + + // RCZ01 remote commands starts with a lead bit set + if ((data & kRcz01SignatureMask) != kRcz01Signature) { + DPRINT(" decodeDoshisha data "); + DPRINT(uint64ToString(data, 16)); + DPRINT(" masked "); + DPRINT(uint64ToString(data & kRcz01SignatureMask, 16)); + DPRINT(" not matching "); + DPRINT(uint64ToString(kRcz01Signature, 16)); + DPRINTLN(" ."); + return false; // expected lead bits not matching + } + + // Success + results->decode_type = decode_type_t::DOSHISHA; + results->bits = nbits; + results->value = data; + results->command = data & kRcz01CommandMask; + results->address = data & kRcz01ChannelMask; + return true; +} +#endif // DECODE_DOSHISHA diff --git a/src/libraries/IRremoteESP8266/src/ir_Ecoclim.cpp b/src/libraries/IRremoteESP8266/src/ir_Ecoclim.cpp new file mode 100644 index 000000000..8553150e0 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Ecoclim.cpp @@ -0,0 +1,425 @@ +// Copyright 2021 David Conran + +/// @file +/// @brief EcoClim A/C protocol. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1397 + +#include "ir_Ecoclim.h" +// #include +#include +#include "IRac.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint8_t kEcoclimSections = 3; +const uint8_t kEcoclimExtraTolerance = 5; ///< Percentage (extra) +const uint16_t kEcoclimHdrMark = 5730; ///< uSeconds +const uint16_t kEcoclimHdrSpace = 1935; ///< uSeconds +const uint16_t kEcoclimBitMark = 440; ///< uSeconds +const uint16_t kEcoclimOneSpace = 1739; ///< uSeconds +const uint16_t kEcoclimZeroSpace = 637; ///< uSeconds +const uint16_t kEcoclimFooterMark = 7820; ///< uSeconds +const uint32_t kEcoclimGap = kDefaultMessageGap; // Just a guess. + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::minsToString; + +#if SEND_ECOCLIM +/// Send a EcoClim A/C formatted message. +/// Status: STABLE / Confirmed working on real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendEcoclim(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + enableIROut(38, kDutyDefault); + for (uint16_t r = 0; r <= repeat; r++) { + for (uint8_t section = 0; section < kEcoclimSections; section++) { + // Header + Data + sendGeneric(kEcoclimHdrMark, kEcoclimHdrSpace, + kEcoclimBitMark, kEcoclimOneSpace, + kEcoclimBitMark, kEcoclimZeroSpace, + 0, 0, data, nbits, 38, true, 0, kDutyDefault); + } + mark(kEcoclimFooterMark); + space(kEcoclimGap); + } +} +#endif // SEND_ECOCLIM + +#if DECODE_ECOCLIM +/// Decode the supplied EcoClim A/C message. +/// Status: STABLE / Confirmed working on real remote. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeEcoclim(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < (2 * nbits + kHeader) * kEcoclimSections + + kFooter - 1 + offset) + return false; // Can't possibly be a valid Ecoclim message. + if (strict) { + switch (nbits) { + case kEcoclimShortBits: + case kEcoclimBits: + break; + default: + return false; // Unexpected bit size. + } + } + + for (uint8_t section = 0; section < kEcoclimSections; section++) { + uint16_t used; + uint64_t data; + // Header + Data Block + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kEcoclimHdrMark, kEcoclimHdrSpace, + kEcoclimBitMark, kEcoclimOneSpace, + kEcoclimBitMark, kEcoclimZeroSpace, + 0, 0, // No footer. + false, _tolerance + kEcoclimExtraTolerance); + if (!used) return false; + DPRINTLN("DEBUG: Data section matched okay."); + offset += used; + // Compliance + if (strict) { + if (section) { // Each section should contain the same data. + if (data != results->value) return false; + } else { + results->value = data; + } + } + } + + // Footer + if (!matchMark(results->rawbuf[offset++], kEcoclimFooterMark, + _tolerance + kEcoclimExtraTolerance)) + return false; + if (results->rawlen <= offset && !matchAtLeast(results->rawbuf[offset++], + kEcoclimGap)) + return false; + // Success + results->bits = nbits; + results->decode_type = ECOCLIM; + // No need to record the value as we stored it as we decoded it. + return true; +} +#endif // DECODE_ECOCLIM + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IREcoclimAc::IREcoclimAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IREcoclimAc::stateReset(void) { _.raw = kEcoclimDefaultState; } + +/// Set up hardware to be able to send a message. +void IREcoclimAc::begin(void) { _irsend.begin(); } + +#if SEND_ECOCLIM +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IREcoclimAc::send(const uint16_t repeat) { + _irsend.sendEcoclim(getRaw(), kEcoclimBits, repeat); +} +#endif // SEND_ECOCLIM + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +uint64_t IREcoclimAc::getRaw(void) const { return _.raw; } + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IREcoclimAc::setRaw(const uint64_t new_code) { _.raw = new_code; } + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IREcoclimAc::setTemp(const uint8_t celsius) { + // Range check. + uint8_t temp = ::min(celsius, kEcoclimTempMax); + temp = ::max(temp, kEcoclimTempMin); + _.Temp = temp - kEcoclimTempMin; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IREcoclimAc::getTemp(void) const { return _.Temp + kEcoclimTempMin; } + +/// Set the sensor temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IREcoclimAc::setSensorTemp(const uint8_t celsius) { + // Range check. + uint8_t temp = ::min(celsius, kEcoclimTempMax); + temp = ::max(temp, kEcoclimTempMin); + _.SensorTemp = temp - kEcoclimTempMin; +} + +/// Get the sensor temperature setting. +/// @return The current setting for sensor temp. in degrees celsius. +uint8_t IREcoclimAc::getSensorTemp(void) const { + return _.SensorTemp + kEcoclimTempMin; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IREcoclimAc::getPower(void) const { return _.Power; } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IREcoclimAc::setPower(const bool on) { _.Power = on; } + +/// Change the power setting to On. +void IREcoclimAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IREcoclimAc::off(void) { setPower(false); } + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IREcoclimAc::getFan(void) const { return _.Fan; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IREcoclimAc::setFan(const uint8_t speed) { + _.Fan = ::min(speed, kEcoclimFanAuto); +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IREcoclimAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kEcoclimFanMin; + case stdAc::fanspeed_t::kMedium: return kEcoclimFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kEcoclimFanMax; + default: return kCoolixFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IREcoclimAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kEcoclimFanMax: return stdAc::fanspeed_t::kMax; + case kEcoclimFanMed: return stdAc::fanspeed_t::kMedium; + case kEcoclimFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IREcoclimAc::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IREcoclimAc::setMode(const uint8_t mode) { + switch (mode) { + case kEcoclimAuto: + case kEcoclimCool: + case kEcoclimDry: + case kEcoclimRecycle: + case kEcoclimFan: + case kEcoclimHeat: + case kEcoclimSleep: + _.Mode = mode; + break; + default: // Anything else, go with Auto mode. + setMode(kEcoclimAuto); + } +} + +/// Convert a standard A/C mode into its native mode. +/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent. +/// @return The corresponding native mode. +uint8_t IREcoclimAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kEcoclimCool; + case stdAc::opmode_t::kHeat: return kEcoclimHeat; + case stdAc::opmode_t::kDry: return kEcoclimDry; + case stdAc::opmode_t::kFan: return kEcoclimFan; + default: return kEcoclimAuto; + } +} + +/// Convert a native mode to it's common stdAc::opmode_t equivalent. +/// @param[in] mode A native operation mode to be converted. +/// @return The corresponding common stdAc::opmode_t mode. +stdAc::opmode_t IREcoclimAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kEcoclimCool: return stdAc::opmode_t::kCool; + case kEcoclimHeat: return stdAc::opmode_t::kHeat; + case kEcoclimDry: return stdAc::opmode_t::kDry; + case kEcoclimFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Get the clock time of the A/C unit. +/// @return Nr. of minutes past midnight. +uint16_t IREcoclimAc::getClock(void) const { return _.Clock; } + +/// Set the clock time on the A/C unit. +/// @param[in] nr_of_mins Nr. of minutes past midnight. +void IREcoclimAc::setClock(const uint16_t nr_of_mins) { + _.Clock = ::min(nr_of_mins, (uint16_t)(24 * 60 - 1)); +} + +/// Get the Unit type/DIP switch settings of the remote. +/// @return The binary representation of the 4 DIP switches on the remote. +uint8_t IREcoclimAc::getType(void) const { return _.DipConfig; } + +/// Set the Unit type/DIP switch settings for the remote. +/// @param[in] code The binary representation of the remote's 4 DIP switches. +void IREcoclimAc::setType(const uint8_t code) { + switch (code) { + case kEcoclimDipMaster: + case kEcoclimDipSlave: + _.DipConfig = code; + break; + default: + setType(kEcoclimDipMaster); + } +} + +/// Set & enable the On Timer for the A/C. +/// @param[in] nr_of_mins The time, in minutes since midnight. +void IREcoclimAc::setOnTimer(const uint16_t nr_of_mins) { + if (nr_of_mins < 24 * 60) { + _.OnHours = nr_of_mins / 60; + _.OnTenMins = (nr_of_mins % 60) / 10; // Store in tens of mins resolution. + } +} + +/// Get the On Timer for the A/C. +/// @return The On Time, in minutes since midnight. +uint16_t IREcoclimAc::getOnTimer(void) const { + return _.OnHours * 60 + _.OnTenMins * 10; +} + +/// Check if the On Timer is enabled. +/// @return true, if the timer is enabled, otherwise false. +bool IREcoclimAc::isOnTimerEnabled(void) const { + return (getOnTimer() != kEcoclimTimerDisable); +} + +/// Disable & clear the On Timer. +void IREcoclimAc::disableOnTimer(void) { + _.OnHours = 0x1F; + _.OnTenMins = 0x7; +} + +/// Set & enable the Off Timer for the A/C. +/// @param[in] nr_of_mins The time, in minutes since midnight. +void IREcoclimAc::setOffTimer(const uint16_t nr_of_mins) { + if (nr_of_mins < 24 * 60) { + _.OffHours = nr_of_mins / 60; + _.OffTenMins = (nr_of_mins % 60) / 10; // Store in tens of mins resolution. + } +} + +/// Get the Off Timer for the A/C. +/// @return The Off Time, in minutes since midnight. +uint16_t IREcoclimAc::getOffTimer(void) const { + return _.OffHours * 60 + _.OffTenMins * 10; +} + +/// Check if the Off Timer is enabled. +/// @return true, if the timer is enabled, otherwise false. +bool IREcoclimAc::isOffTimerEnabled(void) const { + return (getOffTimer() != kEcoclimTimerDisable); +} + +/// Disable & clear the Off Timer. +void IREcoclimAc::disableOffTimer(void) { + _.OffHours = 0x1F; + _.OffTenMins = 0x7; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IREcoclimAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::ECOCLIM; + result.power = _.Power; + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.sensorTemperature = getSensorTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.sleep = (getMode() == kEcoclimSleep) ? 0 : -1; + result.clock = getClock(); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IREcoclimAc::toString(void) const { + String result = ""; + result.reserve(140); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + // Custom Mode output as this protocol has Recycle and Sleep as modes. + result += addIntToString(_.Mode, kModeStr); + result += kSpaceLBraceStr; + switch (_.Mode) { + case kEcoclimAuto: result += kAutoStr; break; + case kEcoclimCool: result += kCoolStr; break; + case kEcoclimHeat: result += kHeatStr; break; + case kEcoclimDry: result += kDryStr; break; + case kEcoclimFan: result += kFanStr; break; + case kEcoclimRecycle: result += kRecycleStr; break; + case kEcoclimSleep: result += kSleepStr; break; + default: result += kUnknownStr; + } + result += ')'; + result += addTempToString(getTemp()); + result += kCommaSpaceStr; + result += kSensorStr; + result += addTempToString(getSensorTemp(), true, false); + result += addFanToString(_.Fan, kEcoclimFanMax, + kEcoclimFanMin, + kEcoclimFanAuto, + kEcoclimFanAuto, // Unused (No Quiet) + kEcoclimFanMed, + kEcoclimFanMax); + result += addLabeledString(minsToString(_.Clock), kClockStr); + result += addLabeledString( + isOnTimerEnabled() ? minsToString(getOnTimer()) : kOffStr, kOnTimerStr); + result += addLabeledString( + isOffTimerEnabled() ? minsToString(getOffTimer()) : kOffStr, + kOffTimerStr); + result += addIntToString(_.DipConfig, kTypeStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Ecoclim.h b/src/libraries/IRremoteESP8266/src/ir_Ecoclim.h new file mode 100644 index 000000000..8fa46360b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Ecoclim.h @@ -0,0 +1,142 @@ +// Copyright 2021 David Conran + +/// @file +/// @brief EcoClim A/C protocol. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1397 + +// Supports: +// Brand: EcoClim, Model: HYSFR-P348 remote +// Brand: EcoClim, Model: ZC200DPO A/C + +#ifndef IR_ECOCLIM_H_ +#define IR_ECOCLIM_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +// Constants +// Modes +const uint8_t kEcoclimAuto = 0b000; ///< 0. a.k.a Slave +const uint8_t kEcoclimCool = 0b001; ///< 1 +const uint8_t kEcoclimDry = 0b010; ///< 2 +const uint8_t kEcoclimRecycle = 0b011; ///< 3 +const uint8_t kEcoclimFan = 0b100; ///< 4 +const uint8_t kEcoclimHeat = 0b101; ///< 5 +const uint8_t kEcoclimSleep = 0b111; ///< 7 +// Fan Control +const uint8_t kEcoclimFanMin = 0b00; ///< 0 +const uint8_t kEcoclimFanMed = 0b01; ///< 1 +const uint8_t kEcoclimFanMax = 0b10; ///< 2 +const uint8_t kEcoclimFanAuto = 0b11; ///< 3 +// DIP settings +const uint8_t kEcoclimDipMaster = 0b0000; +const uint8_t kEcoclimDipSlave = 0b0111; +// Temperature +const uint8_t kEcoclimTempMin = 5; // Celsius +const uint8_t kEcoclimTempMax = kEcoclimTempMin + 31; // Celsius +// Timer +const uint16_t kEcoclimTimerDisable = 0x1F * 60 + 7 * 10; // 4774 + +// Power: Off, Mode: Auto, Temp: 11C, Sensor: 22C, Fan: Auto, Clock: 00:00 +const uint64_t kEcoclimDefaultState = 0x11063000FFFF02; + +/// Native representation of a Ecoclim A/C message. +union EcoclimProtocol { + uint64_t raw; ///< The state in IR code form. + struct { // Only 56 bits (7 bytes are used. + // Byte + uint64_t :3; ///< Fixed 0b010 + uint64_t :1; ///< Unknown + uint64_t DipConfig :4; ///< 0b0000 = Master, 0b0111 = Slave + // Byte + uint64_t OffTenMins :3; ///< Off Timer minutes (in tens of mins) + uint64_t OffHours :5; ///< Off Timer nr of Hours + // Byte + uint64_t OnTenMins :3; ///< On Timer minutes (in tens of mins) + uint64_t OnHours :5; ///< On Timer nr of Hours + // Byte+Byte + uint64_t Clock :11; + uint64_t :1; ///< Unknown + uint64_t Fan :2; ///< Fan Speed + uint64_t Power :1; ///< Power control + uint64_t Clear :1; // Not sure what this is + // Byte + uint64_t Temp :5; ///< Desired Temperature (Celsius) + uint64_t Mode :3; ///< Operating Mode + // Byte + uint64_t SensorTemp :5; ///< Sensed Temperature (Celsius) + uint64_t :3; ///< Fixed + }; +}; + +// Classes + +/// Class for handling detailed EcoClim A/C 56 bit messages. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1397 +class IREcoclimAc { + public: + explicit IREcoclimAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_ECOCLIM + void send(const uint16_t repeat = kNoRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_ECOCLIM + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t celsius); + uint8_t getTemp(void) const; + void setSensorTemp(const uint8_t celsius); + uint8_t getSensorTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setClock(const uint16_t nr_of_mins); + uint16_t getClock(void) const; + uint64_t getRaw(void) const; + void setRaw(const uint64_t new_code); + void setType(const uint8_t code); + uint8_t getType(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + void setOnTimer(const uint16_t nr_of_mins); + uint16_t getOnTimer(void) const; + bool isOnTimerEnabled(void) const; + void disableOnTimer(void); + void setOffTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void) const; + bool isOffTimerEnabled(void) const; + void disableOffTimer(void); + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + EcoclimProtocol _; ///< The state of the IR remote in IR code form. +}; + +#endif // IR_ECOCLIM_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Electra.cpp b/src/libraries/IRremoteESP8266/src/ir_Electra.cpp new file mode 100644 index 000000000..236486630 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Electra.cpp @@ -0,0 +1,458 @@ +// Copyright 2018-2021 David Conran +/// @file +/// @brief Support for Electra A/C protocols. +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/AUXHeatpumpIR.cpp +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/527 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/642 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/778 + +#include "ir_Electra.h" +//// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kElectraAcHdrMark = 9166; +const uint16_t kElectraAcBitMark = 646; +const uint16_t kElectraAcHdrSpace = 4470; +const uint16_t kElectraAcOneSpace = 1647; +const uint16_t kElectraAcZeroSpace = 547; +const uint32_t kElectraAcMessageGap = kDefaultMessageGap; // Just a guess. + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::addToggleToString; + +#if SEND_ELECTRA_AC +/// Send a Electra A/C formatted message. +/// Status: Alpha / Needs testing against a real device. +/// @param[in] data The message to be sent. +/// @note Guessing MSBF order. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendElectraAC(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + for (uint16_t r = 0; r <= repeat; r++) + sendGeneric(kElectraAcHdrMark, kElectraAcHdrSpace, kElectraAcBitMark, + kElectraAcOneSpace, kElectraAcBitMark, kElectraAcZeroSpace, + kElectraAcBitMark, kElectraAcMessageGap, data, nbytes, + 38000, // Complete guess of the modulation frequency. + false, // Send data in LSB order per byte + 0, 50); +} +#endif + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRElectraAc::IRElectraAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { + stateReset(); +} + +/// Reset the internal state to a fixed known good state. +void IRElectraAc::stateReset(void) { + for (uint8_t i = 1; i < kElectraAcStateLength - 2; i++) _.raw[i] = 0; + _.raw[0] = 0xC3; + _.LightToggle = kElectraAcLightToggleOff; + // [12] is the checksum. +} + +/// Set up hardware to be able to send a message. +void IRElectraAc::begin(void) { _irsend.begin(); } + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @param[in] length The length of the state array. +/// @return The calculated checksum stored in a uint_8. +uint8_t IRElectraAc::calcChecksum(const uint8_t state[], + const uint16_t length) { + if (length == 0) return state[0]; + return sumBytes(state, length - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The state to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRElectraAc::validChecksum(const uint8_t state[], const uint16_t length) { + if (length < 2) + return true; // No checksum to compare with. Assume okay. + return (state[length - 1] == calcChecksum(state, length)); +} + +/// Calculate and set the checksum values for the internal state. +/// @param[in] length The length of the state array. +void IRElectraAc::checksum(uint16_t length) { + if (length < 2) return; + _.Sum = calcChecksum(_.raw, length); +} + +#if SEND_ELECTRA_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRElectraAc::send(const uint16_t repeat) { + _irsend.sendElectraAC(getRaw(), kElectraAcStateLength, repeat); +} +#endif // SEND_ELECTRA_AC + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRElectraAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length of the code array. +void IRElectraAc::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kElectraAcStateLength)); +} + +/// Change the power setting to On. +void IRElectraAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRElectraAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getPower(void) const { + return _.Power; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRElectraAc::setMode(const uint8_t mode) { + switch (mode) { + case kElectraAcAuto: + case kElectraAcDry: + case kElectraAcCool: + case kElectraAcHeat: + case kElectraAcFan: + _.Mode = mode; + break; + default: + // If we get an unexpected mode, default to AUTO. + _.Mode = kElectraAcAuto; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRElectraAc::getMode(void) const { + return _.Mode; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRElectraAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kElectraAcCool; + case stdAc::opmode_t::kHeat: return kElectraAcHeat; + case stdAc::opmode_t::kDry: return kElectraAcDry; + case stdAc::opmode_t::kFan: return kElectraAcFan; + default: return kElectraAcAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRElectraAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kElectraAcCool: return stdAc::opmode_t::kCool; + case kElectraAcHeat: return stdAc::opmode_t::kHeat; + case kElectraAcDry: return stdAc::opmode_t::kDry; + case kElectraAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRElectraAc::setTemp(const uint8_t temp) { + uint8_t newtemp = ::max(kElectraAcMinTemp, temp); + newtemp = ::min(kElectraAcMaxTemp, newtemp) - kElectraAcTempDelta; + _.Temp = newtemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRElectraAc::getTemp(void) const { + return _.Temp + kElectraAcTempDelta; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @note 0 is auto, 1-3 is the speed +void IRElectraAc::setFan(const uint8_t speed) { + switch (speed) { + case kElectraAcFanAuto: + case kElectraAcFanHigh: + case kElectraAcFanMed: + case kElectraAcFanLow: + _.Fan = speed; + break; + default: + // If we get an unexpected speed, default to Auto. + _.Fan = kElectraAcFanAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRElectraAc::getFan(void) const { + return _.Fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRElectraAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kElectraAcFanLow; + case stdAc::fanspeed_t::kMedium: return kElectraAcFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kElectraAcFanHigh; + default: return kElectraAcFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRElectraAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kElectraAcFanHigh: return stdAc::fanspeed_t::kMax; + case kElectraAcFanMed: return stdAc::fanspeed_t::kMedium; + case kElectraAcFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setSwingV(const bool on) { + _.SwingV = (on ? kElectraAcSwingOn : kElectraAcSwingOff); +} + +/// Get the Vertical Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getSwingV(void) const { + return !_.SwingV; +} + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setSwingH(const bool on) { + _.SwingH = (on ? kElectraAcSwingOn : kElectraAcSwingOff); +} + +/// Get the Horizontal Swing mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getSwingH(void) const { + return !_.SwingH; +} + +/// Set the Light (LED) Toggle mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setLightToggle(const bool on) { + _.LightToggle = (on ? kElectraAcLightToggleOn : kElectraAcLightToggleOff); +} + +/// Get the Light (LED) Toggle mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getLightToggle(void) const { + return (_.LightToggle & kElectraAcLightToggleMask) == + kElectraAcLightToggleMask; +} + +/// Set the Clean mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setClean(const bool on) { + _.Clean = on; +} + +/// Get the Clean mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getClean(void) const { + return _.Clean; +} + +/// Set the Turbo mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setTurbo(const bool on) { + _.Turbo = on; +} + +/// Get the Turbo mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getTurbo(void) const { + return _.Turbo; +} + +/// Get the IFeel mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getIFeel(void) const { return _.IFeel; } + +/// Set the IFeel mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setIFeel(const bool on) { + _.IFeel = on; + if (_.IFeel) + // Make sure there is a reasonable value in _.SensorTemp + setSensorTemp(getSensorTemp()); + else + // Clear any previous stored temp.. + _.SensorTemp = kElectraAcSensorMinTemp; +} + +/// Get the silent Sensor Update setting of the message. +/// i.e. Is this _just_ a sensor temp update message from the remote? +/// @note The A/C just takes the sensor temp value from the message and +/// will not follow any of the other settings in the message. +/// @return true, the setting is on. false, the setting is off. +bool IRElectraAc::getSensorUpdate(void) const { return _.SensorUpdate; } + +/// Set the silent Sensor Update setting of the message. +/// i.e. Is this _just_ a sensor temp update message from the remote? +/// @note The A/C will just take the sensor temp value from the message and +/// will not follow any of the other settings in the message. If set, the A/C +/// unit will also not beep in response to the message. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRElectraAc::setSensorUpdate(const bool on) { _.SensorUpdate = on; } + +/// Set the Sensor temperature for the IFeel mode. +/// @param[in] temp The temperature in degrees celsius. +void IRElectraAc::setSensorTemp(const uint8_t temp) { + _.SensorTemp = ::min(kElectraAcSensorMaxTemp, + ::max(kElectraAcSensorMinTemp, temp)) + + kElectraAcSensorTempDelta; +} + +/// Get the current sensor temperature setting for the IFeel mode. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRElectraAc::getSensorTemp(void) const { + return ::max(kElectraAcSensorTempDelta, _.SensorTemp) - + kElectraAcSensorTempDelta; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRElectraAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::ELECTRA_AC; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.sensorTemperature = getSensorTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = getSwingV() ? stdAc::swingv_t::kAuto + : stdAc::swingv_t::kOff; + result.swingh = getSwingH() ? stdAc::swingh_t::kAuto + : stdAc::swingh_t::kOff; + result.light = getLightToggle(); + result.turbo = _.Turbo; + result.clean = _.Clean; + result.iFeel = getIFeel(); + // Not supported. + result.model = -1; // No models used. + result.quiet = false; + result.econo = false; + result.filter = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRElectraAc::toString(void) const { + String result = ""; + result.reserve(160); // Reserve some heap for the string to reduce fragging. + if (!_.SensorUpdate) { + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kElectraAcAuto, kElectraAcCool, + kElectraAcHeat, kElectraAcDry, kElectraAcFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kElectraAcFanHigh, kElectraAcFanLow, + kElectraAcFanAuto, kElectraAcFanAuto, + kElectraAcFanMed); + result += addBoolToString(getSwingV(), kSwingVStr); + result += addBoolToString(getSwingH(), kSwingHStr); + result += addToggleToString(getLightToggle(), kLightStr); + result += addBoolToString(_.Clean, kCleanStr); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.IFeel, kIFeelStr); + } + if (_.IFeel || _.SensorUpdate) { + result += addIntToString(getSensorTemp(), kSensorTempStr, !_.SensorUpdate); + result += 'C'; + } + return result; +} + +#if DECODE_ELECTRA_AC +/// Decode the supplied Electra A/C message. +/// Status: STABLE / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeElectraAC(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (strict) { + if (nbits != kElectraAcBits) + return false; // Not strictly a ELECTRA_AC message. + } + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kElectraAcHdrMark, kElectraAcHdrSpace, + kElectraAcBitMark, kElectraAcOneSpace, + kElectraAcBitMark, kElectraAcZeroSpace, + kElectraAcBitMark, kElectraAcMessageGap, true, + _tolerance, 0, false)) return false; + + // Compliance + if (strict) { + // Verify the checksum. + if (!IRElectraAc::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = decode_type_t::ELECTRA_AC; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_ELECTRA_AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Electra.h b/src/libraries/IRremoteESP8266/src/ir_Electra.h new file mode 100644 index 000000000..83dd5209d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Electra.h @@ -0,0 +1,178 @@ +// Copyright 2019-2021 David Conran +/// @file +/// @brief Support for Electra A/C protocols. +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/AUXHeatpumpIR.cpp + +// Supports: +// Brand: AUX, Model: KFR-35GW/BpNFW=3 A/C +// Brand: AUX, Model: YKR-T/011 remote +// Brand: Electra, Model: Classic INV 17 / AXW12DCS A/C +// Brand: Electra, Model: YKR-M/003E remote +// Brand: Frigidaire, Model: FGPC102AB1 A/C +// Brand: Subtropic, Model: SUB-07HN1_18Y A/C +// Brand: Subtropic, Model: YKR-H/102E remote +// Brand: Centek, Model: SCT-65Q09 A/C +// Brand: Centek, Model: YKR-P/002E remote +// Brand: AEG, Model: Chillflex Pro AXP26U338CW A/C + +#ifndef IR_ELECTRA_H_ +#define IR_ELECTRA_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Electra A/C message. +union ElectraProtocol { + uint8_t raw[kElectraAcStateLength]; ///< The state of the IR remote + struct { + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t SwingV :3; + uint8_t Temp :5; + // Byte 2 + uint8_t :5; + uint8_t SwingH :3; + // Byte 3 + uint8_t :6; + uint8_t SensorUpdate :1; + uint8_t :1; + // Byte 4 + uint8_t :5; + uint8_t Fan :3; + // Byte 5 + uint8_t :6; + uint8_t Turbo :1; + uint8_t :1; + // Byte 6 + uint8_t :3; + uint8_t IFeel :1; + uint8_t :1; + uint8_t Mode :3; + // Byte 7 + uint8_t SensorTemp :8; + // Byte 8 + uint8_t :8; + // Byte 9 + uint8_t :2; + uint8_t Clean :1; + uint8_t :2; + uint8_t Power :1; + uint8_t :2; + // Byte 10 + uint8_t :8; + // Byte 11 + uint8_t LightToggle :8; + // Byte 12 + uint8_t Sum :8; + }; +}; + +// Constants +const uint8_t kElectraAcMinTemp = 16; // 16C +const uint8_t kElectraAcMaxTemp = 32; // 32C +const uint8_t kElectraAcTempDelta = 8; +const uint8_t kElectraAcSwingOn = 0b000; +const uint8_t kElectraAcSwingOff = 0b111; + +const uint8_t kElectraAcFanAuto = 0b101; +const uint8_t kElectraAcFanLow = 0b011; +const uint8_t kElectraAcFanMed = 0b010; +const uint8_t kElectraAcFanHigh = 0b001; + +const uint8_t kElectraAcAuto = 0b000; +const uint8_t kElectraAcCool = 0b001; +const uint8_t kElectraAcDry = 0b010; +const uint8_t kElectraAcHeat = 0b100; +const uint8_t kElectraAcFan = 0b110; + +const uint8_t kElectraAcLightToggleOn = 0x15; +// Light has known ON values of 0x15 (0b00010101) or 0x19 (0b00011001) +// Thus common bits ON are: 0b00010001 (0x11) +// We will use this for the getLightToggle() test. +const uint8_t kElectraAcLightToggleMask = 0x11; +// and known OFF values of 0x08 (0b00001000) & 0x05 (0x00000101) +const uint8_t kElectraAcLightToggleOff = 0x08; + +// Re: Byte[7]. Or Delta == 0xA and Temperature are stored in last 6 bits, +// and bit 7 stores Unknown flag +const uint8_t kElectraAcSensorTempDelta = 0x4A; +const uint8_t kElectraAcSensorMinTemp = 0; // 0C +const uint8_t kElectraAcSensorMaxTemp = 50; // 50C + +// Classes +/// Class for handling detailed Electra A/C messages. +class IRElectraAc { + public: + explicit IRElectraAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_ELECTRA_AC + void send(const uint16_t repeat = kElectraAcMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_ELECTRA_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + void setSwingH(const bool on); + bool getSwingH(void) const; + void setClean(const bool on); + bool getClean(void) const; + void setLightToggle(const bool on); + bool getLightToggle(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setIFeel(const bool on); + bool getIFeel(void) const; + void setSensorUpdate(const bool on); + bool getSensorUpdate(void) const; + void setSensorTemp(const uint8_t temp); + uint8_t getSensorTemp(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kElectraAcStateLength); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kElectraAcStateLength); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kElectraAcStateLength); + String toString(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + ElectraProtocol _; + void checksum(const uint16_t length = kElectraAcStateLength); +}; +#endif // IR_ELECTRA_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_EliteScreens.cpp b/src/libraries/IRremoteESP8266/src/ir_EliteScreens.cpp new file mode 100644 index 000000000..bfbdcc1e7 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_EliteScreens.cpp @@ -0,0 +1,89 @@ +// Copyright 2020 David Conran +/// @file +/// @brief Elite Screens protocol support +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1306 +/// @see https://elitescreens.com/kcfinder/upload/files/FAQ/ir_commands.pdf + +// Supports: +// Brand: Elite Screens, Model: Spectrum series +// Brand: Elite Screens, Model: VMAX2 / VMAX2 Plus series +// Brand: Elite Screens, Model: VMAX Plus4 series +// Brand: Elite Screens, Model: Home2 / Home3 series +// Brand: Elite Screens, Model: CineTension2 / CineTension3 series +// Brand: Elite Screens, Model: ZSP-IR-B / ZSP-IR-W remote +// Brand: Lumene Screens, Model: Embassy + +// Known Elite Screens commands: +// 0xFEA3387 (STOP) +// 0xFDA2256 (UP) +// 0xFBA1136 (DOWN) + +// Known Lumene Screens commands: +// 0xFDE3322 (STOP) +// 0xFEE2221 (UP) +// 0xFBE11E0 (DOWN) +// 0xF7E2EBD (STEP UP) +// 0xEFE1E2C (STEP DOWN) + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + + +// Constants +const uint16_t kEliteScreensOne = 470; +const uint16_t kEliteScreensZero = 1214; +const uint16_t kEliteScreensGap = 29200; + +#if SEND_ELITESCREENS +/// Send an Elite Screens formatted message. +/// Status: BETA / Probably Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendElitescreens(uint64_t data, uint16_t nbits, uint16_t repeat) { + // Protocol uses a constant bit time encoding. + sendGeneric(0, 0, // No header. + kEliteScreensOne, kEliteScreensZero, + kEliteScreensZero, kEliteScreensOne, + 0, kEliteScreensGap, data, nbits, 38000, true, repeat, 50); +} +#endif + +#if DECODE_ELITESCREENS +/// Decode the supplied Elite Screens message. +/// Status: STABLE / Confirmed working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeElitescreens(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance check. + if (strict && nbits != kEliteScreensBits) return false; + + uint64_t data = 0; + + // Data + Footer + if (!matchGenericConstBitTime(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + // Header (None) + 0, 0, + // Data + kEliteScreensOne, kEliteScreensZero, + // Footer (None) + 0, kEliteScreensGap, true)) return false; + + // Success + results->decode_type = decode_type_t::ELITESCREENS; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + results->repeat = false; + return true; +} +#endif diff --git a/src/libraries/IRremoteESP8266/src/ir_Epson.cpp b/src/libraries/IRremoteESP8266/src/ir_Epson.cpp new file mode 100644 index 000000000..f5207c8a7 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Epson.cpp @@ -0,0 +1,111 @@ +// Copyright 2020 David Conran +/// @file +/// @brief Support for Epson protocols. +/// Epson is an NEC-like protocol, except it doesn't use the NEC style repeat. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1034 + +// Supports: +// Brand: Epson, Model: EN-TW9100W Projector +// Brand: Epson, Model: VS230 Projector +// Brand: Epson, Model: VS330 Projector +// Brand: Epson, Model: EX3220 Projector +// Brand: Epson, Model: EX5220 Projector +// Brand: Epson, Model: EX5230 Projector +// Brand: Epson, Model: EX6220 Projector +// Brand: Epson, Model: EX7220 Projector + +#define __STDC_LIMIT_MACROS +#include +//// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" +#include "ir_NEC.h" + +#if SEND_EPSON +/// Send an Epson formatted message. +/// Status: Beta / Probably works. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of nbits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendEpson(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendGeneric(kNecHdrMark, kNecHdrSpace, kNecBitMark, kNecOneSpace, kNecBitMark, + kNecZeroSpace, kNecBitMark, kNecMinGap, kNecMinCommandLength, + data, nbits, 38, true, repeat, 33); +} + +#endif // SEND_EPSON + +#if DECODE_EPSON +/// Decode the supplied Epson message. +/// Status: Beta / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @note Experimental data indicates there are at least three messages +/// (first + 2 repeats). We only require the first + a single repeat to match. +/// This helps us distinguish it from NEC messages which are near identical. +bool IRrecv::decodeEpson(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + const uint8_t kEpsonMinMesgsForDecode = 2; + + if (results->rawlen < kEpsonMinMesgsForDecode * (2 * nbits + kHeader + + kFooter) + offset - 1) + return false; // Can't possibly be a valid Epson message. + if (strict && nbits != kEpsonBits) + return false; // Not strictly an Epson message. + + uint64_t data = 0; + uint64_t first_data = 0; + bool first = true; + + for (uint8_t i = 0; i < kEpsonMinMesgsForDecode; i++) { + // Match Header + Data + Footer + uint16_t delta = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kNecHdrMark, kNecHdrSpace, + kNecBitMark, kNecOneSpace, + kNecBitMark, kNecZeroSpace, + kNecBitMark, kNecMinGap, true); + if (!delta) return false; + offset += delta; + if (first) + first_data = data; + else if (data != first_data) return false; + first = false; // No longer the first message. + } + // Compliance + // Calculate command and optionally enforce integrity checking. + uint8_t command = (data & 0xFF00) >> 8; + // Command is sent twice, once as plain and then inverted. + if ((command ^ 0xFF) != (data & 0xFF)) { + if (strict) return false; // Command integrity failed. + command = 0; // The command value isn't valid, so default to zero. + } + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = EPSON; + // Epson command and address are technically in LSB first order so the + // final versions have to be reversed. + results->command = reverseBits(command, 8); + // Normal Epson (NEC) protocol has an 8 bit address sent, + // followed by it inverted. + uint8_t address = (data & 0xFF000000) >> 24; + uint8_t address_inverted = (data & 0x00FF0000) >> 16; + if (address == (address_inverted ^ 0xFF)) + // Inverted, so it is normal Epson (NEC) protocol. + results->address = reverseBits(address, 8); + else + // Not inverted, so must be Extended Epson (NEC) protocol, + // thus 16 bit address. + results->address = reverseBits((data >> 16) & UINT16_MAX, 16); + results->repeat = !first; + return true; +} +#endif // DECODE_EPSON diff --git a/src/libraries/IRremoteESP8266/src/ir_Fujitsu.cpp b/src/libraries/IRremoteESP8266/src/ir_Fujitsu.cpp new file mode 100644 index 000000000..a2cff79ec --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Fujitsu.cpp @@ -0,0 +1,1101 @@ +// Copyright 2017 Jonny Graham +// Copyright 2017-2022 David Conran +// Copyright 2021 siriuslzx + +/// @file +/// @brief Support for Fujitsu A/C protocols. +/// Fujitsu A/C support added by Jonny Graham & David Conran +/// @warning Use of incorrect model may cause the A/C unit to lock up. +/// e.g. An A/C that uses an AR-RAH1U remote may lock up requiring a physical +/// power rest, if incorrect model (ARRAH2E) is used with a Swing command. +/// The correct model for it is ARREB1E. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1376 + +#include "ir_Fujitsu.h" +// #include +#ifndef ARDUINO +//#include +#endif +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Ref: +// These values are based on averages of measurements +const uint16_t kFujitsuAcHdrMark = 3324; +const uint16_t kFujitsuAcHdrSpace = 1574; +const uint16_t kFujitsuAcBitMark = 448; +const uint16_t kFujitsuAcOneSpace = 1182; +const uint16_t kFujitsuAcZeroSpace = 390; +const uint16_t kFujitsuAcMinGap = 8100; +const uint8_t kFujitsuAcExtraTolerance = 5; // Extra tolerance percentage. + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addFanToString; +using irutils::addTempFloatToString; +using irutils::minsToString; + +#if SEND_FUJITSU_AC +/// Send a Fujitsu A/C formatted message. +/// Status: STABLE / Known Good. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// Typically one of: +/// kFujitsuAcStateLength, +/// kFujitsuAcStateLength - 1, +/// kFujitsuAcStateLengthShort, +/// kFujitsuAcStateLengthShort - 1 +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendFujitsuAC(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kFujitsuAcHdrMark, kFujitsuAcHdrSpace, kFujitsuAcBitMark, + kFujitsuAcOneSpace, kFujitsuAcBitMark, kFujitsuAcZeroSpace, + kFujitsuAcBitMark, kFujitsuAcMinGap, data, nbytes, 38, false, + repeat, 50); +} +#endif // SEND_FUJITSU_AC + +// Code to emulate Fujitsu A/C IR remote control unit. + +/// Class Constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] model The enum for the model of A/C to be emulated. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRFujitsuAC::IRFujitsuAC(const uint16_t pin, + const fujitsu_ac_remote_model_t model, + const bool inverted, const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { + setModel(model); + stateReset(); +} + +/// Set the currently emulated model of the A/C. +/// @param[in] model An enum representing the model to support/emulate. +void IRFujitsuAC::setModel(const fujitsu_ac_remote_model_t model) { + _model = model; + switch (model) { + case fujitsu_ac_remote_model_t::ARDB1: + case fujitsu_ac_remote_model_t::ARJW2: + _state_length = kFujitsuAcStateLength - 1; + _state_length_short = kFujitsuAcStateLengthShort - 1; + break; + case fujitsu_ac_remote_model_t::ARRY4: + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREB1E: + default: + _state_length = kFujitsuAcStateLength; + _state_length_short = kFujitsuAcStateLengthShort; + } +} + +/// Get the currently emulated/detected model of the A/C. +/// @return The enum representing the model of A/C. +fujitsu_ac_remote_model_t IRFujitsuAC::getModel(void) const { return _model; } + +/// Reset the state of the remote to a known good state/sequence. +void IRFujitsuAC::stateReset(void) { + for (size_t i = 0; i < kFujitsuAcStateLength; i++) { + _.longcode[i] = 0; + } + setTemp(24); + _.Fan = kFujitsuAcFanHigh; + _.Mode = kFujitsuAcModeCool; + _.Swing = kFujitsuAcSwingBoth; + _cmd = kFujitsuAcCmdTurnOn; + _.Filter = false; + _.Clean = false; + _.TimerType = kFujitsuAcStopTimers; + _.OnTimer = 0; + _.OffTimer = 0; + _.longcode[0] = 0x14; + _.longcode[1] = 0x63; + _.longcode[3] = 0x10; + _.longcode[4] = 0x10; + _rawstatemodified = true; +} + +/// Set up hardware to be able to send a message. +void IRFujitsuAC::begin(void) { _irsend.begin(); } + +#if SEND_FUJITSU_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRFujitsuAC::send(const uint16_t repeat) { + _irsend.sendFujitsuAC(getRaw(), getStateLength(), repeat); +} +#endif // SEND_FUJITSU_AC + +/// Update the length (size) of the state code for the current configuration. +/// @return true, if use long codes; false, use short codes. +bool IRFujitsuAC::updateUseLongOrShort(void) { + bool fullCmd = false; + switch (_cmd) { + case kFujitsuAcCmdTurnOff: // 0x02 + case kFujitsuAcCmdEcono: // 0x09 + case kFujitsuAcCmdPowerful: // 0x39 + case kFujitsuAcCmdStepVert: // 0x6C + case kFujitsuAcCmdToggleSwingVert: // 0x6D + case kFujitsuAcCmdStepHoriz: // 0x79 + case kFujitsuAcCmdToggleSwingHoriz: // 0x7A + _.Cmd = _cmd; + _rawstatemodified = true; + break; + default: + switch (_model) { + case fujitsu_ac_remote_model_t::ARRY4: + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARREW4E: + _.Cmd = 0xFE; + _rawstatemodified = true; + break; + case fujitsu_ac_remote_model_t::ARDB1: + case fujitsu_ac_remote_model_t::ARJW2: + _.Cmd = 0xFC; + _rawstatemodified = true; + break; + } + fullCmd = true; + break; + } + return fullCmd; +} + +/// Calculate and set the checksum values for the internal state. +void IRFujitsuAC::checkSum(void) { + _rawstatemodified = true; + if (updateUseLongOrShort()) { // Is it going to be a long code? + // Nr. of bytes in the message after this byte. + _.RestLength = _state_length - 7; + _.Protocol = (_model == fujitsu_ac_remote_model_t::ARREW4E) ? 0x31 : 0x30; + _.Power = (_cmd == kFujitsuAcCmdTurnOn) || get10CHeat(); + + // These values depend on model + if (_model != fujitsu_ac_remote_model_t::ARREB1E && + _model != fujitsu_ac_remote_model_t::ARREW4E) { + _.OutsideQuiet = 0; + if (_model != fujitsu_ac_remote_model_t::ARRAH2E) { + _.TimerType = kFujitsuAcStopTimers; + } + } + if (_model != fujitsu_ac_remote_model_t::ARRY4) { + switch (_model) { + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREW4E: + break; + default: + _.Clean = false; + } + _.Filter = false; + } + // Set the On/Off/Sleep timer Nr of mins. + _.OffTimer = getOffSleepTimer(); + _.OnTimer = getOnTimer(); + // Enable bit for the Off/Sleep timer + _.OffTimerEnable = _.OffTimer > 0; + // Enable bit for the On timer + _.OnTimerEnable = _.OnTimer > 0; + + uint8_t checksum = 0; + uint8_t checksum_complement = 0; + switch (_model) { + case fujitsu_ac_remote_model_t::ARDB1: + case fujitsu_ac_remote_model_t::ARJW2: + _.Swing = kFujitsuAcSwingOff; + checksum = sumBytes(_.longcode, _state_length - 1); + checksum_complement = 0x9B; + break; + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARRY4: + _.unknown = 1; + // FALL THRU + default: + checksum = sumBytes(_.longcode + _state_length_short, + _state_length - _state_length_short - 1); + } + // and negate the checksum and store it in the last byte. + _.longcode[_state_length - 1] = checksum_complement - checksum; + } else { // short codes + for (size_t i = 0; i < _state_length_short; i++) { + _.shortcode[i] = _.longcode[i]; + } + switch (_model) { + case fujitsu_ac_remote_model_t::ARRY4: + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARREW4E: + // The last byte is the inverse of penultimate byte + _.shortcode[_state_length_short - 1] = + ~_.shortcode[_state_length_short - 2]; + break; + default: + {}; // We don't need to do anything for the others. + } + } +} + +/// Get the length (size) of the state code for the current configuration. +/// @return The length of the state array required for this config. +uint8_t IRFujitsuAC::getStateLength(void) { + return updateUseLongOrShort() ? _state_length : _state_length_short; +} + +/// Is the current binary state representation a long or a short code? +/// @return true, if long; false, if short. +bool IRFujitsuAC::isLongCode(void) const { + return _.Cmd == 0xFE || _.Cmd == 0xFC; +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRFujitsuAC::getRaw(void) { + checkSum(); + return isLongCode() ? _.longcode : _.shortcode; +} + +/// Build the internal state/config from the current (raw) A/C message. +/// @param[in] length Size of the current/used (raw) A/C message array. +void IRFujitsuAC::buildFromState(const uint16_t length) { + switch (length) { + case kFujitsuAcStateLength - 1: + case kFujitsuAcStateLengthShort - 1: + setModel(fujitsu_ac_remote_model_t::ARDB1); + // ARJW2 has horizontal swing. + if (_.Swing > kFujitsuAcSwingVert) + setModel(fujitsu_ac_remote_model_t::ARJW2); + break; + default: + switch (_.Cmd) { + case kFujitsuAcCmdEcono: + case kFujitsuAcCmdPowerful: + setModel(fujitsu_ac_remote_model_t::ARREB1E); + break; + default: + setModel(fujitsu_ac_remote_model_t::ARRAH2E); + } + } + switch (_.RestLength) { + case 8: + if (_model != fujitsu_ac_remote_model_t::ARJW2) + setModel(fujitsu_ac_remote_model_t::ARDB1); + break; + case 9: + if (_model != fujitsu_ac_remote_model_t::ARREB1E) + setModel(fujitsu_ac_remote_model_t::ARRAH2E); + break; + } + if (_.Power) + setCmd(kFujitsuAcCmdTurnOn); + else + setCmd(kFujitsuAcCmdStayOn); + // Currently the only way we know how to tell ARRAH2E & ARRY4 apart is if + // either the raw Filter or Clean setting is on. + if (_model == fujitsu_ac_remote_model_t::ARRAH2E && (_.Filter || _.Clean) && + !get10CHeat()) + setModel(fujitsu_ac_remote_model_t::ARRY4); + if (_state_length == kFujitsuAcStateLength && _.OutsideQuiet) + setModel(fujitsu_ac_remote_model_t::ARREB1E); + switch (_.Cmd) { + case kFujitsuAcCmdTurnOff: + case kFujitsuAcCmdStepHoriz: + case kFujitsuAcCmdToggleSwingHoriz: + case kFujitsuAcCmdStepVert: + case kFujitsuAcCmdToggleSwingVert: + case kFujitsuAcCmdEcono: + case kFujitsuAcCmdPowerful: + setCmd(_.Cmd); + break; + } + if (_.Protocol == 0x31) setModel(fujitsu_ac_remote_model_t::ARREW4E); +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +/// @param[in] length Size of the newState array. +/// @return true, if successful; Otherwise false. (i.e. size check) +bool IRFujitsuAC::setRaw(const uint8_t newState[], const uint16_t length) { + if (length > kFujitsuAcStateLength) return false; + for (uint16_t i = 0; i < kFujitsuAcStateLength; i++) { + if (i < length) + _.longcode[i] = newState[i]; + else + _.longcode[i] = 0; + } + buildFromState(length); + _rawstatemodified = false; + return true; +} + +/// Request the A/C to step the Horizontal Swing. +void IRFujitsuAC::stepHoriz(void) { setCmd(kFujitsuAcCmdStepHoriz); } + +/// Request the A/C to toggle the Horizontal Swing mode. +/// @param[in] update Do we need to update the general swing config? +void IRFujitsuAC::toggleSwingHoriz(const bool update) { + // Toggle the current setting. + if (update) setSwing(getSwing() ^ kFujitsuAcSwingHoriz); + // and set the appropriate special command. + setCmd(kFujitsuAcCmdToggleSwingHoriz); +} + +/// Request the A/C to step the Vertical Swing. +void IRFujitsuAC::stepVert(void) { setCmd(kFujitsuAcCmdStepVert); } + +/// Request the A/C to toggle the Vertical Swing mode. +/// @param[in] update Do we need to update the general swing config? +void IRFujitsuAC::toggleSwingVert(const bool update) { + // Toggle the current setting. + if (update) setSwing(getSwing() ^ kFujitsuAcSwingVert); + // and set the appropriate special command. + setCmd(kFujitsuAcCmdToggleSwingVert); +} + +/// Set the requested (special) command part for the A/C message. +/// @param[in] cmd The special command code. +void IRFujitsuAC::setCmd(const uint8_t cmd) { + switch (cmd) { + case kFujitsuAcCmdTurnOff: + case kFujitsuAcCmdTurnOn: + case kFujitsuAcCmdStayOn: + case kFujitsuAcCmdStepVert: + case kFujitsuAcCmdToggleSwingVert: + _cmd = cmd; + break; + case kFujitsuAcCmdStepHoriz: + case kFujitsuAcCmdToggleSwingHoriz: + switch (_model) { + // Only these remotes have horizontal. + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARJW2: + _cmd = cmd; + break; + default: + _cmd = kFujitsuAcCmdStayOn; + } + break; + case kFujitsuAcCmdEcono: + case kFujitsuAcCmdPowerful: + switch (_model) { + // Only these remotes have these commands. + case ARREB1E: + case ARREW4E: + _cmd = cmd; + break; + default: + _cmd = kFujitsuAcCmdStayOn; + } + break; + default: + _cmd = kFujitsuAcCmdStayOn; + } +} + +/// Set the requested (special) command part for the A/C message. +/// @return The special command code. +uint8_t IRFujitsuAC::getCmd(void) const { + return _cmd; +} + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRFujitsuAC::setPower(const bool on) { + setCmd(on ? kFujitsuAcCmdTurnOn : kFujitsuAcCmdTurnOff); +} + +/// Set the requested power state of the A/C to off. +void IRFujitsuAC::off(void) { setPower(false); } + +/// Set the requested power state of the A/C to on. +void IRFujitsuAC::on(void) { setPower(true); } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRFujitsuAC::getPower(void) const { return _cmd != kFujitsuAcCmdTurnOff; } + +/// Set the Outside Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRFujitsuAC::setOutsideQuiet(const bool on) { + _.OutsideQuiet = on; + _rawstatemodified = true; + setCmd(kFujitsuAcCmdStayOn); // No special command involved. +} + +/// Get the Outside Quiet mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRFujitsuAC::getOutsideQuiet(void) const { + switch (_model) { + // Only ARREB1E & ARREW4E seems to have this mode. + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARREW4E: + return _.OutsideQuiet; + default: return false; + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees. +/// @param[in] useCelsius Use Celsius or Fahrenheit? +void IRFujitsuAC::setTemp(const float temp, const bool useCelsius) { + float mintemp; + float maxtemp; + uint8_t offset; + bool _useCelsius; + float _temp; + + switch (_model) { + // These models have native Fahrenheit & Celsius upport. + case fujitsu_ac_remote_model_t::ARREW4E: + _useCelsius = useCelsius; + _temp = temp; + break; + // Make sure everything else uses Celsius. + default: + _useCelsius = true; + _temp = useCelsius ? temp : fahrenheitToCelsius(temp); + } + setCelsius(_useCelsius); + if (_useCelsius) { + mintemp = kFujitsuAcMinTemp; + maxtemp = kFujitsuAcMaxTemp; + offset = kFujitsuAcTempOffsetC; + } else { + mintemp = kFujitsuAcMinTempF; + maxtemp = kFujitsuAcMaxTempF; + offset = kFujitsuAcTempOffsetF; + } + _temp = ::max(mintemp, _temp); + _temp = ::min(maxtemp, _temp); + if (_useCelsius) { + if (_model == fujitsu_ac_remote_model_t::ARREW4E) + _.Temp = (_temp - (offset / 2)) * 2; + else + _.Temp = (_temp - offset) * 4; + } else { + _.Temp = _temp - offset; + } + _rawstatemodified = true; + setCmd(kFujitsuAcCmdStayOn); // No special command involved. +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees of the currently set units. +float IRFujitsuAC::getTemp(void) const { + if (_model == fujitsu_ac_remote_model_t::ARREW4E) { + if (_.Fahrenheit) // Currently only ARREW4E supports native Fahrenheit. + return _.Temp + kFujitsuAcTempOffsetF; + else + return (_.Temp / 2.0) + (kFujitsuAcMinTemp / 2); + } else { + return _.Temp / 4 + kFujitsuAcMinTemp; + } +} + +/// Set the speed of the fan. +/// @param[in] fanSpeed The desired setting. +void IRFujitsuAC::setFanSpeed(const uint8_t fanSpeed) { + if (fanSpeed > kFujitsuAcFanQuiet) + _.Fan = kFujitsuAcFanHigh; // Set the fan to maximum if out of range. + else + _.Fan = fanSpeed; + _rawstatemodified = true; + setCmd(kFujitsuAcCmdStayOn); // No special command involved. +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRFujitsuAC::getFanSpeed(void) const { return _.Fan; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRFujitsuAC::setMode(const uint8_t mode) { + if (mode > kFujitsuAcModeHeat) + _.Mode = kFujitsuAcModeHeat; // Set the mode to maximum if out of range. + else + _.Mode = mode; + _rawstatemodified = true; + setCmd(kFujitsuAcCmdStayOn); // No special command involved. +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRFujitsuAC::getMode(void) const { return _.Mode; } + +/// Set the requested swing operation mode of the A/C unit. +/// @param[in] swingMode The swingMode code for the A/C. +/// Vertical, Horizon, or Both. See constants for details. +/// @note Not all models support all possible swing modes. +void IRFujitsuAC::setSwing(const uint8_t swingMode) { + _.Swing = swingMode; + _rawstatemodified = true; + switch (_model) { + // No Horizontal support. + case fujitsu_ac_remote_model_t::ARDB1: + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARRY4: + // Set the mode to max if out of range + if (swingMode > kFujitsuAcSwingVert) _.Swing = kFujitsuAcSwingVert; + break; + // Has Horizontal support. + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARJW2: + default: + // Set the mode to max if out of range + if (swingMode > kFujitsuAcSwingBoth) _.Swing = kFujitsuAcSwingBoth; + } + setCmd(kFujitsuAcCmdStayOn); // No special command involved. +} + +/// Get the requested swing operation mode of the A/C unit. +/// @return The contents of the swing state/mode. +uint8_t IRFujitsuAC::getSwing(void) const { return _.Swing; } + +/// Set the Clean mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRFujitsuAC::setClean(const bool on) { + _.Clean = on; + _rawstatemodified = true; + setCmd(kFujitsuAcCmdStayOn); // No special command involved. +} + +/// Get the Clean mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRFujitsuAC::getClean(void) const { + switch (_model) { + case fujitsu_ac_remote_model_t::ARRY4: return _.Clean; + default: return false; + } +} + +/// Set the Filter mode status of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRFujitsuAC::setFilter(const bool on) { + _.Filter = on; + _rawstatemodified = true; + setCmd(kFujitsuAcCmdStayOn); // No special command involved. +} + +/// Get the Filter mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRFujitsuAC::getFilter(void) const { + switch (_model) { + case fujitsu_ac_remote_model_t::ARRY4: return _.Filter; + default: return false; + } +} + +/// Set the 10C heat status of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRFujitsuAC::set10CHeat(const bool on) { + switch (_model) { + // Only selected models support this. + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREW4E: + setClean(on); // 10C Heat uses the same bit as Clean + if (on) { + _.Mode = kFujitsuAcModeFan; + _.Power = true; + _.Fan = kFujitsuAcFanAuto; + _.Swing = kFujitsuAcSwingOff; + _rawstatemodified = true; + } + default: + break; + } +} + +/// Get the 10C heat status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRFujitsuAC::get10CHeat(void) const { + switch (_model) { + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREW4E: + return (_.Clean && _.Power && _.Mode == kFujitsuAcModeFan && + _.Fan == kFujitsuAcFanAuto && _.Swing == kFujitsuAcSwingOff); + default: return false; + } +} + +/// Get the Timer type of the A/C message. +/// @return The current timer type in numeric form. +uint8_t IRFujitsuAC::getTimerType(void) const { + switch (_model) { + // These models seem to have timer support. + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREB1E: return _.TimerType; + default: return kFujitsuAcStopTimers; + } +} + +/// Set the Timer type of the A/C message. +/// @param[in] timertype The kind of timer to use for the message. +void IRFujitsuAC::setTimerType(const uint8_t timertype) { + switch (timertype) { + case kFujitsuAcSleepTimer: + case kFujitsuAcOnTimer: + case kFujitsuAcOffTimer: + case kFujitsuAcStopTimers: + _.TimerType = timertype; + break; + default: _.TimerType = kFujitsuAcStopTimers; + } + _rawstatemodified = true; +} + +/// Get the On Timer setting of the A/C. +/// @return nr of minutes left on the timer. 0 means disabled/not supported. +uint16_t IRFujitsuAC::getOnTimer(void) const { + if (getTimerType() == kFujitsuAcOnTimer) + return _.OnTimer; + return 0; +} + +/// Set the On Timer setting of the A/C. +/// @param[in] nr_mins Nr. of minutes to set the timer to. 0 means disabled. +void IRFujitsuAC::setOnTimer(const uint16_t nr_mins) { + _.OnTimer = ::min(kFujitsuAcTimerMax, nr_mins); // Bounds check. + _rawstatemodified = true; + if (_.OnTimer) { + _.TimerType = kFujitsuAcOnTimer; + } else if (getTimerType() == kFujitsuAcOnTimer) { + _.TimerType = kFujitsuAcStopTimers; + } +} + +/// Get the Off/Sleep Timer setting of the A/C. +/// @return nr of minutes left on the timer. 0 means disabled/not supported. +uint16_t IRFujitsuAC::getOffSleepTimer(void) const { + switch (getTimerType()) { + case kFujitsuAcOffTimer: + case kFujitsuAcSleepTimer: return _.OffTimer; + default: return 0; + } +} + +/// Set the Off/Sleep Timer time for the A/C. +/// @param[in] nr_mins Nr. of minutes to set the timer to. 0 means disabled. +inline void IRFujitsuAC::setOffSleepTimer(const uint16_t nr_mins) { + _.OffTimer = ::min(kFujitsuAcTimerMax, nr_mins); // Bounds check. + _rawstatemodified = true; +} + +/// Set the Off Timer time for the A/C. +/// @param[in] nr_mins Nr. of minutes to set the timer to. 0 means disabled. +void IRFujitsuAC::setOffTimer(const uint16_t nr_mins) { + setOffSleepTimer(nr_mins); // This will also set _rawstatemodified to true. + if (nr_mins) + _.TimerType = kFujitsuAcOffTimer; + else if (getTimerType() != kFujitsuAcOnTimer) + _.TimerType = kFujitsuAcStopTimers; +} + +/// Set the Sleep Timer time for the A/C. +/// @param[in] nr_mins Nr. of minutes to set the timer to. 0 means disabled. +void IRFujitsuAC::setSleepTimer(const uint16_t nr_mins) { + setOffSleepTimer(nr_mins); // This will also set _rawstatemodified to true. + if (nr_mins) + _.TimerType = kFujitsuAcSleepTimer; + else if (getTimerType() != kFujitsuAcOnTimer) + _.TimerType = kFujitsuAcStopTimers; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRFujitsuAC::validChecksum(uint8_t state[], const uint16_t length) { + uint8_t sum = 0; + uint8_t sum_complement = 0; + uint8_t checksum = state[length - 1]; + switch (length) { + case kFujitsuAcStateLengthShort: // ARRAH2E, ARREB1E, & ARRY4 + return state[length - 1] == (uint8_t)~state[length - 2]; + case kFujitsuAcStateLength - 1: // ARDB1 & ARJW2 + sum = sumBytes(state, length - 1); + sum_complement = 0x9B; + break; + case kFujitsuAcStateLength: // ARRAH2E, ARRY4, & ARREB1E + sum = sumBytes(state + kFujitsuAcStateLengthShort, + length - 1 - kFujitsuAcStateLengthShort); + break; + default: // Includes ARDB1 & ARJW2 short. + return true; // Assume the checksum is valid for other lengths. + } + return checksum == (uint8_t)(sum_complement - sum); // Does it match? +} + +/// Set the device's remote ID number. +/// @param[in] num The ID for the remote. Valid number range is 0 to 3. +void IRFujitsuAC::setId(const uint8_t num) { + _.Id = num; + _rawstatemodified = true; +} + +/// Get the current device's remote ID number. +/// @return The current device's remote ID number. +uint8_t IRFujitsuAC::getId(void) const { return _.Id; } + +/// Set the Temperature units for the A/C. +/// @param[in] on true, use Celsius. false, use Fahrenheit. +void IRFujitsuAC::setCelsius(const bool on) { + _.Fahrenheit = !on; + _rawstatemodified = true; +} + +/// Get the Clean mode status of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRFujitsuAC::getCelsius(void) const { return !_.Fahrenheit; } + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRFujitsuAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kFujitsuAcModeCool; + case stdAc::opmode_t::kHeat: return kFujitsuAcModeHeat; + case stdAc::opmode_t::kDry: return kFujitsuAcModeDry; + case stdAc::opmode_t::kFan: return kFujitsuAcModeFan; + default: return kFujitsuAcModeAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRFujitsuAC::convertFan(stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kFujitsuAcFanQuiet; + case stdAc::fanspeed_t::kLow: return kFujitsuAcFanLow; + case stdAc::fanspeed_t::kMedium: return kFujitsuAcFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kFujitsuAcFanHigh; + default: return kFujitsuAcFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRFujitsuAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kFujitsuAcModeCool: return stdAc::opmode_t::kCool; + case kFujitsuAcModeHeat: return stdAc::opmode_t::kHeat; + case kFujitsuAcModeDry: return stdAc::opmode_t::kDry; + case kFujitsuAcModeFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRFujitsuAC::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kFujitsuAcFanHigh: return stdAc::fanspeed_t::kMax; + case kFujitsuAcFanMed: return stdAc::fanspeed_t::kMedium; + case kFujitsuAcFanLow: return stdAc::fanspeed_t::kLow; + case kFujitsuAcFanQuiet: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to a previous state. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRFujitsuAC::toCommon(const stdAc::state_t *prev) { + stdAc::state_t result{}; + if (prev != NULL) result = *prev; + result.protocol = decode_type_t::FUJITSU_AC; + checkSum(); + result.model = _model; + result.power = getPower(); + // Only update these settings if it is a long message, or we have no previous + // state info for those settings. + if (isLongCode() || prev == NULL) { + result.mode = toCommonMode(_.Mode); + result.celsius = getCelsius(); + { + const float minHeat = result.celsius ? kFujitsuAcMinHeat + : kFujitsuAcMinHeatF; + result.degrees = get10CHeat() ? minHeat : getTemp(); + } + result.fanspeed = toCommonFanSpeed(_.Fan); + uint8_t swing = _.Swing; + switch (result.model) { + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARRY4: + result.clean = _.Clean; + result.filter = _.Filter; + result.swingv = (swing & kFujitsuAcSwingVert) ? stdAc::swingv_t::kAuto + : stdAc::swingv_t::kOff; + result.swingh = (swing & kFujitsuAcSwingHoriz) ? stdAc::swingh_t::kAuto + : stdAc::swingh_t::kOff; + break; + case fujitsu_ac_remote_model_t::ARDB1: + case fujitsu_ac_remote_model_t::ARJW2: + default: + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + } + } + result.quiet = _.Fan == kFujitsuAcFanQuiet; + result.turbo = _cmd == kFujitsuAcCmdPowerful; + result.econo = _cmd == kFujitsuAcCmdEcono; + // Not supported. + result.light = false; + result.filter = false; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRFujitsuAC::toString(void) const { + String result = ""; + result.reserve(180); // Reserve some heap for the string to reduce fragging. + fujitsu_ac_remote_model_t model = _model; + result += addModelToString(decode_type_t::FUJITSU_AC, model, false); + result += addIntToString(_.Id, kIdStr); + result += addBoolToString(getPower(), kPowerStr); + if (_rawstatemodified || isLongCode()) { + result += addModeToString(_.Mode, kFujitsuAcModeAuto, kFujitsuAcModeCool, + kFujitsuAcModeHeat, kFujitsuAcModeDry, + kFujitsuAcModeFan); + { + const bool isCelsius = getCelsius(); + const float minHeat = isCelsius ? kFujitsuAcMinHeat : kFujitsuAcMinHeatF; + result += addTempFloatToString(get10CHeat() ? minHeat : getTemp(), + isCelsius); + } + result += addFanToString(_.Fan, kFujitsuAcFanHigh, kFujitsuAcFanLow, + kFujitsuAcFanAuto, kFujitsuAcFanQuiet, + kFujitsuAcFanMed); + switch (model) { + // These models have no internal swing, clean. or filter state. + case fujitsu_ac_remote_model_t::ARDB1: + case fujitsu_ac_remote_model_t::ARJW2: + break; + // These models have Clean & Filter, plus Swing (via fall thru) + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARRY4: + result += addBoolToString(getClean(), kCleanStr); + result += addBoolToString(getFilter(), kFilterStr); + // FALL THRU + default: // e.g. ARREW4E + switch (model) { + case fujitsu_ac_remote_model_t::ARRAH2E: + case fujitsu_ac_remote_model_t::ARREW4E: + result += addBoolToString(get10CHeat(), k10CHeatStr); + break; + default: + break; + } + result += addIntToString(_.Swing, kSwingStr); + result += kSpaceLBraceStr; + switch (_.Swing) { + case kFujitsuAcSwingOff: + result += kOffStr; + break; + case kFujitsuAcSwingVert: + result += kSwingVStr; + break; + case kFujitsuAcSwingHoriz: + result += kSwingHStr; + break; + case kFujitsuAcSwingBoth: + result += kSwingVStr; + result += '+'; + result += kSwingHStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + } + } + result += kCommaSpaceStr; + result += kCommandStr; + result += kColonSpaceStr; + switch (_cmd) { + case kFujitsuAcCmdStepHoriz: + result += kStepStr; + result += ' '; + result += kSwingHStr; + break; + case kFujitsuAcCmdStepVert: + result += kStepStr; + result += ' '; + result += kSwingVStr; + break; + case kFujitsuAcCmdToggleSwingHoriz: + result += kToggleStr; + result += ' '; + result += kSwingHStr; + break; + case kFujitsuAcCmdToggleSwingVert: + result += kToggleStr; + result += ' '; + result += kSwingVStr; + break; + case kFujitsuAcCmdEcono: + result += kEconoStr; + break; + case kFujitsuAcCmdPowerful: + result += kPowerfulStr; + break; + default: + result += kNAStr; + } + if (_rawstatemodified || isLongCode()) { + uint16_t mins = 0; + String type_str = kTimerStr; + switch (model) { + case fujitsu_ac_remote_model_t::ARREB1E: + case fujitsu_ac_remote_model_t::ARREW4E: + result += addBoolToString(getOutsideQuiet(), kOutsideQuietStr); + // FALL THRU + // These models seem to have timer support. + case fujitsu_ac_remote_model_t::ARRAH2E: + switch (getTimerType()) { + case kFujitsuAcOnTimer: + type_str = kOnTimerStr; + mins = getOnTimer(); + break; + case kFujitsuAcOffTimer: + type_str = kOffTimerStr; + mins = getOffSleepTimer(); + break; + case kFujitsuAcSleepTimer: + type_str = kSleepTimerStr; + mins = getOffSleepTimer(); + break; + } + result += addLabeledString(mins ? minsToString(mins) : kOffStr, + type_str); + break; + default: + break; + } + } + return result; +} + +#if DECODE_FUJITSU_AC +/// Decode the supplied Fujitsu AC IR message if possible. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeFujitsuAC(decode_results* results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + uint16_t dataBitsSoFar = 0; + + // Have we got enough data to successfully decode? + if (results->rawlen < (2 * kFujitsuAcMinBits) + kHeader + kFooter - 1 + + offset) + return false; // Can't possibly be a valid message. + + // Compliance + if (strict) { + switch (nbits) { + case kFujitsuAcBits: + case kFujitsuAcBits - 8: + case kFujitsuAcMinBits: + case kFujitsuAcMinBits + 8: break; + default: return false; // Must be called with the correct nr. of bits. + } + } + + // Header / Some of the Data + uint16_t used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, kFujitsuAcMinBits - 8, + kFujitsuAcHdrMark, kFujitsuAcHdrSpace, // Header + kFujitsuAcBitMark, kFujitsuAcOneSpace, // Data + kFujitsuAcBitMark, kFujitsuAcZeroSpace, + 0, 0, // No Footer (yet) + false, _tolerance + kFujitsuAcExtraTolerance, 0, + false); // LSBF + if (!used) return false; + offset += used; + // Check we have the typical data header. + if (results->state[0] != 0x14 || results->state[1] != 0x63) return false; + dataBitsSoFar += kFujitsuAcMinBits - 8; + + // Keep reading bytes until we either run out of message or state to fill. + match_result_t data_result; + for (uint16_t i = 5; + offset <= results->rawlen - 16 && i < kFujitsuAcStateLength; + i++, dataBitsSoFar += 8, offset += data_result.used) { + data_result = matchData( + &(results->rawbuf[offset]), 8, kFujitsuAcBitMark, kFujitsuAcOneSpace, + kFujitsuAcBitMark, kFujitsuAcZeroSpace, + _tolerance + kFujitsuAcExtraTolerance, 0, false); + if (data_result.success == false) break; // Fail + results->state[i] = data_result.data; + } + + // Footer + if (offset > results->rawlen || + !matchMark(results->rawbuf[offset++], kFujitsuAcBitMark)) + return false; + // The space is optional if we are out of capture. + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kFujitsuAcMinGap)) + return false; + + // Compliance + if (strict) { + if (dataBitsSoFar != nbits) return false; + } + + results->decode_type = FUJITSU_AC; + results->bits = dataBitsSoFar; + + // Compliance + switch (dataBitsSoFar) { + case kFujitsuAcMinBits: + // Check if this values indicate that this should have been a long state + // message. + if (results->state[5] == 0xFC) return false; + return true; // Success + case kFujitsuAcMinBits + 8: + // Check if this values indicate that this should have been a long state + // message. + if (results->state[5] == 0xFE) return false; + // The last byte needs to be the inverse of the penultimate byte. + if (results->state[5] != (uint8_t)~results->state[6]) return false; + return true; // Success + case kFujitsuAcBits - 8: + // Long messages of this size require this byte be correct. + if (results->state[5] != 0xFC) return false; + break; + case kFujitsuAcBits: + // Long messages of this size require this byte be correct. + if (results->state[5] != 0xFE) return false; + break; + default: + return false; // Unexpected size. + } + if (!IRFujitsuAC::validChecksum(results->state, dataBitsSoFar / 8)) + return false; + + // Success + return true; // All good. +} +#endif // DECODE_FUJITSU_AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Fujitsu.h b/src/libraries/IRremoteESP8266/src/ir_Fujitsu.h new file mode 100644 index 000000000..b46d6b3dd --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Fujitsu.h @@ -0,0 +1,266 @@ +// Copyright 2017 Jonny Graham +// Copyright 2018-2022 David Conran +// Copyright 2021 siriuslzx + +/// @file +/// @brief Support for Fujitsu A/C protocols. +/// Fujitsu A/C support added by Jonny Graham +/// @warning Use of incorrect model may cause the A/C unit to lock up. +/// e.g. An A/C that uses an AR-RAH1U remote may lock up requiring a physical +/// power rest, if incorrect model (ARRAH2E) is used with a Swing command. +/// The correct model for it is ARREB1E. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1376 + +// Supports: +// Brand: Fujitsu, Model: AR-RAH2E remote (ARRAH2E) +// Brand: Fujitsu, Model: ASYG30LFCA A/C (ARRAH2E) +// Brand: Fujitsu General, Model: AR-RCE1E remote (ARRAH2E) +// Brand: Fujitsu General, Model: ASHG09LLCA A/C (ARRAH2E) +// Brand: Fujitsu General, Model: AOHG09LLC A/C (ARRAH2E) +// Brand: Fujitsu, Model: AR-DB1 remote (ARDB1) +// Brand: Fujitsu, Model: AST9RSGCW A/C (ARDB1) +// Brand: Fujitsu, Model: AR-REB1E remote (ARREB1E) +// Brand: Fujitsu, Model: ASYG7LMCA A/C (ARREB1E) +// Brand: Fujitsu, Model: AR-RAE1E remote (ARRAH2E) +// Brand: Fujitsu, Model: AGTV14LAC A/C (ARRAH2E) +// Brand: Fujitsu, Model: AR-RAC1E remote (ARRAH2E) +// Brand: Fujitsu, Model: ASTB09LBC A/C (ARRY4) +// Brand: Fujitsu, Model: AR-RY4 remote (ARRY4) +// Brand: Fujitsu General, Model: AR-JW2 remote (ARJW2) +// Brand: Fujitsu, Model: AR-DL10 remote (ARDB1) +// Brand: Fujitsu, Model: ASU30C1 A/C (ARDB1) +// Brand: Fujitsu, Model: AR-RAH1U remote (ARREB1E) +// Brand: Fujitsu, Model: AR-RAH2U remote (ARRAH2E) +// Brand: Fujitsu, Model: ASU12RLF A/C (ARREB1E) +// Brand: Fujitsu, Model: AR-REW4E remote (ARREW4E) +// Brand: Fujitsu, Model: ASYG09KETA-B A/C (ARREW4E) +// Brand: Fujitsu, Model: AR-REB4E remote (ARREB1E) +// Brand: Fujitsu, Model: ASTG09K A/C (ARREW4E) +// Brand: Fujitsu, Model: ASTG18K A/C (ARREW4E) +// Brand: Fujitsu, Model: AR-REW1E remote (ARREW4E) +// Brand: Fujitsu, Model: AR-REG1U remote (ARRAH2E) +// Brand: OGeneral, Model: AR-RCL1E remote (ARRAH2E) +// Brand: Fujitsu General, Model: AR-JW17 remote (ARDB1) + +#ifndef IR_FUJITSU_H_ +#define IR_FUJITSU_H_ + +#define __STDC_LIMIT_MACROS +#include +//#ifdef ARDUINO +#include "String.h" +//#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Fujitsu A/C message. +union FujitsuProtocol { + struct { + uint8_t longcode[kFujitsuAcStateLength]; ///< The state of the IR remote. + uint8_t shortcode[kFujitsuAcStateLengthShort]; + }; + struct { + // Byte 0~1 + uint64_t :16; // Fixed header + // Byte 2 + uint64_t :4; + uint64_t Id :2; // Device Number/Identifier + uint64_t :2; + // Byte 3-4 + uint64_t :16; + // Byte 5 + uint64_t Cmd :8; // short codes:cmd; long codes:fixed value + // Byte 6 + uint64_t RestLength :8; // Nr. of bytes in the message after this byte. + // Byte 7 + uint64_t Protocol :8; // Seems like a protocol version number. Not sure. + // Byte 8 + uint64_t Power :1; + uint64_t Fahrenheit :1; + uint64_t Temp :6; // Internal representation varies between models. + // Byte 9 + uint64_t Mode :3; + uint64_t Clean :1; // Also 10C Heat in ARREW4E. + uint64_t TimerType :2; + uint64_t :2; + // Byte 10 + uint64_t Fan :3; + uint64_t :1; + uint64_t Swing :2; + uint64_t :2; + // Byte 11~13 + uint64_t OffTimer :11; // Also is the sleep timer value + uint64_t OffTimerEnable :1; + uint64_t OnTimer :11; + uint64_t OnTimerEnable :1; + // Byte 14 + uint64_t :3; + uint64_t Filter :1; + uint64_t :1; + uint64_t unknown :1; + uint64_t :1; + uint64_t OutsideQuiet :1; + // Byte 15 + uint64_t :0; // Checksum + }; +}; + +// Constants +const uint8_t kFujitsuAcModeAuto = 0x0; // 0b000 +const uint8_t kFujitsuAcModeCool = 0x1; // 0b001 +const uint8_t kFujitsuAcModeDry = 0x2; // 0b010 +const uint8_t kFujitsuAcModeFan = 0x3; // 0b011 +const uint8_t kFujitsuAcModeHeat = 0x4; // 0b100 + +const uint8_t kFujitsuAcCmdStayOn = 0x00; // b00000000 +const uint8_t kFujitsuAcCmdTurnOn = 0x01; // b00000001 +const uint8_t kFujitsuAcCmdTurnOff = 0x02; // b00000010 +const uint8_t kFujitsuAcCmdEcono = 0x09; // b00001001 +const uint8_t kFujitsuAcCmdPowerful = 0x39; // b00111001 +const uint8_t kFujitsuAcCmdStepVert = 0x6C; // b01101100 +const uint8_t kFujitsuAcCmdToggleSwingVert = 0x6D; // b01101101 +const uint8_t kFujitsuAcCmdStepHoriz = 0x79; // b01111001 +const uint8_t kFujitsuAcCmdToggleSwingHoriz = 0x7A; // b01111010 + +const uint8_t kFujitsuAcFanAuto = 0x00; +const uint8_t kFujitsuAcFanHigh = 0x01; +const uint8_t kFujitsuAcFanMed = 0x02; +const uint8_t kFujitsuAcFanLow = 0x03; +const uint8_t kFujitsuAcFanQuiet = 0x04; + +const float kFujitsuAcMinHeat = 10; // 10C +const float kFujitsuAcMinTemp = 16; // 16C +const float kFujitsuAcMaxTemp = 30; // 30C +const uint8_t kFujitsuAcTempOffsetC = kFujitsuAcMinTemp; +const float kFujitsuAcMinHeatF = 50; // 50F +const float kFujitsuAcMinTempF = 60; // 60F +const float kFujitsuAcMaxTempF = 88; // 88F +const uint8_t kFujitsuAcTempOffsetF = 44; + +const uint8_t kFujitsuAcSwingOff = 0x00; +const uint8_t kFujitsuAcSwingVert = 0x01; +const uint8_t kFujitsuAcSwingHoriz = 0x02; +const uint8_t kFujitsuAcSwingBoth = 0x03; + +const uint8_t kFujitsuAcStopTimers = 0b00; // 0 +const uint8_t kFujitsuAcSleepTimer = 0b01; // 1 +const uint8_t kFujitsuAcOffTimer = 0b10; // 2 +const uint8_t kFujitsuAcOnTimer = 0b11; // 3 +const uint16_t kFujitsuAcTimerMax = 12 * 60; ///< Minutes. + +// Legacy defines. +#define FUJITSU_AC_MODE_AUTO kFujitsuAcModeAuto +#define FUJITSU_AC_MODE_COOL kFujitsuAcModeCool +#define FUJITSU_AC_MODE_DRY kFujitsuAcModeDry +#define FUJITSU_AC_MODE_FAN kFujitsuAcModeFan +#define FUJITSU_AC_MODE_HEAT kFujitsuAcModeHeat +#define FUJITSU_AC_CMD_STAY_ON kFujitsuAcCmdStayOn +#define FUJITSU_AC_CMD_TURN_ON kFujitsuAcCmdTurnOn +#define FUJITSU_AC_CMD_TURN_OFF kFujitsuAcCmdTurnOff +#define FUJITSU_AC_CMD_STEP_HORIZ kFujitsuAcCmdStepHoriz +#define FUJITSU_AC_CMD_STEP_VERT kFujitsuAcCmdStepVert +#define FUJITSU_AC_FAN_AUTO kFujitsuAcFanAuto +#define FUJITSU_AC_FAN_HIGH kFujitsuAcFanHigh +#define FUJITSU_AC_FAN_MED kFujitsuAcFanMed +#define FUJITSU_AC_FAN_LOW kFujitsuAcFanLow +#define FUJITSU_AC_FAN_QUIET kFujitsuAcFanQuiet +#define FUJITSU_AC_MIN_TEMP kFujitsuAcMinTemp +#define FUJITSU_AC_MAX_TEMP kFujitsuAcMaxTemp +#define FUJITSU_AC_SWING_OFF kFujitsuAcSwingOff +#define FUJITSU_AC_SWING_VERT kFujitsuAcSwingVert +#define FUJITSU_AC_SWING_HORIZ kFujitsuAcSwingHoriz +#define FUJITSU_AC_SWING_BOTH kFujitsuAcSwingBoth + +/// Class for handling detailed Fujitsu A/C messages. +class IRFujitsuAC { + public: + explicit IRFujitsuAC(const uint16_t pin, + const fujitsu_ac_remote_model_t model = ARRAH2E, + const bool inverted = false, + const bool use_modulation = true); + void setModel(const fujitsu_ac_remote_model_t model); + fujitsu_ac_remote_model_t getModel(void) const; + void stateReset(void); +#if SEND_FUJITSU_AC + void send(const uint16_t repeat = kFujitsuAcMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_FUJITSU_AC + void begin(void); + void stepHoriz(void); + void toggleSwingHoriz(const bool update = true); + void stepVert(void); + void toggleSwingVert(const bool update = true); + void setCmd(const uint8_t cmd); + uint8_t getCmd(void) const; + void setTemp(const float temp, const bool useCelsius = true); + float getTemp(void) const; + void setFanSpeed(const uint8_t fan); + uint8_t getFanSpeed(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwing(const uint8_t mode); + uint8_t getSwing(void) const; + uint8_t* getRaw(void); + bool setRaw(const uint8_t newState[], const uint16_t length); + uint8_t getStateLength(void); + static bool validChecksum(uint8_t* state, const uint16_t length); + bool isLongCode(void) const; + void setPower(const bool on); + void off(void); + void on(void); + bool getPower(void) const; + void setClean(const bool on); + bool getClean(void) const; + void setFilter(const bool on); + bool getFilter(void) const; + void set10CHeat(const bool on); + bool get10CHeat(void) const; + void setOutsideQuiet(const bool on); + bool getOutsideQuiet(void) const; + uint8_t getTimerType(void) const; + void setTimerType(const uint8_t timertype); + uint16_t getOnTimer(void) const; + void setOnTimer(const uint16_t nr_mins); + uint16_t getOffSleepTimer(void) const; + void setOffTimer(const uint16_t nr_mins); + void setSleepTimer(const uint16_t nr_mins); + void setId(const uint8_t num); + uint8_t getId(void) const; + void setCelsius(const bool on); + bool getCelsius(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL); + arduino::String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + FujitsuProtocol _; + uint8_t _cmd; + fujitsu_ac_remote_model_t _model; + uint8_t _state_length; + uint8_t _state_length_short; + bool _rawstatemodified; + void checkSum(void); + bool updateUseLongOrShort(void); + void buildFromState(const uint16_t length); + void setOffSleepTimer(const uint16_t nr_mins); +}; + +#endif // IR_FUJITSU_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_GICable.cpp b/src/libraries/IRremoteESP8266/src/ir_GICable.cpp new file mode 100644 index 000000000..1ef806331 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_GICable.cpp @@ -0,0 +1,95 @@ +// Copyright 2018 David Conran + +/// @file +/// @brief G.I. Cable +/// @see https://github.com/cyborg5/IRLib2/blob/master/IRLibProtocols/IRLib_P09_GICable.h +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/447 + +// Supports: +// Brand: G.I. Cable, Model: XRC-200 remote + +#define __STDC_LIMIT_MACROS +#include +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kGicableHdrMark = 9000; +const uint16_t kGicableHdrSpace = 4400; +const uint16_t kGicableBitMark = 550; +const uint16_t kGicableOneSpace = 4400; +const uint16_t kGicableZeroSpace = 2200; +const uint16_t kGicableRptSpace = 2200; +const uint32_t kGicableMinCommandLength = 99600; +const uint32_t kGicableMinGap = + kGicableMinCommandLength - + (kGicableHdrMark + kGicableHdrSpace + + kGicableBits * (kGicableBitMark + kGicableOneSpace) + kGicableBitMark); + +#if SEND_GICABLE +/// Send a raw G.I. Cable formatted message. +/// Status: Alpha / Untested. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendGICable(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendGeneric(kGicableHdrMark, kGicableHdrSpace, kGicableBitMark, + kGicableOneSpace, kGicableBitMark, kGicableZeroSpace, + kGicableBitMark, kGicableMinGap, kGicableMinCommandLength, data, + nbits, 39, true, 0, // Repeats are handled later. + 50); + // Message repeat sequence. + if (repeat) + sendGeneric(kGicableHdrMark, kGicableRptSpace, 0, 0, 0, + 0, // No actual data sent. + kGicableBitMark, kGicableMinGap, kGicableMinCommandLength, 0, + 0, // No data to be sent. + 39, true, repeat - 1, 50); +} +#endif // SEND_GICABLE + +#if DECODE_GICABLE +/// Decode the supplied G.I. Cable message. +/// Status: Alpha / Not tested against a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeGICable(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kGicableBits) + return false; // Not strictly an GICABLE message. + + uint64_t data = 0; + // Match Header + Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kGicableHdrMark, kGicableHdrSpace, + kGicableBitMark, kGicableOneSpace, + kGicableBitMark, kGicableZeroSpace, + kGicableBitMark, kGicableMinGap, true); + if (!used) return false; + offset += used; + // Compliance + if (strict) { + // We expect a repeat frame. + if (!matchMark(results->rawbuf[offset++], kGicableHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kGicableRptSpace)) return false; + if (!matchMark(results->rawbuf[offset++], kGicableBitMark)) return false; + } + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = GICABLE; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_GICABLE diff --git a/src/libraries/IRremoteESP8266/src/ir_GlobalCache.cpp b/src/libraries/IRremoteESP8266/src/ir_GlobalCache.cpp new file mode 100644 index 000000000..8acd6834a --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_GlobalCache.cpp @@ -0,0 +1,64 @@ +// Copyright 2016 Hisham Khalifa +// Copyright 2017 David Conran + +/// @file +/// @brief Global Cache IR format sender +/// Originally added by Hisham Khalifa (http://www.hishamkhalifa.com) +/// @see https://irdb.globalcache.com/Home/Database + +// Supports: +// Brand: Global Cache, Model: Control Tower IR DB + +// #include +#include "IRsend.h" +#include "minmax.h" + +// Constants +const uint16_t kGlobalCacheMaxRepeat = 50; +const uint32_t kGlobalCacheMinUsec = 80; +const uint8_t kGlobalCacheFreqIndex = 0; +const uint8_t kGlobalCacheRptIndex = kGlobalCacheFreqIndex + 1; +const uint8_t kGlobalCacheRptStartIndex = kGlobalCacheRptIndex + 1; +const uint8_t kGlobalCacheStartIndex = kGlobalCacheRptStartIndex + 1; + +#if SEND_GLOBALCACHE +/// Send a shortened GlobalCache (GC) IRdb/control tower formatted message. +/// Status: STABLE / Known working. +/// @param[in] buf Array of uint16_t containing the shortened GlobalCache data. +/// @param[in] len Nr. of entries in the buf[] array. +/// @note Global Cache format without the emitter ID or request ID. +/// Starts at the frequency (Hertz), followed by nr. of times to emit (count), +/// then the offset for repeats (where a repeat will start from), +/// then the rest of entries are the actual IR message as units of periodic +/// time. +/// e.g. sendir,1:1,1,38000,1,1,9,70,9,30,9,... -> 38000,1,1,9,70,9,30,9,... +/// @see https://irdb.globalcache.com/Home/Database +void IRsend::sendGC(uint16_t buf[], uint16_t len) { + uint16_t hz = buf[kGlobalCacheFreqIndex]; // GC frequency is in Hz. + enableIROut(hz); + uint32_t periodic_time = calcUSecPeriod(hz, false); + uint8_t emits = + ::min(buf[kGlobalCacheRptIndex], (uint16_t)kGlobalCacheMaxRepeat); + // Repeat + for (uint8_t repeat = 0; repeat < emits; repeat++) { + // First time through, start at the beginning (kGlobalCacheStartIndex), + // otherwise for repeats, we start a specified offset from that. + uint16_t offset = kGlobalCacheStartIndex; + if (repeat) offset += buf[kGlobalCacheRptStartIndex] - 1; + // Data + for (; offset < len; offset++) { + // Convert periodic units to microseconds. + // Minimum is kGlobalCacheMinUsec for actual GC units. + uint32_t microseconds = + ::max(buf[offset] * periodic_time, kGlobalCacheMinUsec); + // These codes start at an odd index (not even as with sendRaw). + if (offset & 1) // Odd bit. + mark(microseconds); + else // Even bit. + space(microseconds); + } + } + // It's possible that we've ended on a mark(), thus ensure the LED is off. + ledOff(); +} +#endif diff --git a/src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp b/src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp new file mode 100644 index 000000000..00da7e309 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp @@ -0,0 +1,500 @@ +// Copyright 2019 ribeirodanielf +// Copyright 2019 David Conran +/// @file +/// @brief Support for Goodweather compatible HVAC protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/697 + +#include "ir_Goodweather.h" +// #include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::addToggleToString; + +#if SEND_GOODWEATHER +/// Send a Goodweather HVAC formatted message. +/// Status: BETA / Needs testing on real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendGoodweather(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + if (nbits != kGoodweatherBits) + return; // Wrong nr. of bits to send a proper message. + // Set IR carrier frequency + enableIROut(38); + + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kGoodweatherHdrMark); + space(kGoodweatherHdrSpace); + + // Data + for (int16_t i = 0; i < nbits; i += 8) { + uint16_t chunk = (data >> i) & 0xFF; // Grab a byte at a time. + chunk = (~chunk) << 8 | chunk; // Prepend a inverted copy of the byte. + sendData(kGoodweatherBitMark, kGoodweatherOneSpace, + kGoodweatherBitMark, kGoodweatherZeroSpace, + chunk, 16, false); + } + // Footer + mark(kGoodweatherBitMark); + space(kGoodweatherHdrSpace); + mark(kGoodweatherBitMark); + space(kDefaultMessageGap); + } +} +#endif // SEND_GOODWEATHER + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRGoodweatherAc::IRGoodweatherAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IRGoodweatherAc::stateReset(void) { _.raw = kGoodweatherStateInit; } + +/// Set up hardware to be able to send a message. +void IRGoodweatherAc::begin(void) { _irsend.begin(); } + +#if SEND_GOODWEATHER +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRGoodweatherAc::send(const uint16_t repeat) { + _irsend.sendGoodweather(getRaw(), kGoodweatherBits, repeat); +} +#endif // SEND_GOODWEATHER + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +uint64_t IRGoodweatherAc::getRaw(void) { return _.raw; } + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRGoodweatherAc::setRaw(const uint64_t state) { _.raw = state; } + +/// Change the power setting to On. +void IRGoodweatherAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRGoodweatherAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGoodweatherAc::setPower(const bool on) { + _.Command = kGoodweatherCmdPower; + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRGoodweatherAc::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRGoodweatherAc::setTemp(const uint8_t temp) { + uint8_t new_temp = ::max(kGoodweatherTempMin, temp); + new_temp = ::min(kGoodweatherTempMax, new_temp); + if (new_temp > getTemp()) _.Command = kGoodweatherCmdUpTemp; + if (new_temp < getTemp()) _.Command = kGoodweatherCmdDownTemp; + _.Temp = new_temp - kGoodweatherTempMin; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRGoodweatherAc::getTemp(void) const { + return _.Temp + kGoodweatherTempMin; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRGoodweatherAc::setFan(const uint8_t speed) { + _.Command = kGoodweatherCmdFan; + switch (speed) { + case kGoodweatherFanAuto: + case kGoodweatherFanLow: + case kGoodweatherFanMed: + case kGoodweatherFanHigh: + _.Fan = speed; + break; + default: + _.Fan = kGoodweatherFanAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRGoodweatherAc::getFan(void) const { + return _.Fan; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRGoodweatherAc::setMode(const uint8_t mode) { + _.Command = kGoodweatherCmdMode; + switch (mode) { + case kGoodweatherAuto: + case kGoodweatherDry: + case kGoodweatherCool: + case kGoodweatherFan: + case kGoodweatherHeat: + _.Mode = mode; + break; + default: + _.Mode = kGoodweatherAuto; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRGoodweatherAc::getMode(void) const { + return _.Mode; +} + +/// Set the Light (LED) Toggle setting of the A/C. +/// @param[in] toggle true, the setting is on. false, the setting is off. +void IRGoodweatherAc::setLight(const bool toggle) { + _.Command = kGoodweatherCmdLight; + _.Light = toggle; +} + +/// Get the Light (LED) Toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGoodweatherAc::getLight(void) const { + return _.Light; +} + +/// Set the Sleep Toggle setting of the A/C. +/// @param[in] toggle true, the setting is on. false, the setting is off. +void IRGoodweatherAc::setSleep(const bool toggle) { + _.Command = kGoodweatherCmdSleep; + _.Sleep = toggle; +} + +/// Get the Sleep Toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGoodweatherAc::getSleep(void) const { + return _.Sleep; +} + +/// Set the Turbo Toggle setting of the A/C. +/// @param[in] toggle true, the setting is on. false, the setting is off. +void IRGoodweatherAc::setTurbo(const bool toggle) { + _.Command = kGoodweatherCmdTurbo; + _.Turbo = toggle; +} + +/// Get the Turbo Toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGoodweatherAc::getTurbo(void) const { + return _.Turbo; +} + +/// Set the Vertical Swing speed of the A/C. +/// @param[in] speed The speed to set the swing to. +void IRGoodweatherAc::setSwing(const uint8_t speed) { + _.Command = kGoodweatherCmdSwing; + switch (speed) { + case kGoodweatherSwingOff: + case kGoodweatherSwingSlow: + case kGoodweatherSwingFast: + _.Swing = speed; + break; + default: + _.Swing = kGoodweatherSwingOff; + } +} + +/// Get the Vertical Swing speed of the A/C. +/// @return The native swing speed setting. +uint8_t IRGoodweatherAc::getSwing(void) const { + return _.Swing; +} + +/// Set the remote Command type/button pressed. +/// @param[in] cmd The command/button that was issued/pressed. +void IRGoodweatherAc::setCommand(const uint8_t cmd) { + if (cmd <= kGoodweatherCmdLight) + _.Command = cmd; +} + +/// Get the Command type/button pressed from the current settings +/// @return The command/button that was issued/pressed. +uint8_t IRGoodweatherAc::getCommand(void) const { + return _.Command; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRGoodweatherAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kGoodweatherCool; + case stdAc::opmode_t::kHeat: return kGoodweatherHeat; + case stdAc::opmode_t::kDry: return kGoodweatherDry; + case stdAc::opmode_t::kFan: return kGoodweatherFan; + default: return kGoodweatherAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRGoodweatherAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kGoodweatherFanLow; + case stdAc::fanspeed_t::kMedium: return kGoodweatherFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kGoodweatherFanHigh; + default: return kGoodweatherFanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] swingv The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRGoodweatherAc::convertSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: return kGoodweatherSwingFast; + case stdAc::swingv_t::kLow: + case stdAc::swingv_t::kLowest: + case stdAc::swingv_t::kAuto: return kGoodweatherSwingSlow; + default: return kGoodweatherSwingOff; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRGoodweatherAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kGoodweatherCool: return stdAc::opmode_t::kCool; + case kGoodweatherHeat: return stdAc::opmode_t::kHeat; + case kGoodweatherDry: return stdAc::opmode_t::kDry; + case kGoodweatherFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRGoodweatherAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kGoodweatherFanHigh: return stdAc::fanspeed_t::kMax; + case kGoodweatherFanMed: return stdAc::fanspeed_t::kMedium; + case kGoodweatherFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRGoodweatherAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::GOODWEATHER; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = (_.Swing == kGoodweatherSwingOff ? + stdAc::swingv_t::kOff : stdAc::swingv_t::kAuto); + result.turbo = _.Turbo; + result.light = _.Light; + result.sleep = _.Sleep ? 0: -1; + // Not supported. + result.model = -1; + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.econo = false; + result.filter = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRGoodweatherAc::toString(void) const { + String result = ""; + result.reserve(150); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kGoodweatherAuto, kGoodweatherCool, + kGoodweatherHeat, kGoodweatherDry, kGoodweatherFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kGoodweatherFanHigh, kGoodweatherFanLow, + kGoodweatherFanAuto, kGoodweatherFanAuto, + kGoodweatherFanMed); + + result += addToggleToString(_.Turbo, kTurboStr); + result += addToggleToString(_.Light, kLightStr); + result += addToggleToString(_.Sleep, kSleepStr); + result += addIntToString(_.Swing, kSwingStr); + result += kSpaceLBraceStr; + switch (_.Swing) { + case kGoodweatherSwingFast: + result += kFastStr; + break; + case kGoodweatherSwingSlow: + result += kSlowStr; + break; + case kGoodweatherSwingOff: + result += kOffStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addIntToString(_.Command, kCommandStr); + result += kSpaceLBraceStr; + switch (_.Command) { + case kGoodweatherCmdPower: + result += kPowerStr; + break; + case kGoodweatherCmdMode: + result += kModeStr; + break; + case kGoodweatherCmdUpTemp: + result += kTempUpStr; + break; + case kGoodweatherCmdDownTemp: + result += kTempDownStr; + break; + case kGoodweatherCmdSwing: + result += kSwingStr; + break; + case kGoodweatherCmdFan: + result += kFanStr; + break; + case kGoodweatherCmdTimer: + result += kTimerStr; + break; + case kGoodweatherCmdAirFlow: + result += kAirFlowStr; + break; + case kGoodweatherCmdHold: + result += kHoldStr; + break; + case kGoodweatherCmdSleep: + result += kSleepStr; + break; + case kGoodweatherCmdTurbo: + result += kTurboStr; + break; + case kGoodweatherCmdLight: + result += kLightStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + return result; +} + +#if DECODE_GOODWEATHER +/// Decode the supplied Goodweather message. +/// Status: BETA / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeGoodweather(decode_results* results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * (2 * nbits) + kHeader + 2 * kFooter - 1 + offset) + return false; // Can't possibly be a valid Goodweather message. + if (strict && nbits != kGoodweatherBits) + return false; // Not strictly a Goodweather message. + + uint64_t dataSoFar = 0; + uint16_t dataBitsSoFar = 0; + match_result_t data_result; + + // Header + if (!matchMark(results->rawbuf[offset++], kGoodweatherHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kGoodweatherHdrSpace)) + return false; + + // Data + for (; offset <= results->rawlen - 32 && dataBitsSoFar < nbits; + dataBitsSoFar += 8) { + DPRINT("DEBUG: Attempting Byte #"); + DPRINTLN(dataBitsSoFar / 8); + // Read in a byte at a time. + // Normal first. + data_result = matchData(&(results->rawbuf[offset]), 8, + kGoodweatherBitMark, kGoodweatherOneSpace, + kGoodweatherBitMark, kGoodweatherZeroSpace, + _tolerance + kGoodweatherExtraTolerance, + kMarkExcess, false); + if (data_result.success == false) return false; + DPRINTLN("DEBUG: Normal byte read okay."); + offset += data_result.used; + uint8_t data = (uint8_t)data_result.data; + // Then inverted. + data_result = matchData(&(results->rawbuf[offset]), 8, + kGoodweatherBitMark, kGoodweatherOneSpace, + kGoodweatherBitMark, kGoodweatherZeroSpace, + _tolerance + kGoodweatherExtraTolerance, + kMarkExcess, false); + if (data_result.success == false) return false; + DPRINTLN("DEBUG: Inverted byte read okay."); + offset += data_result.used; + uint8_t inverted = (uint8_t)data_result.data; + DPRINT("DEBUG: data = "); + DPRINTLN((uint16_t)data); + DPRINT("DEBUG: inverted = "); + DPRINTLN((uint16_t)inverted); + if (data != (inverted ^ 0xFF)) return false; // Data integrity failed. + dataSoFar |= (uint64_t)data << dataBitsSoFar; + } + + // Footer. + if (!matchMark(results->rawbuf[offset++], kGoodweatherBitMark, + _tolerance + kGoodweatherExtraTolerance)) return false; + if (!matchSpace(results->rawbuf[offset++], kGoodweatherHdrSpace)) + return false; + if (!matchMark(results->rawbuf[offset++], kGoodweatherBitMark, + _tolerance + kGoodweatherExtraTolerance)) return false; + if (offset <= results->rawlen && + !matchAtLeast(results->rawbuf[offset], kGoodweatherHdrSpace)) + return false; + + // Compliance + if (strict && (dataBitsSoFar != kGoodweatherBits)) return false; + + // Success + results->decode_type = decode_type_t::GOODWEATHER; + results->bits = dataBitsSoFar; + results->value = dataSoFar; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_GOODWEATHER diff --git a/src/libraries/IRremoteESP8266/src/ir_Goodweather.h b/src/libraries/IRremoteESP8266/src/ir_Goodweather.h new file mode 100644 index 000000000..94a453c84 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Goodweather.h @@ -0,0 +1,154 @@ +// Copyright 2019 ribeirodanielf +// Copyright 2019 David Conran + +/// @file +/// @brief Support for Goodweather compatible HVAC protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/697 + +// Supports: +// Brand: Goodweather, Model: ZH/JT-03 remote + +#ifndef IR_GOODWEATHER_H_ +#define IR_GOODWEATHER_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Goodweather A/C message. +union GoodweatherProtocol { + uint64_t raw; ///< The state of the IR remote in IR code form. + struct { + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t Light :1; + uint8_t :2; + uint8_t Turbo :1; + uint8_t :0; + // Byte 2 + uint8_t Command :4; + uint8_t :0; + // Byte 3 + uint8_t Sleep :1; + uint8_t Power :1; + uint8_t Swing :2; + uint8_t AirFlow :1; + uint8_t Fan :2; + uint8_t :0; + // Byte 4 + uint8_t Temp :4; + uint8_t :1; + uint8_t Mode :3; + uint8_t :0; + }; +}; + +// Constants +// Timing +const uint16_t kGoodweatherBitMark = 580; +const uint16_t kGoodweatherOneSpace = 580; +const uint16_t kGoodweatherZeroSpace = 1860; +const uint16_t kGoodweatherHdrMark = 6820; +const uint16_t kGoodweatherHdrSpace = 6820; +const uint8_t kGoodweatherExtraTolerance = 12; // +12% extra + +// Modes +const uint8_t kGoodweatherAuto = 0b000; +const uint8_t kGoodweatherCool = 0b001; +const uint8_t kGoodweatherDry = 0b010; +const uint8_t kGoodweatherFan = 0b011; +const uint8_t kGoodweatherHeat = 0b100; +// Swing +const uint8_t kGoodweatherSwingFast = 0b00; +const uint8_t kGoodweatherSwingSlow = 0b01; +const uint8_t kGoodweatherSwingOff = 0b10; +// Fan Control +const uint8_t kGoodweatherFanAuto = 0b00; +const uint8_t kGoodweatherFanHigh = 0b01; +const uint8_t kGoodweatherFanMed = 0b10; +const uint8_t kGoodweatherFanLow = 0b11; +// Temperature +const uint8_t kGoodweatherTempMin = 16; // Celsius +const uint8_t kGoodweatherTempMax = 31; // Celsius +// Commands +const uint8_t kGoodweatherCmdPower = 0x00; +const uint8_t kGoodweatherCmdMode = 0x01; +const uint8_t kGoodweatherCmdUpTemp = 0x02; +const uint8_t kGoodweatherCmdDownTemp = 0x03; +const uint8_t kGoodweatherCmdSwing = 0x04; +const uint8_t kGoodweatherCmdFan = 0x05; +const uint8_t kGoodweatherCmdTimer = 0x06; +const uint8_t kGoodweatherCmdAirFlow = 0x07; +const uint8_t kGoodweatherCmdHold = 0x08; +const uint8_t kGoodweatherCmdSleep = 0x09; +const uint8_t kGoodweatherCmdTurbo = 0x0A; +const uint8_t kGoodweatherCmdLight = 0x0B; +// PAD EOF +const uint64_t kGoodweatherStateInit = 0xD50000000000; + + +// Classes +/// Class for handling detailed Goodweather A/C messages. +class IRGoodweatherAc { + public: + explicit IRGoodweatherAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_GOODWEATHER + void send(const uint16_t repeat = kGoodweatherMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_GOODWEATHER + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwing(const uint8_t speed); + uint8_t getSwing(void) const; + void setSleep(const bool toggle); + bool getSleep(void) const; + void setTurbo(const bool toggle); + bool getTurbo(void) const; + void setLight(const bool toggle); + bool getLight(void) const; + void setCommand(const uint8_t cmd); + uint8_t getCommand(void) const; + uint64_t getRaw(void); + void setRaw(const uint64_t state); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t swingv); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + GoodweatherProtocol _; +}; +#endif // IR_GOODWEATHER_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Gorenje.cpp b/src/libraries/IRremoteESP8266/src/ir_Gorenje.cpp new file mode 100644 index 000000000..f93e4e231 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Gorenje.cpp @@ -0,0 +1,71 @@ +// Copyright 2022 Mateusz Bronk (mbronk) +/// @file +/// @brief Support for the Gorenje cooker hood IR protocols. +/// @see https://techfresh.pl/wp-content/uploads/2017/08/Gorenje-DKF-2600-MWT.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1887 + +// Supports: +// Brand: Gorenje, Model: DKF 2600 MWT Cooker Hood + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +const uint32_t kGorenjeMinGap = 100000U; // 0.1s +const uint16_t kGorenjeHdrMark = 0; +const uint32_t kGorenjeHdrSpace = 0; +const uint16_t kGorenjeBitMark = 1300; +const uint32_t kGorenjeOneSpace = 5700; +const uint32_t kGorenjeZeroSpace = 1700; +const uint16_t kGorenjeFreq = 38000; // Hz +const uint16_t kGorenjeTolerance = 7; // % + +#if SEND_GORENJE +/// Send a Gorenje Cooker Hood formatted message. +/// Status: STABLE / Known working. +/// @param[in] data containing the IR command to be sent. +/// @param[in] nbits Nr. of bits of the message to send. usually kGorenjeBits +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendGorenje(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kGorenjeHdrMark, kGorenjeHdrSpace, + kGorenjeBitMark, kGorenjeOneSpace, + kGorenjeBitMark, kGorenjeZeroSpace, + kGorenjeBitMark, kGorenjeMinGap, + data, nbits, kGorenjeFreq, true, repeat, kDutyDefault); +} +#endif // SEND_GORENJE + +#if DECODE_GORENJE +/// Decode the supplied Gorenje Cooker Hood message. +/// Status: STABLE / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the +/// decoded result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeGorenje(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kGorenjeBits) + return false; // We expect Gorenje to be a certain sized message. + + uint64_t data = 0; + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kGorenjeHdrMark, kGorenjeHdrSpace, + kGorenjeBitMark, kGorenjeOneSpace, + kGorenjeBitMark, kGorenjeZeroSpace, + kGorenjeBitMark, kGorenjeMinGap, + true, kGorenjeTolerance)) return false; + + // Matched! + results->bits = nbits; + results->value = data; + results->decode_type = decode_type_t::GORENJE; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_GORENJE diff --git a/src/libraries/IRremoteESP8266/src/ir_Gree.cpp b/src/libraries/IRremoteESP8266/src/ir_Gree.cpp new file mode 100644 index 000000000..3bb3a68d3 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Gree.cpp @@ -0,0 +1,751 @@ +// Copyright 2017 Ville Skyttä (scop) +// Copyright 2017, 2018 David Conran + +/// @file +/// @brief Support for Gree A/C protocols. +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.h +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1508 + +#include "ir_Gree.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "ir_Kelvinator.h" +#include "minmax.h" + +// Constants +const uint16_t kGreeHdrMark = 9000; +const uint16_t kGreeHdrSpace = 4500; ///< See #684 & real example in unit tests +const uint16_t kGreeBitMark = 620; +const uint16_t kGreeOneSpace = 1600; +const uint16_t kGreeZeroSpace = 540; +const uint16_t kGreeMsgSpace = 19980; ///< See #1508, #386, & Kelvinator +const uint8_t kGreeBlockFooter = 0b010; +const uint8_t kGreeBlockFooterBits = 3; + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addFanToString; +using irutils::addSwingHToString; +using irutils::addTempToString; +using irutils::minsToString; + +#if SEND_GREE +/// Send a Gree Heat Pump formatted message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendGree(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kGreeStateLength) + return; // Not enough bytes to send a proper message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Block #1 + sendGeneric(kGreeHdrMark, kGreeHdrSpace, kGreeBitMark, kGreeOneSpace, + kGreeBitMark, kGreeZeroSpace, 0, 0, // No Footer. + data, 4, 38, false, 0, 50); + // Footer #1 + sendGeneric(0, 0, // No Header + kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace, + kGreeBitMark, kGreeMsgSpace, 0b010, 3, 38, false, 0, 50); + + // Block #2 + sendGeneric(0, 0, // No Header for Block #2 + kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace, + kGreeBitMark, kGreeMsgSpace, data + 4, nbytes - 4, 38, false, 0, + 50); + } +} + +/// Send a Gree Heat Pump formatted message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendGree(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + if (nbits != kGreeBits) + return; // Wrong nr. of bits to send a proper message. + // Set IR carrier frequency + enableIROut(38); + + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kGreeHdrMark); + space(kGreeHdrSpace); + + // Data + for (int16_t i = 8; i <= nbits; i += 8) { + sendData(kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace, + (data >> (nbits - i)) & 0xFF, 8, false); + if (i == nbits / 2) { + // Send the mid-message Footer. + sendData(kGreeBitMark, kGreeOneSpace, kGreeBitMark, kGreeZeroSpace, + 0b010, 3); + mark(kGreeBitMark); + space(kGreeMsgSpace); + } + } + // Footer + mark(kGreeBitMark); + space(kGreeMsgSpace); + } +} +#endif // SEND_GREE + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] model The enum of the model to be emulated. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRGreeAC::IRGreeAC(const uint16_t pin, const gree_ac_remote_model_t model, + const bool inverted, const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { + stateReset(); + setModel(model); +} + +/// Reset the internal state to a fixed known good state. +void IRGreeAC::stateReset(void) { + // This resets to a known-good state to Power Off, Fan Auto, Mode Auto, 25C. + memset(_.remote_state, 0, sizeof _.remote_state); + _.Temp = 9; // _.remote_state[1] = 0x09; + _.Light = true; // _.remote_state[2] = 0x20; + _.unknown1 = 5; // _.remote_state[3] = 0x50; + _.unknown2 = 4; // _.remote_state[5] = 0x20; +} + +/// Fix up the internal state so it is correct. +/// @note Internal use only. +void IRGreeAC::fixup(void) { + setPower(getPower()); // Redo the power bits as they differ between models. + checksum(); // Calculate the checksums +} + +/// Set up hardware to be able to send a message. +void IRGreeAC::begin(void) { _irsend.begin(); } + +#if SEND_GREE +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRGreeAC::send(const uint16_t repeat) { + _irsend.sendGree(getRaw(), kGreeStateLength, repeat); +} +#endif // SEND_GREE + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRGreeAC::getRaw(void) { + fixup(); // Ensure correct settings before sending. + return _.remote_state; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRGreeAC::setRaw(const uint8_t new_code[]) { + memcpy(_.remote_state, new_code, kGreeStateLength); + // We can only detect the difference between models when the power is on. + if (_.Power) { + if (_.ModelA) + _model = gree_ac_remote_model_t::YAW1F; + else + _model = gree_ac_remote_model_t::YBOFB; + } + if (_.Mode == kGreeEcono) _model = gree_ac_remote_model_t::YX1FSF; +} + +/// Calculate and set the checksum values for the internal state. +/// @param[in] length The size/length of the state array to fix the checksum of. +void IRGreeAC::checksum(const uint16_t length) { + // Gree uses the same checksum alg. as Kelvinator's block checksum. + _.Sum = IRKelvinatorAC::calcBlockChecksum(_.remote_state, length); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRGreeAC::validChecksum(const uint8_t state[], const uint16_t length) { + // Top 4 bits of the last byte in the state is the state's checksum. + return GETBITS8(state[length - 1], kHighNibble, kNibbleSize) == + IRKelvinatorAC::calcBlockChecksum(state, length); +} + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRGreeAC::setModel(const gree_ac_remote_model_t model) { + switch (model) { + case gree_ac_remote_model_t::YAW1F: + case gree_ac_remote_model_t::YBOFB: + case gree_ac_remote_model_t::YX1FSF: _model = model; break; + default: _model = gree_ac_remote_model_t::YAW1F; + } +} + +/// Get/Detect the model of the A/C. +/// @return The enum of the compatible model. +gree_ac_remote_model_t IRGreeAC::getModel(void) const { return _model; } + +/// Change the power setting to On. +void IRGreeAC::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRGreeAC::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/814 +void IRGreeAC::setPower(const bool on) { + _.Power = on; + // May not be needed. See #814 + _.ModelA = (on && _model == gree_ac_remote_model_t::YAW1F); +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/814 +bool IRGreeAC::getPower(void) const { + // See #814. Not checking/requiring: (_.ModelA) + return _.Power; +} + +/// Set the default temperature units to use. +/// @param[in] on Use Fahrenheit as the units. +/// true is Fahrenheit, false is Celsius. +void IRGreeAC::setUseFahrenheit(const bool on) { _.UseFahrenheit = on; } + +/// Get the default temperature units in use. +/// @return true is Fahrenheit, false is Celsius. +bool IRGreeAC::getUseFahrenheit(void) const { return _.UseFahrenheit; } + +/// Set the temp. in degrees +/// @param[in] temp Desired temperature in Degrees. +/// @param[in] fahrenheit Use units of Fahrenheit and set that as units used. +/// false is Celsius (Default), true is Fahrenheit. +/// @note The unit actually works in Celsius with a special optional +/// "extra degree" when sending Fahrenheit. +void IRGreeAC::setTemp(const uint8_t temp, const bool fahrenheit) { + float safecelsius = temp; + if (fahrenheit) + // Covert to F, and add a fudge factor to round to the expected degree. + // Why 0.6 you ask?! Because it works. Ya'd thing 0.5 would be good for + // rounding, but Noooooo! + safecelsius = fahrenheitToCelsius(temp + 0.6); + setUseFahrenheit(fahrenheit); // Set the correct Temp units. + + // Make sure we have desired temp in the correct range. + safecelsius = ::max(static_cast(kGreeMinTempC), safecelsius); + safecelsius = ::min(static_cast(kGreeMaxTempC), safecelsius); + // An operating mode of Auto locks the temp to a specific value. Do so. + if (_.Mode == kGreeAuto) safecelsius = 25; + + // Set the "main" Celsius degrees. + _.Temp = safecelsius - kGreeMinTempC; + // Deal with the extra degree fahrenheit difference. + _.TempExtraDegreeF = (static_cast(safecelsius * 2) & 1); +} + +/// Get the set temperature +/// @return The temperature in degrees in the current units (C/F) set. +uint8_t IRGreeAC::getTemp(void) const { + uint8_t deg = kGreeMinTempC + _.Temp; + if (_.UseFahrenheit) { + deg = celsiusToFahrenheit(deg); + // Retrieve the "extra" fahrenheit from elsewhere in the code. + if (_.TempExtraDegreeF) deg++; + deg = ::max(deg, kGreeMinTempF); // Cover the fact that 61F is < 16C + } + return deg; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. 0 is auto, 1-3 is the speed. +void IRGreeAC::setFan(const uint8_t speed) { + uint8_t fan = ::min(kGreeFanMax, speed); // Bounds check + if (_.Mode == kGreeDry) fan = 1; // DRY mode is always locked to fan 1. + // Set the basic fan values. + _.Fan = fan; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRGreeAC::getFan(void) const { return _.Fan; } + +/// Set the operating mode of the A/C. +/// @param[in] new_mode The desired operating mode. +void IRGreeAC::setMode(const uint8_t new_mode) { + uint8_t mode = new_mode; + switch (mode) { + // AUTO is locked to 25C + case kGreeAuto: setTemp(25); break; + // DRY always sets the fan to 1. + case kGreeDry: setFan(1); break; + case kGreeCool: + case kGreeFan: + case kGreeEcono: + case kGreeHeat: break; + // If we get an unexpected mode, default to AUTO. + default: mode = kGreeAuto; + } + _.Mode = mode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRGreeAC::getMode(void) const { return _.Mode; } + +/// Set the Light (LED) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setLight(const bool on) { _.Light = on; } + +/// Get the Light (LED) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getLight(void) const { return _.Light; } + +/// Set the IFeel setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setIFeel(const bool on) { _.IFeel = on; } + +/// Get the IFeel setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getIFeel(void) const { return _.IFeel; } + +/// Set the Wifi (enabled) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setWiFi(const bool on) { _.WiFi = on; } + +/// Get the Wifi (enabled) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getWiFi(void) const { return _.WiFi; } + +/// Set the XFan (Mould) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setXFan(const bool on) { _.Xfan = on; } + +/// Get the XFan (Mould) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getXFan(void) const { return _.Xfan; } + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setSleep(const bool on) { _.Sleep = on; } + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getSleep(void) const { return _.Sleep; } + +/// Set the Turbo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setTurbo(const bool on) { _.Turbo = on; } + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getTurbo(void) const { return _.Turbo; } + +/// Set the Econo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setEcono(const bool on) { + _.Econo = on; + if (on && getModel() == gree_ac_remote_model_t::YX1FSF) + setMode(kGreeEcono); +} + +/// Get the Econo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getEcono(void) const { + return _.Econo || getMode() == kGreeEcono; +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] automatic Do we use the automatic setting? +/// @param[in] position The position/mode to set the vanes to. +void IRGreeAC::setSwingVertical(const bool automatic, const uint8_t position) { + _.SwingAuto = automatic; + uint8_t new_position = position; + if (!automatic) { + switch (position) { + case kGreeSwingUp: + case kGreeSwingMiddleUp: + case kGreeSwingMiddle: + case kGreeSwingMiddleDown: + case kGreeSwingDown: + break; + default: + new_position = kGreeSwingLastPos; + } + } else { + switch (position) { + case kGreeSwingAuto: + case kGreeSwingDownAuto: + case kGreeSwingMiddleAuto: + case kGreeSwingUpAuto: + break; + default: + new_position = kGreeSwingAuto; + } + } + _.SwingV = new_position; +} + +/// Get the Vertical Swing Automatic mode setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getSwingVerticalAuto(void) const { return _.SwingAuto; } + +/// Get the Vertical Swing position setting of the A/C. +/// @return The native position/mode. +uint8_t IRGreeAC::getSwingVerticalPosition(void) const { return _.SwingV; } + +/// Get the Horizontal Swing position setting of the A/C. +/// @return The native position/mode. +uint8_t IRGreeAC::getSwingHorizontal(void) const { return _.SwingH; } + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] position The position/mode to set the vanes to. +void IRGreeAC::setSwingHorizontal(const uint8_t position) { + if (position <= kGreeSwingHMaxRight) + _.SwingH = position; + else + _.SwingH = kGreeSwingHOff; +} + +/// Set the timer enable setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRGreeAC::setTimerEnabled(const bool on) { _.TimerEnabled = on; } + +/// Get the timer enabled setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRGreeAC::getTimerEnabled(void) const { return _.TimerEnabled; } + +/// Get the timer time value from the A/C. +/// @return The number of minutes the timer is set for. +uint16_t IRGreeAC::getTimer(void) const { + uint16_t hrs = irutils::bcdToUint8((_.TimerTensHr << kNibbleSize) | + _.TimerHours); + return hrs * 60 + (_.TimerHalfHr ? 30 : 0); +} + +/// Set the A/C's timer to turn off in X many minutes. +/// @param[in] minutes The number of minutes the timer should be set for. +/// @note Stores time internally in 30 min units. +/// e.g. 5 mins means 0 (& Off), 95 mins is 90 mins (& On). Max is 24 hours. +void IRGreeAC::setTimer(const uint16_t minutes) { + uint16_t mins = ::min(kGreeTimerMax, minutes); // Bounds check. + setTimerEnabled(mins >= 30); // Timer is enabled when >= 30 mins. + uint8_t hours = mins / 60; + // Set the half hour bit. + _.TimerHalfHr = (mins % 60) >= 30; + // Set the "tens" digit of hours. + _.TimerTensHr = hours / 10; + // Set the "units" digit of hours. + _.TimerHours = hours % 10; +} + +/// Set temperature display mode. +/// i.e. Internal, External temperature sensing. +/// @param[in] mode The desired temp source to display. +/// @note In order for the A/C unit properly accept these settings. You must +/// cycle (send) in the following order: +/// kGreeDisplayTempOff(0) -> kGreeDisplayTempSet(1) -> +/// kGreeDisplayTempInside(2) ->kGreeDisplayTempOutside(3) -> +/// kGreeDisplayTempOff(0). +/// The unit will no behave correctly if the changes of this setting are sent +/// out of order. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1118#issuecomment-628242152 +void IRGreeAC::setDisplayTempSource(const uint8_t mode) { + _.DisplayTemp = mode; +} + +/// Get the temperature display mode. +/// i.e. Internal, External temperature sensing. +/// @return The current temp source being displayed. +uint8_t IRGreeAC::getDisplayTempSource(void) const { return _.DisplayTemp; } + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRGreeAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kGreeCool; + case stdAc::opmode_t::kHeat: return kGreeHeat; + case stdAc::opmode_t::kDry: return kGreeDry; + case stdAc::opmode_t::kFan: return kGreeFan; + default: return kGreeAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRGreeAC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kGreeFanMin; + case stdAc::fanspeed_t::kLow: + case stdAc::fanspeed_t::kMedium: return kGreeFanMax - 1; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kGreeFanMax; + default: return kGreeFanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] swingv The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRGreeAC::convertSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHighest: return kGreeSwingUp; + case stdAc::swingv_t::kHigh: return kGreeSwingMiddleUp; + case stdAc::swingv_t::kMiddle: return kGreeSwingMiddle; + case stdAc::swingv_t::kLow: return kGreeSwingMiddleDown; + case stdAc::swingv_t::kLowest: return kGreeSwingDown; + default: return kGreeSwingAuto; + } +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] swingh The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRGreeAC::convertSwingH(const stdAc::swingh_t swingh) { + switch (swingh) { + case stdAc::swingh_t::kAuto: return kGreeSwingHAuto; + case stdAc::swingh_t::kLeftMax: return kGreeSwingHMaxLeft; + case stdAc::swingh_t::kLeft: return kGreeSwingHLeft; + case stdAc::swingh_t::kMiddle: return kGreeSwingHMiddle; + case stdAc::swingh_t::kRight: return kGreeSwingHRight; + case stdAc::swingh_t::kRightMax: return kGreeSwingHMaxRight; + default: return kGreeSwingHOff; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRGreeAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kGreeCool: return stdAc::opmode_t::kCool; + case kGreeHeat: return stdAc::opmode_t::kHeat; + case kGreeDry: return stdAc::opmode_t::kDry; + case kGreeFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRGreeAC::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kGreeFanMax: return stdAc::fanspeed_t::kMax; + case kGreeFanMax - 1: return stdAc::fanspeed_t::kMedium; + case kGreeFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a native Vertical Swing into its stdAc equivalent. +/// @param[in] pos The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::swingv_t IRGreeAC::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kGreeSwingUp: return stdAc::swingv_t::kHighest; + case kGreeSwingMiddleUp: return stdAc::swingv_t::kHigh; + case kGreeSwingMiddle: return stdAc::swingv_t::kMiddle; + case kGreeSwingMiddleDown: return stdAc::swingv_t::kLow; + case kGreeSwingDown: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert a native Horizontal Swing into its stdAc equivalent. +/// @param[in] pos The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::swingh_t IRGreeAC::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kGreeSwingHAuto: return stdAc::swingh_t::kAuto; + case kGreeSwingHMaxLeft: return stdAc::swingh_t::kLeftMax; + case kGreeSwingHLeft: return stdAc::swingh_t::kLeft; + case kGreeSwingHMiddle: return stdAc::swingh_t::kMiddle; + case kGreeSwingHRight: return stdAc::swingh_t::kRight; + case kGreeSwingHMaxRight: return stdAc::swingh_t::kRightMax; + default: return stdAc::swingh_t::kOff; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRGreeAC::toCommon(void) { + stdAc::state_t result{}; + result.protocol = decode_type_t::GREE; + result.model = _model; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = !_.UseFahrenheit; + result.degrees = getTemp(); + // no support for Sensor temp. + result.iFeel = getIFeel(); + result.fanspeed = toCommonFanSpeed(_.Fan); + if (_.SwingAuto) + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = toCommonSwingV(_.SwingV); + result.swingh = toCommonSwingH(_.SwingH); + result.turbo = _.Turbo; + result.econo = getEcono(); + result.light = _.Light; + result.clean = _.Xfan; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.quiet = false; + result.filter = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRGreeAC::toString(void) { + String result = ""; + result.reserve(220); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::GREE, _model, false); + result += addBoolToString(_.Power, kPowerStr); + if (_model == gree_ac_remote_model_t::YX1FSF && _.Mode == kGreeEcono) { + result += addIntToString(_.Mode, kModeStr); + result += kSpaceLBraceStr; + result += kEconoStr; + result += ')'; + } else { + result += addModeToString(_.Mode, kGreeAuto, kGreeCool, kGreeHeat, + kGreeDry, kGreeFan); + } + result += addTempToString(getTemp(), !_.UseFahrenheit); + result += addFanToString(_.Fan, kGreeFanMax, kGreeFanMin, kGreeFanAuto, + kGreeFanAuto, kGreeFanMed); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addBoolToString(_.IFeel, kIFeelStr); + result += addBoolToString(_.WiFi, kWifiStr); + result += addBoolToString(_.Xfan, kXFanStr); + result += addBoolToString(_.Light, kLightStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(_.SwingAuto ? kAutoStr : kManualStr, + kSwingVModeStr); + result += addIntToString(_.SwingV, kSwingVStr); + result += kSpaceLBraceStr; + switch (_.SwingV) { + case kGreeSwingLastPos: + result += kLastStr; + break; + case kGreeSwingAuto: + result += kAutoStr; + break; + default: result += kUnknownStr; + } + result += ')'; + result += addSwingHToString(_.SwingH, kGreeSwingHAuto, kGreeSwingHMaxLeft, + kGreeSwingHLeft, kGreeSwingHMiddle, + kGreeSwingHRight, kGreeSwingHMaxRight, + kGreeSwingHOff, + // rest are unused. + 0xFF, 0xFF, 0xFF, 0xFF); + result += addLabeledString( + _.TimerEnabled ? minsToString(getTimer()) : kOffStr, kTimerStr); + uint8_t src = _.DisplayTemp; + result += addIntToString(src, kDisplayTempStr); + result += kSpaceLBraceStr; + switch (src) { + case kGreeDisplayTempOff: + result += kOffStr; + break; + case kGreeDisplayTempSet: + result += kSetStr; + break; + case kGreeDisplayTempInside: + result += kInsideStr; + break; + case kGreeDisplayTempOutside: + result += kOutsideStr; + break; + default: result += kUnknownStr; + } + result += ')'; + return result; +} + +#if DECODE_GREE +/// Decode the supplied Gree HVAC message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeGree(decode_results* results, uint16_t offset, + const uint16_t nbits, bool const strict) { + if (results->rawlen <= + 2 * (nbits + kGreeBlockFooterBits) + (kHeader + kFooter + 1) - 1 + offset) + return false; // Can't possibly be a valid Gree message. + if (strict && nbits != kGreeBits) + return false; // Not strictly a Gree message. + + // There are two blocks back-to-back in a full Gree IR message + // sequence. + + uint16_t used; + // Header + Data Block #1 (32 bits) + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits / 2, + kGreeHdrMark, kGreeHdrSpace, + kGreeBitMark, kGreeOneSpace, + kGreeBitMark, kGreeZeroSpace, + 0, 0, false, + _tolerance, kMarkExcess, false); + if (used == 0) return false; + offset += used; + + // Block #1 footer (3 bits, B010) + match_result_t data_result; + data_result = matchData(&(results->rawbuf[offset]), kGreeBlockFooterBits, + kGreeBitMark, kGreeOneSpace, kGreeBitMark, + kGreeZeroSpace, _tolerance, kMarkExcess, false); + if (data_result.success == false) return false; + if (data_result.data != kGreeBlockFooter) return false; + offset += data_result.used; + + // Inter-block gap + Data Block #2 (32 bits) + Footer + if (!matchGeneric(results->rawbuf + offset, results->state + 4, + results->rawlen - offset, nbits / 2, + kGreeBitMark, kGreeMsgSpace, + kGreeBitMark, kGreeOneSpace, + kGreeBitMark, kGreeZeroSpace, + kGreeBitMark, kGreeMsgSpace, true, + _tolerance, kMarkExcess, false)) return false; + + // Compliance + if (strict) { + // Verify the message's checksum is correct. + if (!IRGreeAC::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = GREE; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_GREE diff --git a/src/libraries/IRremoteESP8266/src/ir_Gree.h b/src/libraries/IRremoteESP8266/src/ir_Gree.h new file mode 100644 index 000000000..76bb41662 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Gree.h @@ -0,0 +1,240 @@ +// Copyright 2016-2022 David Conran + +/// @file +/// @brief Support for Gree A/C protocols. +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.h +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1508 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1821 + +// Supports: +// Brand: Ultimate, Model: Heat Pump +// Brand: EKOKAI, Model: A/C +// Brand: RusClimate, Model: EACS/I-09HAR_X/N3 A/C +// Brand: RusClimate, Model: YAW1F remote +// Brand: Green, Model: YBOFB remote +// Brand: Green, Model: YBOFB2 remote +// Brand: Gree, Model: YAA1FBF remote +// Brand: Gree, Model: YB1F2F remote +// Brand: Gree, Model: YAN1F1 remote +// Brand: Gree, Model: YX1F2F remote (YX1FSF) +// Brand: Gree, Model: VIR09HP115V1AH A/C +// Brand: Gree, Model: VIR12HP230V1AH A/C +// Brand: Amana, Model: PBC093G00CC A/C +// Brand: Amana, Model: YX1FF remote +// Brand: Cooper & Hunter, Model: YB1F2 remote +// Brand: Cooper & Hunter, Model: CH-S09FTXG A/C +// Brand: Vailland, Model: YACIFB remote +// Brand: Vailland, Model: VAI5-035WNI A/C +// Brand: Soleus Air, Model: window A/C (YX1FSF) + +#ifndef IR_GREE_H_ +#define IR_GREE_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Gree A/C message. +union GreeProtocol{ + uint8_t remote_state[kGreeStateLength]; ///< The state in native IR code form + struct { + // Byte 0 + uint8_t Mode :3; + uint8_t Power :1; + uint8_t Fan :2; + uint8_t SwingAuto :1; + uint8_t Sleep :1; + // Byte 1 + uint8_t Temp :4; + uint8_t TimerHalfHr :1; + uint8_t TimerTensHr :2; + uint8_t TimerEnabled:1; + // Byte 2 + uint8_t TimerHours:4; + uint8_t Turbo :1; + uint8_t Light :1; + uint8_t ModelA :1; // model==YAW1F + uint8_t Xfan :1; + // Byte 3 + uint8_t :2; + uint8_t TempExtraDegreeF:1; + uint8_t UseFahrenheit :1; + uint8_t unknown1 :4; // value=0b0101 + // Byte 4 + uint8_t SwingV :4; + uint8_t SwingH :3; + uint8_t :1; + // Byte 5 + uint8_t DisplayTemp :2; + uint8_t IFeel :1; + uint8_t unknown2 :3; // value = 0b100 + uint8_t WiFi :1; + uint8_t :1; + // Byte 6 + uint8_t :8; + // Byte 7 + uint8_t :2; + uint8_t Econo :1; + uint8_t :1; + uint8_t Sum :4; + }; +}; + +// Constants + +const uint8_t kGreeAuto = 0; +const uint8_t kGreeCool = 1; +const uint8_t kGreeDry = 2; +const uint8_t kGreeFan = 3; +const uint8_t kGreeHeat = 4; +const uint8_t kGreeEcono = 5; + +const uint8_t kGreeFanAuto = 0; +const uint8_t kGreeFanMin = 1; +const uint8_t kGreeFanMed = 2; +const uint8_t kGreeFanMax = 3; + +const uint8_t kGreeMinTempC = 16; // Celsius +const uint8_t kGreeMaxTempC = 30; // Celsius +const uint8_t kGreeMinTempF = 61; // Fahrenheit +const uint8_t kGreeMaxTempF = 86; // Fahrenheit +const uint16_t kGreeTimerMax = 24 * 60; + +const uint8_t kGreeSwingLastPos = 0b0000; // 0 +const uint8_t kGreeSwingAuto = 0b0001; // 1 +const uint8_t kGreeSwingUp = 0b0010; // 2 +const uint8_t kGreeSwingMiddleUp = 0b0011; // 3 +const uint8_t kGreeSwingMiddle = 0b0100; // 4 +const uint8_t kGreeSwingMiddleDown = 0b0101; // 5 +const uint8_t kGreeSwingDown = 0b0110; // 6 +const uint8_t kGreeSwingDownAuto = 0b0111; // 7 +const uint8_t kGreeSwingMiddleAuto = 0b1001; // 9 +const uint8_t kGreeSwingUpAuto = 0b1011; // 11 + +const uint8_t kGreeSwingHOff = 0b000; // 0 +const uint8_t kGreeSwingHAuto = 0b001; // 1 +const uint8_t kGreeSwingHMaxLeft = 0b010; // 2 +const uint8_t kGreeSwingHLeft = 0b011; // 3 +const uint8_t kGreeSwingHMiddle = 0b100; // 4 +const uint8_t kGreeSwingHRight = 0b101; // 5 +const uint8_t kGreeSwingHMaxRight = 0b110; // 6 + +const uint8_t kGreeDisplayTempOff = 0b00; // 0 +const uint8_t kGreeDisplayTempSet = 0b01; // 1 +const uint8_t kGreeDisplayTempInside = 0b10; // 2 +const uint8_t kGreeDisplayTempOutside = 0b11; // 3 + +// Legacy defines. +#define GREE_AUTO kGreeAuto +#define GREE_COOL kGreeCool +#define GREE_DRY kGreeDry +#define GREE_FAN kGreeFan +#define GREE_HEAT kGreeHeat +#define GREE_MIN_TEMP kGreeMinTempC +#define GREE_MAX_TEMP kGreeMaxTempC +#define GREE_FAN_MAX kGreeFanMax +#define GREE_SWING_LAST_POS kGreeSwingLastPos +#define GREE_SWING_AUTO kGreeSwingAuto +#define GREE_SWING_UP kGreeSwingUp +#define GREE_SWING_MIDDLE_UP kGreeSwingMiddleUp +#define GREE_SWING_MIDDLE kGreeSwingMiddle +#define GREE_SWING_MIDDLE_DOWN kGreeSwingMiddleDown +#define GREE_SWING_DOWN kGreeSwingDown +#define GREE_SWING_DOWN_AUTO kGreeSwingDownAuto +#define GREE_SWING_MIDDLE_AUTO kGreeSwingMiddleAuto +#define GREE_SWING_UP_AUTO kGreeSwingUpAuto + +// Classes +/// Class for handling detailed Gree A/C messages. +class IRGreeAC { + public: + explicit IRGreeAC( + const uint16_t pin, + const gree_ac_remote_model_t model = gree_ac_remote_model_t::YAW1F, + const bool inverted = false, const bool use_modulation = true); + void stateReset(void); +#if SEND_GREE + void send(const uint16_t repeat = kGreeDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_GREE + void begin(void); + void on(void); + void off(void); + void setModel(const gree_ac_remote_model_t model); + gree_ac_remote_model_t getModel(void) const; + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp, const bool fahrenheit = false); + uint8_t getTemp(void) const; + void setUseFahrenheit(const bool on); + bool getUseFahrenheit(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t new_mode); + uint8_t getMode(void) const; + void setLight(const bool on); + bool getLight(void) const; + void setXFan(const bool on); + bool getXFan(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setIFeel(const bool on); + bool getIFeel(void) const; + void setWiFi(const bool on); + bool getWiFi(void) const; + void setSwingVertical(const bool automatic, const uint8_t position); + bool getSwingVerticalAuto(void) const; + uint8_t getSwingVerticalPosition(void) const; + void setSwingHorizontal(const uint8_t position); + uint8_t getSwingHorizontal(void) const; + uint16_t getTimer(void) const; + void setTimer(const uint16_t minutes); + void setDisplayTempSource(const uint8_t mode); + uint8_t getDisplayTempSource(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t swingv); + static uint8_t convertSwingH(const stdAc::swingh_t swingh); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + stdAc::state_t toCommon(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kGreeStateLength); + String toString(void); +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + GreeProtocol _; + gree_ac_remote_model_t _model; + void checksum(const uint16_t length = kGreeStateLength); + void fixup(void); + void setTimerEnabled(const bool on); + bool getTimerEnabled(void) const; +}; + +#endif // IR_GREE_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Haier.cpp b/src/libraries/IRremoteESP8266/src/ir_Haier.cpp new file mode 100644 index 000000000..4585224c2 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Haier.cpp @@ -0,0 +1,2170 @@ +// Copyright 2018-2021 crankyoldgit +/// @file +/// @brief Support for Haier A/C protocols. +/// The specifics of reverse engineering the protocols details: +/// * HSU07-HEA03 by kuzin2006. +/// * YR-W02/HSU-09HMC203 by non7top. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/404 +/// @see https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/485 +/// @see https://www.dropbox.com/sh/w0bt7egp0fjger5/AADRFV6Wg4wZskJVdFvzb8Z0a?dl=0&preview=haer2.ods +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1480 + +#include "ir_Haier.h" +// #include +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kHaierAcHdr = 3000; +const uint16_t kHaierAcHdrGap = 4300; +const uint16_t kHaierAcBitMark = 520; +const uint16_t kHaierAcOneSpace = 1650; +const uint16_t kHaierAcZeroSpace = 650; +const uint32_t kHaierAcMinGap = 150000; // Completely made up value. + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addSwingHToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::minsToString; + +#define GETTIME(x) _.x##Hours * 60 + _.x##Mins +#define SETTIME(x, n) do { \ + uint16_t mins = n;\ + if (n > kHaierAcMaxTime) mins = kHaierAcMaxTime;\ + _.x##Hours = mins / 60;\ + _.x##Mins = mins % 60;\ +} while (0) + +#if (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC160 || \ + SEND_HAIER_AC176) +/// Send a Haier A/C formatted message. (HSU07-HEA03 remote) +/// Status: STABLE / Known to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendHaierAC(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHaierACStateLength) return; + + for (uint16_t r = 0; r <= repeat; r++) { + enableIROut(38000); + mark(kHaierAcHdr); + space(kHaierAcHdr); + sendGeneric(kHaierAcHdr, kHaierAcHdrGap, kHaierAcBitMark, kHaierAcOneSpace, + kHaierAcBitMark, kHaierAcZeroSpace, kHaierAcBitMark, + kHaierAcMinGap, data, nbytes, 38, true, + 0, // Repeats handled elsewhere + 50); + } +} +#endif // (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC176) + +#if SEND_HAIER_AC_YRW02 +/// Send a Haier YR-W02 remote A/C formatted message. +/// Status: STABLE / Known to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendHaierACYRW02(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes >= kHaierACYRW02StateLength) sendHaierAC(data, nbytes, repeat); +} +#endif // SEND_HAIER_AC_YRW02 + +#if SEND_HAIER_AC176 +/// Send a Haier 176 bit remote A/C formatted message. +/// Status: STABLE / Known to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendHaierAC176(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes >= kHaierAC176StateLength) sendHaierAC(data, nbytes, repeat); +} +#endif // SEND_HAIER_AC176 + +#if SEND_HAIER_AC160 +/// Send a Haier 160 bit remote A/C formatted message. +/// Status: STABLE / Known to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendHaierAC160(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes >= kHaierAC160StateLength) sendHaierAC(data, nbytes, repeat); +} +#endif // SEND_HAIER_AC160 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHaierAC::IRHaierAC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRHaierAC::begin(void) { _irsend.begin(); } + +#if SEND_HAIER_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHaierAC::send(const uint16_t repeat) { + _irsend.sendHaierAC(getRaw(), kHaierACStateLength, repeat); +} +#endif // SEND_HAIER_AC + +/// Calculate and set the checksum values for the internal state. +void IRHaierAC::checksum(void) { + _.Sum = sumBytes(_.remote_state, kHaierACStateLength - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRHaierAC::validChecksum(uint8_t state[], const uint16_t length) { + if (length < 2) return false; // 1 byte of data can't have a checksum. + return (state[length - 1] == sumBytes(state, length - 1)); +} + +/// Reset the internal state to a fixed known good state. +void IRHaierAC::stateReset(void) { + memset(_.remote_state, 0, sizeof _.remote_state); + _.Prefix = kHaierAcPrefix; + _.unknown = 1; // const value + _.OffHours = 12; // default initial state + _.Temp = kHaierAcDefTemp - kHaierAcMinTemp; + _.Fan = 3; // kHaierAcFanLow; + _.Command = kHaierAcCmdOn; +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRHaierAC::getRaw(void) { + checksum(); + return _.remote_state; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRHaierAC::setRaw(const uint8_t new_code[]) { + memcpy(_.remote_state, new_code, kHaierACStateLength); +} + +/// Set the Command/Button setting of the A/C. +/// @param[in] command The value of the command/button that was pressed. +void IRHaierAC::setCommand(const uint8_t command) { + switch (command) { + case kHaierAcCmdOff: + case kHaierAcCmdOn: + case kHaierAcCmdMode: + case kHaierAcCmdFan: + case kHaierAcCmdTempUp: + case kHaierAcCmdTempDown: + case kHaierAcCmdSleep: + case kHaierAcCmdTimerSet: + case kHaierAcCmdTimerCancel: + case kHaierAcCmdHealth: + case kHaierAcCmdSwing: + _.Command = command; + } +} + +/// Get the Command/Button setting of the A/C. +/// @return The value of the command/button that was pressed. +uint8_t IRHaierAC::getCommand(void) const { + return _.Command; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHaierAC::setFan(const uint8_t speed) { + uint8_t new_speed = kHaierAcFanAuto; + switch (speed) { + case kHaierAcFanLow: new_speed = 3; break; + case kHaierAcFanMed: new_speed = 2; break; + case kHaierAcFanHigh: new_speed = 1; break; + // Default to auto for anything else. + default: new_speed = kHaierAcFanAuto; + } + + if (speed != getFan()) _.Command = kHaierAcCmdFan; + _.Fan = new_speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHaierAC::getFan(void) const { + switch (_.Fan) { + case 1: return kHaierAcFanHigh; + case 2: return kHaierAcFanMed; + case 3: return kHaierAcFanLow; + default: return kHaierAcFanAuto; + } +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHaierAC::setMode(const uint8_t mode) { + uint8_t new_mode = mode; + _.Command = kHaierAcCmdMode; + // If out of range, default to auto mode. + if (mode > kHaierAcFan) new_mode = kHaierAcAuto; + _.Mode = new_mode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHaierAC::getMode(void) const { + return _.Mode; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRHaierAC::setTemp(const uint8_t degrees) { + uint8_t temp = degrees; + if (temp < kHaierAcMinTemp) + temp = kHaierAcMinTemp; + else if (temp > kHaierAcMaxTemp) + temp = kHaierAcMaxTemp; + + uint8_t old_temp = getTemp(); + if (old_temp == temp) return; + if (old_temp > temp) + _.Command = kHaierAcCmdTempDown; + else + _.Command = kHaierAcCmdTempUp; + _.Temp = temp - kHaierAcMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRHaierAC::getTemp(void) const { + return _.Temp + kHaierAcMinTemp; +} + +/// Set the Health (filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC::setHealth(const bool on) { + _.Command = kHaierAcCmdHealth; + _.Health = on; +} + +/// Get the Health (filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC::getHealth(void) const { + return _.Health; +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC::setSleep(const bool on) { + _.Command = kHaierAcCmdSleep; + _.Sleep = on; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC::getSleep(void) const { + return _.Sleep; +} + +/// Get the On Timer value/setting of the A/C. +/// @return Nr of minutes the timer is set to. -1 is Off/not set etc. +int16_t IRHaierAC::getOnTimer(void) const { + // Check if the timer is turned on. + if (_.OnTimer) + return GETTIME(On); + else + return -1; +} + +/// Get the Off Timer value/setting of the A/C. +/// @return Nr of minutes the timer is set to. -1 is Off/not set etc. +int16_t IRHaierAC::getOffTimer(void) const { + // Check if the timer is turned on. + if (_.OffTimer) + return GETTIME(Off); + else + return -1; +} + +/// Get the clock value of the A/C. +/// @return The clock time, in Nr of minutes past midnight. +uint16_t IRHaierAC::getCurrTime(void) const { return GETTIME(Curr); } + +/// Set & enable the On Timer. +/// @param[in] nr_mins The time expressed in total number of minutes. +void IRHaierAC::setOnTimer(const uint16_t nr_mins) { + _.Command = kHaierAcCmdTimerSet; + _.OnTimer = 1; + + SETTIME(On, nr_mins); +} + +/// Set & enable the Off Timer. +/// @param[in] nr_mins The time expressed in total number of minutes. +void IRHaierAC::setOffTimer(const uint16_t nr_mins) { + _.Command = kHaierAcCmdTimerSet; + _.OffTimer = 1; + + SETTIME(Off, nr_mins); +} + +/// Cancel/disable the On & Off timers. +void IRHaierAC::cancelTimers(void) { + _.Command = kHaierAcCmdTimerCancel; + _.OffTimer = 0; + _.OnTimer = 0; +} + +/// Set the clock value for the A/C. +/// @param[in] nr_mins The clock time, in Nr of minutes past midnight. +void IRHaierAC::setCurrTime(const uint16_t nr_mins) { + SETTIME(Curr, nr_mins); +} + +/// Get the Vertical Swing position setting of the A/C. +/// @return The native vertical swing mode. +uint8_t IRHaierAC::getSwingV(void) const { + return _.SwingV; +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] state The mode to set the vanes to. +void IRHaierAC::setSwingV(const uint8_t state) { + if (state == _.SwingV) return; // Nothing to do. + switch (state) { + case kHaierAcSwingVOff: + case kHaierAcSwingVUp: + case kHaierAcSwingVDown: + case kHaierAcSwingVChg: + _.Command = kHaierAcCmdSwing; + _.SwingV = state; + break; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kHaierAcCool; + case stdAc::opmode_t::kHeat: return kHaierAcHeat; + case stdAc::opmode_t::kDry: return kHaierAcDry; + case stdAc::opmode_t::kFan: return kHaierAcFan; + default: return kHaierAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kHaierAcFanLow; + case stdAc::fanspeed_t::kMedium: return kHaierAcFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kHaierAcFanHigh; + default: return kHaierAcFanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: return kHaierAcSwingVUp; + case stdAc::swingv_t::kLow: + case stdAc::swingv_t::kLowest: return kHaierAcSwingVDown; + case stdAc::swingv_t::kOff: return kHaierAcSwingVOff; + default: return kHaierAcSwingVChg; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRHaierAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kHaierAcCool: return stdAc::opmode_t::kCool; + case kHaierAcHeat: return stdAc::opmode_t::kHeat; + case kHaierAcDry: return stdAc::opmode_t::kDry; + case kHaierAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHaierAC::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kHaierAcFanHigh: return stdAc::fanspeed_t::kMax; + case kHaierAcFanMed: return stdAc::fanspeed_t::kMedium; + case kHaierAcFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] pos The enum to be converted. +/// @return The native equivalent of the enum. +stdAc::swingv_t IRHaierAC::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kHaierAcSwingVUp: return stdAc::swingv_t::kHighest; + case kHaierAcSwingVDown: return stdAc::swingv_t::kLowest; + case kHaierAcSwingVOff: return stdAc::swingv_t::kOff; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHaierAC::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::HAIER_AC; + result.model = -1; // No models used. + result.power = true; + if (_.Command == kHaierAcCmdOff) result.power = false; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = toCommonSwingV(_.SwingV); + result.filter = _.Health; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.turbo = false; + result.econo = false; + result.light = false; + result.clean = false; + result.beep = true; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRHaierAC::toString(void) const { + String result = ""; + result.reserve(170); // Reserve some heap for the string to reduce fragging. + uint8_t cmd = _.Command; + result += addIntToString(cmd, kCommandStr, false); + result += kSpaceLBraceStr; + switch (cmd) { + case kHaierAcCmdOff: + result += kOffStr; + break; + case kHaierAcCmdOn: + result += kOnStr; + break; + case kHaierAcCmdMode: + result += kModeStr; + break; + case kHaierAcCmdFan: + result += kFanStr; + break; + case kHaierAcCmdTempUp: + result += kTempUpStr; + break; + case kHaierAcCmdTempDown: + result += kTempDownStr; + break; + case kHaierAcCmdSleep: + result += kSleepStr; + break; + case kHaierAcCmdTimerSet: + result += kTimerStr; + result += ' '; + result += kSetStr; + break; + case kHaierAcCmdTimerCancel: + result += kTimerStr; + result += ' '; + result += kCancelStr; + break; + case kHaierAcCmdHealth: + result += kHealthStr; + break; + case kHaierAcCmdSwing: + result += kSwingVStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addModeToString(_.Mode, kHaierAcAuto, kHaierAcCool, kHaierAcHeat, + kHaierAcDry, kHaierAcFan); + result += addTempToString(getTemp()); + result += addFanToString(getFan(), kHaierAcFanHigh, kHaierAcFanLow, + kHaierAcFanAuto, kHaierAcFanAuto, kHaierAcFanMed); + result += addIntToString(_.SwingV, kSwingVStr); + result += kSpaceLBraceStr; + switch (_.SwingV) { + case kHaierAcSwingVOff: + result += kOffStr; + break; + case kHaierAcSwingVUp: + result += kUpStr; + break; + case kHaierAcSwingVDown: + result += kDownStr; + break; + case kHaierAcSwingVChg: + result += kChangeStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(_.Health, kHealthStr); + result += addLabeledString(minsToString(getCurrTime()), kClockStr); + result += addLabeledString( + getOnTimer() >= 0 ? minsToString(getOnTimer()) : kOffStr, kOnTimerStr); + result += addLabeledString( + getOffTimer() >= 0 ? minsToString(getOffTimer()) : kOffStr, + kOffTimerStr); + return result; +} +// End of IRHaierAC class. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHaierAC176::IRHaierAC176(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRHaierAC176::begin(void) { _irsend.begin(); } + +#if SEND_HAIER_AC176 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHaierAC176::send(const uint16_t repeat) { + _irsend.sendHaierAC176(getRaw(), kHaierAC176StateLength, repeat); +} +#endif // SEND_HAIER_AC176 + +/// Calculate and set the checksum values for the internal state. +void IRHaierAC176::checksum(void) { + _.Sum = sumBytes(_.raw, kHaierACYRW02StateLength - 1); + _.Sum2 = sumBytes(_.raw + kHaierACYRW02StateLength, + kHaierAC176StateLength - kHaierACYRW02StateLength - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRHaierAC176::validChecksum(const uint8_t state[], const uint16_t length) { + if (length < 2) return false; // 1 byte of data can't have a checksum. + if (length < kHaierAC160StateLength) { // Is it too short? + // Then it is just a checksum of the whole thing. + return (state[length - 1] == sumBytes(state, length - 1)); + } else { // It is long enough for two checksums. + return (state[kHaierACYRW02StateLength - 1] == + sumBytes(state, kHaierACYRW02StateLength - 1)) && + (state[length - 1] == + sumBytes(state + kHaierACYRW02StateLength, + length - kHaierACYRW02StateLength - 1)); + } +} + +/// Reset the internal state to a fixed known good state. +void IRHaierAC176::stateReset(void) { + memset(_.raw, 0, sizeof _.raw); + _.Model = kHaierAcYrw02ModelA; + _.Prefix2 = kHaierAc176Prefix; + _.Temp = kHaierAcYrw02DefTempC - kHaierAcYrw02MinTempC; + _.Health = true; + setFan(kHaierAcYrw02FanAuto); + _.Power = true; + _.Button = kHaierAcYrw02ButtonPower; +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRHaierAC176::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRHaierAC176::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kHaierAC176StateLength); +} + +/// Set the Button/Command setting of the A/C. +/// @param[in] button The value of the button/command that was pressed. +void IRHaierAC176::setButton(uint8_t button) { + switch (button) { + case kHaierAcYrw02ButtonTempUp: + case kHaierAcYrw02ButtonTempDown: + case kHaierAcYrw02ButtonSwingV: + case kHaierAcYrw02ButtonSwingH: + case kHaierAcYrw02ButtonFan: + case kHaierAcYrw02ButtonPower: + case kHaierAcYrw02ButtonMode: + case kHaierAcYrw02ButtonHealth: + case kHaierAcYrw02ButtonTurbo: + case kHaierAcYrw02ButtonSleep: + case kHaierAcYrw02ButtonLock: + case kHaierAcYrw02ButtonCFAB: + _.Button = button; + } +} + +/// Get/Detect the model of the A/C. +/// @return The enum of the compatible model. +haier_ac176_remote_model_t IRHaierAC176::getModel(void) const { + switch (_.Model) { + case kHaierAcYrw02ModelB: return haier_ac176_remote_model_t::V9014557_B; + default: return haier_ac176_remote_model_t::V9014557_A; + } +} + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRHaierAC176::setModel(haier_ac176_remote_model_t model) { + _.Button = kHaierAcYrw02ButtonCFAB; + switch (model) { + case haier_ac176_remote_model_t::V9014557_B: + _.Model = kHaierAcYrw02ModelB; + break; + default: + _.Model = kHaierAcYrw02ModelA; + } +} + +/// Get the Button/Command setting of the A/C. +/// @return The value of the button/command that was pressed. +uint8_t IRHaierAC176::getButton(void) const { + return _.Button; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHaierAC176::setMode(uint8_t mode) { + switch (mode) { + case kHaierAcYrw02Auto: + case kHaierAcYrw02Dry: + case kHaierAcYrw02Fan: + // Turbo & Quiet is only available in Cool/Heat mode. + _.Turbo = false; + _.Quiet = false; + // FALL-THRU + case kHaierAcYrw02Cool: + case kHaierAcYrw02Heat: + _.Button = kHaierAcYrw02ButtonMode; + _.Mode = mode; + break; + default: + setMode(kHaierAcYrw02Auto); // Unexpected, default to auto mode. + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHaierAC176::getMode(void) const { return _.Mode; } + +/// Set the default temperature units to use. +/// @param[in] on Use Fahrenheit as the units. +/// true is Fahrenheit, false is Celsius. +void IRHaierAC176::setUseFahrenheit(const bool on) { _.UseFahrenheit = on; } + +/// Get the default temperature units in use. +/// @return true is Fahrenheit, false is Celsius. +bool IRHaierAC176::getUseFahrenheit(void) const { return _.UseFahrenheit; } + +/// Set the temperature. +/// @param[in] degree The temperature in degrees. +/// @param[in] fahrenheit Use units of Fahrenheit and set that as units used. +void IRHaierAC176::setTemp(const uint8_t degree, const bool fahrenheit) { + uint8_t old_temp = getTemp(); + if (old_temp == degree) return; + + if (_.UseFahrenheit == fahrenheit) { + if (old_temp > degree) + _.Button = kHaierAcYrw02ButtonTempDown; + else + _.Button = kHaierAcYrw02ButtonTempUp; + } else { + _.Button = kHaierAcYrw02ButtonCFAB; + } + _.UseFahrenheit = fahrenheit; + + uint8_t temp = degree; + if (fahrenheit) { + if (temp < kHaierAcYrw02MinTempF) + temp = kHaierAcYrw02MinTempF; + else if (temp > kHaierAcYrw02MaxTempF) + temp = kHaierAcYrw02MaxTempF; + if (degree >= 77) { temp++; } + if (degree >= 79) { temp++; } + // See at IRHaierAC176::getTemp() comments for clarification + _.ExtraDegreeF = temp % 2; + _.Temp = (temp - kHaierAcYrw02MinTempF -_.ExtraDegreeF) >> 1; + } else { + if (temp < kHaierAcYrw02MinTempC) + temp = kHaierAcYrw02MinTempC; + else if (temp > kHaierAcYrw02MaxTempC) + temp = kHaierAcYrw02MaxTempC; + _.Temp = temp - kHaierAcYrw02MinTempC; + } +} + +/// Get the current temperature setting. +/// The unit of temperature is specified by UseFahrenheit value. +/// @return The current setting for temperature. +uint8_t IRHaierAC176::getTemp(void) const { + if (!_.UseFahrenheit) { return _.Temp + kHaierAcYrw02MinTempC; } + uint8_t degree = _.Temp*2 + kHaierAcYrw02MinTempF + _.ExtraDegreeF; + // The way of coding the temperature in degree Fahrenheit is + // kHaierAcYrw02MinTempF + Temp*2 + ExtraDegreeF, for example + // Temp = 0b0011, ExtraDegreeF = 0b1, temperature is 60 + 3*2 + 1 = 67F + // But around 78F there is unconsistency, see table below + // + // | Fahrenheit | Temp | ExtraDegreeF | + // | 60F | 0b0000 | 0b0 | + // | 61F | 0b0000 | 0b1 | + // | 62F | 0b0001 | 0b0 | + // | 63F | 0b0001 | 0b1 | + // | 64F | 0b0010 | 0b0 | + // | 65F | 0b0010 | 0b1 | + // | 66F | 0b0011 | 0b0 | + // | 67F | 0b0011 | 0b1 | + // | 68F | 0b0100 | 0b0 | + // | 69F | 0b0100 | 0b1 | + // | 70F | 0b0101 | 0b0 | + // | 71F | 0b0101 | 0b1 | + // | 72F | 0b0110 | 0b0 | + // | 73F | 0b0110 | 0b1 | + // | 74F | 0b0111 | 0b0 | + // | 75F | 0b0111 | 0b1 | + // | 76F | 0b1000 | 0b0 | + // | Not Used | 0b1000 | 0b1 | + // | 77F | 0b1001 | 0b0 | + // | Not Used | 0b1001 | 0b1 | + // | 78F | 0b1010 | 0b0 | + // | 79F | 0b1010 | 0b1 | + // | 80F | 0b1011 | 0b0 | + // | 81F | 0b1011 | 0b1 | + // | 82F | 0b1100 | 0b0 | + // | 83F | 0b1100 | 0b1 | + // | 84F | 0b1101 | 0b0 | + // | 86F | 0b1110 | 0b0 | + // | 85F | 0b1101 | 0b1 | + if (degree >= 77) { degree--; } + if (degree >= 79) { degree--; } + return degree; +} + +/// Set the Health (filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC176::setHealth(const bool on) { + _.Button = kHaierAcYrw02ButtonHealth; + _.Health = on; +} + +/// Get the Health (filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC176::getHealth(void) const { return _.Health; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC176::getPower(void) const { return _.Power; } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC176::setPower(const bool on) { + _.Button = kHaierAcYrw02ButtonPower; + _.Power = on; +} + +/// Change the power setting to On. +void IRHaierAC176::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRHaierAC176::off(void) { setPower(false); } + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC176::getSleep(void) const { return _.Sleep; } + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC176::setSleep(const bool on) { + _.Button = kHaierAcYrw02ButtonSleep; + _.Sleep = on; +} + +/// Get the Turbo setting of the A/C. +/// @return The current turbo setting. +bool IRHaierAC176::getTurbo(void) const { return _.Turbo; } + +/// Set the Turbo setting of the A/C. +/// @param[in] on The desired turbo setting. +/// @note Turbo & Quiet can't be on at the same time, and only in Heat/Cool mode +void IRHaierAC176::setTurbo(const bool on) { + switch (getMode()) { + case kHaierAcYrw02Cool: + case kHaierAcYrw02Heat: + _.Turbo = on; + _.Button = kHaierAcYrw02ButtonTurbo; + if (on) _.Quiet = false; + } +} + +/// Get the Quiet setting of the A/C. +/// @return The current Quiet setting. +bool IRHaierAC176::getQuiet(void) const { return _.Quiet; } + +/// Set the Quiet setting of the A/C. +/// @param[in] on The desired Quiet setting. +/// @note Turbo & Quiet can't be on at the same time, and only in Heat/Cool mode +void IRHaierAC176::setQuiet(const bool on) { + switch (getMode()) { + case kHaierAcYrw02Cool: + case kHaierAcYrw02Heat: + _.Quiet = on; + _.Button = kHaierAcYrw02ButtonTurbo; + if (on) _.Turbo = false; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHaierAC176::getFan(void) const { return _.Fan; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHaierAC176::setFan(uint8_t speed) { + switch (speed) { + case kHaierAcYrw02FanLow: + case kHaierAcYrw02FanMed: + case kHaierAcYrw02FanHigh: + case kHaierAcYrw02FanAuto: + _.Fan = speed; + _.Fan2 = (speed == kHaierAcYrw02FanAuto) ? 0 : speed; + _.Button = kHaierAcYrw02ButtonFan; + } +} + +/// For backward compatibility. Use getSwingV() instead. +/// Get the Vertical Swing position setting of the A/C. +/// @return The native position/mode. +uint8_t IRHaierAC176::getSwing(void) const { + return IRHaierAC176::getSwingV(); +} + +/// For backward compatibility. Use setSwingV() instead. +/// Set the Vertical Swing mode of the A/C. +/// @param[in] pos The position/mode to set the vanes to. +void IRHaierAC176::setSwing(uint8_t pos) { setSwingV(pos); } + +/// Get the Vertical Swing position setting of the A/C. +/// @return The native position/mode. +uint8_t IRHaierAC176::getSwingV(void) const { return _.SwingV; } + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] pos The position/mode to set the vanes to. +void IRHaierAC176::setSwingV(uint8_t pos) { + uint8_t newpos = pos; + switch (pos) { + case kHaierAcYrw02SwingVOff: + case kHaierAcYrw02SwingVAuto: + case kHaierAcYrw02SwingVTop: + case kHaierAcYrw02SwingVMiddle: + case kHaierAcYrw02SwingVBottom: + case kHaierAcYrw02SwingVDown: _.Button = kHaierAcYrw02ButtonSwingV; break; + default: return; // Unexpected value so don't do anything. + } + // Heat mode has no MIDDLE setting, use BOTTOM instead. + if (pos == kHaierAcYrw02SwingVMiddle && _.Mode == kHaierAcYrw02Heat) + newpos = kHaierAcYrw02SwingVBottom; + // BOTTOM is only allowed if we are in Heat mode, otherwise MIDDLE. + if (pos == kHaierAcYrw02SwingVBottom && _.Mode != kHaierAcYrw02Heat) + newpos = kHaierAcYrw02SwingVMiddle; + _.SwingV = newpos; +} + +/// Get the Horizontal Swing position setting of the A/C. +/// @return The native position/mode. +uint8_t IRHaierAC176::getSwingH(void) const { return _.SwingH; } + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] pos The position/mode to set the vanes to. +void IRHaierAC176::setSwingH(uint8_t pos) { + switch (pos) { + case kHaierAcYrw02SwingHMiddle: + case kHaierAcYrw02SwingHLeftMax: + case kHaierAcYrw02SwingHLeft: + case kHaierAcYrw02SwingHRight: + case kHaierAcYrw02SwingHRightMax: + case kHaierAcYrw02SwingHAuto: _.Button = kHaierAcYrw02ButtonSwingH; break; + default: return; // Unexpected value so don't do anything. + } + _.SwingH = pos; +} + + +/// Set the Timer operating mode. +/// @param[in] mode The timer mode to use. +void IRHaierAC176::setTimerMode(const uint8_t mode) { + _.TimerMode = (mode > kHaierAcYrw02OffThenOnTimer) ? kHaierAcYrw02NoTimers + : mode; + switch (_.TimerMode) { + case kHaierAcYrw02NoTimers: + setOnTimer(0); // Disable the On timer. + setOffTimer(0); // Disable the Off timer. + break; + case kHaierAcYrw02OffTimer: + setOnTimer(0); // Disable the On timer. + break; + case kHaierAcYrw02OnTimer: + setOffTimer(0); // Disable the Off timer. + break; + } +} + +/// Get the Timer operating mode. +/// @return The mode of the timer is currently configured to. +uint8_t IRHaierAC176::getTimerMode(void) const { return _.TimerMode; } + +/// Set the number of minutes of the On Timer setting. +/// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer. +void IRHaierAC176::setOnTimer(const uint16_t mins) { + const uint16_t nr_mins = ::min((uint16_t)(23 * 60 + 59), mins); + _.OnTimerHrs = nr_mins / 60; + _.OnTimerMins = nr_mins % 60; + + const bool enabled = (nr_mins > 0); + uint8_t mode = getTimerMode(); + switch (mode) { + case kHaierAcYrw02OffTimer: + mode = enabled ? kHaierAcYrw02OffThenOnTimer : mode; + break; + case kHaierAcYrw02OnThenOffTimer: + case kHaierAcYrw02OffThenOnTimer: + mode = enabled ? kHaierAcYrw02OffThenOnTimer : kHaierAcYrw02OffTimer; + break; + default: + // Enable/Disable the On timer for the simple case. + mode = enabled << 1; + } + _.TimerMode = mode; +} + +/// Get the number of minutes of the On Timer setting. +/// @return Nr of minutes. +uint16_t IRHaierAC176::getOnTimer(void) const { + return _.OnTimerHrs * 60 + _.OnTimerMins; +} + +/// Set the number of minutes of the Off Timer setting. +/// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer. +void IRHaierAC176::setOffTimer(const uint16_t mins) { + const uint16_t nr_mins = ::min((uint16_t)(23 * 60 + 59), mins); + _.OffTimerHrs = nr_mins / 60; + _.OffTimerMins = nr_mins % 60; + + const bool enabled = (nr_mins > 0); + uint8_t mode = getTimerMode(); + switch (mode) { + case kHaierAcYrw02OnTimer: + mode = enabled ? kHaierAcYrw02OnThenOffTimer : mode; + break; + case kHaierAcYrw02OnThenOffTimer: + case kHaierAcYrw02OffThenOnTimer: + mode = enabled ? kHaierAcYrw02OnThenOffTimer : kHaierAcYrw02OnTimer; + break; + default: + // Enable/Disable the Off timer for the simple case. + mode = enabled; + } + _.TimerMode = mode; +} + +/// Get the number of minutes of the Off Timer setting. +/// @return Nr of minutes. +uint16_t IRHaierAC176::getOffTimer(void) const { + return _.OffTimerHrs * 60 + _.OffTimerMins; +} + +/// Get the Lock setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC176::getLock(void) const { return _.Lock; } + +/// Set the Lock setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC176::setLock(const bool on) { + _.Button = kHaierAcYrw02ButtonLock; + _.Lock = on; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC176::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kHaierAcYrw02Cool; + case stdAc::opmode_t::kHeat: return kHaierAcYrw02Heat; + case stdAc::opmode_t::kDry: return kHaierAcYrw02Dry; + case stdAc::opmode_t::kFan: return kHaierAcYrw02Fan; + default: return kHaierAcYrw02Auto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC176::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kHaierAcYrw02FanLow; + case stdAc::fanspeed_t::kMedium: return kHaierAcYrw02FanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kHaierAcYrw02FanHigh; + default: return kHaierAcYrw02FanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC176::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: return kHaierAcYrw02SwingVTop; + case stdAc::swingv_t::kMiddle: return kHaierAcYrw02SwingVMiddle; + case stdAc::swingv_t::kLow: return kHaierAcYrw02SwingVDown; + case stdAc::swingv_t::kLowest: return kHaierAcYrw02SwingVBottom; + case stdAc::swingv_t::kOff: return kHaierAcYrw02SwingVOff; + default: return kHaierAcYrw02SwingVAuto; + } +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC176::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kMiddle: return kHaierAcYrw02SwingHMiddle; + case stdAc::swingh_t::kLeftMax: return kHaierAcYrw02SwingHLeftMax; + case stdAc::swingh_t::kLeft: return kHaierAcYrw02SwingHLeft; + case stdAc::swingh_t::kRight: return kHaierAcYrw02SwingHRight; + case stdAc::swingh_t::kRightMax: return kHaierAcYrw02SwingHRightMax; + case stdAc::swingh_t::kAuto: return kHaierAcYrw02SwingHAuto; + default: return kHaierAcYrw02SwingHMiddle; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRHaierAC176::toCommonMode(const uint8_t mode) { + switch (mode) { + case kHaierAcYrw02Cool: return stdAc::opmode_t::kCool; + case kHaierAcYrw02Heat: return stdAc::opmode_t::kHeat; + case kHaierAcYrw02Dry: return stdAc::opmode_t::kDry; + case kHaierAcYrw02Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHaierAC176::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kHaierAcYrw02FanHigh: return stdAc::fanspeed_t::kMax; + case kHaierAcYrw02FanMed: return stdAc::fanspeed_t::kMedium; + case kHaierAcYrw02FanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] pos The enum to be converted. +/// @return The native equivalent of the enum. +stdAc::swingv_t IRHaierAC176::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kHaierAcYrw02SwingVTop: return stdAc::swingv_t::kHighest; + case kHaierAcYrw02SwingVMiddle: return stdAc::swingv_t::kMiddle; + case kHaierAcYrw02SwingVDown: return stdAc::swingv_t::kLow; + case kHaierAcYrw02SwingVBottom: return stdAc::swingv_t::kLowest; + case kHaierAcYrw02SwingVOff: return stdAc::swingv_t::kOff; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] pos The enum to be converted. +/// @return The native equivalent of the enum. +stdAc::swingh_t IRHaierAC176::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kHaierAcYrw02SwingHMiddle: return stdAc::swingh_t::kMiddle; + case kHaierAcYrw02SwingHLeftMax: return stdAc::swingh_t::kLeftMax; + case kHaierAcYrw02SwingHLeft: return stdAc::swingh_t::kLeft; + case kHaierAcYrw02SwingHRight: return stdAc::swingh_t::kRight; + case kHaierAcYrw02SwingHRightMax: return stdAc::swingh_t::kRightMax; + case kHaierAcYrw02SwingHAuto: return stdAc::swingh_t::kAuto; + default: return stdAc::swingh_t::kOff; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHaierAC176::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::HAIER_AC_YRW02; + result.model = getModel(); + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = !_.UseFahrenheit; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = toCommonSwingV(_.SwingV); + result.swingh = toCommonSwingH(_.SwingH); + result.filter = _.Health; + result.sleep = _.Sleep ? 0 : -1; + result.turbo = _.Turbo; + result.quiet = _.Quiet; + // Not supported. + result.econo = false; + result.light = false; + result.clean = false; + result.beep = true; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRHaierAC176::toString(void) const { + String result = ""; + result.reserve(280); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::HAIER_AC176, getModel(), false); + result += addBoolToString(_.Power, kPowerStr); + uint8_t cmd = _.Button; + result += addIntToString(cmd, kButtonStr); + result += kSpaceLBraceStr; + switch (cmd) { + case kHaierAcYrw02ButtonPower: + result += kPowerStr; + break; + case kHaierAcYrw02ButtonMode: + result += kModeStr; + break; + case kHaierAcYrw02ButtonFan: + result += kFanStr; + break; + case kHaierAcYrw02ButtonTempUp: + result += kTempUpStr; + break; + case kHaierAcYrw02ButtonTempDown: + result += kTempDownStr; + break; + case kHaierAcYrw02ButtonSleep: + result += kSleepStr; + break; + case kHaierAcYrw02ButtonHealth: + result += kHealthStr; + break; + case kHaierAcYrw02ButtonSwingV: + result += kSwingVStr; + break; + case kHaierAcYrw02ButtonSwingH: + result += kSwingHStr; + break; + case kHaierAcYrw02ButtonTurbo: + result += kTurboStr; + break; + case kHaierAcYrw02ButtonTimer: + result += kTimerStr; + break; + case kHaierAcYrw02ButtonLock: + result += kLockStr; + break; + case kHaierAcYrw02ButtonCFAB: + result += kCelsiusFahrenheitStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addModeToString(_.Mode, kHaierAcYrw02Auto, kHaierAcYrw02Cool, + kHaierAcYrw02Heat, kHaierAcYrw02Dry, + kHaierAcYrw02Fan); + result += addTempToString(getTemp(), !_.UseFahrenheit); + result += addFanToString(_.Fan, kHaierAcYrw02FanHigh, kHaierAcYrw02FanLow, + kHaierAcYrw02FanAuto, kHaierAcYrw02FanAuto, + kHaierAcYrw02FanMed); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Quiet, kQuietStr); + result += addIntToString(_.SwingV, kSwingVStr); + result += kSpaceLBraceStr; + switch (_.SwingV) { + case kHaierAcYrw02SwingVOff: + result += kOffStr; + break; + case kHaierAcYrw02SwingVAuto: + result += kAutoStr; + break; + case kHaierAcYrw02SwingVBottom: + result += kLowestStr; + break; + case kHaierAcYrw02SwingVDown: + result += kLowStr; + break; + case kHaierAcYrw02SwingVTop: + result += kHighestStr; + break; + case kHaierAcYrw02SwingVMiddle: + result += kMiddleStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addSwingHToString(_.SwingH, kHaierAcYrw02SwingHAuto, + kHaierAcYrw02SwingHLeftMax, + kHaierAcYrw02SwingHLeft, + kHaierAcYrw02SwingHMiddle, + kHaierAcYrw02SwingHRight, + kHaierAcYrw02SwingHRightMax, + // Below are unused. + kHaierAcYrw02SwingHMiddle, + kHaierAcYrw02SwingHMiddle, + kHaierAcYrw02SwingHMiddle, + kHaierAcYrw02SwingHMiddle, + kHaierAcYrw02SwingHMiddle); + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(_.Health, kHealthStr); + const uint8_t tmode = getTimerMode(); + result += addIntToString(tmode, kTimerModeStr); + result += kSpaceLBraceStr; + switch (tmode) { + case kHaierAcYrw02NoTimers: + result += kNAStr; + break; + case kHaierAcYrw02OnTimer: + result += kOnStr; + break; + case kHaierAcYrw02OffTimer: + result += kOffStr; + break; + case kHaierAcYrw02OnThenOffTimer: + result += kOnStr; + result += '-'; + result += kOffStr; + break; + case kHaierAcYrw02OffThenOnTimer: + result += kOffStr; + result += '-'; + result += kOnStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addLabeledString((tmode != kHaierAcYrw02NoTimers && + tmode != kHaierAcYrw02OffTimer) ? + minsToString(getOnTimer()) : kOffStr, kOnTimerStr); + result += addLabeledString((tmode != kHaierAcYrw02NoTimers && + tmode != kHaierAcYrw02OnTimer) ? + minsToString(getOffTimer()) : kOffStr, kOffTimerStr); + result += addBoolToString(_.Lock, kLockStr); + return result; +} +// End of IRHaierAC176 class. + + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHaierACYRW02::IRHaierACYRW02(const uint16_t pin, const bool inverted, + const bool use_modulation) + : IRHaierAC176(pin, inverted, use_modulation) { stateReset(); } + +#if SEND_HAIER_AC_YRW02 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHaierACYRW02::send(const uint16_t repeat) { + _irsend.sendHaierACYRW02(getRaw(), kHaierACYRW02StateLength, repeat); +} +#endif // SEND_HAIER_AC_YRW02 + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRHaierACYRW02::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kHaierACYRW02StateLength); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRHaierACYRW02::validChecksum(const uint8_t state[], + const uint16_t length) { + return IRHaierAC176::validChecksum(state, length); +} +// End of IRHaierACYRW02 class. + +#if (DECODE_HAIER_AC || DECODE_HAIER_AC_YRW02 || DECODE_HAIER_AC160 || \ + DECODE_HAIER_AC176) +/// Decode the supplied Haier HSU07-HEA03 remote message. +/// Status: STABLE / Known to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeHaierAC(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict) { + if (nbits != kHaierACBits) + return false; // Not strictly a HAIER_AC message. + } + + if (results->rawlen <= (2 * nbits + kHeader) + kFooter - 1 + offset) + return false; // Can't possibly be a valid HAIER_AC message. + + // Pre-Header + if (!matchMark(results->rawbuf[offset++], kHaierAcHdr)) return false; + if (!matchSpace(results->rawbuf[offset++], kHaierAcHdr)) return false; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kHaierAcHdr, kHaierAcHdrGap, + kHaierAcBitMark, kHaierAcOneSpace, + kHaierAcBitMark, kHaierAcZeroSpace, + kHaierAcBitMark, kHaierAcMinGap, true, + _tolerance, kMarkExcess)) return false; + + // Compliance + if (strict) { + if (results->state[0] != kHaierAcPrefix) return false; + if (!IRHaierAC::validChecksum(results->state, nbits / 8)) return false; + } + + // Success + results->decode_type = HAIER_AC; + results->bits = nbits; + return true; +} +#endif // (DECODE_HAIER_AC || DECODE_HAIER_AC_YRW02) + +#if DECODE_HAIER_AC_YRW02 +/// Decode the supplied Haier YR-W02 remote A/C message. +/// Status: BETA / Appears to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeHaierACYRW02(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict) { + if (nbits != kHaierACYRW02Bits) + return false; // Not strictly a HAIER_AC_YRW02 message. + } + + // The protocol is almost exactly the same as HAIER_AC + if (!decodeHaierAC(results, offset, nbits, false)) return false; + + // Compliance + if (strict) { + if (results->state[0] != kHaierAcYrw02ModelA) return false; + if (!IRHaierACYRW02::validChecksum(results->state, nbits / 8)) return false; + } + + // Success + // It looks correct, but we haven't check the checksum etc. + results->decode_type = HAIER_AC_YRW02; + return true; +} +#endif // DECODE_HAIER_AC_YRW02 + +#if DECODE_HAIER_AC176 +/// Decode the supplied Haier 176 bit remote A/C message. +/// Status: STABLE / Known to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeHaierAC176(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict) { + if (nbits != kHaierAC176Bits) + return false; // Not strictly a HAIER_AC176 message. + } + + // The protocol is almost exactly the same as HAIER_AC + if (!decodeHaierAC(results, offset, nbits, false)) return false; + + // Compliance + if (strict) { + if ((results->state[0] != kHaierAcYrw02ModelA) && + (results->state[0] != kHaierAcYrw02ModelB)) return false; + if (!IRHaierAC176::validChecksum(results->state, nbits / 8)) return false; + } + + // Success + // It looks correct, but we haven't check the checksum etc. + results->decode_type = HAIER_AC176; + return true; +} +#endif // DECODE_HAIER_AC176 + +#if DECODE_HAIER_AC160 +/// Decode the supplied Haier 160 bit remote A/C message. +/// Status: STABLE / Known to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeHaierAC160(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict) { + if (nbits != kHaierAC160Bits) + return false; // Not strictly a HAIER_AC160 message. + } + + // The protocol is almost exactly the same as HAIER_AC + if (!decodeHaierAC(results, offset, nbits, false)) return false; + + // Compliance + if (strict) { + if (!IRHaierAC176::validChecksum(results->state, nbits / 8)) return false; + } + + // Success + // It looks correct, but we haven't check the checksum etc. + results->decode_type = HAIER_AC160; + return true; +} +#endif // DECODE_HAIER_AC160 + + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHaierAC160::IRHaierAC160(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRHaierAC160::begin(void) { _irsend.begin(); } + +#if SEND_HAIER_AC160 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHaierAC160::send(const uint16_t repeat) { + _irsend.sendHaierAC160(getRaw(), kHaierAC160StateLength, repeat); +} +#endif // SEND_HAIER_AC160 + +/// Calculate and set the checksum values for the internal state. +void IRHaierAC160::checksum(void) { + _.Sum = sumBytes(_.raw, kHaierACYRW02StateLength - 1); + _.Sum2 = sumBytes(_.raw + kHaierACYRW02StateLength, + kHaierAC160StateLength - kHaierACYRW02StateLength - 1); +} + +/// Reset the internal state to a fixed known good state. +void IRHaierAC160::stateReset(void) { + memset(_.raw, 0, sizeof _.raw); + _.Model = kHaierAcYrw02ModelA; + _.Prefix = kHaierAc160Prefix; + _.Temp = kHaierAcYrw02DefTempC - kHaierAcYrw02MinTempC; + setClean(false); + setFan(kHaierAcYrw02FanAuto); + _.Power = true; + _.Button = kHaierAcYrw02ButtonPower; +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRHaierAC160::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRHaierAC160::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kHaierAC160StateLength); +} + +/// Set the Button/Command setting of the A/C. +/// @param[in] button The value of the button/command that was pressed. +void IRHaierAC160::setButton(uint8_t button) { + switch (button) { + case kHaierAcYrw02ButtonTempUp: + case kHaierAcYrw02ButtonTempDown: + case kHaierAcYrw02ButtonSwingV: + case kHaierAcYrw02ButtonSwingH: + case kHaierAcYrw02ButtonFan: + case kHaierAcYrw02ButtonPower: + case kHaierAcYrw02ButtonMode: + case kHaierAcYrw02ButtonHealth: + case kHaierAcYrw02ButtonTurbo: + case kHaierAcYrw02ButtonSleep: + case kHaierAcYrw02ButtonLock: + case kHaierAc160ButtonClean: + case kHaierAcYrw02ButtonCFAB: + _.Button = button; + } +} + +/// Get the Button/Command setting of the A/C. +/// @return The value of the button/command that was pressed. +uint8_t IRHaierAC160::getButton(void) const { return _.Button; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHaierAC160::setMode(uint8_t mode) { + switch (mode) { + case kHaierAcYrw02Auto: + case kHaierAcYrw02Dry: + case kHaierAcYrw02Fan: + // Turbo & Quiet is only available in Cool/Heat mode. + _.Turbo = false; + _.Quiet = false; + // FALL-THRU + case kHaierAcYrw02Cool: + case kHaierAcYrw02Heat: + _.Button = kHaierAcYrw02ButtonMode; + _.Mode = mode; + break; + default: + setMode(kHaierAcYrw02Auto); // Unexpected, default to auto mode. + } + _.AuxHeating = (_.Mode == kHaierAcYrw02Heat); // Set only if heat mode. +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHaierAC160::getMode(void) const { return _.Mode; } + +/// Set the default temperature units to use. +/// @param[in] on Use Fahrenheit as the units. +/// true is Fahrenheit, false is Celsius. +void IRHaierAC160::setUseFahrenheit(const bool on) { _.UseFahrenheit = on; } + +/// Get the default temperature units in use. +/// @return true is Fahrenheit, false is Celsius. +bool IRHaierAC160::getUseFahrenheit(void) const { return _.UseFahrenheit; } + +/// Set the temperature. +/// @param[in] degree The temperature in degrees. +/// @param[in] fahrenheit Use units of Fahrenheit and set that as units used. +void IRHaierAC160::setTemp(const uint8_t degree, const bool fahrenheit) { + uint8_t old_temp = getTemp(); + if (old_temp == degree) return; + + if (_.UseFahrenheit == fahrenheit) { + if (old_temp > degree) + _.Button = kHaierAcYrw02ButtonTempDown; + else + _.Button = kHaierAcYrw02ButtonTempUp; + } else { + _.Button = kHaierAcYrw02ButtonCFAB; + } + _.UseFahrenheit = fahrenheit; + + uint8_t temp = degree; + if (fahrenheit) { + if (temp < kHaierAcYrw02MinTempF) + temp = kHaierAcYrw02MinTempF; + else if (temp > kHaierAcYrw02MaxTempF) + temp = kHaierAcYrw02MaxTempF; + if (degree >= 77) { temp++; } + if (degree >= 79) { temp++; } + // See at IRHaierAC160::getTemp() comments for clarification + _.ExtraDegreeF = temp % 2; + _.Temp = (temp - kHaierAcYrw02MinTempF -_.ExtraDegreeF) >> 1; + } else { + if (temp < kHaierAcYrw02MinTempC) + temp = kHaierAcYrw02MinTempC; + else if (temp > kHaierAcYrw02MaxTempC) + temp = kHaierAcYrw02MaxTempC; + _.Temp = temp - kHaierAcYrw02MinTempC; + } +} + +/// Get the current temperature setting. +/// The unit of temperature is specified by UseFahrenheit value. +/// @return The current setting for temperature. +uint8_t IRHaierAC160::getTemp(void) const { + if (!_.UseFahrenheit) { return _.Temp + kHaierAcYrw02MinTempC; } + uint8_t degree = _.Temp*2 + kHaierAcYrw02MinTempF + _.ExtraDegreeF; + // The way of coding the temperature in degree Fahrenheit is + // kHaierAcYrw02MinTempF + Temp*2 + ExtraDegreeF, for example + // Temp = 0b0011, ExtraDegreeF = 0b1, temperature is 60 + 3*2 + 1 = 67F + // But around 78F there is unconsistency, see table below + // + // | Fahrenheit | Temp | ExtraDegreeF | + // | 60F | 0b0000 | 0b0 | + // | 61F | 0b0000 | 0b1 | + // | 62F | 0b0001 | 0b0 | + // | 63F | 0b0001 | 0b1 | + // | 64F | 0b0010 | 0b0 | + // | 65F | 0b0010 | 0b1 | + // | 66F | 0b0011 | 0b0 | + // | 67F | 0b0011 | 0b1 | + // | 68F | 0b0100 | 0b0 | + // | 69F | 0b0100 | 0b1 | + // | 70F | 0b0101 | 0b0 | + // | 71F | 0b0101 | 0b1 | + // | 72F | 0b0110 | 0b0 | + // | 73F | 0b0110 | 0b1 | + // | 74F | 0b0111 | 0b0 | + // | 75F | 0b0111 | 0b1 | + // | 76F | 0b1000 | 0b0 | + // | Not Used | 0b1000 | 0b1 | + // | 77F | 0b1001 | 0b0 | + // | Not Used | 0b1001 | 0b1 | + // | 78F | 0b1010 | 0b0 | + // | 79F | 0b1010 | 0b1 | + // | 80F | 0b1011 | 0b0 | + // | 81F | 0b1011 | 0b1 | + // | 82F | 0b1100 | 0b0 | + // | 83F | 0b1100 | 0b1 | + // | 84F | 0b1101 | 0b0 | + // | 86F | 0b1110 | 0b0 | + // | 85F | 0b1101 | 0b1 | + if (degree >= 77) { degree--; } + if (degree >= 79) { degree--; } + return degree; +} + +/// Set the Clean setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC160::setClean(const bool on) { + _.Button = kHaierAc160ButtonClean; + _.Clean = on; + _.Clean2 = on; +} + +/// Get the Clean setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC160::getClean(void) const { return _.Clean && _.Clean2; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC160::getPower(void) const { return _.Power; } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC160::setPower(const bool on) { + _.Button = kHaierAcYrw02ButtonPower; + _.Power = on; +} + +/// Change the power setting to On. +void IRHaierAC160::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRHaierAC160::off(void) { setPower(false); } + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC160::getSleep(void) const { return _.Sleep; } + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC160::setSleep(const bool on) { + _.Button = kHaierAcYrw02ButtonSleep; + _.Sleep = on; +} + +/// Get the Turbo setting of the A/C. +/// @return The current turbo setting. +bool IRHaierAC160::getTurbo(void) const { return _.Turbo; } + +/// Set the Turbo setting of the A/C. +/// @param[in] on The desired turbo setting. +/// @note Turbo & Quiet can't be on at the same time, and only in Heat/Cool mode +void IRHaierAC160::setTurbo(const bool on) { + switch (getMode()) { + case kHaierAcYrw02Cool: + case kHaierAcYrw02Heat: + _.Turbo = on; + _.Button = kHaierAcYrw02ButtonTurbo; + if (on) _.Quiet = false; + } +} + +/// Get the Quiet setting of the A/C. +/// @return The current Quiet setting. +bool IRHaierAC160::getQuiet(void) const { return _.Quiet; } + +/// Set the Quiet setting of the A/C. +/// @param[in] on The desired Quiet setting. +/// @note Turbo & Quiet can't be on at the same time, and only in Heat/Cool mode +void IRHaierAC160::setQuiet(const bool on) { + switch (getMode()) { + case kHaierAcYrw02Cool: + case kHaierAcYrw02Heat: + _.Quiet = on; + _.Button = kHaierAcYrw02ButtonTurbo; + if (on) _.Turbo = false; + } +} + +/// Get the value of the Aux Heating setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC160::getAuxHeating(void) const { return _.AuxHeating; } + +/// Change the Aux Heating setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC160::setAuxHeating(const bool on) { + _.Button = kHaierAc160ButtonAuxHeating; + _.AuxHeating = on; +} + +/// Get the value of the current Light toggle setting. +/// @return true, the setting is on. false, the setting is off. +/// @note This setting seems to be controlled just by the button setting. +bool IRHaierAC160::getLightToggle(void) const { + return _.Button == kHaierAc160ButtonLight; +} + +/// Set the Light Toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note This setting seems to be controlled just by the button setting. +void IRHaierAC160::setLightToggle(const bool on) { + _.Button = on ? kHaierAc160ButtonLight : kHaierAcYrw02ButtonPower; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHaierAC160::getFan(void) const { return _.Fan; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHaierAC160::setFan(uint8_t speed) { + switch (speed) { + case kHaierAcYrw02FanLow: + case kHaierAcYrw02FanMed: + case kHaierAcYrw02FanHigh: + case kHaierAcYrw02FanAuto: + _.Fan = speed; + _.Fan2 = (speed == kHaierAcYrw02FanAuto) ? 0 : speed; + _.Button = kHaierAcYrw02ButtonFan; + } +} + +/// Set the Health (filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC160::setHealth(const bool on) { + _.Button = kHaierAcYrw02ButtonHealth; + _.Health = on; +} + +/// Get the Health (filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC160::getHealth(void) const { return _.Health; } + +/// Get the Vertical Swing position setting of the A/C. +/// @return The native position/mode. +uint8_t IRHaierAC160::getSwingV(void) const { return _.SwingV; } + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] pos The position/mode to set the vanes to. +void IRHaierAC160::setSwingV(const uint8_t pos) { + switch (pos) { + case kHaierAc160SwingVOff: + case kHaierAc160SwingVAuto: + case kHaierAc160SwingVTop: + case kHaierAc160SwingVHighest: + case kHaierAc160SwingVHigh: + case kHaierAc160SwingVMiddle: + case kHaierAc160SwingVLow: + case kHaierAc160SwingVLowest: + _.Button = kHaierAcYrw02ButtonSwingV; + _.SwingV = pos; + break; + default: return; // If in doubt, Do nothing. + } +} + +/// Set the Timer operating mode. +/// @param[in] mode The timer mode to use. +void IRHaierAC160::setTimerMode(const uint8_t mode) { + _.TimerMode = (mode > kHaierAcYrw02OffThenOnTimer) ? kHaierAcYrw02NoTimers + : mode; + switch (_.TimerMode) { + case kHaierAcYrw02NoTimers: + setOnTimer(0); // Disable the On timer. + setOffTimer(0); // Disable the Off timer. + break; + case kHaierAcYrw02OffTimer: + setOnTimer(0); // Disable the On timer. + break; + case kHaierAcYrw02OnTimer: + setOffTimer(0); // Disable the Off timer. + break; + } +} + +/// Get the Timer operating mode. +/// @return The mode of the timer is currently configured to. +uint8_t IRHaierAC160::getTimerMode(void) const { return _.TimerMode; } + +/// Set the number of minutes of the On Timer setting. +/// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer. +void IRHaierAC160::setOnTimer(const uint16_t mins) { + const uint16_t nr_mins = ::min((uint16_t)(23 * 60 + 59), mins); + _.OnTimerHrs = nr_mins / 60; + _.OnTimerMins = nr_mins % 60; + + const bool enabled = (nr_mins > 0); + uint8_t mode = getTimerMode(); + switch (mode) { + case kHaierAcYrw02OffTimer: + mode = enabled ? kHaierAcYrw02OffThenOnTimer : mode; + break; + case kHaierAcYrw02OnThenOffTimer: + case kHaierAcYrw02OffThenOnTimer: + mode = enabled ? kHaierAcYrw02OffThenOnTimer : kHaierAcYrw02OffTimer; + break; + default: + // Enable/Disable the On timer for the simple case. + mode = enabled << 1; + } + _.TimerMode = mode; +} + +/// Get the number of minutes of the On Timer setting. +/// @return Nr of minutes. +uint16_t IRHaierAC160::getOnTimer(void) const { + return _.OnTimerHrs * 60 + _.OnTimerMins; +} + +/// Set the number of minutes of the Off Timer setting. +/// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer. +void IRHaierAC160::setOffTimer(const uint16_t mins) { + const uint16_t nr_mins = ::min((uint16_t)(23 * 60 + 59), mins); + _.OffTimerHrs = nr_mins / 60; + _.OffTimerMins = nr_mins % 60; + + const bool enabled = (nr_mins > 0); + uint8_t mode = getTimerMode(); + switch (mode) { + case kHaierAcYrw02OnTimer: + mode = enabled ? kHaierAcYrw02OnThenOffTimer : mode; + break; + case kHaierAcYrw02OnThenOffTimer: + case kHaierAcYrw02OffThenOnTimer: + mode = enabled ? kHaierAcYrw02OnThenOffTimer : kHaierAcYrw02OnTimer; + break; + default: + // Enable/Disable the Off timer for the simple case. + mode = enabled; + } + _.TimerMode = mode; +} + +/// Get the number of minutes of the Off Timer setting. +/// @return Nr of minutes. +uint16_t IRHaierAC160::getOffTimer(void) const { + return _.OffTimerHrs * 60 + _.OffTimerMins; +} + +/// Get the Lock setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHaierAC160::getLock(void) const { return _.Lock; } + +/// Set the Lock setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHaierAC160::setLock(const bool on) { + _.Button = kHaierAcYrw02ButtonLock; + _.Lock = on; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC160::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kHaierAcYrw02Cool; + case stdAc::opmode_t::kHeat: return kHaierAcYrw02Heat; + case stdAc::opmode_t::kDry: return kHaierAcYrw02Dry; + case stdAc::opmode_t::kFan: return kHaierAcYrw02Fan; + default: return kHaierAcYrw02Auto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC160::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kHaierAcYrw02FanLow; + case stdAc::fanspeed_t::kMedium: return kHaierAcYrw02FanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kHaierAcYrw02FanHigh; + default: return kHaierAcYrw02FanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHaierAC160::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: return kHaierAc160SwingVTop; + case stdAc::swingv_t::kHigh: return kHaierAc160SwingVHigh; + case stdAc::swingv_t::kMiddle: return kHaierAc160SwingVMiddle; + case stdAc::swingv_t::kLow: return kHaierAc160SwingVLow; + case stdAc::swingv_t::kLowest: return kHaierAc160SwingVLowest; + case stdAc::swingv_t::kOff: return kHaierAc160SwingVOff; + default: return kHaierAc160SwingVAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRHaierAC160::toCommonMode(const uint8_t mode) { + switch (mode) { + case kHaierAcYrw02Cool: return stdAc::opmode_t::kCool; + case kHaierAcYrw02Heat: return stdAc::opmode_t::kHeat; + case kHaierAcYrw02Dry: return stdAc::opmode_t::kDry; + case kHaierAcYrw02Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHaierAC160::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kHaierAcYrw02FanHigh: return stdAc::fanspeed_t::kMax; + case kHaierAcYrw02FanMed: return stdAc::fanspeed_t::kMedium; + case kHaierAcYrw02FanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] pos The enum to be converted. +/// @return The native equivalent of the enum. +stdAc::swingv_t IRHaierAC160::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kHaierAc160SwingVTop: + case kHaierAc160SwingVHighest: return stdAc::swingv_t::kHighest; + case kHaierAc160SwingVHigh: return stdAc::swingv_t::kHigh; + case kHaierAc160SwingVMiddle: return stdAc::swingv_t::kMiddle; + case kHaierAc160SwingVLow: return stdAc::swingv_t::kLow; + case kHaierAc160SwingVLowest: return stdAc::swingv_t::kLowest; + case kHaierAc160SwingVOff: return stdAc::swingv_t::kOff; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHaierAC160::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.light = false; + } + result.protocol = decode_type_t::HAIER_AC160; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = !_.UseFahrenheit; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = toCommonSwingV(_.SwingV); + result.swingh = stdAc::swingh_t::kOff; + result.sleep = _.Sleep ? 0 : -1; + result.turbo = _.Turbo; + result.quiet = _.Quiet; + result.clean = _.Clean && _.Clean2; + result.light ^= getLightToggle(); + result.filter = _.Health; + // Not supported. + result.model = -1; + result.econo = false; + result.beep = true; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRHaierAC160::toString(void) const { + String result = ""; + result.reserve(280); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + uint8_t cmd = _.Button; + result += addIntToString(cmd, kButtonStr); + result += kSpaceLBraceStr; + switch (cmd) { + case kHaierAcYrw02ButtonPower: + result += kPowerStr; + break; + case kHaierAcYrw02ButtonMode: + result += kModeStr; + break; + case kHaierAcYrw02ButtonFan: + result += kFanStr; + break; + case kHaierAcYrw02ButtonTempUp: + result += kTempUpStr; + break; + case kHaierAcYrw02ButtonTempDown: + result += kTempDownStr; + break; + case kHaierAcYrw02ButtonSleep: + result += kSleepStr; + break; + case kHaierAcYrw02ButtonHealth: + result += kHealthStr; + break; + case kHaierAcYrw02ButtonSwingV: + result += kSwingVStr; + break; + case kHaierAcYrw02ButtonSwingH: + result += kSwingHStr; + break; + case kHaierAcYrw02ButtonTurbo: + result += kTurboStr; + break; + case kHaierAcYrw02ButtonTimer: + result += kTimerStr; + break; + case kHaierAcYrw02ButtonLock: + result += kLockStr; + break; + case kHaierAc160ButtonClean: + result += kCleanStr; + break; + case kHaierAc160ButtonLight: + result += kLightStr; + break; + case kHaierAc160ButtonAuxHeating: + result += kHeatingStr; + break; + case kHaierAcYrw02ButtonCFAB: + result += kCelsiusFahrenheitStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addModeToString(_.Mode, kHaierAcYrw02Auto, kHaierAcYrw02Cool, + kHaierAcYrw02Heat, kHaierAcYrw02Dry, + kHaierAcYrw02Fan); + result += addTempToString(getTemp(), !_.UseFahrenheit); + result += addFanToString(_.Fan, kHaierAcYrw02FanHigh, kHaierAcYrw02FanLow, + kHaierAcYrw02FanAuto, kHaierAcYrw02FanAuto, + kHaierAcYrw02FanMed); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Quiet, kQuietStr); + result += addBoolToString(_.Health, kHealthStr); + result += addIntToString(_.SwingV, kSwingVStr); + result += kSpaceLBraceStr; + switch (_.SwingV) { + case kHaierAc160SwingVOff: result += kOffStr; break; + case kHaierAc160SwingVAuto: result += kAutoStr; break; + case kHaierAc160SwingVTop: result += kTopStr; break; + case kHaierAc160SwingVHighest: result += kHighestStr; break; + case kHaierAc160SwingVHigh: result += kHighStr; break; + case kHaierAc160SwingVMiddle: result += kMiddleStr; break; + case kHaierAc160SwingVLow: result += kLowStr; break; + case kHaierAc160SwingVLowest: result += kLowestStr; break; + default: result += kUnknownStr; + } + result += ')'; + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(getClean(), kCleanStr); + const uint8_t tmode = getTimerMode(); + result += addIntToString(tmode, kTimerModeStr); + result += kSpaceLBraceStr; + switch (tmode) { + case kHaierAcYrw02NoTimers: + result += kNAStr; + break; + case kHaierAcYrw02OnTimer: + result += kOnStr; + break; + case kHaierAcYrw02OffTimer: + result += kOffStr; + break; + case kHaierAcYrw02OnThenOffTimer: + result += kOnStr; + result += '-'; + result += kOffStr; + break; + case kHaierAcYrw02OffThenOnTimer: + result += kOffStr; + result += '-'; + result += kOnStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addLabeledString((tmode != kHaierAcYrw02NoTimers && + tmode != kHaierAcYrw02OffTimer) ? + minsToString(getOnTimer()) : kOffStr, kOnTimerStr); + result += addLabeledString((tmode != kHaierAcYrw02NoTimers && + tmode != kHaierAcYrw02OnTimer) ? + minsToString(getOffTimer()) : kOffStr, kOffTimerStr); + result += addBoolToString(_.Lock, kLockStr); + result += addBoolToString(_.AuxHeating, kHeatingStr); + return result; +} +// End of IRHaierAC160 class. diff --git a/src/libraries/IRremoteESP8266/src/ir_Haier.h b/src/libraries/IRremoteESP8266/src/ir_Haier.h new file mode 100644 index 000000000..32c2af77b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Haier.h @@ -0,0 +1,653 @@ +// Copyright 2018-2021 crankyoldgit +/// @file +/// @brief Support for Haier A/C protocols. +/// The specifics of reverse engineering the protocols details: +/// * HSU07-HEA03 by kuzin2006. +/// * YR-W02/HSU-09HMC203 by non7top. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/404 +/// @see https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/485 +/// @see https://www.dropbox.com/sh/w0bt7egp0fjger5/AADRFV6Wg4wZskJVdFvzb8Z0a?dl=0&preview=haer2.ods +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1804 + +// Supports: +// Brand: Haier, Model: HSU07-HEA03 remote (HAIER_AC) +// Brand: Haier, Model: YR-W02 remote (HAIER_AC_YRW02) +// Brand: Haier, Model: HSU-09HMC203 A/C (HAIER_AC_YRW02) +// Brand: Haier, Model: V9014557 M47 8D remote (HAIER_AC176) +// Brand: Mabe, Model: MMI18HDBWCA6MI8 A/C (HAIER_AC176) +// Brand: Mabe, Model: V12843 HJ200223 remote (HAIER_AC176) +// Brand: Daichi, Model: D-H A/C (HAIER_AC176) +// Brand: Haier, Model: KFR-26GW/83@UI-Ge A/C (HAIER_AC160) + +#ifndef IR_HAIER_H_ +#define IR_HAIER_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Haier HSU07-HEA03 A/C message. +union HaierProtocol{ + ///< The state in native IR code form + uint8_t remote_state[kHaierACStateLength]; + struct { + // Byte 0 + uint8_t Prefix; + // Byte 1 + uint8_t Command:4; + uint8_t Temp :4; + // Byte 2 + uint8_t CurrHours:5; + uint8_t unknown :1; // value=1 + uint8_t SwingV :2; + // Byte 3 + uint8_t CurrMins:6; + uint8_t OffTimer:1; + uint8_t OnTimer :1; + // Byte 4 + uint8_t OffHours:5; + uint8_t Health :1; + uint8_t :0; + // Byte 5 + uint8_t OffMins:6; + uint8_t Fan :2; + // Byte 6 + uint8_t OnHours:5; + uint8_t Mode :3; + // Byte 7 + uint8_t OnMins:6; + uint8_t Sleep :1; + uint8_t :0; + // Byte 8 + uint8_t Sum; + }; +}; + +// Constants + +const uint8_t kHaierAcPrefix = 0b10100101; + +const uint8_t kHaierAcMinTemp = 16; +const uint8_t kHaierAcDefTemp = 25; +const uint8_t kHaierAcMaxTemp = 30; +const uint8_t kHaierAcCmdOff = 0b0000; +const uint8_t kHaierAcCmdOn = 0b0001; +const uint8_t kHaierAcCmdMode = 0b0010; +const uint8_t kHaierAcCmdFan = 0b0011; +const uint8_t kHaierAcCmdTempUp = 0b0110; +const uint8_t kHaierAcCmdTempDown = 0b0111; +const uint8_t kHaierAcCmdSleep = 0b1000; +const uint8_t kHaierAcCmdTimerSet = 0b1001; +const uint8_t kHaierAcCmdTimerCancel = 0b1010; +const uint8_t kHaierAcCmdHealth = 0b1100; +const uint8_t kHaierAcCmdSwing = 0b1101; + +const uint8_t kHaierAcSwingVOff = 0b00; +const uint8_t kHaierAcSwingVUp = 0b01; +const uint8_t kHaierAcSwingVDown = 0b10; +const uint8_t kHaierAcSwingVChg = 0b11; + +const uint8_t kHaierAcAuto = 0; +const uint8_t kHaierAcCool = 1; +const uint8_t kHaierAcDry = 2; +const uint8_t kHaierAcHeat = 3; +const uint8_t kHaierAcFan = 4; + +const uint8_t kHaierAcFanAuto = 0; +const uint8_t kHaierAcFanLow = 1; +const uint8_t kHaierAcFanMed = 2; +const uint8_t kHaierAcFanHigh = 3; + +const uint16_t kHaierAcMaxTime = (23 * 60) + 59; + +const uint8_t kHaierAcSleepBit = 0b01000000; + +// Legacy Haier AC defines. +#define HAIER_AC_MIN_TEMP kHaierAcMinTemp +#define HAIER_AC_DEF_TEMP kHaierAcDefTemp +#define HAIER_AC_MAX_TEMP kHaierAcMaxTemp +#define HAIER_AC_CMD_OFF kHaierAcCmdOff +#define HAIER_AC_CMD_ON kHaierAcCmdOn +#define HAIER_AC_CMD_MODE kHaierAcCmdMode +#define HAIER_AC_CMD_FAN kHaierAcCmdFan +#define HAIER_AC_CMD_TEMP_UP kHaierAcCmdTempUp +#define HAIER_AC_CMD_TEMP_DOWN kHaierAcCmdTempDown +#define HAIER_AC_CMD_SLEEP kHaierAcCmdSleep +#define HAIER_AC_CMD_TIMER_SET kHaierAcCmdTimerSet +#define HAIER_AC_CMD_TIMER_CANCEL kHaierAcCmdTimerCancel +#define HAIER_AC_CMD_HEALTH kHaierAcCmdHealth +#define HAIER_AC_CMD_SWINGV kHaierAcCmdSwing +#define HAIER_AC_SWINGV_OFF kHaierAcSwingVOff +#define HAIER_AC_SWINGV_UP kHaierAcSwingVUp +#define HAIER_AC_SWINGV_DOWN kHaierAcSwingVDown +#define HAIER_AC_SWINGV_CHG kHaierAcSwingVChg +#define HAIER_AC_AUTO kHaierAcAuto +#define HAIER_AC_COOL kHaierAcCool +#define HAIER_AC_DRY kHaierAcDry +#define HAIER_AC_HEAT kHaierAcHeat +#define HAIER_AC_FAN kHaierAcFan +#define HAIER_AC_FAN_AUTO kHaierAcFanAuto +#define HAIER_AC_FAN_LOW kHaierAcFanLow +#define HAIER_AC_FAN_MED kHaierAcFanMed +#define HAIER_AC_FAN_HIGH kHaierAcFanHigh + +const uint8_t kHaierAcYrw02MinTempC = 16; +const uint8_t kHaierAcYrw02MaxTempC = 30; +const uint8_t kHaierAcYrw02MinTempF = 60; +const uint8_t kHaierAcYrw02MaxTempF = 86; +const uint8_t kHaierAcYrw02DefTempC = 25; + +const uint8_t kHaierAcYrw02ModelA = 0xA6; +const uint8_t kHaierAcYrw02ModelB = 0x59; +const uint8_t kHaierAc176Prefix = 0xB7; +const uint8_t kHaierAc160Prefix = 0xB5; + +const uint8_t kHaierAcYrw02SwingVOff = 0x0; +const uint8_t kHaierAcYrw02SwingVTop = 0x1; +const uint8_t kHaierAcYrw02SwingVMiddle = 0x2; // Not available in heat mode. +const uint8_t kHaierAcYrw02SwingVBottom = 0x3; // Only available in heat mode. +const uint8_t kHaierAcYrw02SwingVDown = 0xA; +const uint8_t kHaierAcYrw02SwingVAuto = 0xC; // Airflow + +const uint8_t kHaierAc160SwingVOff = 0b0000; +const uint8_t kHaierAc160SwingVTop = 0b0001; +const uint8_t kHaierAc160SwingVHighest = 0b0010; +const uint8_t kHaierAc160SwingVHigh = 0b0100; +const uint8_t kHaierAc160SwingVMiddle = 0b0110; +const uint8_t kHaierAc160SwingVLow = 0b1000; +const uint8_t kHaierAc160SwingVLowest = 0b0011; +const uint8_t kHaierAc160SwingVAuto = 0b1100; // Airflow + +const uint8_t kHaierAcYrw02SwingHMiddle = 0x0; +const uint8_t kHaierAcYrw02SwingHLeftMax = 0x3; +const uint8_t kHaierAcYrw02SwingHLeft = 0x4; +const uint8_t kHaierAcYrw02SwingHRight = 0x5; +const uint8_t kHaierAcYrw02SwingHRightMax = 0x6; +const uint8_t kHaierAcYrw02SwingHAuto = 0x7; + +const uint8_t kHaierAcYrw02FanHigh = 0b001; +const uint8_t kHaierAcYrw02FanMed = 0b010; +const uint8_t kHaierAcYrw02FanLow = 0b011; +const uint8_t kHaierAcYrw02FanAuto = 0b101; // HAIER_AC176 uses `0` in Fan2 + +const uint8_t kHaierAcYrw02Auto = 0b000; // 0 +const uint8_t kHaierAcYrw02Cool = 0b001; // 1 +const uint8_t kHaierAcYrw02Dry = 0b010; // 2 +const uint8_t kHaierAcYrw02Heat = 0b100; // 4 +const uint8_t kHaierAcYrw02Fan = 0b110; // 5 + +const uint8_t kHaierAcYrw02ButtonTempUp = 0b00000; +const uint8_t kHaierAcYrw02ButtonTempDown = 0b00001; +const uint8_t kHaierAcYrw02ButtonSwingV = 0b00010; +const uint8_t kHaierAcYrw02ButtonSwingH = 0b00011; +const uint8_t kHaierAcYrw02ButtonFan = 0b00100; +const uint8_t kHaierAcYrw02ButtonPower = 0b00101; +const uint8_t kHaierAcYrw02ButtonMode = 0b00110; +const uint8_t kHaierAcYrw02ButtonHealth = 0b00111; +const uint8_t kHaierAcYrw02ButtonTurbo = 0b01000; +const uint8_t kHaierAcYrw02ButtonSleep = 0b01011; +const uint8_t kHaierAcYrw02ButtonTimer = 0b10000; +const uint8_t kHaierAcYrw02ButtonLock = 0b10100; +const uint8_t kHaierAc160ButtonLight = 0b10101; +const uint8_t kHaierAc160ButtonAuxHeating = 0b10110; +const uint8_t kHaierAc160ButtonClean = 0b11001; +const uint8_t kHaierAcYrw02ButtonCFAB = 0b11010; + +const uint8_t kHaierAcYrw02NoTimers = 0b000; +const uint8_t kHaierAcYrw02OffTimer = 0b001; +const uint8_t kHaierAcYrw02OnTimer = 0b010; +const uint8_t kHaierAcYrw02OnThenOffTimer = 0b100; +const uint8_t kHaierAcYrw02OffThenOnTimer = 0b101; + +/// Native representation of a Haier 176 bit A/C message. +union HaierAc176Protocol{ + uint8_t raw[kHaierAC176StateLength]; ///< The state in native form + struct { + // Byte 0 + uint8_t Model :8; + // Byte 1 + uint8_t SwingV :4; + uint8_t Temp :4; // 16C~30C + // Byte 2 + uint8_t :5; + uint8_t SwingH :3; + // Byte 3 + uint8_t :1; + uint8_t Health :1; + uint8_t :3; + uint8_t TimerMode :3; + // Byte 4 + uint8_t :6; + uint8_t Power :1; + uint8_t :1; + // Byte 5 + uint8_t OffTimerHrs :5; + uint8_t Fan :3; + // Byte 6 + uint8_t OffTimerMins:6; + uint8_t Turbo :1; + uint8_t Quiet :1; + // Byte 7 + uint8_t OnTimerHrs :5; + uint8_t Mode :3; + // Byte 8 + uint8_t OnTimerMins :6; + uint8_t :1; + uint8_t Sleep :1; + // Byte 9 + uint8_t :8; + // Byte 10 + uint8_t ExtraDegreeF :1; + uint8_t :4; + uint8_t UseFahrenheit:1; + uint8_t :2; + // Byte 11 + uint8_t :8; + // Byte 12 + uint8_t Button :5; + uint8_t Lock :1; + uint8_t :2; + // Byte 13 + uint8_t Sum :8; + // Byte 14 + uint8_t Prefix2 :8; + // Byte 15 + uint8_t :8; + // Byte 16 + uint8_t :6; + uint8_t Fan2 :2; + // Byte 17 + uint8_t :8; + // Byte 18 + uint8_t :8; + // Byte 19 + uint8_t :8; + // Byte 20 + uint8_t :8; + // Byte 21 + uint8_t Sum2 :8; + }; +}; + +/// Native representation of a Haier 160 bit A/C message. +union HaierAc160Protocol{ + uint8_t raw[kHaierAC160StateLength]; ///< The state in native form + struct { + // Byte 0 + uint8_t Model :8; + // Byte 1 + uint8_t SwingV :4; + uint8_t Temp :4; // 16C~30C + // Byte 2 + uint8_t :5; + uint8_t SwingH :3; + // Byte 3 + uint8_t :1; + uint8_t Health :1; + uint8_t :3; + uint8_t TimerMode :3; + // Byte 4 + uint8_t :6; + uint8_t Power :1; + uint8_t AuxHeating :1; + // Byte 5 + uint8_t OffTimerHrs :5; + uint8_t Fan :3; + // Byte 6 + uint8_t OffTimerMins:6; + uint8_t Turbo :1; + uint8_t Quiet :1; + // Byte 7 + uint8_t OnTimerHrs :5; + uint8_t Mode :3; + // Byte 8 + uint8_t OnTimerMins :6; + uint8_t :1; + uint8_t Sleep :1; + // Byte 9 + uint8_t :8; + // Byte 10 + uint8_t ExtraDegreeF :1; + uint8_t :3; + uint8_t Clean :1; + uint8_t UseFahrenheit:1; + uint8_t :2; + // Byte 11 + uint8_t :8; + // Byte 12 + uint8_t Button :5; + uint8_t Lock :1; + uint8_t :2; + // Byte 13 + uint8_t Sum :8; + // Byte 14 + uint8_t Prefix :8; + // Byte 15 + uint8_t :6; + uint8_t Clean2 :1; + uint8_t :1; + // Byte 16 + uint8_t :5; + uint8_t Fan2 :3; + // Byte 17 + uint8_t :8; + // Byte 18 + uint8_t :8; + // Byte 19 + uint8_t Sum2 :8; + }; +}; + +// Legacy Haier YRW02 remote defines. +#define HAIER_AC_YRW02_SWING_OFF kHaierAcYrw02SwingOff +#define HAIER_AC_YRW02_SWING_TOP kHaierAcYrw02SwingTop +#define HAIER_AC_YRW02_SWING_MIDDLE kHaierAcYrw02SwingMiddle +#define HAIER_AC_YRW02_SWING_BOTTOM kHaierAcYrw02SwingBottom +#define HAIER_AC_YRW02_SWING_DOWN kHaierAcYrw02SwingDown +#define HAIER_AC_YRW02_SWING_AUTO kHaierAcYrw02SwingAuto +#define HAIER_AC_YRW02_FAN_HIGH kHaierAcYrw02FanHigh +#define HAIER_AC_YRW02_FAN_MED kHaierAcYrw02FanMed +#define HAIER_AC_YRW02_FAN_LOW kHaierAcYrw02FanLow +#define HAIER_AC_YRW02_FAN_AUTO kHaierAcYrw02FanAuto +#define HAIER_AC_YRW02_TURBO_OFF kHaierAcYrw02TurboOff +#define HAIER_AC_YRW02_AUTO kHaierAcYrw02Auto +#define HAIER_AC_YRW02_COOL kHaierAcYrw02Cool +#define HAIER_AC_YRW02_DRY kHaierAcYrw02Dry +#define HAIER_AC_YRW02_HEAT kHaierAcYrw02Heat +#define HAIER_AC_YRW02_FAN kHaierAcYrw02Fan +#define HAIER_AC_YRW02_BUTTON_TEMP_UP kHaierAcYrw02ButtonTempUp +#define HAIER_AC_YRW02_BUTTON_TEMP_DOWN kHaierAcYrw02ButtonTempDown +#define HAIER_AC_YRW02_BUTTON_SWING kHaierAcYrw02ButtonSwing +#define HAIER_AC_YRW02_BUTTON_FAN kHaierAcYrw02ButtonFan +#define HAIER_AC_YRW02_BUTTON_POWER kHaierAcYrw02ButtonPower +#define HAIER_AC_YRW02_BUTTON_MODE kHaierAcYrw02ButtonMode +#define HAIER_AC_YRW02_BUTTON_HEALTH kHaierAcYrw02ButtonHealth +#define HAIER_AC_YRW02_BUTTON_TURBO kHaierAcYrw02ButtonTurbo +#define HAIER_AC_YRW02_BUTTON_SLEEP kHaierAcYrw02ButtonSleep + +// Classes +/// Class for handling detailed Haier A/C messages. +class IRHaierAC { + public: + explicit IRHaierAC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_HAIER_AC + void send(const uint16_t repeat = kHaierAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HAIER_AC + void begin(void); + void stateReset(void); + + void setCommand(const uint8_t command); + uint8_t getCommand(void) const; + + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + + bool getSleep(void) const; + void setSleep(const bool on); + bool getHealth(void) const; + void setHealth(const bool on); + + int16_t getOnTimer(void) const; + void setOnTimer(const uint16_t mins); + int16_t getOffTimer(void) const; + void setOffTimer(const uint16_t mins); + void cancelTimers(void); + + uint16_t getCurrTime(void) const; + void setCurrTime(const uint16_t mins); + + uint8_t getSwingV(void) const; + void setSwingV(const uint8_t state); + + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static bool validChecksum(uint8_t state[], + const uint16_t length = kHaierACStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + HaierProtocol _; + void checksum(void); +}; + +/// Class for handling detailed Haier 176 bit A/C messages. +class IRHaierAC176 { + friend class IRHaierACYRW02; + public: + explicit IRHaierAC176(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_HAIER_AC176 + virtual void send(const uint16_t repeat = kHaierAc176DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HAIER_AC176 + void begin(void); + void stateReset(void); + + void setModel(const haier_ac176_remote_model_t model); + haier_ac176_remote_model_t getModel(void) const; + + void setButton(const uint8_t button); + uint8_t getButton(void) const; + + void setUseFahrenheit(const bool on); + bool getUseFahrenheit(void) const; + void setTemp(const uint8_t temp, const bool fahrenheit = false); + uint8_t getTemp(void) const; + + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + + bool getPower(void) const; + void setPower(const bool on); + void on(void); + void off(void); + + bool getSleep(void) const; + void setSleep(const bool on); + bool getHealth(void) const; + void setHealth(const bool on); + + bool getTurbo(void) const; + void setTurbo(const bool on); + bool getQuiet(void) const; + void setQuiet(const bool on); + + uint8_t getSwingV(void) const; + void setSwingV(const uint8_t pos); + uint8_t getSwingH(void) const; + void setSwingH(const uint8_t pos); + + /// These functions are for backward compatibility. + /// Use getSwingV() and setSwingV() instead. + uint8_t getSwing(void) const; + void setSwing(const uint8_t pos); + + void setTimerMode(const uint8_t setting); + uint8_t getTimerMode(void) const; + void setOnTimer(const uint16_t mins); + uint16_t getOnTimer(void) const; + void setOffTimer(const uint16_t mins); + uint16_t getOffTimer(void) const; + + bool getLock(void) const; + void setLock(const bool on); + + uint8_t* getRaw(void); + virtual void setRaw(const uint8_t new_code[]); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kHaierAC176StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + static bool toCommonTurbo(const uint8_t speed); + static bool toCommonQuiet(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + HaierAc176Protocol _; + void checksum(void); +}; + +/// Class for handling detailed Haier ACYRW02 A/C messages. +class IRHaierACYRW02 : public IRHaierAC176 { + public: + explicit IRHaierACYRW02(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_HAIER_AC_YRW02 + void send(const uint16_t repeat = kHaierAcYrw02DefaultRepeat) override; + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HAIER_AC_YRW02 + void setRaw(const uint8_t new_code[]) override; + static bool validChecksum( + const uint8_t state[], + const uint16_t length = kHaierACYRW02StateLength); +}; + +/// Class for handling detailed Haier 160 bit A/C messages. +class IRHaierAC160 { + public: + explicit IRHaierAC160(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_HAIER_AC160 + virtual void send(const uint16_t repeat = kHaierAc160DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HAIER_AC160 + void begin(void); + void stateReset(void); + + void setButton(const uint8_t button); + uint8_t getButton(void) const; + + void setUseFahrenheit(const bool on); + bool getUseFahrenheit(void) const; + void setTemp(const uint8_t temp, const bool fahrenheit = false); + uint8_t getTemp(void) const; + + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + + bool getPower(void) const; + void setPower(const bool on); + void on(void); + void off(void); + + bool getSleep(void) const; + void setSleep(const bool on); + bool getClean(void) const; + void setClean(const bool on); + bool getLightToggle(void) const; + void setLightToggle(const bool on); + + bool getTurbo(void) const; + void setTurbo(const bool on); + bool getQuiet(void) const; + void setQuiet(const bool on); + bool getAuxHeating(void) const; + void setAuxHeating(const bool on); + + uint8_t getSwingV(void) const; + void setSwingV(const uint8_t pos); + + void setTimerMode(const uint8_t setting); + uint8_t getTimerMode(void) const; + void setOnTimer(const uint16_t mins); + uint16_t getOnTimer(void) const; + void setOffTimer(const uint16_t mins); + uint16_t getOffTimer(void) const; + + bool getLock(void) const; + void setLock(const bool on); + + bool getHealth(void) const; + void setHealth(const bool on); + + uint8_t* getRaw(void); + virtual void setRaw(const uint8_t new_code[]); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kHaierAC160StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static bool toCommonTurbo(const uint8_t speed); + static bool toCommonQuiet(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + HaierAc160Protocol _; + void checksum(void); +}; +#endif // IR_HAIER_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Hitachi.cpp b/src/libraries/IRremoteESP8266/src/ir_Hitachi.cpp new file mode 100644 index 000000000..257c45f76 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Hitachi.cpp @@ -0,0 +1,1997 @@ +// Copyright 2018-2019 David Conran +/// @file +/// @brief Support for Hitachi A/C protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/417 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/453 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/973 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1056 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1060 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1134 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1757 + +#include "ir_Hitachi.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kHitachiAcHdrMark = 3300; +const uint16_t kHitachiAcHdrSpace = 1700; +const uint16_t kHitachiAc1HdrMark = 3400; +const uint16_t kHitachiAc1HdrSpace = 3400; +const uint16_t kHitachiAcBitMark = 400; +const uint16_t kHitachiAcOneSpace = 1250; +const uint16_t kHitachiAcZeroSpace = 500; +const uint32_t kHitachiAcMinGap = kDefaultMessageGap; // Just a guess. +// Support for HitachiAc424 protocol +const uint16_t kHitachiAc424LdrMark = 29784; // Leader +const uint16_t kHitachiAc424LdrSpace = 49290; // Leader +const uint16_t kHitachiAc424HdrMark = 3416; // Header +const uint16_t kHitachiAc424HdrSpace = 1604; // Header +const uint16_t kHitachiAc424BitMark = 463; +const uint16_t kHitachiAc424OneSpace = 1208; +const uint16_t kHitachiAc424ZeroSpace = 372; + +// Support for HitachiAc3 protocol +const uint16_t kHitachiAc3HdrMark = 3400; // Header +const uint16_t kHitachiAc3HdrSpace = 1660; // Header +const uint16_t kHitachiAc3BitMark = 460; +const uint16_t kHitachiAc3OneSpace = 1250; +const uint16_t kHitachiAc3ZeroSpace = 410; + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::checkInvertedBytePairs; +using irutils::invertBytePairs; +using irutils::minsToString; + +#if (SEND_HITACHI_AC || SEND_HITACHI_AC2 || SEND_HITACHI_AC264 || \ + SEND_HITACHI_AC344) +/// Send a Hitachi 28-byte/224-bit A/C formatted message. (HITACHI_AC) +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/417 +void IRsend::sendHitachiAC(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHitachiAcStateLength) + return; // Not enough bytes to send a proper message. + + bool MSBfirst = true; + switch (nbytes) { + case kHitachiAc264StateLength: + case kHitachiAc296StateLength: + case kHitachiAc344StateLength: + MSBfirst = false; + } + + sendGeneric(kHitachiAcHdrMark, kHitachiAcHdrSpace, kHitachiAcBitMark, + kHitachiAcOneSpace, kHitachiAcBitMark, kHitachiAcZeroSpace, + kHitachiAcBitMark, kHitachiAcMinGap, data, nbytes, 38, MSBfirst, + repeat, 50); +} +#endif // (SEND_HITACHI_AC || SEND_HITACHI_AC2 || SEND_HITACHI_AC264 || + // SEND_HITACHI_AC344) + +#if SEND_HITACHI_AC1 +/// Send a Hitachi 13 byte/224-bit A/C formatted message. (HITACHI_AC1) +/// Status: STABLE / Confirmed Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Basically the same as sendHitachiAC() except different size & header. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/453 +void IRsend::sendHitachiAC1(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHitachiAc1StateLength) + return; // Not enough bytes to send a proper message. + sendGeneric(kHitachiAc1HdrMark, kHitachiAc1HdrSpace, kHitachiAcBitMark, + kHitachiAcOneSpace, kHitachiAcBitMark, kHitachiAcZeroSpace, + kHitachiAcBitMark, kHitachiAcMinGap, data, nbytes, kHitachiAcFreq, + true, repeat, kDutyDefault); +} +#endif // SEND_HITACHI_AC1 + +#if SEND_HITACHI_AC2 +/// Send a Hitachi 53 byte/424-bit A/C formatted message. (HITACHI_AC2) +/// Basically the same as sendHitachiAC() except different size. +/// Status: STABLE / Expected to work. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendHitachiAC2(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHitachiAc2StateLength) + return; // Not enough bytes to send a proper message. + sendHitachiAC(data, nbytes, repeat); +} +#endif // SEND_HITACHI_AC2 + +#if SEND_HITACHI_AC344 +/// Send a Hitachi A/C 43-byte/344-bit message. (HITACHI_AC344) +/// Basically the same as sendHitachiAC() except different size. +/// Status: Beta / Probably works. +/// @param[in] data An array of bytes containing the IR command. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// @param[in] repeat Nr. of times the message is to be repeated. (Default = 0). +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1134 +void IRsend::sendHitachiAc344(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHitachiAc344StateLength) + return; // Not enough bytes to send a proper message. + sendHitachiAC(data, nbytes, repeat); +} +#endif // SEND_HITACHI_AC344 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHitachiAc::IRHitachiAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IRHitachiAc::stateReset(void) { + _.raw[0] = 0x80; + _.raw[1] = 0x08; + _.raw[2] = 0x0C; + _.raw[3] = 0x02; + _.raw[4] = 0xFD; + _.raw[5] = 0x80; + _.raw[6] = 0x7F; + _.raw[7] = 0x88; + _.raw[8] = 0x48; + _.raw[9] = 0x10; + for (uint8_t i = 10; i < kHitachiAcStateLength; i++) _.raw[i] = 0x00; + _.raw[14] = 0x60; + _.raw[15] = 0x60; + _.raw[24] = 0x80; + setTemp(23); +} + +/// Set up hardware to be able to send a message. +void IRHitachiAc::begin(void) { _irsend.begin(); } + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @param[in] length The size/length of the state. +/// @return The calculated checksum value. +uint8_t IRHitachiAc::calcChecksum(const uint8_t state[], + const uint16_t length) { + uint8_t sum = 62; + for (uint16_t i = 0; i < length - 1; i++) sum -= reverseBits(state[i], 8); + return reverseBits(sum, 8); +} + +/// Calculate and set the checksum values for the internal state. +/// @param[in] length The size/length of the state. +void IRHitachiAc::checksum(const uint16_t length) { + _.Sum = calcChecksum(_.raw, length); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRHitachiAc::validChecksum(const uint8_t state[], const uint16_t length) { + if (length < 2) return true; // Assume true for lengths that are too short. + return (state[length - 1] == calcChecksum(state, length)); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRHitachiAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length of the new_code array. +void IRHitachiAc::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kHitachiAcStateLength)); +} + +#if SEND_HITACHI_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHitachiAc::send(const uint16_t repeat) { + _irsend.sendHitachiAC(getRaw(), kHitachiAcStateLength, repeat); +} +#endif // SEND_HITACHI_AC + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc::getPower(void) const { + return _.Power; +} + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc::setPower(const bool on) { + _.Power = on; +} + +/// Change the power setting to On. +void IRHitachiAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRHitachiAc::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHitachiAc::getMode(void) const { return reverseBits(_.Mode, 8); } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHitachiAc::setMode(const uint8_t mode) { + uint8_t newmode = mode; + switch (mode) { + // Fan mode sets a special temp. + case kHitachiAcFan: setTemp(64); break; + case kHitachiAcAuto: + case kHitachiAcHeat: + case kHitachiAcCool: + case kHitachiAcDry: break; + default: newmode = kHitachiAcAuto; + } + _.Mode = reverseBits(newmode, 8); + if (mode != kHitachiAcFan) setTemp(_previoustemp); + setFan(getFan()); // Reset the fan speed after the mode change. +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRHitachiAc::getTemp(void) const { + return reverseBits(_.Temp, 8) >> 1; +} + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IRHitachiAc::setTemp(const uint8_t celsius) { + uint8_t temp; + if (celsius != 64) _previoustemp = celsius; + switch (celsius) { + case 64: + temp = celsius; + break; + default: + temp = ::min(celsius, kHitachiAcMaxTemp); + temp = ::max(temp, kHitachiAcMinTemp); + } + _.Temp = reverseBits(temp << 1, 8); + if (temp == kHitachiAcMinTemp) + _.raw[9] = 0x90; + else + _.raw[9] = 0x10; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHitachiAc::getFan(void) const { return reverseBits(_.Fan, 8); } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHitachiAc::setFan(const uint8_t speed) { + uint8_t fanmin = kHitachiAcFanAuto; + uint8_t fanmax = kHitachiAcFanHigh; + switch (getMode()) { + case kHitachiAcDry: // Only 2 x low speeds in Dry mode. + fanmin = kHitachiAcFanLow; + fanmax = kHitachiAcFanLow + 1; + break; + case kHitachiAcFan: + fanmin = kHitachiAcFanLow; // No Auto in Fan mode. + break; + } + uint8_t newspeed = ::max(speed, fanmin); + newspeed = ::min(newspeed, fanmax); + _.Fan = reverseBits(newspeed, 8); +} + +/// Get the Vertical Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc::getSwingVertical(void) const { + return _.SwingV; +} + +/// Set the Vertical Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc::setSwingVertical(const bool on) { + _.SwingV = on; +} + +/// Get the Horizontal Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc::getSwingHorizontal(void) const { + return _.SwingH; +} + +/// Set the Horizontal Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc::setSwingHorizontal(const bool on) { + _.SwingH = on; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kHitachiAcCool; + case stdAc::opmode_t::kHeat: return kHitachiAcHeat; + case stdAc::opmode_t::kDry: return kHitachiAcDry; + case stdAc::opmode_t::kFan: return kHitachiAcFan; + default: return kHitachiAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kHitachiAcFanLow; + case stdAc::fanspeed_t::kMedium: return kHitachiAcFanLow + 1; + case stdAc::fanspeed_t::kHigh: return kHitachiAcFanHigh - 1; + case stdAc::fanspeed_t::kMax: return kHitachiAcFanHigh; + default: return kHitachiAcFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRHitachiAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kHitachiAcCool: return stdAc::opmode_t::kCool; + case kHitachiAcHeat: return stdAc::opmode_t::kHeat; + case kHitachiAcDry: return stdAc::opmode_t::kDry; + case kHitachiAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHitachiAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kHitachiAcFanHigh: return stdAc::fanspeed_t::kMax; + case kHitachiAcFanHigh - 1: return stdAc::fanspeed_t::kHigh; + case kHitachiAcFanLow + 1: return stdAc::fanspeed_t::kMedium; + case kHitachiAcFanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHitachiAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::HITACHI_AC; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = (_.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff); + result.swingh = (_.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff); + // Not supported. + result.quiet = false; + result.turbo = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRHitachiAc::toString(void) const { + String result = ""; + result.reserve(110); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(getMode(), kHitachiAcAuto, kHitachiAcCool, + kHitachiAcHeat, kHitachiAcDry, kHitachiAcFan); + result += addTempToString(getTemp()); + result += addFanToString(getFan(), kHitachiAcFanHigh, kHitachiAcFanLow, + kHitachiAcFanAuto, kHitachiAcFanAuto, + kHitachiAcFanMed); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.SwingH, kSwingHStr); + return result; +} + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHitachiAc1::IRHitachiAc1(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IRHitachiAc1::stateReset(void) { + for (uint8_t i = 0; i < kHitachiAc1StateLength; i++) _.raw[i] = 0x00; + // Copy in a known good state. + _.raw[0] = 0xB2; + _.raw[1] = 0xAE; + _.raw[2] = 0x4D; + _.raw[3] = 0x91; + _.raw[4] = 0xF0; + _.raw[5] = 0xE1; + _.raw[6] = 0xA4; + _.raw[11] = 0x61; + _.raw[12] = 0x24; +} + +/// Set up hardware to be able to send a message. +void IRHitachiAc1::begin(void) { _irsend.begin(); } + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @param[in] length The size/length of the state. +/// @return The calculated checksum value. +uint8_t IRHitachiAc1::calcChecksum(const uint8_t state[], + const uint16_t length) { + uint8_t sum = 0; + for (uint16_t i = kHitachiAc1ChecksumStartByte; i < length - 1; i++) { + sum += reverseBits(GETBITS8(state[i], kLowNibble, kNibbleSize), + kNibbleSize); + sum += reverseBits(GETBITS8(state[i], kHighNibble, kNibbleSize), + kNibbleSize); + } + return reverseBits(sum, 8); +} + +/// Calculate and set the checksum values for the internal state. +/// @param[in] length The size/length of the state. +void IRHitachiAc1::checksum(const uint16_t length) { + _.Sum = calcChecksum(_.raw, length); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRHitachiAc1::validChecksum(const uint8_t state[], const uint16_t length) { + if (length < 2) return true; // Assume true for lengths that are too short. + return (state[length - 1] == calcChecksum(state, length)); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRHitachiAc1::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length of the new_code array. +void IRHitachiAc1::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kHitachiAc1StateLength)); +} + +#if SEND_HITACHI_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHitachiAc1::send(const uint16_t repeat) { + _irsend.sendHitachiAC1(getRaw(), kHitachiAc1StateLength, repeat); + // Clear the toggle bits as we have actioned them by sending them. + setPowerToggle(false); + setSwingToggle(false); +} +#endif // SEND_HITACHI_AC + +/// Get/Detect the model of the A/C. +/// @return The enum of the compatible model. +hitachi_ac1_remote_model_t IRHitachiAc1::getModel(void) const { + switch (_.Model) { + case kHitachiAc1Model_B: return hitachi_ac1_remote_model_t::R_LT0541_HTA_B; + default: return hitachi_ac1_remote_model_t::R_LT0541_HTA_A; + } +} + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRHitachiAc1::setModel(const hitachi_ac1_remote_model_t model) { + uint8_t value = 0; + switch (model) { + case hitachi_ac1_remote_model_t::R_LT0541_HTA_B: + value = kHitachiAc1Model_B; + break; + default: + value = kHitachiAc1Model_A; // i.e. 'A' mode. + } + _.Model = value; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc1::getPower(void) const { + return _.Power; +} + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc1::setPower(const bool on) { + // If the power changes, set the power toggle bit. + if (on != _.Power) setPowerToggle(true); + _.Power = on; +} + +/// Get the value of the current power toggle setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc1::getPowerToggle(void) const { + return _.PowerToggle; +} + +/// Change the power toggle setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc1::setPowerToggle(const bool on) { + _.PowerToggle = on; +} + +/// Change the power setting to On. +void IRHitachiAc1::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRHitachiAc1::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHitachiAc1::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHitachiAc1::setMode(const uint8_t mode) { + switch (mode) { + case kHitachiAc1Auto: + setTemp(kHitachiAc1TempAuto); + // FALL THRU + case kHitachiAc1Fan: + case kHitachiAc1Heat: + case kHitachiAc1Cool: + case kHitachiAc1Dry: + _.Mode = mode; + break; + default: + setTemp(kHitachiAc1TempAuto); + _.Mode = kHitachiAc1Auto; + break; + } + setSleep(_.Sleep); // Correct the sleep mode if required. + setFan(_.Fan); // Correct the fan speed if required. +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRHitachiAc1::getTemp(void) const { + return reverseBits(_.Temp, kHitachiAc1TempSize) + kHitachiAc1TempDelta; +} + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IRHitachiAc1::setTemp(const uint8_t celsius) { + if (_.Mode == kHitachiAc1Auto) return; // Can't change temp in Auto mode. + uint8_t temp = ::min(celsius, kHitachiAcMaxTemp); + temp = ::max(temp, kHitachiAcMinTemp); + temp -= kHitachiAc1TempDelta; + temp = reverseBits(temp, kHitachiAc1TempSize); + _.Temp = temp; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHitachiAc1::getFan(void) const { + return _.Fan; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @param[in] force Deprecated +void IRHitachiAc1::setFan(const uint8_t speed, const bool /*force*/) { + // restrictions + switch (_.Mode) { + case kHitachiAc1Dry: + _.Fan = kHitachiAc1FanLow; // Dry is locked to Low speed. + return; + case kHitachiAc1Auto: + _.Fan = kHitachiAc1FanAuto; // Auto is locked to Auto speed. + return; + case kHitachiAc1Heat: + case kHitachiAc1Fan: // Auto speed not allowed in these modes. + if (speed == kHitachiAc1FanAuto || _.Fan == kHitachiAc1FanAuto) + _.Fan = kHitachiAc1FanLow; + return; + } + + switch (speed) { + case kHitachiAc1FanAuto: + case kHitachiAc1FanHigh: + case kHitachiAc1FanMed: + case kHitachiAc1FanLow: + _.Fan = speed; + break; + default: _.Fan = kHitachiAc1FanAuto; + } +} + +/// Get the Swing Toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc1::getSwingToggle(void) const { + return _.SwingToggle; +} + +/// Set the Swing toggle setting of the A/C. +/// @param[in] toggle true, the setting is on. false, the setting is off. +void IRHitachiAc1::setSwingToggle(const bool toggle) { + _.SwingToggle = toggle; +} + +/// Get the Vertical Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc1::getSwingV(void) const { + return _.SwingV; +} + +/// Set the Vertical Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc1::setSwingV(const bool on) { + _.SwingV = on; +} + +/// Get the Horizontal Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc1::getSwingH(void) const { + return _.SwingH; +} + +/// Set the Horizontal Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc1::setSwingH(const bool on) { + _.SwingH = on; +} + +/// Get the Sleep setting of the A/C. +/// @return The currently configured sleep mode. +/// @note Sleep modes only available in Auto & Cool modes, otherwise it's off. +uint8_t IRHitachiAc1::getSleep(void) const { + return _.Sleep; +} + +/// Set the Sleep setting of the A/C. +/// @param[in] mode The mode of sleep to set the A/C to. +/// @note Sleep modes only available in Auto & Cool modes, otherwise it's off. +void IRHitachiAc1::setSleep(const uint8_t mode) { + switch (_.Mode) { + case kHitachiAc1Auto: + case kHitachiAc1Cool: + _.Sleep = ::min(mode, kHitachiAc1Sleep4); + break; + default: + _.Sleep = kHitachiAc1SleepOff; + } +} + +/// Set the On Timer time. +/// @param[in] mins The time expressed in total number of minutes. +void IRHitachiAc1::setOnTimer(const uint16_t mins) { + const uint16_t mins_lsb = reverseBits(mins, kHitachiAc1TimerSize); + _.OnTimerLow = GETBITS16(mins_lsb, 8, 8); + _.OnTimerHigh = GETBITS16(mins_lsb, 0, 8); +} + +/// Get the On Timer vtime of the A/C. +/// @return Nr of minutes the timer is set to. +uint16_t IRHitachiAc1::getOnTimer(void) const { + return reverseBits( + (_.OnTimerLow << 8) | _.OnTimerHigh, kHitachiAc1TimerSize); +} + +/// Set the Off Timer time. +/// @param[in] mins The time expressed in total number of minutes. +void IRHitachiAc1::setOffTimer(const uint16_t mins) { + const uint16_t mins_lsb = reverseBits(mins, kHitachiAc1TimerSize); + _.OffTimerLow = GETBITS16(mins_lsb, 8, 8); + _.OffTimerHigh = GETBITS16(mins_lsb, 0, 8); +} + +/// Get the Off Timer vtime of the A/C. +/// @return Nr of minutes the timer is set to. +uint16_t IRHitachiAc1::getOffTimer(void) const { + return reverseBits( + (_.OffTimerLow << 8) | _.OffTimerHigh, kHitachiAc1TimerSize); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc1::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kHitachiAc1Cool; + case stdAc::opmode_t::kHeat: return kHitachiAc1Heat; + case stdAc::opmode_t::kDry: return kHitachiAc1Dry; + case stdAc::opmode_t::kFan: return kHitachiAc1Fan; + default: return kHitachiAc1Auto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc1::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kHitachiAc1FanLow; + case stdAc::fanspeed_t::kMedium: return kHitachiAc1FanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kHitachiAc1FanHigh; + default: return kHitachiAc1FanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRHitachiAc1::toCommonMode(const uint8_t mode) { + switch (mode) { + case kHitachiAc1Cool: return stdAc::opmode_t::kCool; + case kHitachiAc1Heat: return stdAc::opmode_t::kHeat; + case kHitachiAc1Dry: return stdAc::opmode_t::kDry; + case kHitachiAc1Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHitachiAc1::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kHitachiAc1FanHigh: return stdAc::fanspeed_t::kMax; + case kHitachiAc1FanMed: return stdAc::fanspeed_t::kMedium; + case kHitachiAc1FanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHitachiAc1::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::HITACHI_AC1; + result.model = getModel(); + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : + stdAc::swingv_t::kOff; + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : + stdAc::swingh_t::kOff; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.quiet = false; + result.turbo = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.light = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRHitachiAc1::toString(void) const { + String result = ""; + result.reserve(170); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::HITACHI_AC1, getModel(), false); + result += addBoolToString(_.Power, kPowerStr); + result += addBoolToString(_.PowerToggle, kPowerToggleStr); + result += addModeToString(_.Mode, kHitachiAc1Auto, kHitachiAc1Cool, + kHitachiAc1Heat, kHitachiAc1Dry, kHitachiAc1Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kHitachiAc1FanHigh, kHitachiAc1FanLow, + kHitachiAc1FanAuto, kHitachiAc1FanAuto, + kHitachiAc1FanMed); + result += addBoolToString(_.SwingToggle, kSwingVToggleStr); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addLabeledString(_.Sleep ? uint64ToString(_.Sleep) : kOffStr, + kSleepStr); + result += addLabeledString(getOnTimer() ? minsToString(getOnTimer()) + : kOffStr, + kOnTimerStr); + result += addLabeledString(getOffTimer() ? minsToString(getOffTimer()) + : kOffStr, + kOffTimerStr); + return result; +} + +#if (DECODE_HITACHI_AC || DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2 || \ + DECODE_HITACHI_AC344 || DECODE_HITACHI_AC264) +/// Decode the supplied Hitachi A/C message. +/// Status: STABLE / Expected to work. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// Typically kHitachiAcBits, kHitachiAc1Bits, kHitachiAc2Bits, +/// kHitachiAc344Bits, kHitachiAc264Bits +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @param[in] MSBfirst Is the data per byte stored in MSB First (true) or +/// LSB First order(false)? +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/417 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/453 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1134 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1729 +bool IRrecv::decodeHitachiAC(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict, + const bool MSBfirst) { + const uint8_t k_tolerance = _tolerance + 5; + + if (strict) { + switch (nbits) { + case kHitachiAcBits: + case kHitachiAc1Bits: + case kHitachiAc2Bits: + case kHitachiAc264Bits: + case kHitachiAc344Bits: + break; // Okay to continue. + default: + return false; // Not strictly a Hitachi message. + } + } + uint16_t hmark; + uint32_t hspace; + if (nbits == kHitachiAc1Bits) { + hmark = kHitachiAc1HdrMark; + hspace = kHitachiAc1HdrSpace; + } else { + hmark = kHitachiAcHdrMark; + hspace = kHitachiAcHdrSpace; + } + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + hmark, hspace, + kHitachiAcBitMark, kHitachiAcOneSpace, + kHitachiAcBitMark, kHitachiAcZeroSpace, + kHitachiAcBitMark, kHitachiAcMinGap, true, + k_tolerance, kMarkExcess, MSBfirst)) return false; + + // Compliance + if (strict) { + const uint16_t nbytes = nbits / 8; + switch (nbytes) { + case kHitachiAcStateLength: + if (!IRHitachiAc::validChecksum(results->state, nbytes)) return false; + break; + case kHitachiAc1StateLength: + if (!IRHitachiAc1::validChecksum(results->state, nbytes)) return false; + break; + case kHitachiAc264StateLength: + case kHitachiAc344StateLength: + if (!IRHitachiAc3::hasInvertedStates(results->state, nbytes)) + return false; + break; + } + } + + // Success + switch (nbits) { + case kHitachiAc1Bits: + results->decode_type = decode_type_t::HITACHI_AC1; + break; + case kHitachiAc2Bits: + results->decode_type = decode_type_t::HITACHI_AC2; + break; + case kHitachiAc264Bits: + results->decode_type = decode_type_t::HITACHI_AC264; + break; + case kHitachiAc344Bits: + results->decode_type = decode_type_t::HITACHI_AC344; + break; + case kHitachiAcBits: + default: + results->decode_type = decode_type_t::HITACHI_AC; + } + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // (DECODE_HITACHI_AC || DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2 || + // DECODE_HITACHI_AC344 || DECODE_HITACHI_AC264) + +#if SEND_HITACHI_AC424 +/// Send a Hitachi 53-byte/424-bit A/C formatted message. (HITACHI_AC424) +/// Status: STABLE / Reported as working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol is almost exactly the same as HitachiAC2 except this +/// variant has a leader section as well, and subtle timing differences. +/// It is also in LSBF order (per byte), rather than MSBF order. +void IRsend::sendHitachiAc424(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + enableIROut(kHitachiAcFreq); + for (uint16_t r = 0; r <= repeat; r++) { + // Leader + mark(kHitachiAc424LdrMark); + space(kHitachiAc424LdrSpace); + // Header + Data + Footer + sendGeneric(kHitachiAc424HdrMark, kHitachiAc424HdrSpace, + kHitachiAc424BitMark, kHitachiAc424OneSpace, + kHitachiAc424BitMark, kHitachiAc424ZeroSpace, + kHitachiAc424BitMark, kHitachiAcMinGap, + data, nbytes, // Bytes + kHitachiAcFreq, false, kNoRepeat, kDutyDefault); + } +} +#endif // SEND_HITACHI_AC424 + +#if DECODE_HITACHI_AC424 +/// Decode the supplied Hitachi 53-byte/424-bit A/C message. +/// Status: STABLE / Reported as working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note This protocol is almost exactly the same as HitachiAC2 except this +/// variant has a leader section as well, and subtle timing differences. +/// It is also in LSBF order (per byte), rather than MSBF order. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/973 +/// @see (Japanese Manual) https://kadenfan.hitachi.co.jp/support/raj/item/docs/ras_aj22h_a_tori.pdf +bool IRrecv::decodeHitachiAc424(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kHeader + kFooter - 1 + offset) + return false; // Too short a message to match. + if (strict && nbits != kHitachiAc424Bits) + return false; + + uint16_t used; + + // Leader + if (!matchMark(results->rawbuf[offset++], kHitachiAc424LdrMark)) + return false; + if (!matchSpace(results->rawbuf[offset++], kHitachiAc424LdrSpace)) + return false; + + // Header + Data + Footer + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kHitachiAc424HdrMark, kHitachiAc424HdrSpace, + kHitachiAc424BitMark, kHitachiAc424OneSpace, + kHitachiAc424BitMark, kHitachiAc424ZeroSpace, + kHitachiAc424BitMark, kHitachiAcMinGap, true, + kUseDefTol, 0, false); + if (used == 0) return false; // We failed to find any data. + + // Success + results->decode_type = decode_type_t::HITACHI_AC424; + results->bits = nbits; + return true; +} +#endif // DECODE_HITACHI_AC424 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHitachiAc424::IRHitachiAc424(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +/// @note Reset to auto fan, cooling, 23° Celsius +void IRHitachiAc424::stateReset(void) { + for (uint8_t i = 0; i < kHitachiAc424StateLength; i++) + _.raw[i] = 0x00; + + _.raw[0] = 0x01; + _.raw[1] = 0x10; + _.raw[3] = 0x40; + _.raw[5] = 0xFF; + _.raw[7] = 0xCC; + _.raw[27] = 0xE1; + _.raw[33] = 0x80; + _.raw[35] = 0x03; + _.raw[37] = 0x01; + _.raw[39] = 0x88; + _.raw[45] = 0xFF; + _.raw[47] = 0xFF; + _.raw[49] = 0xFF; + _.raw[51] = 0xFF; + + setTemp(23); + setPower(true); + setMode(kHitachiAc424Cool); + setFan(kHitachiAc424FanAuto); +} + +/// Update the internal consistency check for the protocol. +void IRHitachiAc424::setInvertedStates(void) { + invertBytePairs(_.raw + 3, kHitachiAc424StateLength - 3); +} + +/// Set up hardware to be able to send a message. +void IRHitachiAc424::begin(void) { _irsend.begin(); } + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRHitachiAc424::getRaw(void) { + setInvertedStates(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length of the new_code array. +void IRHitachiAc424::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kHitachiAc424StateLength)); +} + +#if SEND_HITACHI_AC424 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHitachiAc424::send(const uint16_t repeat) { + _irsend.sendHitachiAc424(getRaw(), kHitachiAc424StateLength, repeat); +} +#endif // SEND_HITACHI_AC424 + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc424::getPower(void) const { return _.Power; } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc424::setPower(const bool on) { + _.Power = on; + setButton(kHitachiAc424ButtonPowerMode); +} + +/// Change the power setting to On. +void IRHitachiAc424::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRHitachiAc424::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHitachiAc424::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHitachiAc424::setMode(const uint8_t mode) { + uint8_t newMode = mode; + switch (mode) { + // Fan mode sets a special temp. + case kHitachiAc424Fan: setTemp(kHitachiAc424FanTemp, false); break; + case kHitachiAc424Heat: + case kHitachiAc424Cool: + case kHitachiAc424Dry: break; + default: newMode = kHitachiAc424Cool; + } + _.Mode = newMode; + if (newMode != kHitachiAc424Fan) setTemp(_previoustemp); + setFan(_.Fan); // Reset the fan speed after the mode change. + setButton(kHitachiAc424ButtonPowerMode); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRHitachiAc424::getTemp(void) const { + return _.Temp; +} + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +/// @param[in] setPrevious true, remember this if we change mode. false, don't. +void IRHitachiAc424::setTemp(const uint8_t celsius, bool setPrevious) { + uint8_t temp; + temp = ::min(celsius, kHitachiAc424MaxTemp); + temp = ::max(temp, kHitachiAc424MinTemp); + _.Temp = temp; + if (_previoustemp > temp) + setButton(kHitachiAc424ButtonTempDown); + else if (_previoustemp < temp) + setButton(kHitachiAc424ButtonTempUp); + if (setPrevious) _previoustemp = temp; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHitachiAc424::getFan(void) const { + return _.Fan; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHitachiAc424::setFan(const uint8_t speed) { + uint8_t newSpeed = ::max(speed, kHitachiAc424FanMin); + uint8_t fanMax = kHitachiAc424FanMax; + + // Only 2 x low speeds in Dry mode or Auto + if (_.Mode == kHitachiAc424Dry && speed == kHitachiAc424FanAuto) { + fanMax = kHitachiAc424FanAuto; + } else if (_.Mode == kHitachiAc424Dry) { + fanMax = kHitachiAc424FanMaxDry; + } else if (_.Mode == kHitachiAc424Fan && speed == kHitachiAc424FanAuto) { + // Fan Mode does not have auto. Set to safe low + newSpeed = kHitachiAc424FanMin; + } + + newSpeed = ::min(newSpeed, fanMax); + // Handle the setting the button value if we are going to change the value. + if (newSpeed != _.Fan) setButton(kHitachiAc424ButtonFan); + // Set the values + _.Fan = newSpeed; + _.raw[9] = 0x92; + _.raw[29] = 0x00; + + // When fan is at min/max, additional bytes seem to be set + if (newSpeed == kHitachiAc424FanMin) _.raw[9] = 0x98; + if (newSpeed == kHitachiAc424FanMax) { + _.raw[9] = 0xA9; + _.raw[29] = 0x30; + } +} + +/// Get the Button/Command setting of the A/C. +/// @return The value of the button/command that was pressed. +uint8_t IRHitachiAc424::getButton(void) const { + return _.Button; +} + +/// Set the Button/Command pressed setting of the A/C. +/// @param[in] button The value of the button/command that was pressed. +void IRHitachiAc424::setButton(const uint8_t button) { + _.Button = button; +} + +/// Set the Vertical Swing toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note The remote does not keep state of the vertical swing. +/// A byte is sent indicating the swing button is pressed on the remote +void IRHitachiAc424::setSwingVToggle(const bool on) { + uint8_t button = _.Button; // Get the current button value. + if (on) + button = kHitachiAc424ButtonSwingV; // Set the button to SwingV. + else if (button == kHitachiAc424ButtonSwingV) // Asked to unset it + // It was set previous, so use Power as a default + button = kHitachiAc424ButtonPowerMode; + setButton(button); +} + +/// Get the Vertical Swing toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc424::getSwingVToggle(void) const { + return _.Button == kHitachiAc424ButtonSwingV; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc424::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kHitachiAc424Cool; + case stdAc::opmode_t::kHeat: return kHitachiAc424Heat; + case stdAc::opmode_t::kDry: return kHitachiAc424Dry; + case stdAc::opmode_t::kFan: return kHitachiAc424Fan; + default: return kHitachiAc424Cool; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc424::convertFan(const stdAc::fanspeed_t speed) const { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kHitachiAc424FanMin; + case stdAc::fanspeed_t::kLow: return kHitachiAc424FanLow; + case stdAc::fanspeed_t::kMedium: return kHitachiAc424FanMedium; + case stdAc::fanspeed_t::kHigh: return kHitachiAc424FanHigh; + case stdAc::fanspeed_t::kMax: return kHitachiAc424FanMax; + default: return kHitachiAc424FanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRHitachiAc424::toCommonMode(const uint8_t mode) { + switch (mode) { + case kHitachiAc424Cool: return stdAc::opmode_t::kCool; + case kHitachiAc424Heat: return stdAc::opmode_t::kHeat; + case kHitachiAc424Dry: return stdAc::opmode_t::kDry; + case kHitachiAc424Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHitachiAc424::toCommonFanSpeed(const uint8_t speed) const { + switch (speed) { + case kHitachiAc424FanMax: return stdAc::fanspeed_t::kMax; + case kHitachiAc424FanHigh: return stdAc::fanspeed_t::kHigh; + case kHitachiAc424FanMedium: return stdAc::fanspeed_t::kMedium; + case kHitachiAc424FanLow: return stdAc::fanspeed_t::kLow; + case kHitachiAc424FanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHitachiAc424::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::HITACHI_AC424; + result.model = -1; // No models used. + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = getSwingVToggle() ? stdAc::swingv_t::kAuto + : stdAc::swingv_t::kOff; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.turbo = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the internal state into a human readable string for the settings +/// that are common to protocols of this nature. +/// @return A string containing the common settings in human-readable form. +String IRHitachiAc424::_toString(void) const { + String result = ""; + result.reserve(100); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, 0, kHitachiAc424Cool, + kHitachiAc424Heat, kHitachiAc424Dry, + kHitachiAc424Fan); + result += addTempToString(_.Temp); + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + switch (_.Fan) { + case kHitachiAc424FanAuto: result += kAutoStr; break; + case kHitachiAc424FanMax: result += kMaxStr; break; + case kHitachiAc424FanHigh: result += kHighStr; break; + case kHitachiAc424FanMedium: result += kMedStr; break; + case kHitachiAc424FanLow: result += kLowStr; break; + case kHitachiAc424FanMin: result += kMinStr; break; + default: result += kUnknownStr; + } + result += ')'; + result += addIntToString(_.Button, kButtonStr); + result += kSpaceLBraceStr; + switch (_.Button) { + case kHitachiAc424ButtonPowerMode: + result += kPowerStr; + result += '/'; + result += kModeStr; + break; + case kHitachiAc424ButtonFan: result += kFanStr; break; + case kHitachiAc424ButtonSwingV: result += kSwingVStr; break; + case kHitachiAc344ButtonSwingH: result += kSwingHStr; break; + case kHitachiAc424ButtonTempDown: result += kTempDownStr; break; + case kHitachiAc424ButtonTempUp: result += kTempUpStr; break; + default: result += kUnknownStr; + } + result += ')'; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRHitachiAc424::toString(void) const { + return _toString() + addBoolToString(getSwingVToggle(), kSwingVToggleStr); +} + + +#if SEND_HITACHI_AC3 +/// Send a Hitachi(3) A/C formatted message. (HITACHI_AC3) +/// Status: STABLE / Working fine. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol is almost exactly the same as HitachiAC424 except this +/// variant has subtle timing differences. There are five(5) typical sizes: +/// kHitachiAc3MinStateLength (Cancel Timer), +/// kHitachiAc3MinStateLength + 2 (Change Temp), +/// kHitachiAc3StateLength - 6 (Change Mode), +/// kHitachiAc3StateLength - 4 (Normal), & +/// kHitachiAc3StateLength (Set Timer) +void IRsend::sendHitachiAc3(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // Header + Data + Footer + sendGeneric(kHitachiAc3HdrMark, kHitachiAc3HdrSpace, + kHitachiAc3BitMark, kHitachiAc3OneSpace, + kHitachiAc3BitMark, kHitachiAc3ZeroSpace, + kHitachiAc3BitMark, kHitachiAcMinGap, + data, nbytes, // Bytes + kHitachiAcFreq, false, repeat, kDutyDefault); +} +#endif // SEND_HITACHI_AC3 + + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHitachiAc3::IRHitachiAc3(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +/// @note Reset to auto fan, cooling, 23° Celsius +void IRHitachiAc3::stateReset(void) { + for (uint8_t i = 0; i < kHitachiAc3StateLength; i++) + remote_state[i] = 0x00; + remote_state[0] = 0x01; + remote_state[1] = 0x10; + remote_state[3] = 0x40; + remote_state[5] = 0xFF; + remote_state[7] = 0xE8; + remote_state[9] = 0x89; + remote_state[11] = 0x0B; + remote_state[13] = 0x3F; + remote_state[15] = 0x15; + remote_state[21] = 0x4B; + remote_state[23] = 0x18; + setInvertedStates(); +} + +/// Invert every second byte of the internal state, after the fixed header. +/// @param[in] length The size of the state array. +/// @note This is this protocols integrity check. +void IRHitachiAc3::setInvertedStates(const uint16_t length) { + if (length > 3) invertBytePairs(remote_state + 3, length - 3); +} + +/// Check if every second byte of the state, after the fixed header +/// is inverted to the previous byte. +/// @param[in] state The state array to be checked. +/// @param[in] length The size of the state array. +/// @note This is this protocols integrity check. +bool IRHitachiAc3::hasInvertedStates(const uint8_t state[], + const uint16_t length) { + return (length <= 3 || checkInvertedBytePairs(state + 3, length - 3)); +} + +/// Set up hardware to be able to send a message. +void IRHitachiAc3::begin(void) { _irsend.begin(); } + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRHitachiAc3::getRaw(void) { + setInvertedStates(); + return remote_state; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length of the new_code array. +void IRHitachiAc3::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(remote_state, new_code, ::min(length, kHitachiAc3StateLength)); +} + +#if DECODE_HITACHI_AC3 +/// Decode the supplied Hitachi 15to27-byte/120to216-bit A/C message. +/// Status: STABLE / Works fine. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note This protocol is almost exactly the same as HitachiAC424 except this +/// variant has subtle timing differences and multiple lengths. +/// There are five(5) typical lengths: +/// kHitachiAc3MinStateLength (Cancel Timer), +/// kHitachiAc3MinStateLength + 2 (Change Temp), +/// kHitachiAc3StateLength - 6 (Change Mode), +/// kHitachiAc3StateLength - 4 (Normal), & +/// kHitachiAc3StateLength (Set Timer) +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1060 +bool IRrecv::decodeHitachiAc3(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset) + return false; // Too short a message to match. + if (strict) { + // Check the requested bit length. + switch (nbits) { + case kHitachiAc3MinBits: // Cancel Timer (Min Size) + case kHitachiAc3MinBits + 2 * 8: // Change Temp + case kHitachiAc3Bits - 6 * 8: // Change Mode + case kHitachiAc3Bits - 4 * 8: // Normal + case kHitachiAc3Bits: // Set Temp (Max Size) + break; + default: return false; + } + } + + // Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kHitachiAc3HdrMark, kHitachiAc3HdrSpace, + kHitachiAc3BitMark, kHitachiAc3OneSpace, + kHitachiAc3BitMark, kHitachiAc3ZeroSpace, + kHitachiAc3BitMark, kHitachiAcMinGap, true, + kUseDefTol, 0, false)) + return false; // We failed to find any data. + + // Compliance + if (strict && !IRHitachiAc3::hasInvertedStates(results->state, nbits / 8)) + return false; + // Success + results->decode_type = decode_type_t::HITACHI_AC3; + results->bits = nbits; + return true; +} +#endif // DECODE_HITACHI_AC3 + +/// Class constructor for handling detailed Hitachi_AC344 43 byte A/C messages. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHitachiAc344::IRHitachiAc344(const uint16_t pin, const bool inverted, + const bool use_modulation) + : IRHitachiAc424(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to auto fan, cooling, 23° Celsius +void IRHitachiAc344::stateReset(void) { + IRHitachiAc424::stateReset(); + _.raw[37] = 0x00; + _.raw[39] = 0x00; +} + +#if SEND_HITACHI_AC344 +/// Create and send the IR message to the A/C. +/// @param[in] repeat Nr. of times to repeat the message. +void IRHitachiAc344::send(const uint16_t repeat) { + _irsend.sendHitachiAc344(getRaw(), kHitachiAc344StateLength, repeat); +} +#endif // SEND_HITACHI_AC344 + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length Size (in bytes) of the code for this protocol. +void IRHitachiAc344::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kHitachiAc344StateLength)); +} + +/// Control the vertical swing setting. +/// @param[in] on True, turns on the feature. False, turns off the feature. +void IRHitachiAc344::setSwingV(const bool on) { + setSwingVToggle(on); // Set the button value. + _.SwingV = on; +} + +/// Get the current vertical swing setting. +/// @return True, if the setting is on. False, it is off. +bool IRHitachiAc344::getSwingV(void) const { + return _.SwingV; +} + +/// Control the horizontal swing setting. +/// @param[in] position The position to set the horizontal swing to. +void IRHitachiAc344::setSwingH(const uint8_t position) { + if (position > kHitachiAc344SwingHLeftMax) + _.SwingH = kHitachiAc344SwingHMiddle; + else + _.SwingH = position; + setButton(kHitachiAc344ButtonSwingH); +} + +/// Get the current horizontal swing setting. +/// @return The current position horizontal swing is set to. +uint8_t IRHitachiAc344::getSwingH(void) const { + return _.SwingH; +} + +/// Convert a standard A/C horizontal swing into its native setting. +/// @param[in] position A stdAc::swingh_t position to convert. +/// @return The equivilent native horizontal swing position. +uint8_t IRHitachiAc344::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kAuto: return kHitachiAc344SwingHAuto; + case stdAc::swingh_t::kLeftMax: return kHitachiAc344SwingHLeftMax; + case stdAc::swingh_t::kLeft: return kHitachiAc344SwingHLeft; + case stdAc::swingh_t::kRight: return kHitachiAc344SwingHRight; + case stdAc::swingh_t::kRightMax: return kHitachiAc344SwingHRightMax; + default: return kHitachiAc344SwingHMiddle; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRHitachiAc344::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kHitachiAc344SwingHLeftMax: return stdAc::swingh_t::kLeftMax; + case kHitachiAc344SwingHLeft: return stdAc::swingh_t::kLeft; + case kHitachiAc344SwingHRight: return stdAc::swingh_t::kRight; + case kHitachiAc344SwingHRightMax: return stdAc::swingh_t::kRightMax; + case kHitachiAc344SwingHAuto: return stdAc::swingh_t::kAuto; + default: return stdAc::swingh_t::kOff; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHitachiAc344::toCommon(void) const { + stdAc::state_t result = IRHitachiAc424::toCommon(); + result.protocol = decode_type_t::HITACHI_AC344; + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.swingh = toCommonSwingH(_.SwingH); + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRHitachiAc344::toString(void) const { + String result; + result.reserve(120); // Reserve some heap for the string to reduce fragging. + result += _toString(); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addIntToString(_.SwingH, kSwingHStr); + result += kSpaceLBraceStr; + switch (_.SwingH) { + case kHitachiAc344SwingHLeftMax: result += kLeftMaxStr; break; + case kHitachiAc344SwingHLeft: result += kLeftStr; break; + case kHitachiAc344SwingHMiddle: result += kMiddleStr; break; + case kHitachiAc344SwingHRight: result += kRightStr; break; + case kHitachiAc344SwingHRightMax: result += kRightMaxStr; break; + case kHitachiAc344SwingHAuto: result += kAutoStr; break; + default: result += kUnknownStr; + } + result += ')'; + return result; +} + + +#if SEND_HITACHI_AC264 +/// Send a Hitachi 33-byte/264-bit A/C message (HITACHI_AC264) +/// Basically the same as sendHitachiAC() except different size. +/// Status: STABLE / Reported as working. +/// @param[in] data An array of bytes containing the IR command. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// @param[in] repeat Nr. of times the message is to be repeated. (Default = 0). +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1729 +void IRsend::sendHitachiAc264(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHitachiAc264StateLength) + return; // Not enough bytes to send a proper message. + sendHitachiAC(data, nbytes, repeat); +} +#endif // SEND_HITACHI_AC264 + +// Class constructor for handling detailed Hitachi_AC344 43 byte A/C messages. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHitachiAc264::IRHitachiAc264(const uint16_t pin, const bool inverted, + const bool use_modulation) + : IRHitachiAc424(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to auto fan, cooling, 23° Celsius +void IRHitachiAc264::stateReset(void) { + IRHitachiAc424::stateReset(); + _.raw[9] = 0x92; + _.raw[27] = 0xC1; +} + +#if SEND_HITACHI_AC264 +/// Create and send the IR message to the A/C. +/// @param[in] repeat Nr. of times to repeat the message. +void IRHitachiAc264::send(const uint16_t repeat) { + _irsend.sendHitachiAc264(getRaw(), kHitachiAc264StateLength, repeat); +} +#endif // SEND_HITACHI_AC264 + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length Size (in bytes) of the code for this protocol. +void IRHitachiAc264::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kHitachiAc264StateLength)); +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHitachiAc264::setFan(const uint8_t speed) { + switch (speed) { + case kHitachiAc264FanMin: + case kHitachiAc264FanMedium: + case kHitachiAc264FanHigh: + case kHitachiAc264FanAuto: + _.Fan = speed; + break; + default: + setFan(kHitachiAc264FanAuto); + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc264::convertFan(const stdAc::fanspeed_t speed) const { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kHitachiAc264FanMin; + case stdAc::fanspeed_t::kMedium: return kHitachiAc264FanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kHitachiAc424FanHigh; + default: return kHitachiAc424FanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHitachiAc264::toCommonFanSpeed(const uint8_t speed) const { + switch (speed) { + case kHitachiAc264FanHigh: return stdAc::fanspeed_t::kHigh; + case kHitachiAc264FanMedium: return stdAc::fanspeed_t::kMedium; + case kHitachiAc264FanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHitachiAc264::toCommon(void) const { + stdAc::state_t result = IRHitachiAc424::toCommon(); + result.protocol = decode_type_t::HITACHI_AC264; + result.swingv = stdAc::swingv_t::kOff; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRHitachiAc264::toString(void) const { + String result; + result.reserve(120); // Reserve some heap for the string to reduce fragging. + result += _toString(); + return result; +} + +#if DECODE_HITACHI_AC264 +// For Decoding HITACHI_AC264, see `decodeHitachiAC` +#endif // DECODE_HITACHI_AC264 + + +#if SEND_HITACHI_AC296 +/// Send a HitachiAc 37-byte/296-bit A/C message (HITACHI_AC296) +/// Status: STABLE / Working on a real device. +/// @param[in] data containing the IR command. +/// @param[in] nbytes Nr. of bytes to send. usually kHitachiAc296StateLength +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendHitachiAc296(const unsigned char data[], + const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHitachiAc296StateLength) + return; // Not enough bytes to send a proper message. + sendHitachiAC(data, nbytes, repeat); +} +#endif // SEND_HITACHIAC296 + +// Class constructor for handling detailed Hitachi_AC296 37 byte A/C messages. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRHitachiAc296::IRHitachiAc296(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to auto fan, heating, & 24° Celsius +void IRHitachiAc296::stateReset(void) { + // Header + _.raw[0] = 0x01; + _.raw[1] = 0x10; + _.raw[2] = 0x00; + + // Every next byte is a parity byte + _.raw[3] = 0x40; + _.raw[5] = 0xFF; + _.raw[7] = 0xCC; + _.raw[9] = 0x92; + _.raw[11] = 0x43; + // 13-14 is Temperature and parity + _.raw[15] = 0x00; + _.raw[17] = 0x00; // Off timer LSB + _.raw[19] = 0x00; // Off timer cont + _.raw[21] = 0x00; // On timer LSB + _.raw[23] = 0x00; // On timer cont + // 25-26 is Mode and fan + _.raw[27] = 0xF1; // Power on + _.raw[29] = 0x00; + _.raw[31] = 0x00; + _.raw[33] = 0x00; + _.raw[35] = 0x03; // Humidity + + setTemp(24); + setMode(kHitachiAc296Heat); + setFan(kHitachiAc296FanAuto); + + setInvertedStates(); +} + +/// Update the internal consistency check for the protocol. +void IRHitachiAc296::setInvertedStates(void) { + invertBytePairs(_.raw + 3, kHitachiAc296StateLength - 3); +} + +/// Check if every second byte of the state, after the fixed header +/// is inverted to the previous byte. +/// @param[in] state The state array to be checked. +/// @param[in] length The size of the state array. +/// @note This is this protocols integrity check. +bool IRHitachiAc296::hasInvertedStates(const uint8_t state[], + const uint16_t length) { + return IRHitachiAc3::hasInvertedStates(state, length); +} + +/// Set up hardware to be able to send a message. +void IRHitachiAc296::begin(void) { _irsend.begin(); } + +#if SEND_HITACHI_AC296 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHitachiAc296::send(const uint16_t repeat) { + _irsend.sendHitachiAc296(getRaw(), kHitachiAc296StateLength, repeat); +} +#endif // SEND_HITACHI_AC296 + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc296::getPower(void) const { return _.Power; } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc296::setPower(const bool on) { _.Power = on; } + +/// Change the power setting to On. +void IRHitachiAc296::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRHitachiAc296::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHitachiAc296::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHitachiAc296::setMode(const uint8_t mode) { + switch (mode) { + case kHitachiAc296Heat: + case kHitachiAc296Cool: + case kHitachiAc296Dehumidify: + case kHitachiAc296AutoDehumidifying: + case kHitachiAc296Auto: + _.Mode = mode; + setTemp(getTemp()); // Reset the temp to handle "Auto"'s special temp. + break; + default: + setMode(kHitachiAc296Auto); + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc296::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kHitachiAc296Cool; + case stdAc::opmode_t::kHeat: return kHitachiAc296Heat; + case stdAc::opmode_t::kDry: return kHitachiAc296Dehumidify; + default: return kHitachiAc296Auto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRHitachiAc296::toCommonMode(const uint8_t mode) { + switch (mode) { + case kHitachiAc296DryCool: + case kHitachiAc296Cool: return stdAc::opmode_t::kCool; + case kHitachiAc296Heat: return stdAc::opmode_t::kHeat; + case kHitachiAc296AutoDehumidifying: + case kHitachiAc296Dehumidify: return stdAc::opmode_t::kDry; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRHitachiAc296::getTemp(void) const { return _.Temp; } + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IRHitachiAc296::setTemp(const uint8_t celsius) { + uint8_t temp = celsius; + if (getMode() == kHitachiAc296Auto) { // Special temp for auto mode + temp = kHitachiAc296TempAuto; + } else { // Normal temp setting. + temp = ::min(temp, kHitachiAc296MaxTemp); + temp = ::max(temp, kHitachiAc296MinTemp); + } + _.Temp = temp; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHitachiAc296::getFan(void) const { return _.Fan; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHitachiAc296::setFan(const uint8_t speed) { + uint8_t newSpeed = ::max(speed, kHitachiAc296FanSilent); + _.Fan = ::min(newSpeed, kHitachiAc296FanAuto); +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRHitachiAc296::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kHitachiAc296FanSilent; + case stdAc::fanspeed_t::kLow: return kHitachiAc296FanLow; + case stdAc::fanspeed_t::kMedium: return kHitachiAc296FanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kHitachiAc296FanHigh; + default: return kHitachiAc296FanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRHitachiAc296::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kHitachiAc296FanHigh: return stdAc::fanspeed_t::kHigh; + case kHitachiAc296FanMedium: return stdAc::fanspeed_t::kMedium; + case kHitachiAc296FanLow: return stdAc::fanspeed_t::kLow; + case kHitachiAc296FanSilent: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRHitachiAc296::getRaw(void) { + setInvertedStates(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length Size (in bytes) of the code for this protocol. +void IRHitachiAc296::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kHitachiAc296StateLength)); +} + + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRHitachiAc296::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::HITACHI_AC296; + result.model = -1; // No models used. + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = toCommonFanSpeed(_.Fan); + result.quiet = _.Fan == kHitachiAc296FanSilent; + // Not supported. + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRHitachiAc296::toString(void) const { + String result = ""; + result.reserve(70); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kHitachiAc296Auto, kHitachiAc296Cool, + kHitachiAc296Heat, kHitachiAc1Dry, + kHitachiAc296Auto); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kHitachiAc296FanHigh, kHitachiAc296FanLow, + kHitachiAc296FanAuto, kHitachiAc296FanSilent, + kHitachiAc296FanMedium); + return result; +} + +#if DECODE_HITACHI_AC296 +/// Decode the supplied Hitachi 37-byte A/C message. +/// Status: STABLE / Working on a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1757 +bool IRrecv::decodeHitachiAc296(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kHitachiAcHdrMark, kHitachiAcHdrSpace, + kHitachiAcBitMark, kHitachiAcOneSpace, + kHitachiAcBitMark, kHitachiAcZeroSpace, + kHitachiAcBitMark, kHitachiAcMinGap, true, + kUseDefTol, 0, false)) return false; + + // Compliance + if (strict && !IRHitachiAc296::hasInvertedStates(results->state, nbits / 8)) + return false; + + // Success + results->decode_type = decode_type_t::HITACHI_AC296; + results->bits = nbits; + return true; +} +#endif // DECODE_HITACHI_AC296 diff --git a/src/libraries/IRremoteESP8266/src/ir_Hitachi.h b/src/libraries/IRremoteESP8266/src/ir_Hitachi.h new file mode 100644 index 000000000..47047f036 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Hitachi.h @@ -0,0 +1,667 @@ +// Copyright 2018-2020 David Conran +/// @file +/// @brief Support for Hitachi A/C protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/417 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/453 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/973 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1056 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1060 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1134 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1729 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1757 + +// Supports: +// Brand: Hitachi, Model: RAS-35THA6 remote +// Brand: Hitachi, Model: LT0541-HTA remote (HITACHI_AC1) +// Brand: Hitachi, Model: Series VI A/C (Circa 2007) (HITACHI_AC1) +// Brand: Hitachi, Model: RAR-8P2 remote (HITACHI_AC424) +// Brand: Hitachi, Model: RAS-AJ25H A/C (HITACHI_AC424) +// Brand: Hitachi, Model: PC-LH3B (HITACHI_AC3) +// Brand: Hitachi, Model: KAZE-312KSDP A/C (HITACHI_AC1) +// Brand: Hitachi, Model: R-LT0541-HTA/Y.K.1.1-1 V2.3 remote (HITACHI_AC1) +// Brand: Hitachi, Model: RAS-22NK A/C (HITACHI_AC344) +// Brand: Hitachi, Model: RF11T1 remote (HITACHI_AC344) +// Brand: Hitachi, Model: RAR-2P2 remote (HITACHI_AC264) +// Brand: Hitachi, Model: RAK-25NH5 A/C (HITACHI_AC264) +// Brand: Hitachi, Model: RAR-3U3 remote (HITACHI_AC296) +// Brand: Hitachi, Model: RAS-70YHA3 A/C (HITACHI_AC296) + +#ifndef IR_HITACHI_H_ +#define IR_HITACHI_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Hitachi 224-bit A/C message. +union HitachiProtocol{ + uint8_t raw[kHitachiAcStateLength]; ///< The state in native code. + struct { + // Byte 0~9 + uint8_t pad0[10]; + // Byte 10 + uint8_t Mode :8; + // Byte 11 + uint8_t Temp :8; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t Fan :8; + // Byte 14 + uint8_t :7; + uint8_t SwingV :1; + // Byte 15 + uint8_t :7; + uint8_t SwingH :1; + // Byte 16 + uint8_t :8; + // Byte 17 + uint8_t Power :1; + uint8_t :7; + // Byte 18~26 + uint8_t pad1[9]; + // Byte 27 + uint8_t Sum :8; + }; +}; + +// Constants +const uint16_t kHitachiAcFreq = 38000; // Hz. +const uint8_t kHitachiAcAuto = 2; +const uint8_t kHitachiAcHeat = 3; +const uint8_t kHitachiAcCool = 4; +const uint8_t kHitachiAcDry = 5; +const uint8_t kHitachiAcFan = 0xC; +const uint8_t kHitachiAcFanAuto = 1; +const uint8_t kHitachiAcFanLow = 2; +const uint8_t kHitachiAcFanMed = 3; +const uint8_t kHitachiAcFanHigh = 5; +const uint8_t kHitachiAcMinTemp = 16; // 16C +const uint8_t kHitachiAcMaxTemp = 32; // 32C +const uint8_t kHitachiAcAutoTemp = 23; // 23C + +/// Native representation of a Hitachi 53-byte/424-bit A/C message. +union Hitachi424Protocol{ + uint8_t raw[kHitachiAc424StateLength]; ///< The state in native code + struct { + // Byte 0~10 + uint8_t pad0[11]; + // Byte 11 + uint8_t Button :8; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t :2; + uint8_t Temp :6; + // Byte 14~24 + uint8_t pad1[11]; + // Byte 25 + uint8_t Mode :4; + uint8_t Fan :4; + // Byte 26 + uint8_t :8; + // Byte 27 + uint8_t :4; + uint8_t Power :1; + uint8_t :3; + // Byte 28~34 + uint8_t pad2[7]; + // Byte 35 + uint8_t SwingH :3; + uint8_t :5; + // Byte 36 + uint8_t :8; + // Byte 37 + uint8_t :5; + uint8_t SwingV :1; + uint8_t :2; + }; +}; + +// HitachiAc424 & HitachiAc344 +const uint8_t kHitachiAc424ButtonPowerMode = 0x13; +const uint8_t kHitachiAc424ButtonFan = 0x42; +const uint8_t kHitachiAc424ButtonTempDown = 0x43; +const uint8_t kHitachiAc424ButtonTempUp = 0x44; +const uint8_t kHitachiAc424ButtonSwingV = 0x81; +const uint8_t kHitachiAc424ButtonSwingH = 0x8C; +const uint8_t kHitachiAc344ButtonPowerMode = kHitachiAc424ButtonPowerMode; +const uint8_t kHitachiAc344ButtonFan = kHitachiAc424ButtonFan; +const uint8_t kHitachiAc344ButtonTempDown = kHitachiAc424ButtonTempDown; +const uint8_t kHitachiAc344ButtonTempUp = kHitachiAc424ButtonTempUp; +const uint8_t kHitachiAc344ButtonSwingV = kHitachiAc424ButtonSwingV; +const uint8_t kHitachiAc344ButtonSwingH = kHitachiAc424ButtonSwingH; + +const uint8_t kHitachiAc424MinTemp = 16; // 16C +const uint8_t kHitachiAc424MaxTemp = 32; // 32C +const uint8_t kHitachiAc344MinTemp = kHitachiAc424MinTemp; +const uint8_t kHitachiAc344MaxTemp = kHitachiAc424MaxTemp; +const uint8_t kHitachiAc424FanTemp = 27; // 27C + +const uint8_t kHitachiAc424Fan = 1; +const uint8_t kHitachiAc424Cool = 3; +const uint8_t kHitachiAc424Dry = 5; +const uint8_t kHitachiAc424Heat = 6; +const uint8_t kHitachiAc344Fan = kHitachiAc424Fan; +const uint8_t kHitachiAc344Cool = kHitachiAc424Cool; +const uint8_t kHitachiAc344Dry = kHitachiAc424Dry; +const uint8_t kHitachiAc344Heat = kHitachiAc424Heat; + +const uint8_t kHitachiAc424FanMin = 1; +const uint8_t kHitachiAc424FanLow = 2; +const uint8_t kHitachiAc424FanMedium = 3; +const uint8_t kHitachiAc424FanHigh = 4; +const uint8_t kHitachiAc424FanAuto = 5; +const uint8_t kHitachiAc424FanMax = 6; +const uint8_t kHitachiAc424FanMaxDry = 2; +const uint8_t kHitachiAc344FanMin = kHitachiAc424FanMin; +const uint8_t kHitachiAc344FanLow = kHitachiAc424FanLow; +const uint8_t kHitachiAc344FanMedium = kHitachiAc424FanMedium; +const uint8_t kHitachiAc344FanHigh = kHitachiAc424FanHigh; +const uint8_t kHitachiAc344FanAuto = kHitachiAc424FanAuto; +const uint8_t kHitachiAc344FanMax = kHitachiAc424FanMax; + +const uint8_t kHitachiAc344SwingHAuto = 0; // 0b000 +const uint8_t kHitachiAc344SwingHRightMax = 1; // 0b001 +const uint8_t kHitachiAc344SwingHRight = 2; // 0b010 +const uint8_t kHitachiAc344SwingHMiddle = 3; // 0b011 +const uint8_t kHitachiAc344SwingHLeft = 4; // 0b100 +const uint8_t kHitachiAc344SwingHLeftMax = 5; // 0b101 + + +/// Native representation of a Hitachi 104-bit A/C message. +union Hitachi1Protocol{ + uint8_t raw[kHitachiAc1StateLength]; ///< The state in native code. + struct { + // Byte 0~2 + uint8_t pad[3]; + // Byte 3 + uint8_t :6; + uint8_t Model :2; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t Fan :4; + uint8_t Mode :4; + // Byte 6 + uint8_t :2; + uint8_t Temp :5; // stored in LSB order. + uint8_t :1; + // Byte 7 + uint8_t OffTimerLow :8; // nr. of minutes + // Byte 8 + uint8_t OffTimerHigh :8; // & in LSB order. + // Byte 9 + uint8_t OnTimerLow :8; // nr. of minutes + // Byte 10 + uint8_t OnTimerHigh :8; // & in LSB order. + // Byte 11 + uint8_t SwingToggle :1; + uint8_t Sleep :3; + uint8_t PowerToggle :1; + uint8_t Power :1; + uint8_t SwingV :1; + uint8_t SwingH :1; + // Byte 12 + uint8_t Sum :8; + }; +}; +// HitachiAc1 +// Model +const uint8_t kHitachiAc1Model_A = 0b10; +const uint8_t kHitachiAc1Model_B = 0b01; + +// Mode & Fan +const uint8_t kHitachiAc1Dry = 0b0010; // 2 +const uint8_t kHitachiAc1Fan = 0b0100; // 4 +const uint8_t kHitachiAc1Cool = 0b0110; // 6 +const uint8_t kHitachiAc1Heat = 0b1001; // 9 +const uint8_t kHitachiAc1Auto = 0b1110; // 14 +const uint8_t kHitachiAc1FanAuto = 1; // 0b0001 +const uint8_t kHitachiAc1FanHigh = 2; // 0b0010 +const uint8_t kHitachiAc1FanMed = 4; // 0b0100 +const uint8_t kHitachiAc1FanLow = 8; // 0b1000 + +// Temp +const uint8_t kHitachiAc1TempSize = 5; // Mask 0b01111100 +const uint8_t kHitachiAc1TempDelta = 7; +const uint8_t kHitachiAc1TempAuto = 25; // Celsius +// Timer +const uint8_t kHitachiAc1TimerSize = 16; // Mask 0b1111111111111111 +// Sleep +const uint8_t kHitachiAc1SleepOff = 0b000; +const uint8_t kHitachiAc1Sleep1 = 0b001; +const uint8_t kHitachiAc1Sleep2 = 0b010; +const uint8_t kHitachiAc1Sleep3 = 0b011; +const uint8_t kHitachiAc1Sleep4 = 0b100; +// Checksum +const uint8_t kHitachiAc1ChecksumStartByte = 5; + + +/// Native representation of a Hitachi 164-bit A/C message. +union HitachiAC264Protocol{ + uint8_t raw[kHitachiAc264StateLength]; ///< The state in native code. + struct { + // Bytes 0~10 + uint8_t pad0[11]; + // Byte 11 + uint8_t Button :8; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t :2; + uint8_t Temp :6; + // Byte 14 + uint8_t :8; + // Bytes 14~24 + uint8_t pad1[10]; + // Byte 25 + uint8_t Mode :4; + uint8_t Fan :4; + // Byte 26 + uint8_t :8; + // Byte 27 + uint8_t :4; + uint8_t Power :1; + uint8_t :3; + // Byte 28 + uint8_t :8; + // Bytes 29~32 + uint8_t pad2[4]; + }; +}; + +// HitachiAc264 +const uint8_t kHitachiAc264ButtonPowerMode = kHitachiAc424ButtonPowerMode; +const uint8_t kHitachiAc264ButtonFan = kHitachiAc424ButtonFan; +const uint8_t kHitachiAc264ButtonTempDown = kHitachiAc424ButtonTempDown; +const uint8_t kHitachiAc264ButtonTempUp = kHitachiAc424ButtonTempUp; +const uint8_t kHitachiAc264ButtonSwingV = kHitachiAc424ButtonSwingV; +const uint8_t kHitachiAc264MinTemp = kHitachiAc424MinTemp; // 16C +const uint8_t kHitachiAc264MaxTemp = kHitachiAc424MaxTemp; // 32C +const uint8_t kHitachiAc264Fan = kHitachiAc424Fan; +const uint8_t kHitachiAc264Cool = kHitachiAc424Cool; +const uint8_t kHitachiAc264Dry = kHitachiAc424Dry; +const uint8_t kHitachiAc264Heat = kHitachiAc424Heat; +const uint8_t kHitachiAc264FanMin = kHitachiAc424FanMin; +const uint8_t kHitachiAc264FanLow = kHitachiAc424FanMin; +const uint8_t kHitachiAc264FanMedium = kHitachiAc424FanMedium; +const uint8_t kHitachiAc264FanHigh = kHitachiAc424FanHigh; +const uint8_t kHitachiAc264FanAuto = kHitachiAc424FanAuto; + +// HitachiAc296 +union HitachiAC296Protocol{ + uint8_t raw[kHitachiAc296StateLength]; + struct { + // Byte 0~12 + uint8_t pad0[13]; + // Byte 13 + uint8_t :2; + uint8_t Temp :5; // LSB + uint8_t :1; + uint8_t :8; + // Byte 15~16 + uint8_t :8; + uint8_t :8; + // Byte 17~24 + uint8_t OffTimerLow :8; // LSB + uint8_t /* Parity */ :8; + uint8_t OffTimerHigh :8; + uint8_t /* Parity */ :8; + uint8_t OnTimerLow :8; // LSB + uint8_t /* Parity */ :8; + uint8_t OnTimerHigh :4; + uint8_t OffTimerActive :1; + uint8_t OnTimerActive :1; + uint8_t :2; + uint8_t /* Parity */ :8; + // Byte 25~26 + uint8_t Mode :4; + uint8_t Fan :3; + uint8_t :1; + uint8_t :8; + // Byte 27~28 + uint8_t :4; + uint8_t Power :1; + uint8_t :2; + uint8_t TimerActive :1; + uint8_t :8; + // Byte 29~34 + uint8_t pad1[6]; + // Byte 35~36 + uint8_t :4; + uint8_t Humidity :4; // LSB + uint8_t :8; + }; +}; + +// Mode & Fan +const uint8_t kHitachiAc296Cool = 0b0011; +const uint8_t kHitachiAc296DryCool = 0b0100; +const uint8_t kHitachiAc296Dehumidify = 0b0101; +const uint8_t kHitachiAc296Heat = 0b0110; +const uint8_t kHitachiAc296Auto = 0b0111; +const uint8_t kHitachiAc296AutoDehumidifying = 0b1001; +const uint8_t kHitachiAc296QuickLaundry = 0b1010; +const uint8_t kHitachiAc296CondensationControl = 0b1100; + +const uint8_t kHitachiAc296FanSilent = 0b001; +const uint8_t kHitachiAc296FanLow = 0b010; +const uint8_t kHitachiAc296FanMedium = 0b011; +const uint8_t kHitachiAc296FanHigh = 0b100; +const uint8_t kHitachiAc296FanAuto = 0b101; + +const uint8_t kHitachiAc296TempAuto = 1; // Special value for "Auto" op mode. +const uint8_t kHitachiAc296MinTemp = 16; +const uint8_t kHitachiAc296MaxTemp = 31; // Max value you can store in 5 bits. + +const uint8_t kHitachiAc296PowerOn = 1; +const uint8_t kHitachiAc296PowerOff = 0; + + +// Classes +/// Class for handling detailed Hitachi 224-bit A/C messages. +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/HitachiHeatpumpIR.cpp +class IRHitachiAc { + public: + explicit IRHitachiAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_HITACHI_AC + void send(const uint16_t repeat = kHitachiAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HITACHI_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingVertical(const bool on); + bool getSwingVertical(void) const; + void setSwingHorizontal(const bool on); + bool getSwingHorizontal(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAcStateLength); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kHitachiAcStateLength); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kHitachiAcStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + HitachiProtocol _; + void checksum(const uint16_t length = kHitachiAcStateLength); + uint8_t _previoustemp; +}; + +/// Class for handling detailed Hitachi 104-bit A/C messages. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1056 +class IRHitachiAc1 { + public: + explicit IRHitachiAc1(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + void stateReset(void); +#if SEND_HITACHI_AC1 + void send(const uint16_t repeat = kHitachiAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HITACHI_AC1 + void begin(void); + void on(void); + void off(void); + void setModel(const hitachi_ac1_remote_model_t model); + hitachi_ac1_remote_model_t getModel(void) const; + void setPower(const bool on); + bool getPower(void) const; + void setPowerToggle(const bool on); + bool getPowerToggle(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed, const bool force = false); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingToggle(const bool toggle); + bool getSwingToggle(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + void setSwingH(const bool on); + bool getSwingH(void) const; + void setSleep(const uint8_t mode); + uint8_t getSleep(void) const; + void setOnTimer(const uint16_t mins); + uint16_t getOnTimer(void) const; + void setOffTimer(const uint16_t mins); + uint16_t getOffTimer(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAc1StateLength); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kHitachiAc1StateLength); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kHitachiAc1StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Hitachi1Protocol _; + void checksum(const uint16_t length = kHitachiAc1StateLength); +}; + +/// Class for handling detailed Hitachi 53-byte/424-bit A/C messages. +class IRHitachiAc424 { + friend class IRHitachiAc264; + friend class IRHitachiAc344; + public: + explicit IRHitachiAc424(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + virtual void stateReset(void); +#if SEND_HITACHI_AC424 + virtual void send(const uint16_t repeat = kHitachiAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HITACHI_AC424 + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp, bool setPrevious = true); + uint8_t getTemp(void) const; + virtual void setFan(const uint8_t speed); + uint8_t getFan(void) const; + uint8_t getButton(void) const; + void setButton(const uint8_t button); + void setSwingVToggle(const bool on); + bool getSwingVToggle(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + uint8_t* getRaw(void); + virtual void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAc424StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + virtual uint8_t convertFan(const stdAc::fanspeed_t speed) const; + static stdAc::opmode_t toCommonMode(const uint8_t mode); + virtual stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed) const; + virtual stdAc::state_t toCommon(void) const; + virtual String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Hitachi424Protocol _; + void setInvertedStates(void); + String _toString(void) const; + uint8_t _previoustemp; +}; + +/// Class for handling detailed Hitachi 15to27-byte/120to216-bit A/C messages. +class IRHitachiAc3 { + public: + explicit IRHitachiAc3(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + void stateReset(void); +#if SEND_HITACHI_AC3 + void send(const uint16_t repeat = kHitachiAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_HITACHI_AC3 + void begin(void); + uint8_t getMode(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAc3StateLength); + static bool hasInvertedStates(const uint8_t state[], const uint16_t length); +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + uint8_t remote_state[kHitachiAc3StateLength]; ///< The state in native code. + void setInvertedStates(const uint16_t length = kHitachiAc3StateLength); +}; + +/// Class for handling detailed Hitachi 344-bit A/C messages. +class IRHitachiAc344: public IRHitachiAc424 { + public: + explicit IRHitachiAc344(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void) override; + void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAc344StateLength) override; + stdAc::state_t toCommon(void) const override; +#if SEND_HITACHI_AC344 + void send(const uint16_t repeat = kHitachiAcDefaultRepeat) override; +#endif // SEND_HITACHI_AC344 + void setSwingV(const bool on); + bool getSwingV(void) const; + void setSwingH(const uint8_t position); + uint8_t getSwingH(void) const; + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + String toString(void) const override; +}; + +/// Class for handling detailed Hitachi 264-bit A/C messages. +class IRHitachiAc264: public IRHitachiAc424 { + public: + explicit IRHitachiAc264(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void) override; + void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAc264StateLength) override; + void setFan(const uint8_t speed) override; + uint8_t convertFan(const stdAc::fanspeed_t speed) const override; + stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed) const override; + stdAc::state_t toCommon(void) const override; +#if SEND_HITACHI_AC264 + void send(const uint16_t repeat = kHitachiAcDefaultRepeat) override; +#endif // SEND_HITACHI_AC264 + String toString(void) const override; +}; + +class IRHitachiAc296 { + public: + explicit IRHitachiAc296(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); + +#if SEND_HITACHI_AC296 + void send(const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif // SEND_HITACHI_AC296 + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + static bool hasInvertedStates(const uint8_t state[], const uint16_t length); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAc296StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + + HitachiAC296Protocol _; + void setInvertedStates(void); +}; +#endif // IR_HITACHI_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Inax.cpp b/src/libraries/IRremoteESP8266/src/ir_Inax.cpp new file mode 100644 index 000000000..626fb7f04 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Inax.cpp @@ -0,0 +1,73 @@ +// Copyright 2019 David Conran (crankyoldgit) +/// @file +/// @brief Support for the Inax Robot Toilet IR protocols. +/// @see https://www.lixil-manual.com/GCW-1365-16050/GCW-1365-16050.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/706 + +// Supports: +// Brand: Lixil, Model: Inax DT-BA283 Toilet + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kInaxTick = 500; +const uint16_t kInaxHdrMark = 9000; +const uint16_t kInaxHdrSpace = 4500; +const uint16_t kInaxBitMark = 560; +const uint16_t kInaxOneSpace = 1675; +const uint16_t kInaxZeroSpace = kInaxBitMark; +const uint16_t kInaxMinGap = 40000; + +#if SEND_INAX +/// Send a Inax Toilet formatted message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/706 +void IRsend::sendInax(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kInaxHdrMark, kInaxHdrSpace, + kInaxBitMark, kInaxOneSpace, + kInaxBitMark, kInaxZeroSpace, + kInaxBitMark, kInaxMinGap, + data, nbits, 38, true, repeat, kDutyDefault); +} +#endif // SEND_INAX + +#if DECODE_INAX +/// Decode the supplied Inax Toilet message. +/// Status: Stable / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/706 +bool IRrecv::decodeInax(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kInaxBits) + return false; // We expect Inax to be a certain sized message. + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kInaxHdrMark, kInaxHdrSpace, + kInaxBitMark, kInaxOneSpace, + kInaxBitMark, kInaxZeroSpace, + kInaxBitMark, kInaxMinGap, true)) return false; + // Success + results->bits = nbits; + results->value = data; + results->decode_type = decode_type_t::INAX; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_INAX diff --git a/src/libraries/IRremoteESP8266/src/ir_JVC.cpp b/src/libraries/IRremoteESP8266/src/ir_JVC.cpp new file mode 100644 index 000000000..548ba8e98 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_JVC.cpp @@ -0,0 +1,131 @@ +// Copyright 2015 Kristian Lauszus +// Copyright 2017 David Conran + +/// @file +/// @brief Support for JVC protocols. +/// Originally added by Kristian Lauszus +/// Thanks to zenwheel and other people at the original blog post. +/// @see http://www.sbprojects.net/knowledge/ir/jvc.php + +// Supports: +// Brand: JVC, Model: PTU94023B remote + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtimer.h" +#include "IRutils.h" + +// Constants +const uint16_t kJvcTick = 75; +const uint16_t kJvcHdrMarkTicks = 112; +const uint16_t kJvcHdrMark = kJvcHdrMarkTicks * kJvcTick; +const uint16_t kJvcHdrSpaceTicks = 56; +const uint16_t kJvcHdrSpace = kJvcHdrSpaceTicks * kJvcTick; +const uint16_t kJvcBitMarkTicks = 7; +const uint16_t kJvcBitMark = kJvcBitMarkTicks * kJvcTick; +const uint16_t kJvcOneSpaceTicks = 23; +const uint16_t kJvcOneSpace = kJvcOneSpaceTicks * kJvcTick; +const uint16_t kJvcZeroSpaceTicks = 7; +const uint16_t kJvcZeroSpace = kJvcZeroSpaceTicks * kJvcTick; +const uint16_t kJvcRptLengthTicks = 800; +const uint16_t kJvcRptLength = kJvcRptLengthTicks * kJvcTick; +const uint16_t kJvcMinGapTicks = + kJvcRptLengthTicks - + (kJvcHdrMarkTicks + kJvcHdrSpaceTicks + + kJvcBits * (kJvcBitMarkTicks + kJvcOneSpaceTicks) + kJvcBitMarkTicks); +const uint16_t kJvcMinGap = kJvcMinGapTicks * kJvcTick; + +#if SEND_JVC +/// Send a JVC formatted message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see http://www.sbprojects.net/knowledge/ir/jvc.php +void IRsend::sendJVC(uint64_t data, uint16_t nbits, uint16_t repeat) { + // Set 38kHz IR carrier frequency & a 1/3 (33%) duty cycle. + enableIROut(38, 33); + + IRtimer usecs = IRtimer(); + // Header + // Only sent for the first message. + mark(kJvcHdrMark); + space(kJvcHdrSpace); + + // We always send the data & footer at least once, hence '<= repeat'. + for (uint16_t i = 0; i <= repeat; i++) { + sendGeneric(0, 0, // No Header + kJvcBitMark, kJvcOneSpace, kJvcBitMark, kJvcZeroSpace, + kJvcBitMark, kJvcMinGap, data, nbits, 38, true, + 0, // Repeats are handles elsewhere. + 33); + // Wait till the end of the repeat time window before we send another code. + uint32_t elapsed = usecs.elapsed(); + // Avoid potential unsigned integer underflow. + // e.g. when elapsed > kJvcRptLength. + if (elapsed < kJvcRptLength) space(kJvcRptLength - elapsed); + usecs.reset(); + } +} + +/// Calculate the raw JVC data based on address and command. +/// Status: STABLE / Works fine. +/// @param[in] address An 8-bit address value. +/// @param[in] command An 8-bit command value. +/// @return A raw JVC message code, suitable for sendJVC().. +/// @see http://www.sbprojects.net/knowledge/ir/jvc.php +uint16_t IRsend::encodeJVC(uint8_t address, uint8_t command) { + return reverseBits((command << 8) | address, 16); +} +#endif // SEND_JVC + +#if DECODE_JVC +/// Decode the supplied JVC message. +/// Status: Stable / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note JVC repeat codes don't have a header. +/// @see http://www.sbprojects.net/knowledge/ir/jvc.php +bool IRrecv::decodeJVC(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kJvcBits) + return false; // Must be called with the correct nr. of bits. + if (results->rawlen <= 2 * nbits + kFooter - 1 + offset) + return false; // Can't possibly be a valid JVC message. + + uint64_t data = 0; + bool isRepeat = true; + + // Header + // (Optional as repeat codes don't have the header) + if (matchMark(results->rawbuf[offset], kJvcHdrMark)) { + isRepeat = false; + offset++; + if (results->rawlen < 2 * nbits + 4) + return false; // Can't possibly be a valid JVC message with a header. + if (!matchSpace(results->rawbuf[offset++], kJvcHdrSpace)) return false; + } + + // Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + 0, 0, + kJvcBitMark, kJvcOneSpace, + kJvcBitMark, kJvcZeroSpace, + kJvcBitMark, kJvcMinGap, true)) return false; + // Success + results->decode_type = JVC; + results->bits = nbits; + results->value = data; + // command & address are transmitted LSB first, so we need to reverse them. + results->address = reverseBits(data >> 8, 8); // The first 8 bits sent. + results->command = reverseBits(data & 0xFF, 8); // The last 8 bits sent. + results->repeat = isRepeat; + return true; +} +#endif // DECODE_JVC diff --git a/src/libraries/IRremoteESP8266/src/ir_Kelon.cpp b/src/libraries/IRremoteESP8266/src/ir_Kelon.cpp new file mode 100644 index 000000000..3e2c0b93b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Kelon.cpp @@ -0,0 +1,551 @@ +// Copyright 2021 Davide Depau +// Copyright 2022 David Conran + +/// @file +/// @brief Support for Kelon AC protocols. +/// Both sending and decoding should be functional for models of series +/// KELON ON/OFF 9000-12000. +/// All features of the standard remote are implemented. +/// +/// @note Unsupported: +/// - Explicit on/off due to AC unit limitations +/// - Explicit swing position due to AC unit limitations +/// - Fahrenheit. + +// #include +#include + +#include "ir_Kelon.h" + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" +#include "IRtext.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addSignedIntToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::addLabeledString; +using irutils::minsToString; + +// Constants +const uint16_t kKelonHdrMark = 9000; +const uint16_t kKelonHdrSpace = 4600; +const uint16_t kKelonBitMark = 560; +const uint16_t kKelonOneSpace = 1680; +const uint16_t kKelonZeroSpace = 600; +const uint32_t kKelonGap = 2 * kDefaultMessageGap; +const uint16_t kKelonFreq = 38000; + +const uint32_t kKelon168FooterSpace = 8000; +const uint16_t kKelon168Section1Size = 6; +const uint16_t kKelon168Section2Size = 8; +const uint16_t kKelon168Section3Size = 7; + +#if SEND_KELON +/// Send a Kelon 48-bit message. +/// Status: STABLE / Working. +/// @param[in] data The data to be transmitted. +/// @param[in] nbits Nr. of bits of data to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendKelon(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kKelonHdrMark, kKelonHdrSpace, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelonGap, + data, nbits, kKelonFreq, false, // LSB First. + repeat, kDutyDefault); +} +#endif // SEND_KELON + +#if DECODE_KELON +/// Decode the supplied Kelon 48-bit message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeKelon(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kKelonBits) return false; + + if (!matchGeneric(results->rawbuf + offset, &(results->value), + results->rawlen - offset, nbits, + kKelonHdrMark, kKelonHdrSpace, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelonGap, true, + _tolerance, 0, false)) return false; + + results->decode_type = decode_type_t::KELON; + results->address = 0; + results->command = 0; + results->bits = nbits; + return true; +} +#endif // DECODE_KELON + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRKelonAc::IRKelonAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend{pin, inverted, use_modulation}, _{} { stateReset(); } + +/// Reset the internals of the object to a known good state. +void IRKelonAc::stateReset() { + _.raw = 0L; + _.preamble[0] = 0b10000011; + _.preamble[1] = 0b00000110; +} + +#if SEND_KELON + +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRKelonAc::send(const uint16_t repeat) { + _irsend.sendKelon(getRaw(), kKelonBits, repeat); + + // Reset toggle flags + _.PowerToggle = false; + _.SwingVToggle = false; + + // Remove the timer time setting + _.TimerHours = 0; + _.TimerHalfHour = 0; +} + +/// Ensures the AC is on or off by exploiting the fact that setting +/// it to "smart" will always turn it on if it's off. +/// This method will send 2 commands to the AC to do the trick +/// @param[in] on Whether to ensure the AC is on or off +void IRKelonAc::ensurePower(bool on) { + // Try to avoid turning on the compressor for this operation. + // "Dry grade", when in "smart" mode, acts as a temperature offset that + // the user can configure if they feel too cold or too hot. By setting it + // to +2 we're setting the temperature to ~28°C, which will effectively + // set the AC to fan mode. + int8_t previousDry = getDryGrade(); + setDryGrade(2); + setMode(kKelonModeSmart); + send(); + + setDryGrade(previousDry); + setMode(_previousMode); + send(); + + // Now we're sure it's on. Turn it back off. The AC seems to turn back on if + // we don't send this separately + if (!on) { + setTogglePower(true); + send(); + } +} + +#endif // SEND_KELON + +/// Set up hardware to be able to send a message. +void IRKelonAc::begin() { _irsend.begin(); } + +/// Request toggling power - will be reset to false after sending +/// @param[in] toggle Whether to toggle the power state +void IRKelonAc::setTogglePower(const bool toggle) { _.PowerToggle = toggle; } + +/// Get whether toggling power will be requested +/// @return The power toggle state +bool IRKelonAc::getTogglePower() const { return _.PowerToggle; } + +/// Set the temperature setting. +/// @param[in] degrees The temperature in degrees celsius. +void IRKelonAc::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kKelonMinTemp, degrees); + temp = ::min(kKelonMaxTemp, temp); + _previousTemp = _.Temperature; + _.Temperature = temp - kKelonMinTemp; +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRKelonAc::getTemp() const { return _.Temperature + kKelonMinTemp; } + +/// Set the speed of the fan. +/// @param[in] speed 0 is auto, 1-5 is the speed +void IRKelonAc::setFan(const uint8_t speed) { + uint8_t fan = ::min(speed, kKelonFanMax); + + _previousFan = _.Fan; + // Note: Kelon fan speeds are backwards! This code maps the range 0,1:3 to + // 0,3:1 to save the API's user's sanity. + _.Fan = ((static_cast(fan) - 4) * -1) % 4; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRKelonAc::getFan() const { + return ((static_cast(_.Fan) - 4) * -1) % 4;; +} + +/// Set the dehumidification intensity. +/// @param[in] grade has to be in the range [-2 : +2] +void IRKelonAc::setDryGrade(const int8_t grade) { + int8_t drygrade = ::max(kKelonDryGradeMin, grade); + drygrade = ::min(kKelonDryGradeMax, drygrade); + + // Two's complement is clearly too bleeding edge for this manufacturer + uint8_t outval; + if (drygrade < 0) + outval = 0b100 | (-drygrade & 0b011); + else + outval = drygrade & 0b011; + _.DehumidifierGrade = outval; +} + +/// Get the current dehumidification intensity setting. In smart mode, this +/// controls the temperature adjustment. +/// @return The current dehumidification intensity. +int8_t IRKelonAc::getDryGrade() const { + return static_cast(_.DehumidifierGrade & 0b011) * + ((_.DehumidifierGrade & 0b100) ? -1 : 1); +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRKelonAc::setMode(const uint8_t mode) { + if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan || + _.Mode == kKelonModeDry) { + _.Temperature = _previousTemp; + } + if (_.SuperCoolEnabled1) { + // Cancel supercool + _.SuperCoolEnabled1 = false; + _.SuperCoolEnabled2 = false; + _.Temperature = _previousTemp; + _.Fan = _previousFan; + } + _previousMode = _.Mode; + + switch (mode) { + case kKelonModeSmart: + setTemp(26); + _.SmartModeEnabled = true; + _.Mode = mode; + break; + case kKelonModeDry: + case kKelonModeFan: + setTemp(25); + // fallthrough + case kKelonModeCool: + case kKelonModeHeat: + _.Mode = mode; + // fallthrough + default: + _.SmartModeEnabled = false; + } +} + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRKelonAc::getMode() const { return _.Mode; } + +/// Request toggling the vertical swing - will be reset to false after sending +/// @param[in] toggle If true, the swing mode will be toggled when sent. +void IRKelonAc::setToggleSwingVertical(const bool toggle) { + _.SwingVToggle = toggle; +} + +/// Get whether the swing mode is set to be toggled +/// @return Whether the toggle bit is set +bool IRKelonAc::getToggleSwingVertical() const { return _.SwingVToggle; } + +/// Control the current sleep (quiet) setting. +/// @param[in] on The desired setting. +void IRKelonAc::setSleep(const bool on) { _.SleepEnabled = on; } + +/// Is the sleep setting on? +/// @return The current value. +bool IRKelonAc::getSleep() const { return _.SleepEnabled; } + +/// Control the current super cool mode setting. +/// @param[in] on The desired setting. +void IRKelonAc::setSupercool(const bool on) { + if (on) { + setTemp(kKelonMinTemp); + setMode(kKelonModeCool); + setFan(kKelonFanMax); + } else { + // All reverts to previous are handled by setMode as needed + setMode(_previousMode); + } + _.SuperCoolEnabled1 = on; + _.SuperCoolEnabled2 = on; +} + +/// Is the super cool mode setting on? +/// @return The current value. +bool IRKelonAc::getSupercool() const { return _.SuperCoolEnabled1; } + +/// Set the timer time and enable it. Timer is an off timer if the unit is on, +/// it is an on timer if the unit is off. +/// Only multiples of 30m are supported for < 10h, then only multiples of 60m +/// @param[in] mins Nr. of minutes +void IRKelonAc::setTimer(uint16_t mins) { + const uint16_t minutes = ::min(static_cast(mins), 24 * 60); + + if (minutes / 60 >= 10) { + uint8_t hours = minutes / 60 + 10; + _.TimerHalfHour = hours & 1; + _.TimerHours = hours >> 1; + } else { + _.TimerHalfHour = (minutes % 60) >= 30 ? 1 : 0; + _.TimerHours = minutes / 60; + } + + setTimerEnabled(true); +} + +/// Get the set timer. Timer set time is deleted once the command is sent, so +/// calling this after send() will return 0. +/// The AC unit will continue keeping track of the remaining time unless it is +/// later disabled. +/// @return The timer set minutes +uint16_t IRKelonAc::getTimer() const { + if (_.TimerHours >= 10) + return ((uint16_t) ((_.TimerHours << 1) | _.TimerHalfHour) - 10) * 60; + return (((uint16_t) _.TimerHours) * 60) + (_.TimerHalfHour ? 30 : 0); +} + +/// Enable or disable the timer. Note that in order to enable the timer the +/// minutes must be set with setTimer(). +/// @param[in] on Whether to enable or disable the timer +void IRKelonAc::setTimerEnabled(bool on) { _.TimerEnabled = on; } + +/// Get the current timer status +/// @return Whether the timer is enabled. +bool IRKelonAc::getTimerEnabled() const { return _.TimerEnabled; } + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A PTR to the internal state. +uint64_t IRKelonAc::getRaw() const { return _.raw; } + +/// Set the raw state of the object. +/// @param[in] new_code The raw state from the native IR message. +void IRKelonAc::setRaw(const uint64_t new_code) { _.raw = new_code; } + +/// Convert a standard A/C mode (stdAc::opmode_t) into it a native mode. +/// @param[in] mode A stdAc::opmode_t operation mode. +/// @return The native mode equivalent. +uint8_t IRKelonAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kKelonModeCool; + case stdAc::opmode_t::kHeat: return kKelonModeHeat; + case stdAc::opmode_t::kDry: return kKelonModeDry; + case stdAc::opmode_t::kFan: return kKelonModeFan; + default: return kKelonModeSmart; // aka Auto. + } +} + +/// Convert a standard A/C fan speed (stdAc::fanspeed_t) into it a native speed. +/// @param[in] fan A stdAc::fanspeed_t fan speed +/// @return The native speed equivalent. +uint8_t IRKelonAc::convertFan(stdAc::fanspeed_t fan) { + switch (fan) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kKelonFanMin; + case stdAc::fanspeed_t::kMedium: return kKelonFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kKelonFanMax; + default: return kKelonFanAuto; + } +} + +/// Convert a native mode to it's stdAc::opmode_t equivalent. +/// @param[in] mode A native operating mode value. +/// @return The stdAc::opmode_t equivalent. +stdAc::opmode_t IRKelonAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kKelonModeCool: return stdAc::opmode_t::kCool; + case kKelonModeHeat: return stdAc::opmode_t::kHeat; + case kKelonModeDry: return stdAc::opmode_t::kDry; + case kKelonModeFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed to it's stdAc::fanspeed_t equivalent. +/// @param[in] speed A native fan speed value. +/// @return The stdAc::fanspeed_t equivalent. +stdAc::fanspeed_t IRKelonAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kKelonFanMin: return stdAc::fanspeed_t::kLow; + case kKelonFanMedium: return stdAc::fanspeed_t::kMedium; + case kKelonFanMax: return stdAc::fanspeed_t::kHigh; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the internal A/C object state to it's stdAc::state_t equivalent. +/// @return A stdAc::state_t containing the current settings. +stdAc::state_t IRKelonAc::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::KELON; + result.model = -1; // Unused. + // AC only supports toggling it + result.power = (prev == nullptr || prev->power) ^ _.PowerToggle; + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + // AC only supports toggling it + result.swingv = stdAc::swingv_t::kAuto; + if (prev != nullptr && + (prev->swingv != stdAc::swingv_t::kAuto) ^ _.SwingVToggle) + result.swingv = stdAc::swingv_t::kOff; + result.turbo = getSupercool(); + result.sleep = getSleep() ? 0 : -1; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.light = true; + result.beep = true; + result.quiet = false; + result.filter = false; + result.clean = false; + result.econo = false; + result.clock = -1; + return result; +} + +/// Convert the internal settings into a human readable string. +/// @return A String. +String IRKelonAc::toString() const { + String result = ""; + // Reserve some heap for the string to reduce fragging. + result.reserve(160); + result += addTempToString(getTemp(), true, false); + result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, + kKelonModeHeat, kKelonModeDry, kKelonModeFan); + result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, + -1, kKelonFanMedium, kKelonFanMax); + result += addBoolToString(_.SleepEnabled, kSleepStr); + result += addSignedIntToString(getDryGrade(), kDryStr); + result += addLabeledString( + getTimerEnabled() ? (getTimer() > 0 ? minsToString(getTimer()) : kOnStr) + : kOffStr, + kTimerStr); + result += addBoolToString(getSupercool(), kTurboStr); + if (getTogglePower()) + result += addBoolToString(true, kPowerToggleStr); + if (getToggleSwingVertical()) + result += addBoolToString(true, kSwingVToggleStr); + return result; +} + +#if SEND_KELON168 +/// Send a Kelon 168 bit / 21 byte message. +/// Status: BETA / Probably works. +/// @param[in] data The data to be transmitted. +/// @param[in] nbytes Nr. of bytes of data to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendKelon168(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + assert(kKelon168StateLength == kKelon168Section1Size + kKelon168Section2Size + + kKelon168Section3Size); + // Enough bytes to send a proper message? + if (nbytes < kKelon168StateLength) return; + + for (uint16_t r = 0; r <= repeat; r++) { + // Section #1 (48 bits) + sendGeneric(kKelonHdrMark, kKelonHdrSpace, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelon168FooterSpace, + data, kKelon168Section1Size, kKelonFreq, false, // LSB First. + 0, // No repeats here + kDutyDefault); + // Section #2 (64 bits) + sendGeneric(0, 0, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelon168FooterSpace, + data + kKelon168Section1Size, kKelon168Section2Size, + kKelonFreq, false, // LSB First. + 0, // No repeats here + kDutyDefault); + // Section #3 (56 bits) + sendGeneric(0, 0, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelonGap, + data + kKelon168Section1Size + kKelon168Section2Size, + nbytes - (kKelon168Section1Size + kKelon168Section2Size), + kKelonFreq, false, // LSB First. + 0, // No repeats here + kDutyDefault); + } +} +#endif // SEND_KELON168 + +#if DECODE_KELON168 +/// Decode the supplied Kelon 168 bit / 21 byte message. +/// Status: BETA / Probably Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeKelon168(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kKelon168Bits) return false; + if (results->rawlen <= 2 * nbits + kHeader + kFooter * 2 - 1 + offset) + return false; // Can't possibly be a valid Kelon 168 bit message. + + uint16_t used = 0; + + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, kKelon168Section1Size * 8, + kKelonHdrMark, kKelonHdrSpace, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelon168FooterSpace, + false, _tolerance, 0, false); + if (!used) return false; // Failed to match. + offset += used; + + used = matchGeneric(results->rawbuf + offset, + results->state + kKelon168Section1Size, + results->rawlen - offset, kKelon168Section2Size * 8, + 0, 0, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelon168FooterSpace, + false, _tolerance, 0, false); + if (!used) return false; // Failed to match. + offset += used; + + used = matchGeneric(results->rawbuf + offset, + results->state + (kKelon168Section1Size + + kKelon168Section2Size), + results->rawlen - offset, + nbits - (kKelon168Section1Size + + kKelon168Section2Size) * 8, + 0, 0, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelonGap, + true, _tolerance, 0, false); + if (!used) return false; // Failed to match. + + results->decode_type = decode_type_t::KELON168; + results->bits = nbits; + return true; +} +#endif // DECODE_KELON168 diff --git a/src/libraries/IRremoteESP8266/src/ir_Kelon.h b/src/libraries/IRremoteESP8266/src/ir_Kelon.h new file mode 100644 index 000000000..126669940 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Kelon.h @@ -0,0 +1,142 @@ +// Copyright 2021 Davide Depau + +/// @file +/// @brief Support for Kelan AC protocol. +/// @note Both sending and decoding should be functional for models of series +/// KELON ON/OFF 9000-12000. +/// All features of the standard remote are implemented. +/// +/// @note Unsupported: +/// - Explicit on/off due to AC unit limitations +/// - Explicit swing position due to AC unit limitations +/// - Fahrenheit. +/// +/// For KELON168: +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1745 + +// Supports: +// Brand: Kelon, Model: ON/OFF 9000-12000 (KELON) +// Brand: Kelon, Model: DG11R2-01 remote (KELON168) +// Brand: Kelon, Model: AST-09UW4RVETG00A A/C (KELON168) +// Brand: Hisense, Model: AST-09UW4RVETG00A A/C (KELON168) + +#ifndef IR_KELON_H_ +#define IR_KELON_H_ + +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRutils.h" + +union KelonProtocol { + uint64_t raw; + + struct { + uint8_t preamble[2]; + uint8_t Fan: 2; + uint8_t PowerToggle: 1; + uint8_t SleepEnabled: 1; + uint8_t DehumidifierGrade: 3; + uint8_t SwingVToggle: 1; + uint8_t Mode: 3; + uint8_t TimerEnabled: 1; + uint8_t Temperature: 4; + uint8_t TimerHalfHour: 1; + uint8_t TimerHours: 6; + uint8_t SmartModeEnabled: 1; + uint8_t pad1: 4; + uint8_t SuperCoolEnabled1: 1; + uint8_t pad2: 2; + uint8_t SuperCoolEnabled2: 1; + }; +}; + +// Constants +const uint8_t kKelonModeHeat = 0; +const uint8_t kKelonModeSmart = 1; // (temp = 26C, but not shown) +const uint8_t kKelonModeCool = 2; +const uint8_t kKelonModeDry = 3; // (temp = 25C, but not shown) +const uint8_t kKelonModeFan = 4; // (temp = 25C, but not shown) +const uint8_t kKelonFanAuto = 0; +// Note! Kelon fan speeds are actually 0:AUTO, 1:MAX, 2:MED, 3:MIN +// Since this is insane, I decided to invert them in the public API, they are +// converted back in setFan/getFan +const uint8_t kKelonFanMin = 1; +const uint8_t kKelonFanMedium = 2; +const uint8_t kKelonFanMax = 3; + +const int8_t kKelonDryGradeMin = -2; +const int8_t kKelonDryGradeMax = +2; +const uint8_t kKelonMinTemp = 18; +const uint8_t kKelonMaxTemp = 32; + + +class IRKelonAc { + public: + explicit IRKelonAc(uint16_t pin, bool inverted = false, + bool use_modulation = true); + void stateReset(void); + #if SEND_KELON + void send(const uint16_t repeat = kNoRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } + /// Since the AC does not support actually setting the power state to a known + /// value, this utility allow ensuring the AC is on or off by exploiting + /// the fact that the AC, according to the user manual, will always turn on + /// when setting it to "smart" or "super" mode. + void ensurePower(const bool on); + #endif // SEND_KELON + + + void begin(void); + void setTogglePower(const bool toggle); + bool getTogglePower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setDryGrade(const int8_t grade); + int8_t getDryGrade(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setToggleSwingVertical(const bool toggle); + bool getToggleSwingVertical(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setSupercool(const bool on); + bool getSupercool(void) const; + void setTimer(const uint16_t mins); + uint16_t getTimer(void) const; + void setTimerEnabled(const bool on); + bool getTimerEnabled(void) const; + uint64_t getRaw(void) const; + void setRaw(const uint64_t new_code); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t fan); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = nullptr) const; + String toString(void) const; + + private: +#ifndef UNIT_TEST + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + KelonProtocol _; + + // Used when exiting supercool mode + uint8_t _previousMode = 0; + uint8_t _previousTemp = kKelonMinTemp; + uint8_t _previousFan = kKelonFanAuto; +}; +#endif // IR_KELON_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Kelvinator.cpp b/src/libraries/IRremoteESP8266/src/ir_Kelvinator.cpp new file mode 100644 index 000000000..bc39d5a8b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Kelvinator.cpp @@ -0,0 +1,582 @@ +// Copyright 2016 David Conran +/// @file +/// @brief Support for Kelvinator A/C protocols. +/// Code to emulate IR Kelvinator YALIF remote control unit, which should +/// control at least the following Kelvinator A/C units: +/// KSV26CRC, KSV26HRC, KSV35CRC, KSV35HRC, KSV53HRC, KSV62HRC, KSV70CRC, +/// KSV70HRC, KSV80HRC. +/// +/// @note Unsupported: +/// - All Sleep modes. +/// - All Timer modes. +/// - "I Feel" button & mode. +/// - Energy Saving mode. +/// - Low Heat mode. +/// - Fahrenheit. + +#include "ir_Kelvinator.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRac.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kKelvinatorTick = 85; +const uint16_t kKelvinatorHdrMarkTicks = 106; +const uint16_t kKelvinatorHdrMark = kKelvinatorHdrMarkTicks * kKelvinatorTick; +const uint16_t kKelvinatorHdrSpaceTicks = 53; +const uint16_t kKelvinatorHdrSpace = kKelvinatorHdrSpaceTicks * kKelvinatorTick; +const uint16_t kKelvinatorBitMarkTicks = 8; +const uint16_t kKelvinatorBitMark = kKelvinatorBitMarkTicks * kKelvinatorTick; +const uint16_t kKelvinatorOneSpaceTicks = 18; +const uint16_t kKelvinatorOneSpace = kKelvinatorOneSpaceTicks * kKelvinatorTick; +const uint16_t kKelvinatorZeroSpaceTicks = 6; +const uint16_t kKelvinatorZeroSpace = + kKelvinatorZeroSpaceTicks * kKelvinatorTick; +const uint16_t kKelvinatorGapSpaceTicks = 235; +const uint16_t kKelvinatorGapSpace = kKelvinatorGapSpaceTicks * kKelvinatorTick; + +const uint8_t kKelvinatorCmdFooter = 2; +const uint8_t kKelvinatorCmdFooterBits = 3; + +const uint8_t kKelvinatorChecksumStart = 10; + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::addSwingVToString; + +#if SEND_KELVINATOR +/// Send a Kelvinator A/C message. +/// Status: STABLE / Known working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendKelvinator(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kKelvinatorStateLength) + return; // Not enough bytes to send a proper message. + + for (uint16_t r = 0; r <= repeat; r++) { + // Command Block #1 (4 bytes) + sendGeneric(kKelvinatorHdrMark, kKelvinatorHdrSpace, kKelvinatorBitMark, + kKelvinatorOneSpace, kKelvinatorBitMark, kKelvinatorZeroSpace, + 0, 0, // No Footer yet. + data, 4, 38, false, 0, 50); + // Send Footer for the command block (3 bits (b010)) + sendGeneric(0, 0, // No Header + kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark, + kKelvinatorZeroSpace, kKelvinatorBitMark, kKelvinatorGapSpace, + kKelvinatorCmdFooter, kKelvinatorCmdFooterBits, 38, false, 0, + 50); + // Data Block #1 (4 bytes) + sendGeneric(0, 0, // No header + kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark, + kKelvinatorZeroSpace, kKelvinatorBitMark, + kKelvinatorGapSpace * 2, data + 4, 4, 38, false, 0, 50); + // Command Block #2 (4 bytes) + sendGeneric(kKelvinatorHdrMark, kKelvinatorHdrSpace, kKelvinatorBitMark, + kKelvinatorOneSpace, kKelvinatorBitMark, kKelvinatorZeroSpace, + 0, 0, // No Footer yet. + data + 8, 4, 38, false, 0, 50); + // Send Footer for the command block (3 bits (B010)) + sendGeneric(0, 0, // No Header + kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark, + kKelvinatorZeroSpace, kKelvinatorBitMark, kKelvinatorGapSpace, + kKelvinatorCmdFooter, kKelvinatorCmdFooterBits, 38, false, 0, + 50); + // Data Block #2 (4 bytes) + sendGeneric(0, 0, // No header + kKelvinatorBitMark, kKelvinatorOneSpace, kKelvinatorBitMark, + kKelvinatorZeroSpace, kKelvinatorBitMark, + kKelvinatorGapSpace * 2, data + 12, 4, 38, false, 0, 50); + } +} +#endif // SEND_KELVINATOR + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRKelvinatorAC::IRKelvinatorAC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internals of the object to a known good state. +void IRKelvinatorAC::stateReset(void) { + for (uint8_t i = 0; i < kKelvinatorStateLength; i++) _.raw[i] = 0x0; + _.raw[3] = 0x50; + _.raw[11] = 0x70; +} + +/// Set up hardware to be able to send a message. +void IRKelvinatorAC::begin(void) { _irsend.begin(); } + +/// Fix up any odd conditions for the current state. +void IRKelvinatorAC::fixup(void) { + // X-Fan mode is only valid in COOL or DRY modes. + if (_.Mode != kKelvinatorCool && _.Mode != kKelvinatorDry) + setXFan(false); + // Duplicate to the 2nd command chunk. + _.raw[8] = _.raw[0]; + _.raw[9] = _.raw[1]; + _.raw[10] = _.raw[2]; + checksum(); // Calculate the checksums +} + +#if SEND_KELVINATOR +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRKelvinatorAC::send(const uint16_t repeat) { + _irsend.sendKelvinator(getRaw(), kKelvinatorStateLength, repeat); +} +#endif // SEND_KELVINATOR + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A PTR to the internal state. +uint8_t *IRKelvinatorAC::getRaw(void) { + fixup(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the raw state of the object. +/// @param[in] new_code The raw state from the native IR message. +void IRKelvinatorAC::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kKelvinatorStateLength); +} + +/// Calculate the checksum for a given block of state. +/// @param[in] block A pointer to a block to calc the checksum of. +/// @param[in] length Length of the block array to checksum. +/// @return The calculated checksum value. +/// @note Many Bothans died to bring us this information. +uint8_t IRKelvinatorAC::calcBlockChecksum(const uint8_t *block, + const uint16_t length) { + uint8_t sum = kKelvinatorChecksumStart; + // Sum the lower half of the first 4 bytes of this block. + for (uint8_t i = 0; i < 4 && i < length - 1; i++, block++) + sum += (*block & 0b1111); + // then sum the upper half of the next 3 bytes. + for (uint8_t i = 4; i < length - 1; i++, block++) sum += (*block >> 4); + // Trim it down to fit into the 4 bits allowed. i.e. Mod 16. + return sum & 0b1111; +} + +/// Calculate the checksum for the internal state. +void IRKelvinatorAC::checksum(void) { + _.Sum1 = calcBlockChecksum(_.raw); + _.Sum2 = calcBlockChecksum(_.raw + 8); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The size of the state. +/// @return A boolean indicating if it is valid. +bool IRKelvinatorAC::validChecksum(const uint8_t state[], + const uint16_t length) { + for (uint16_t offset = 0; offset + 7 < length; offset += 8) { + // Top 4 bits of the last byte in the block is the block's checksum. + if (GETBITS8(state[offset + 7], kHighNibble, kNibbleSize) != + calcBlockChecksum(state + offset)) + return false; + } + return true; +} + +/// Set the internal state to have the power on. +void IRKelvinatorAC::on(void) { setPower(true); } + +/// Set the internal state to have the power off. +void IRKelvinatorAC::off(void) {setPower(false); } + +/// Set the internal state to have the desired power. +/// @param[in] on The desired power state. +void IRKelvinatorAC::setPower(const bool on) { + _.Power = on; +} + +/// Get the power setting from the internal state. +/// @return A boolean indicating if the power setting. +bool IRKelvinatorAC::getPower(void) const { + return _.Power; +} + +/// Set the temperature setting. +/// @param[in] degrees The temperature in degrees celsius. +void IRKelvinatorAC::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kKelvinatorMinTemp, degrees); + temp = ::min(kKelvinatorMaxTemp, temp); + _.Temp = temp - kKelvinatorMinTemp; +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRKelvinatorAC::getTemp(void) const { + return _.Temp + kKelvinatorMinTemp; +} + +/// Set the speed of the fan. +/// @param[in] speed 0 is auto, 1-5 is the speed +void IRKelvinatorAC::setFan(const uint8_t speed) { + uint8_t fan = ::min(kKelvinatorFanMax, speed); // Bounds check + + // Only change things if we need to. + if (fan != _.Fan) { + // Set the basic fan values. + _.BasicFan = ::min(kKelvinatorBasicFanMax, fan); + // Set the advanced(?) fan value. + _.Fan = fan; + // Turbo mode is turned off if we change the fan settings. + setTurbo(false); + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRKelvinatorAC::getFan(void) const { + return _.Fan; +} + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRKelvinatorAC::getMode(void) const { + return _.Mode; +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRKelvinatorAC::setMode(const uint8_t mode) { + switch (mode) { + case kKelvinatorAuto: + case kKelvinatorDry: + // When the remote is set to Auto or Dry, it defaults to 25C and doesn't + // show it. + setTemp(kKelvinatorAutoTemp); + // FALL-THRU + case kKelvinatorHeat: + case kKelvinatorCool: + case kKelvinatorFan: + _.Mode = mode; + break; + default: + setTemp(kKelvinatorAutoTemp); + _.Mode = kKelvinatorAuto; + break; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] automatic Do we use the automatic setting? +/// @param[in] position The position/mode to set the vanes to. +void IRKelvinatorAC::setSwingVertical(const bool automatic, + const uint8_t position) { + _.SwingAuto = (automatic || _.SwingH); + uint8_t new_position = position; + if (!automatic) { + switch (position) { + case kKelvinatorSwingVHighest: + case kKelvinatorSwingVUpperMiddle: + case kKelvinatorSwingVMiddle: + case kKelvinatorSwingVLowerMiddle: + case kKelvinatorSwingVLowest: + break; + default: + new_position = kKelvinatorSwingVOff; + } + } else { + switch (position) { + case kKelvinatorSwingVAuto: + case kKelvinatorSwingVLowAuto: + case kKelvinatorSwingVMiddleAuto: + case kKelvinatorSwingVHighAuto: + break; + default: + new_position = kKelvinatorSwingVAuto; + } + } + _.SwingV = new_position; +} + +/// Get the Vertical Swing Automatic mode setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRKelvinatorAC::getSwingVerticalAuto(void) const { + return _.SwingV & 0b0001; +} + +/// Get the Vertical Swing position setting of the A/C. +/// @return The native position/mode. +uint8_t IRKelvinatorAC::getSwingVerticalPosition(void) const { + return _.SwingV; +} + +/// Control the current horizontal swing setting. +/// @param[in] on The desired setting. +void IRKelvinatorAC::setSwingHorizontal(const bool on) { + _.SwingH = on; + _.SwingAuto = (on || (_.SwingV & 0b0001)); +} + +/// Is the horizontal swing setting on? +/// @return The current value. +bool IRKelvinatorAC::getSwingHorizontal(void) const { + return _.SwingH; +} + +/// Control the current Quiet setting. +/// @param[in] on The desired setting. +void IRKelvinatorAC::setQuiet(const bool on) { + _.Quiet = on; +} + +/// Is the Quiet setting on? +/// @return The current value. +bool IRKelvinatorAC::getQuiet(void) const { + return _.Quiet; +} + +/// Control the current Ion Filter setting. +/// @param[in] on The desired setting. +void IRKelvinatorAC::setIonFilter(const bool on) { + _.IonFilter = on; +} + +/// Is the Ion Filter setting on? +/// @return The current value. +bool IRKelvinatorAC::getIonFilter(void) const { + return _.IonFilter; +} + +/// Control the current Light setting. +/// i.e. The LED display on the A/C unit that shows the basic settings. +/// @param[in] on The desired setting. +void IRKelvinatorAC::setLight(const bool on) { + _.Light = on; +} + +/// Is the Light (Display) setting on? +/// @return The current value. +bool IRKelvinatorAC::getLight(void) const { + return _.Light; +} + +/// Control the current XFan setting. +/// This setting will cause the unit blow air after power off to dry out the +/// A/C device. +/// @note XFan mode is only valid in Cool or Dry mode. +/// @param[in] on The desired setting. +void IRKelvinatorAC::setXFan(const bool on) { + _.XFan = on; +} + +/// Is the XFan setting on? +/// @return The current value. +bool IRKelvinatorAC::getXFan(void) const { + return _.XFan; +} + +/// Control the current Turbo setting. +/// @note Turbo mode is turned off if the fan speed is changed. +/// @param[in] on The desired setting. +void IRKelvinatorAC::setTurbo(const bool on) { + _.Turbo = on; +} + +/// Is the Turbo setting on? +/// @return The current value. +bool IRKelvinatorAC::getTurbo(void) const { + return _.Turbo; +} + +/// Convert a standard A/C mode (stdAc::opmode_t) into it a native mode. +/// @param[in] mode A stdAc::opmode_t operation mode. +/// @return The native mode equivalent. +uint8_t IRKelvinatorAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kKelvinatorCool; + case stdAc::opmode_t::kHeat: return kKelvinatorHeat; + case stdAc::opmode_t::kDry: return kKelvinatorDry; + case stdAc::opmode_t::kFan: return kKelvinatorFan; + default: return kKelvinatorAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] swingv The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRKelvinatorAC::convertSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHighest: return kKelvinatorSwingVHighest; + case stdAc::swingv_t::kHigh: return kKelvinatorSwingVHighAuto; + case stdAc::swingv_t::kMiddle: return kKelvinatorSwingVMiddle; + case stdAc::swingv_t::kLow: return kKelvinatorSwingVLowAuto; + case stdAc::swingv_t::kLowest: return kKelvinatorSwingVLowest; + default: return kKelvinatorSwingVAuto; + } +} + +/// Convert a native mode to it's stdAc::opmode_t equivalent. +/// @param[in] mode A native operating mode value. +/// @return The stdAc::opmode_t equivalent. +stdAc::opmode_t IRKelvinatorAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kKelvinatorCool: return stdAc::opmode_t::kCool; + case kKelvinatorHeat: return stdAc::opmode_t::kHeat; + case kKelvinatorDry: return stdAc::opmode_t::kDry; + case kKelvinatorFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed to it's stdAc::fanspeed_t equivalent. +/// @param[in] speed A native fan speed value. +/// @return The stdAc::fanspeed_t equivalent. +stdAc::fanspeed_t IRKelvinatorAC::toCommonFanSpeed(const uint8_t speed) { + return (stdAc::fanspeed_t)speed; +} + +/// Convert the internal A/C object state to it's stdAc::state_t equivalent. +/// @return A stdAc::state_t containing the current settings. +stdAc::state_t IRKelvinatorAC::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::KELVINATOR; + result.model = -1; // Unused. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + result.quiet = _.Quiet; + result.turbo = _.Turbo; + result.light = _.Light; + result.filter = _.IonFilter; + result.clean = _.XFan; + // Not supported. + result.econo = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the internal settings into a human readable string. +/// @return A String. +String IRKelvinatorAC::toString(void) const { + String result = ""; + result.reserve(160); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kKelvinatorAuto, kKelvinatorCool, + kKelvinatorHeat, kKelvinatorDry, kKelvinatorFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kKelvinatorFanMax, kKelvinatorFanMin, + kKelvinatorFanAuto, kKelvinatorFanAuto, + kKelvinatorBasicFanMax); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Quiet, kQuietStr); + result += addBoolToString(_.XFan, kXFanStr); + result += addBoolToString(_.IonFilter, kIonStr); + result += addBoolToString(_.Light, kLightStr); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addSwingVToString(_.SwingV, kKelvinatorSwingVAuto, + kKelvinatorSwingVHighest, + kKelvinatorSwingVHighAuto, + kKelvinatorSwingVUpperMiddle, + kKelvinatorSwingVMiddle, + kKelvinatorSwingVLowerMiddle, + kKelvinatorSwingVLowAuto, + kKelvinatorSwingVLowest, + kKelvinatorSwingVOff, + kKelvinatorSwingVAuto, kKelvinatorSwingVAuto, + kKelvinatorSwingVAuto); + return result; +} + +#if DECODE_KELVINATOR +/// Decode the supplied Kelvinator message. +/// Status: STABLE / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeKelvinator(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= + 2 * (nbits + kKelvinatorCmdFooterBits) + (kHeader + kFooter + 1) * 2 - 1 + + offset) + return false; // Can't possibly be a valid Kelvinator message. + if (strict && nbits != kKelvinatorBits) + return false; // Not strictly a Kelvinator message. + + // There are two messages back-to-back in a full Kelvinator IR message + // sequence. + int8_t pos = 0; + for (uint8_t s = 0; s < 2; s++) { + match_result_t data_result; + + uint16_t used; + // Header + Data Block #1 (32 bits) + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, 32, + kKelvinatorHdrMark, kKelvinatorHdrSpace, + kKelvinatorBitMark, kKelvinatorOneSpace, + kKelvinatorBitMark, kKelvinatorZeroSpace, + 0, 0, false, + _tolerance, kMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += 4; + + // Command data footer (3 bits, B010) + data_result = matchData( + &(results->rawbuf[offset]), kKelvinatorCmdFooterBits, + kKelvinatorBitMark, kKelvinatorOneSpace, + kKelvinatorBitMark, kKelvinatorZeroSpace, + _tolerance, kMarkExcess, false); + if (data_result.success == false) return false; + if (data_result.data != kKelvinatorCmdFooter) return false; + offset += data_result.used; + + // Gap + Data (Options) (32 bits) + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, 32, + kKelvinatorBitMark, kKelvinatorGapSpace, + kKelvinatorBitMark, kKelvinatorOneSpace, + kKelvinatorBitMark, kKelvinatorZeroSpace, + kKelvinatorBitMark, kKelvinatorGapSpace * 2, + s > 0, + _tolerance, kMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += 4; + } + + // Compliance + if (strict) { + // Verify the message's checksum is correct. + if (!IRKelvinatorAC::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = decode_type_t::KELVINATOR; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_KELVINATOR diff --git a/src/libraries/IRremoteESP8266/src/ir_Kelvinator.h b/src/libraries/IRremoteESP8266/src/ir_Kelvinator.h new file mode 100644 index 000000000..fce0580af --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Kelvinator.h @@ -0,0 +1,198 @@ +// Copyright 2016 David Conran +/// @file +/// @brief Support for Kelvinator A/C protocols. + +// Supports: +// Brand: Kelvinator, Model: YALIF Remote +// Brand: Kelvinator, Model: KSV26CRC A/C +// Brand: Kelvinator, Model: KSV26HRC A/C +// Brand: Kelvinator, Model: KSV35CRC A/C +// Brand: Kelvinator, Model: KSV35HRC A/C +// Brand: Kelvinator, Model: KSV53HRC A/C +// Brand: Kelvinator, Model: KSV62HRC A/C +// Brand: Kelvinator, Model: KSV70CRC A/C +// Brand: Kelvinator, Model: KSV70HRC A/C +// Brand: Kelvinator, Model: KSV80HRC A/C +// Brand: Green, Model: YAPOF3 remote +// Brand: Gree, Model: YAP0F8 remote +// Brand: Sharp, Model: YB1FA remote +// Brand: Sharp, Model: A5VEY A/C + +#ifndef IR_KELVINATOR_H_ +#define IR_KELVINATOR_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Kelvinator A/C message. +union KelvinatorProtocol{ + uint8_t raw[kKelvinatorStateLength]; ///< The state in IR code form. + struct { + // Byte 0 + uint8_t Mode :3; + uint8_t Power :1; + uint8_t BasicFan :2; + uint8_t SwingAuto :1; + uint8_t :1; // Sleep Modes 1 & 3 (1 = On, 0 = Off) + // Byte 1 + uint8_t Temp :4; // Degrees C. + uint8_t :4; + // Byte 2 + uint8_t :4; + uint8_t Turbo :1; + uint8_t Light :1; + uint8_t IonFilter :1; + uint8_t XFan :1; + // Byte 3 + uint8_t :4; + uint8_t :2; // (possibly timer related) (Typically 0b01) + uint8_t :2; // End of command block (B01) + // (B010 marker and a gap of 20ms) + // Byte 4 + uint8_t SwingV :4; + uint8_t SwingH :1; + uint8_t :3; + // Byte 5~6 + uint8_t pad0[2]; // Timer related. Typically 0 except when timer in use. + // Byte 7 + uint8_t :4; // (Used in Timer mode) + uint8_t Sum1 :4; // checksum of the previous bytes (0-6) + // (gap of 40ms) + // (header mark and space) + // Byte 8~10 + uint8_t pad1[3]; // Repeat of byte 0~2 + // Byte 11 + uint8_t :4; + uint8_t :2; // (possibly timer related) (Typically 0b11) + uint8_t :2; // End of command block (B01) + // (B010 marker and a gap of 20ms) + // Byte 12 + uint8_t :1; // Sleep mode 2 (1 = On, 0=Off) + uint8_t :6; // (Used in Sleep Mode 3, Typically 0b000000) + uint8_t Quiet :1; + // Byte 13 + uint8_t :8; // (Sleep Mode 3 related, Typically 0x00) + // Byte 14 + uint8_t :4; // (Sleep Mode 3 related, Typically 0b0000) + uint8_t Fan :3; + // Byte 15 + uint8_t :4; + uint8_t Sum2 :4; // checksum of the previous bytes (8-14) + }; +}; + +// Constants +const uint8_t kKelvinatorAuto = 0; // (temp = 25C) +const uint8_t kKelvinatorCool = 1; +const uint8_t kKelvinatorDry = 2; // (temp = 25C, but not shown) +const uint8_t kKelvinatorFan = 3; +const uint8_t kKelvinatorHeat = 4; +const uint8_t kKelvinatorBasicFanMax = 3; +const uint8_t kKelvinatorFanAuto = 0; +const uint8_t kKelvinatorFanMin = 1; +const uint8_t kKelvinatorFanMax = 5; +const uint8_t kKelvinatorMinTemp = 16; // 16C +const uint8_t kKelvinatorMaxTemp = 30; // 30C +const uint8_t kKelvinatorAutoTemp = 25; // 25C + +const uint8_t kKelvinatorSwingVOff = 0b0000; // 0 +const uint8_t kKelvinatorSwingVAuto = 0b0001; // 1 +const uint8_t kKelvinatorSwingVHighest = 0b0010; // 2 +const uint8_t kKelvinatorSwingVUpperMiddle = 0b0011; // 3 +const uint8_t kKelvinatorSwingVMiddle = 0b0100; // 4 +const uint8_t kKelvinatorSwingVLowerMiddle = 0b0101; // 5 +const uint8_t kKelvinatorSwingVLowest = 0b0110; // 6 +const uint8_t kKelvinatorSwingVLowAuto = 0b0111; // 7 +const uint8_t kKelvinatorSwingVMiddleAuto = 0b1001; // 9 +const uint8_t kKelvinatorSwingVHighAuto = 0b1011; // 11 + +// Legacy defines (Deprecated) +#define KELVINATOR_MIN_TEMP kKelvinatorMinTemp +#define KELVINATOR_MAX_TEMP kKelvinatorMaxTemp +#define KELVINATOR_HEAT kKelvinatorHeat +#define KELVINATOR_FAN_MAX kKelvinatorFanMax +#define KELVINATOR_FAN_AUTO kKelvinatorFanAuto +#define KELVINATOR_FAN kKelvinatorFan +#define KELVINATOR_DRY kKelvinatorDry +#define KELVINATOR_COOL kKelvinatorCool +#define KELVINATOR_BASIC_FAN_MAX kKelvinatorBasicFanMax +#define KELVINATOR_AUTO_TEMP kKelvinatorAutoTemp +#define KELVINATOR_AUTO kKelvinatorAuto + +// Classes +/// Class for handling detailed Kelvinator A/C messages. +class IRKelvinatorAC { + public: + explicit IRKelvinatorAC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_KELVINATOR + void send(const uint16_t repeat = kKelvinatorDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_KELVINATOR + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingVertical(const bool automatic, const uint8_t position); + bool getSwingVerticalAuto(void) const; + uint8_t getSwingVerticalPosition(void) const; + static uint8_t convertSwingV(const stdAc::swingv_t swingv); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + void setSwingHorizontal(const bool on); + bool getSwingHorizontal(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + void setIonFilter(const bool on); + bool getIonFilter(void) const; + void setLight(const bool on); + bool getLight(void) const; + void setXFan(const bool on); + bool getXFan(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + static uint8_t calcBlockChecksum( + const uint8_t* block, const uint16_t length = kKelvinatorStateLength / 2); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kKelvinatorStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + KelvinatorProtocol _; + void checksum(void); + void fixup(void); +}; + +#endif // IR_KELVINATOR_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_LG.cpp b/src/libraries/IRremoteESP8266/src/ir_LG.cpp new file mode 100644 index 000000000..2d8eed9fe --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_LG.cpp @@ -0,0 +1,863 @@ +// Copyright 2015 Darryl Smith +// Copyright 2015 cheaplin +// Copyright 2017-2021 David Conran + +/// @file +/// @brief Support for LG protocols. +/// LG decode originally added by Darryl Smith (based on the JVC protocol) +/// LG send originally added by https://github.com/chaeplin +/// @see https://github.com/arendst/Tasmota/blob/54c2eb283a02e4287640a4595e506bc6eadbd7f2/sonoff/xdrv_05_irremote.ino#L327-438 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1513 + +#include "ir_LG.h" +// #include +#include "IRac.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::addToggleToString; +using irutils::addSwingVToString; +using irutils::addIntToString; + + +// Constants +// Common timings +const uint16_t kLgBitMark = 550; ///< uSeconds. +const uint16_t kLgOneSpace = 1600; ///< uSeconds. +const uint16_t kLgZeroSpace = 550; ///< uSeconds. +const uint16_t kLgRptSpace = 2250; ///< uSeconds. +const uint16_t kLgMinGap = 39750; ///< uSeconds. +const uint32_t kLgMinMessageLength = 108050; ///< uSeconds. +// LG (28 Bit) +const uint16_t kLgHdrMark = 8500; ///< uSeconds. +const uint16_t kLgHdrSpace = 4250; ///< uSeconds. +// LG (32 Bit) +const uint16_t kLg32HdrMark = 4500; ///< uSeconds. +const uint16_t kLg32HdrSpace = 4450; ///< uSeconds. +const uint16_t kLg32RptHdrMark = 8950; ///< uSeconds. +// LG2 (28 Bit) +const uint16_t kLg2HdrMark = 3200; ///< uSeconds. +const uint16_t kLg2HdrSpace = 9900; ///< uSeconds. +const uint16_t kLg2BitMark = 480; ///< uSeconds. + +const uint32_t kLgAcAKB74955603DetectionMask = 0x0000080; +const uint8_t kLgAcChecksumSize = 4; ///< Size in bits. +// Signature has the checksum removed, and another bit to match both Auto & Off. +const uint8_t kLgAcSwingHOffsetSize = kLgAcChecksumSize + 1; +const uint32_t kLgAcSwingHSignature = kLgAcSwingHOff >> kLgAcSwingHOffsetSize; +const uint32_t kLgAcVaneSwingVBase = 0x8813200; + +#ifdef VANESWINGVPOS +#undef VANESWINGVPOS +#endif +#define VANESWINGVPOS(code) (code % kLgAcVaneSwingVSize) + +#if SEND_LG +/// Send an LG formatted message. (LG) +/// Status: Beta / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// Typically kLgBits or kLg32Bits. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note LG has a separate message to indicate a repeat, like NEC does. +void IRsend::sendLG(uint64_t data, uint16_t nbits, uint16_t repeat) { + uint16_t repeatHeaderMark = 0; + uint8_t duty = kDutyDefault; + + if (nbits >= kLg32Bits) { + // LG 32bit protocol is near identical to Samsung except for repeats. + sendSAMSUNG(data, nbits, 0); // Send it as a single Samsung message. + repeatHeaderMark = kLg32RptHdrMark; + duty = 33; + repeat++; + } else { + // LG (28-bit) protocol. + repeatHeaderMark = kLgHdrMark; + sendGeneric(kLgHdrMark, kLgHdrSpace, kLgBitMark, kLgOneSpace, kLgBitMark, + kLgZeroSpace, kLgBitMark, kLgMinGap, kLgMinMessageLength, data, + nbits, 38, true, 0, // Repeats are handled later. + duty); + } + + // Repeat + // Protocol has a mandatory repeat-specific code sent after every command. + if (repeat) + sendGeneric(repeatHeaderMark, kLgRptSpace, 0, 0, 0, 0, // No data is sent. + kLgBitMark, kLgMinGap, kLgMinMessageLength, 0, 0, // No data. + 38, true, repeat - 1, duty); +} + +/// Send an LG Variant-2 formatted message. (LG2) +/// Status: Beta / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// Typically kLgBits or kLg32Bits. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note LG has a separate message to indicate a repeat, like NEC does. +void IRsend::sendLG2(uint64_t data, uint16_t nbits, uint16_t repeat) { + if (nbits >= kLg32Bits) { + // Let the original routine handle it. + sendLG(data, nbits, repeat); // Send it as a single Samsung message. + return; + } + + // LGv2 (28-bit) protocol. + sendGeneric(kLg2HdrMark, kLg2HdrSpace, kLg2BitMark, kLgOneSpace, kLg2BitMark, + kLgZeroSpace, kLg2BitMark, kLgMinGap, kLgMinMessageLength, data, + nbits, 38, true, 0, // Repeats are handled later. + 33); // Use a duty cycle of 33% (Testing) + + // TODO(crackn): Verify the details of what repeat messages look like. + // Repeat + // Protocol has a mandatory repeat-specific code sent after every command. + if (repeat) + sendGeneric(kLg2HdrMark, kLgRptSpace, 0, 0, 0, 0, // No data is sent. + kLgBitMark, kLgMinGap, kLgMinMessageLength, 0, 0, // No data. + 38, true, repeat - 1, 50); +} + +/// Construct a raw 28-bit LG message code from the supplied address & command. +/// Status: STABLE / Works. +/// @param[in] address The address code. +/// @param[in] command The command code. +/// @return A raw 28-bit LG message code suitable for sendLG() etc. +/// @note Sequence of bits = address + command + checksum. +uint32_t IRsend::encodeLG(uint16_t address, uint16_t command) { + return ((address << 20) | (command << kLgAcChecksumSize) | + irutils::sumNibbles(command, 4)); +} +#endif // SEND_LG + +#if DECODE_LG +/// Decode the supplied LG message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// Typically kLgBits or kLg32Bits. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note LG protocol has a repeat code which is 4 items long. +/// Even though the protocol has 28/32 bits of data, only 24/28 bits are +/// distinct. +/// In transmission order, the 28/32 bits are constructed as follows: +/// 8/12 bits of address + 16 bits of command + 4 bits of checksum. +/// @note LG 32bit protocol appears near identical to the Samsung protocol. +/// They possibly differ on how they repeat and initial HDR mark. +/// @see https://funembedded.wordpress.com/2014/11/08/ir-remote-control-for-lg-conditioner-using-stm32f302-mcu-on-mbed-platform/ +bool IRrecv::decodeLG(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (nbits >= kLg32Bits) { + if (results->rawlen <= 2 * nbits + 2 * (kHeader + kFooter) - 1 + offset) + return false; // Can't possibly be a valid LG32 message. + } else { + if (results->rawlen <= 2 * nbits + kHeader - 1 + offset) + return false; // Can't possibly be a valid LG message. + } + // Compliance + if (strict && nbits != kLgBits && nbits != kLg32Bits) + return false; // Doesn't comply with expected LG protocol. + + // Header (Mark) + uint32_t kHdrSpace; + if (matchMark(results->rawbuf[offset], kLgHdrMark)) + kHdrSpace = kLgHdrSpace; + else if (matchMark(results->rawbuf[offset], kLg2HdrMark)) + kHdrSpace = kLg2HdrSpace; + else if (matchMark(results->rawbuf[offset], kLg32HdrMark)) + kHdrSpace = kLg32HdrSpace; + else + return false; + offset++; + + // Set up the expected data section values. + const uint16_t kBitmark = (kHdrSpace == kLg2HdrSpace) ? kLg2BitMark + : kLgBitMark; + // Header Space + Data + Footer + uint64_t data = 0; + uint16_t used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + 0, // Already matched the Header mark. + kHdrSpace, + kBitmark, kLgOneSpace, kBitmark, kLgZeroSpace, + kBitmark, kLgMinGap, true, kUseDefTol, 0, true); + if (!used) return false; + offset += used; + + // Repeat + if (nbits >= kLg32Bits) { + // If we are expecting the LG 32-bit protocol, there is always + // a repeat message. So, check for it. + uint64_t unused; + if (!matchGeneric(results->rawbuf + offset, &unused, + results->rawlen - offset, 0, // No Data bits to match. + kLg32RptHdrMark, kLgRptSpace, + kBitmark, kLgOneSpace, kBitmark, kLgZeroSpace, + kBitmark, kLgMinGap, true, kUseDefTol)) return false; + } + + // The 16 bits before the checksum. + uint16_t command = (data >> kLgAcChecksumSize); + + // Compliance + if (strict && (data & 0xF) != irutils::sumNibbles(command, 4)) + return false; // The last 4 bits sent are the expected checksum. + // Success + if (kHdrSpace == kLg2HdrSpace) // Was it an LG2 message? + results->decode_type = LG2; + else + results->decode_type = LG; + results->bits = nbits; + results->value = data; + results->command = command; + results->address = data >> 20; // The bits before the command. + return true; +} +#endif // DECODE_LG + +// LG A/C Class + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRLgAc::IRLgAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internals of the object to a known good state. +void IRLgAc::stateReset(void) { + setRaw(kLgAcOffCommand); + setModel(lg_ac_remote_model_t::GE6711AR2853M); + _light = true; + _swingv = kLgAcSwingVOff; + _swingh = false; + for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++) + _vaneswingv[i] = 0; // Reset to an unused value. + updateSwingPrev(); +} + +/// Set up hardware to be able to send a message. +void IRLgAc::begin(void) { _irsend.begin(); } + +#if SEND_LG +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRLgAc::send(const uint16_t repeat) { + if (getPower()) { + _irsend.send(_protocol, getRaw(), kLgBits, repeat); + // Some models have extra/special settings & controls + switch (getModel()) { + case lg_ac_remote_model_t::LG6711A20083V: + // Only send the swing setting if we need to. + if (_swingv != _swingv_prev) + _irsend.send(_protocol, _swingv, kLgBits, repeat); + break; + case lg_ac_remote_model_t::AKB74955603: + // Only send the swing setting if we need to. + if (_swingv != _swingv_prev) + _irsend.send(_protocol, _swingv, kLgBits, repeat); + // Any "normal" command sent will always turn the light on, thus we only + // send it when we want it off. Must be sent last! + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1513#issuecomment-877283080 + if (!_light) _irsend.send(_protocol, kLgAcLightToggle, kLgBits, repeat); + break; + case lg_ac_remote_model_t::AKB73757604: + // Check if we need to send any vane specific swingv's. + for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++) // For all vanes + if (_vaneswingv[i] != _vaneswingv_prev[i]) // Only send if we must. + _irsend.send(_protocol, calcVaneSwingV(i, _vaneswingv[i]), kLgBits, + repeat); + // and if we need to send a swingh message. + if (_swingh != _swingh_prev) + _irsend.send(_protocol, _swingh ? kLgAcSwingHAuto : kLgAcSwingHOff, + kLgBits, repeat); + break; + default: + break; + } + updateSwingPrev(); // Swing changes will have been sent, so make them prev. + } else { + // Always send the special Off command if the power is set to off. + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1008#issuecomment-570763580 + _irsend.send(_protocol, kLgAcOffCommand, kLgBits, repeat); + } +} +#endif // SEND_LG + +/// Is the current message a normal (non-special) message? +/// @return True, if it is a normal message, False, if it is special. +bool IRLgAc::_isNormal(void) const { + switch (_.raw) { + case kLgAcOffCommand: + case kLgAcLightToggle: + return false; + } + if (isSwing()) return false; + return true; +} + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRLgAc::setModel(const lg_ac_remote_model_t model) { + switch (model) { + case lg_ac_remote_model_t::AKB75215403: + case lg_ac_remote_model_t::AKB74955603: + case lg_ac_remote_model_t::AKB73757604: + _protocol = decode_type_t::LG2; + break; + case lg_ac_remote_model_t::GE6711AR2853M: + case lg_ac_remote_model_t::LG6711A20083V: + _protocol = decode_type_t::LG; + break; + default: + return; + } + _model = model; +} + +/// Get the model of the A/C. +/// @return The enum of the compatible model. +lg_ac_remote_model_t IRLgAc::getModel(void) const { return _model; } + +/// Check if the stored code must belong to a AKB74955603 model. +/// @return true, if it is AKB74955603 message. Otherwise, false. +/// @note Internal use only. +bool IRLgAc::_isAKB74955603(void) const { + return ((_.raw & kLgAcAKB74955603DetectionMask) && _isNormal()) || + (isSwingV() && !isSwingVToggle()) || isLightToggle(); +} + +/// Check if the stored code must belong to a AKB73757604 model. +/// @return true, if it is AKB73757604 message. Otherwise, false. +/// @note Internal use only. +bool IRLgAc::_isAKB73757604(void) const { return isSwingH() || isVaneSwingV(); } + +/// Check if the stored code must belong to a LG6711A20083V model. +/// @return true, if it is LG6711A20083V message. Otherwise, false. +/// @note Internal use only. +bool IRLgAc::_isLG6711A20083V(void) const { return isSwingVToggle(); } + +/// Get a copy of the internal state/code for this protocol. +/// @return The code for this protocol based on the current internal state. +uint32_t IRLgAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] protocol A valid decode protocol type to use. +void IRLgAc::setRaw(const uint32_t new_code, const decode_type_t protocol) { + _.raw = new_code; + // Set the default model for this protocol, if the protocol is supplied. + switch (protocol) { + case decode_type_t::LG: + if (isSwingVToggle()) // This model uses a swingv toggle message. + setModel(lg_ac_remote_model_t::LG6711A20083V); + else // Assume others are a different model. + setModel(lg_ac_remote_model_t::GE6711AR2853M); + break; + case decode_type_t::LG2: + setModel(lg_ac_remote_model_t::AKB75215403); + break; + default: + // Don't change anything if it isn't an expected protocol. + break; + } + // Look for model specific settings/features to improve model detection. + if (_isAKB74955603()) { + setModel(lg_ac_remote_model_t::AKB74955603); + if (isSwingV()) _swingv = new_code; + } + if (_isAKB73757604()) { + setModel(lg_ac_remote_model_t::AKB73757604); + if (isVaneSwingV()) { + // Extract just the vane nr and position part of the message. + const uint32_t vanecode = getVaneCode(_.raw); + _vaneswingv[vanecode / kLgAcVaneSwingVSize] = VANESWINGVPOS(vanecode); + } else if (isSwingH()) { + _swingh = (_.raw == kLgAcSwingHAuto); + } + } + _temp = 15; // Ensure there is a "sane" previous temp. + _temp = getTemp(); +} + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @return The calculated checksum value. +uint8_t IRLgAc::calcChecksum(const uint32_t state) { + return irutils::sumNibbles(state >> kLgAcChecksumSize, 4); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The value to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRLgAc::validChecksum(const uint32_t state) { + LGProtocol LGp; + LGp.raw = state; + return calcChecksum(state) == LGp.Sum; +} + +/// Calculate and set the checksum values for the internal state. +void IRLgAc::checksum(void) { + _.Sum = calcChecksum(_.raw); +} + +/// Change the power setting to On. +void IRLgAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRLgAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRLgAc::setPower(const bool on) { + _.Power = (on ? kLgAcPowerOn : kLgAcPowerOff); + if (on) + setTemp(_temp); // Reset the temp if we are on. + else + _setTemp(0); // Off clears the temp. +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRLgAc::getPower(void) const { + return _.Power == kLgAcPowerOn; +} + +/// Is the message a Power Off message? +/// @return true, if it is. false, if not. +bool IRLgAc::isOffCommand(void) const { return _.raw == kLgAcOffCommand; } + +/// Change the light/led/display setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRLgAc::setLight(const bool on) { _light = on; } + +/// Get the value of the current light setting. +/// @return true, the setting is on. false, the setting is off. +bool IRLgAc::getLight(void) const { return _light; } + +/// Is the message a Light Toggle message? +/// @return true, if it is. false, if not. +bool IRLgAc::isLightToggle(void) const { return _.raw == kLgAcLightToggle; } + +/// Set the temperature. +/// @param[in] value The native temperature. +/// @note Internal use only. +inline void IRLgAc::_setTemp(const uint8_t value) { _.Temp = value; } + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRLgAc::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kLgAcMinTemp, degrees); + temp = ::min(kLgAcMaxTemp, temp); + _temp = temp; + _setTemp(temp - kLgAcTempAdjust); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRLgAc::getTemp(void) const { + return _isNormal() ? _.Temp + kLgAcTempAdjust : _temp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRLgAc::setFan(const uint8_t speed) { + uint8_t _speed = speed; + // Only model AKB74955603 has these speeds, so convert if we have to. + if (getModel() != lg_ac_remote_model_t::AKB74955603) { + switch (speed) { + case kLgAcFanLowAlt: + _.Fan = kLgAcFanLow; + return; + case kLgAcFanHigh: + _.Fan = kLgAcFanMax; + return; + } + } + switch (speed) { + case kLgAcFanLow: + case kLgAcFanLowAlt: + _speed = (getModel() != lg_ac_remote_model_t::AKB74955603) + ? kLgAcFanLow : kLgAcFanLowAlt; + break; + case kLgAcFanHigh: + _speed = (getModel() != lg_ac_remote_model_t::AKB74955603) + ? kLgAcFanMax : speed; + break; + case kLgAcFanAuto: + case kLgAcFanLowest: + case kLgAcFanMedium: + case kLgAcFanMax: + _speed = speed; + break; + default: + _speed = kLgAcFanAuto; + } + _.Fan = _speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRLgAc::getFan(void) const { return _.Fan; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRLgAc::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRLgAc::setMode(const uint8_t mode) { + switch (mode) { + case kLgAcAuto: + case kLgAcDry: + case kLgAcHeat: + case kLgAcCool: + case kLgAcFan: + _.Mode = mode; + break; + default: + _.Mode = kLgAcAuto; + } +} + +/// Check if the stored code is a SwingV Toggle message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isSwingVToggle(void) const { return _.raw == kLgAcSwingVToggle; } + +/// Check if the stored code is a Swing message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isSwing(void) const { + return ((_.raw >> 12) == kLgAcSwingSignature) || isSwingVToggle(); +} + +/// Check if the stored code is a non-vane SwingV message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isSwingV(void) const { + const uint32_t code = _.raw >> kLgAcChecksumSize; + return (code >= (kLgAcSwingVLowest >> kLgAcChecksumSize) && + code < (kLgAcSwingHAuto >> kLgAcChecksumSize)) || isSwingVToggle(); +} + +/// Check if the stored code is a SwingH message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isSwingH(void) const { + return (_.raw >> kLgAcSwingHOffsetSize) == kLgAcSwingHSignature; +} + +/// Get the Horizontal Swing position setting of the A/C. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::getSwingH(void) const { return _swingh; } + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRLgAc::setSwingH(const bool on) { _swingh = on; } + +/// Check if the stored code is a vane specific SwingV message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isVaneSwingV(void) const { + return _.raw > kLgAcVaneSwingVBase && + _.raw < (kLgAcVaneSwingVBase + + ((kLgAcSwingVMaxVanes * + kLgAcVaneSwingVSize) << kLgAcChecksumSize)); +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] position The position/mode to set the vanes to. +void IRLgAc::setSwingV(const uint32_t position) { + // Is it a valid position code? + if (position == kLgAcSwingVOff || position == kLgAcSwingVToggle || + toCommonSwingV(position) != stdAc::swingv_t::kOff) { + if (position <= 0xFF) { // It's a short code, convert it. + _swingv = (kLgAcSwingSignature << 8 | position) << kLgAcChecksumSize; + _swingv |= calcChecksum(_swingv); + } else { + _swingv = position; + } + } +} + +// Copy the previous swing settings from the current ones. +void IRLgAc::updateSwingPrev(void) { + _swingv_prev = _swingv; + for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++) + _vaneswingv_prev[i] = _vaneswingv[i]; +} + +/// Get the Vertical Swing position setting of the A/C. +/// @return The native position/mode. +uint32_t IRLgAc::getSwingV(void) const { return _swingv; } + +/// Set the per Vane Vertical Swing mode of the A/C. +/// @param[in] vane The nr. of the vane to control. +/// @param[in] position The position/mode to set the vanes to. +void IRLgAc::setVaneSwingV(const uint8_t vane, const uint8_t position) { + if (vane < kLgAcSwingVMaxVanes) // It's a valid vane nr. + if (position && position <= kLgAcVaneSwingVLowest) // Valid position + _vaneswingv[vane] = position; +} + +/// Get the Vertical Swing position for the given vane of the A/C. +/// @return The native position/mode. +uint8_t IRLgAc::getVaneSwingV(const uint8_t vane) const { + return (vane < kLgAcSwingVMaxVanes) ? _vaneswingv[vane] : 0; +} + +/// Get the vane code of a Vane Vertical Swing message. +/// @param[in] raw A raw number representing a native LG message. +/// @return A number containing just the vane nr, and the position. +uint8_t IRLgAc::getVaneCode(const uint32_t raw) { + return (raw - kLgAcVaneSwingVBase) >> kLgAcChecksumSize; +} + +/// Calculate the Vane specific Vertical Swing code for the A/C. +/// @return The native raw code. +uint32_t IRLgAc::calcVaneSwingV(const uint8_t vane, const uint8_t position) { + uint32_t result = kLgAcVaneSwingVBase; + if (vane < kLgAcSwingVMaxVanes) // It's a valid vane nr. + if (position && position <= kLgAcVaneSwingVLowest) // Valid position + result += ((vane * kLgAcVaneSwingVSize + position) << kLgAcChecksumSize); + return result | calcChecksum(result); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRLgAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kLgAcCool; + case stdAc::opmode_t::kHeat: return kLgAcHeat; + case stdAc::opmode_t::kFan: return kLgAcFan; + case stdAc::opmode_t::kDry: return kLgAcDry; + default: return kLgAcAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRLgAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kLgAcCool: return stdAc::opmode_t::kCool; + case kLgAcHeat: return stdAc::opmode_t::kHeat; + case kLgAcDry: return stdAc::opmode_t::kDry; + case kLgAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRLgAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kLgAcFanLowest; + case stdAc::fanspeed_t::kLow: return kLgAcFanLow; + case stdAc::fanspeed_t::kMedium: return kLgAcFanMedium; + case stdAc::fanspeed_t::kHigh: return kLgAcFanHigh; + case stdAc::fanspeed_t::kMax: return kLgAcFanMax; + default: return kLgAcFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRLgAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kLgAcFanMax: return stdAc::fanspeed_t::kMax; + case kLgAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kLgAcFanMedium: return stdAc::fanspeed_t::kMedium; + case kLgAcFanLow: + case kLgAcFanLowAlt: return stdAc::fanspeed_t::kLow; + case kLgAcFanLowest: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] swingv The enum to be converted. +/// @return The native equivalent of the enum. +uint32_t IRLgAc::convertSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHighest: return kLgAcSwingVHighest; + case stdAc::swingv_t::kHigh: return kLgAcSwingVHigh; + case stdAc::swingv_t::kMiddle: return kLgAcSwingVMiddle; + case stdAc::swingv_t::kLow: return kLgAcSwingVLow; + case stdAc::swingv_t::kLowest: return kLgAcSwingVLowest; + case stdAc::swingv_t::kAuto: return kLgAcSwingVSwing; + default: return kLgAcSwingVOff; + } +} + +/// Convert a native Vertical Swing into its stdAc equivalent. +/// @param[in] code The native code to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::swingv_t IRLgAc::toCommonSwingV(const uint32_t code) { + switch (code) { + case kLgAcSwingVHighest_Short: + case kLgAcSwingVHighest: return stdAc::swingv_t::kHighest; + case kLgAcSwingVHigh_Short: + case kLgAcSwingVHigh: return stdAc::swingv_t::kHigh; + case kLgAcSwingVUpperMiddle_Short: + case kLgAcSwingVUpperMiddle: + case kLgAcSwingVMiddle_Short: + case kLgAcSwingVMiddle: return stdAc::swingv_t::kMiddle; + case kLgAcSwingVLow_Short: + case kLgAcSwingVLow: return stdAc::swingv_t::kLow; + case kLgAcSwingVLowest_Short: + case kLgAcSwingVLowest: return stdAc::swingv_t::kLowest; + case kLgAcSwingVToggle: + case kLgAcSwingVSwing_Short: + case kLgAcSwingVSwing: return stdAc::swingv_t::kAuto; + default: return stdAc::swingv_t::kOff; + } +} + +/// Convert a native Vane specific Vertical Swing into its stdAc equivalent. +/// @param[in] pos The native position to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::swingv_t IRLgAc::toCommonVaneSwingV(const uint8_t pos) { + switch (pos) { + case kLgAcVaneSwingVHigh: return stdAc::swingv_t::kHigh; + case kLgAcVaneSwingVUpperMiddle: + case kLgAcVaneSwingVMiddle: return stdAc::swingv_t::kMiddle; + case kLgAcVaneSwingVLow: return stdAc::swingv_t::kLow; + case kLgAcVaneSwingVLowest: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kHighest; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] swingv The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRLgAc::convertVaneSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHigh: return kLgAcVaneSwingVHigh; + case stdAc::swingv_t::kMiddle: return kLgAcVaneSwingVMiddle; + case stdAc::swingv_t::kLow: return kLgAcVaneSwingVLow; + case stdAc::swingv_t::kLowest: return kLgAcVaneSwingVLowest; + default: return kLgAcVaneSwingVHighest; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRLgAc::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.light = true; + result.swingv = toCommonSwingV(getSwingV()); + } + result.protocol = _protocol; + if (isLightToggle()) { + result.light = !result.light; + return result; + } else { + result.light = _light; + } + result.model = getModel(); + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + if (isSwingV()) result.swingv = toCommonSwingV(getSwingV()); + if (isVaneSwingV()) + result.swingv = toCommonVaneSwingV(VANESWINGVPOS(getVaneCode(_.raw))); + result.swingh = isSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + // Not supported. + result.quiet = false; + result.turbo = false; + result.filter = false; + result.clean = false; + result.econo = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRLgAc::toString(void) const { + String result = ""; + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addModelToString(_protocol, getModel(), false); + if (_isNormal()) { // A "Normal" generic settings message. + result += addBoolToString(getPower(), kPowerStr); + if (getPower()) { // Only display the rest if is in power on state. + result += addModeToString(_.Mode, kLgAcAuto, kLgAcCool, + kLgAcHeat, kLgAcDry, kLgAcFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kLgAcFanHigh, + _isAKB74955603() ? kLgAcFanLowAlt : kLgAcFanLow, + kLgAcFanAuto, kLgAcFanLowest, kLgAcFanMedium, + kLgAcFanMax); + } + } else { // It must be a special single purpose code. + if (isOffCommand()) { + result += addBoolToString(false, kPowerStr); + } else if (isLightToggle()) { + result += addBoolToString(true, kLightToggleStr); + } else if (isSwingH()) { + result += addBoolToString(_swingh, kSwingHStr); + } else if (isSwingV()) { + if (isSwingVToggle()) + result += addToggleToString(isSwingVToggle(), kSwingVStr); + else + result += addSwingVToString((uint8_t)(_swingv >> kLgAcChecksumSize), + 0, // No Auto, See "swing". Unused + kLgAcSwingVHighest_Short, + kLgAcSwingVHigh_Short, + kLgAcSwingVUpperMiddle_Short, + kLgAcSwingVMiddle_Short, + 0, // Unused + kLgAcSwingVLow_Short, + kLgAcSwingVLowest_Short, + kLgAcSwingVOff_Short, + kLgAcSwingVSwing_Short, + 0, 0); + } else if (isVaneSwingV()) { + const uint8_t vane = getVaneCode(_.raw) / kLgAcVaneSwingVSize; + result += addIntToString(vane, kVaneStr); + result += addSwingVToString(_vaneswingv[vane], + 0, // No Auto, See "swing". Unused + kLgAcVaneSwingVHighest, + kLgAcVaneSwingVHigh, + kLgAcVaneSwingVUpperMiddle, + kLgAcVaneSwingVMiddle, + 0, // Unused + kLgAcVaneSwingVLow, + kLgAcVaneSwingVLowest, + // Rest unused + 0, 0, 0, 0); + } + } + return result; +} + +/// Check if the internal state looks like a valid LG A/C message. +/// @return true, the internal state is a valid LG A/C mesg. Otherwise, false. +bool IRLgAc::isValidLgAc(void) const { + return validChecksum(_.raw) && (_.Sign == kLgAcSignature); +} diff --git a/src/libraries/IRremoteESP8266/src/ir_LG.h b/src/libraries/IRremoteESP8266/src/ir_LG.h new file mode 100644 index 000000000..19cfc91e2 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_LG.h @@ -0,0 +1,202 @@ +// Copyright 2017-2021 David Conran + +/// @file +/// @brief Support for LG protocols. +/// @see https://github.com/arendst/Tasmota/blob/54c2eb283a02e4287640a4595e506bc6eadbd7f2/sonoff/xdrv_05_irremote.ino#L327-438 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1513 + +// Supports: +// Brand: LG, Model: 6711A20083V remote (LG - LG6711A20083V) +// Brand: LG, Model: TS-H122ERM1 remote (LG - LG6711A20083V) +// Brand: LG, Model: AKB74395308 remote (LG2) +// Brand: LG, Model: S4-W12JA3AA A/C (LG2) +// Brand: LG, Model: AKB75215403 remote (LG2) +// Brand: LG, Model: AKB74955603 remote (LG2 - AKB74955603) +// Brand: LG, Model: A4UW30GFA2 A/C (LG2 - AKB74955603 & AKB73757604) +// Brand: LG, Model: AMNW09GSJA0 A/C (LG2 - AKB74955603) +// Brand: LG, Model: AMNW24GTPA1 A/C (LG2 - AKB73757604) +// Brand: LG, Model: AKB73757604 remote (LG2 - AKB73757604) +// Brand: LG, Model: AKB73315611 remote (LG2 - AKB74955603) +// Brand: LG, Model: MS05SQ NW0 A/C (LG2 - AKB74955603) +// Brand: General Electric, Model: AG1BH09AW101 A/C (LG - GE6711AR2853M) +// Brand: General Electric, Model: 6711AR2853M Remote (LG - GE6711AR2853M) + +#ifndef IR_LG_H_ +#define IR_LG_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRutils.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a LG A/C message. +union LGProtocol{ + uint32_t raw; ///< The state of the IR remote in IR code form. + struct { + uint32_t Sum :4; + uint32_t Fan :4; + uint32_t Temp :4; + uint32_t Mode :3; + uint32_t :3; + uint32_t Power:2; + uint32_t Sign :8; + }; +}; + +const uint8_t kLgAcFanLowest = 0; // 0b0000 +const uint8_t kLgAcFanLow = 1; // 0b0001 +const uint8_t kLgAcFanMedium = 2; // 0b0010 +const uint8_t kLgAcFanMax = 4; // 0b0100 +const uint8_t kLgAcFanAuto = 5; // 0b0101 +const uint8_t kLgAcFanLowAlt = 9; // 0b1001 +const uint8_t kLgAcFanHigh = 10; // 0b1010 +// Nr. of slots in the look-up table +const uint8_t kLgAcFanEntries = kLgAcFanHigh + 1; +const uint8_t kLgAcTempAdjust = 15; +const uint8_t kLgAcMinTemp = 16; // Celsius +const uint8_t kLgAcMaxTemp = 30; // Celsius +const uint8_t kLgAcCool = 0; // 0b000 +const uint8_t kLgAcDry = 1; // 0b001 +const uint8_t kLgAcFan = 2; // 0b010 +const uint8_t kLgAcAuto = 3; // 0b011 +const uint8_t kLgAcHeat = 4; // 0b100 +const uint8_t kLgAcPowerOff = 3; // 0b11 +const uint8_t kLgAcPowerOn = 0; // 0b00 +const uint8_t kLgAcSignature = 0x88; + +const uint32_t kLgAcOffCommand = 0x88C0051; +const uint32_t kLgAcLightToggle = 0x88C00A6; + +const uint32_t kLgAcSwingVToggle = 0x8810001; +const uint32_t kLgAcSwingSignature = 0x8813; +const uint32_t kLgAcSwingVLowest = 0x8813048; +const uint32_t kLgAcSwingVLow = 0x8813059; +const uint32_t kLgAcSwingVMiddle = 0x881306A; +const uint32_t kLgAcSwingVUpperMiddle = 0x881307B; +const uint32_t kLgAcSwingVHigh = 0x881308C; +const uint32_t kLgAcSwingVHighest = 0x881309D; +const uint32_t kLgAcSwingVSwing = 0x8813149; +const uint32_t kLgAcSwingVAuto = kLgAcSwingVSwing; +const uint32_t kLgAcSwingVOff = 0x881315A; +const uint8_t kLgAcSwingVLowest_Short = 0x04; +const uint8_t kLgAcSwingVLow_Short = 0x05; +const uint8_t kLgAcSwingVMiddle_Short = 0x06; +const uint8_t kLgAcSwingVUpperMiddle_Short = 0x07; +const uint8_t kLgAcSwingVHigh_Short = 0x08; +const uint8_t kLgAcSwingVHighest_Short = 0x09; +const uint8_t kLgAcSwingVSwing_Short = 0x14; +const uint8_t kLgAcSwingVAuto_Short = kLgAcSwingVSwing_Short; +const uint8_t kLgAcSwingVOff_Short = 0x15; + +// AKB73757604 Constants +// SwingH +const uint32_t kLgAcSwingHAuto = 0x881316B; +const uint32_t kLgAcSwingHOff = 0x881317C; +// SwingV +const uint8_t kLgAcVaneSwingVHighest = 1; ///< 0b001 +const uint8_t kLgAcVaneSwingVHigh = 2; ///< 0b010 +const uint8_t kLgAcVaneSwingVUpperMiddle = 3; ///< 0b011 +const uint8_t kLgAcVaneSwingVMiddle = 4; ///< 0b100 +const uint8_t kLgAcVaneSwingVLow = 5; ///< 0b101 +const uint8_t kLgAcVaneSwingVLowest = 6; ///< 0b110 +const uint8_t kLgAcVaneSwingVSize = 8; +const uint8_t kLgAcSwingVMaxVanes = 4; ///< Max Nr. of Vanes + +// Classes +/// Class for handling detailed LG A/C messages. +class IRLgAc { + public: + explicit IRLgAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); + static uint8_t calcChecksum(const uint32_t state); + static bool validChecksum(const uint32_t state); + bool isValidLgAc(void) const; +#if SEND_LG + void send(const uint16_t repeat = kLgDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_LG + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + bool isOffCommand(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setLight(const bool on); + bool getLight(void) const; + bool isLightToggle(void) const; + bool isSwing(void) const; + void setSwingH(const bool on); + bool getSwingH(void) const; + bool isSwingV(void) const; + bool isSwingVToggle(void) const; + bool isVaneSwingV(void) const; + void setSwingV(const uint32_t position); + uint32_t getSwingV(void) const; + void setVaneSwingV(const uint8_t vane, const uint8_t position); + uint8_t getVaneSwingV(const uint8_t vane) const; + static uint32_t calcVaneSwingV(const uint8_t vane, const uint8_t position); + static uint8_t getVaneCode(const uint32_t raw); + bool isSwingH(void) const; + void updateSwingPrev(void); + uint32_t getRaw(void); + void setRaw(const uint32_t new_code, + const decode_type_t protocol = decode_type_t::UNKNOWN); + static uint8_t convertMode(const stdAc::opmode_t mode); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint32_t code); + static stdAc::swingv_t toCommonVaneSwingV(const uint8_t pos); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint32_t convertSwingV(const stdAc::swingv_t swingv); + static uint8_t convertVaneSwingV(const stdAc::swingv_t swingv); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; + void setModel(const lg_ac_remote_model_t model); + lg_ac_remote_model_t getModel(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + LGProtocol _; + uint8_t _temp; + bool _light; + uint32_t _swingv; + uint32_t _swingv_prev; + uint8_t _vaneswingv[kLgAcSwingVMaxVanes]; + uint8_t _vaneswingv_prev[kLgAcSwingVMaxVanes]; + bool _swingh; + bool _swingh_prev; + decode_type_t _protocol; ///< Protocol version + lg_ac_remote_model_t _model; ///< Model type + void checksum(void); + void _setTemp(const uint8_t value); + bool _isAKB74955603(void) const; + bool _isAKB73757604(void) const; + bool _isLG6711A20083V(void) const; + bool _isNormal(void) const; +}; + +#endif // IR_LG_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Lasertag.cpp b/src/libraries/IRremoteESP8266/src/ir_Lasertag.cpp new file mode 100644 index 000000000..b5c01448d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Lasertag.cpp @@ -0,0 +1,116 @@ +// Copyright 2017 David Conran + +/// @file +/// @brief Support for Lasertag protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/366 + +// Supports: +// Brand: Lasertag, Model: Phaser emitters + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kLasertagMinSamples = 13; +const uint16_t kLasertagTick = 333; +const uint32_t kLasertagMinGap = kDefaultMessageGap; // Just a guess. +const uint8_t kLasertagTolerance = 0; // Percentage error margin. +const uint16_t kLasertagExcess = 0; // See kMarkExcess. +const uint16_t kLasertagDelta = 165; // Use instead of Excess and Tolerance. +const int16_t kSpace = 1; +const int16_t kMark = 0; + +#if SEND_LASERTAG +/// Send a Lasertag packet/message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol is pretty much just raw Manchester encoding. +/// @todo Convert this to use `sendManchester()` if we can.` +void IRsend::sendLasertag(uint64_t data, uint16_t nbits, uint16_t repeat) { + if (nbits > sizeof(data) * 8) return; // We can't send something that big. + + // Set 36kHz IR carrier frequency & a 1/4 (25%) duty cycle. + // NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols. + enableIROut(36, 25); + + for (uint16_t i = 0; i <= repeat; i++) { + // Data + for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1) + if (data & mask) { // 1 + space(kLasertagTick); // 1 is space, then mark. + mark(kLasertagTick); + } else { // 0 + mark(kLasertagTick); // 0 is mark, then space. + space(kLasertagTick); + } + // Footer + space(kLasertagMinGap); + } +} +#endif // SEND_LASERTAG + +#if DECODE_LASERTAG +/// Decode the supplied Lasertag message. +/// Status: BETA / Appears to be working 90% of the time. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note This protocol is pretty much just raw Manchester encoding. +/// @see http://www.sbprojects.net/knowledge/ir/rc5.php +/// @see https://en.wikipedia.org/wiki/RC-5 +/// @see https://en.wikipedia.org/wiki/Manchester_code +/// @todo Convert to using `matchManchester()` if we can. +bool IRrecv::decodeLasertag(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= kLasertagMinSamples + offset) return false; + + // Compliance + if (strict && nbits != kLasertagBits) return false; + + uint16_t used = 0; + uint64_t data = 0; + uint16_t actual_bits = 0; + + // No Header + + // Data + for (; offset <= results->rawlen; actual_bits++) { + int16_t levelA = + getRClevel(results, &offset, &used, kLasertagTick, kLasertagTolerance, + kLasertagExcess, kLasertagDelta); + int16_t levelB = + getRClevel(results, &offset, &used, kLasertagTick, kLasertagTolerance, + kLasertagExcess, kLasertagDelta); + if (levelA == kSpace && levelB == kMark) { + data = (data << 1) | 1; // 1 + } else { + if (levelA == kMark && levelB == kSpace) { + data <<= 1; // 0 + } else { + break; + } + } + } + // Footer (None) + + // Compliance + if (actual_bits < nbits) return false; // Less data than we expected. + if (strict && actual_bits != kLasertagBits) return false; + + // Success + results->decode_type = LASERTAG; + results->value = data; + results->address = data & 0xF; // Unit + results->command = data >> 4; // Team + results->repeat = false; + results->bits = actual_bits; + return true; +} +#endif // DECODE_LASERTAG diff --git a/src/libraries/IRremoteESP8266/src/ir_Lego.cpp b/src/libraries/IRremoteESP8266/src/ir_Lego.cpp new file mode 100644 index 000000000..3ed4c174d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Lego.cpp @@ -0,0 +1,106 @@ +// Copyright 2019 David Conran + +/// @file +/// @brief Support for LEGO protocols. +/// @note LEGO is a Registrated Trademark of the Lego Group. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/641 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/files/2974525/LEGO_Power_Functions_RC_v120.pdf + +// Supports: +// Brand: LEGO Power Functions, Model: IR Receiver + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kLegoPfBitMark = 158; +const uint16_t kLegoPfHdrSpace = 1026; +const uint16_t kLegoPfZeroSpace = 263; +const uint16_t kLegoPfOneSpace = 553; +const uint32_t kLegoPfMinCommandLength = 16000; // 16ms + + +#if SEND_LEGOPF +/// Send a LEGO Power Functions message. +/// Status: Beta / Should work. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Non-zero repeats results in at least 5 messages per spec. +void IRsend::sendLegoPf(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + uint8_t channelid = ((data >> (nbits - 4)) & 0b11) + 1; + if (repeat) { + // We are in repeat mode. + // Spec says a pause before transmittion. + if (channelid < 4) space((4 - channelid) * kLegoPfMinCommandLength); + // Spec says there are a minimum of 5 message repeats. + for (uint16_t r = 0; r < ::max(repeat, (uint16_t)5); r++) { + // Lego has a special repeat mode which repeats a message with varying + // start to start times. + sendGeneric(kLegoPfBitMark, kLegoPfHdrSpace, + kLegoPfBitMark, kLegoPfOneSpace, + kLegoPfBitMark, kLegoPfZeroSpace, + kLegoPfBitMark, kLegoPfHdrSpace, + ((r < 2) ? 5 : (6 + 2 * channelid)) * kLegoPfMinCommandLength, + data, nbits, 38000, true, 0, kDutyDefault); + } + } else { // No repeat, just a simple message. + sendGeneric(kLegoPfBitMark, kLegoPfHdrSpace, + kLegoPfBitMark, kLegoPfOneSpace, + kLegoPfBitMark, kLegoPfZeroSpace, + kLegoPfBitMark, kLegoPfHdrSpace, + kLegoPfMinCommandLength * 5, + data, nbits, 38000, true, 0, kDutyDefault); + } +} +#endif // SEND_LEGO + +#if DECODE_LEGOPF +/// Decode the supplied LEGO Power Functions message. +/// Status: STABLE / Appears to work. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeLegoPf(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Check if can possibly be a valid LEGO message. + if (strict && nbits != kLegoPfBits) return false; // Not what is expected + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kLegoPfBitMark, kLegoPfHdrSpace, + kLegoPfBitMark, kLegoPfOneSpace, + kLegoPfBitMark, kLegoPfZeroSpace, + kLegoPfBitMark, kLegoPfMinCommandLength, + true)) return false; + // Compliance + if (strict) { + // Verify the Longitudinal Redundancy Check (LRC) + uint16_t lrc_data = data; + uint8_t lrc = 0xF; + for (uint8_t i = 0; i < 4; i++) { + lrc ^= (lrc_data & 0xF); + lrc_data >>= 4; + } + if (lrc) return false; + } + + // Success + results->decode_type = LEGOPF; + results->bits = nbits; + results->value = data; + results->address = ((data >> (nbits - 4)) & 0b11) + 1; // Channel Id + results->command = (data >> 4) & 0xFF; // Stuff between Channel Id and LRC. + return true; +} +#endif // DECODE_LEGOPF diff --git a/src/libraries/IRremoteESP8266/src/ir_Lutron.cpp b/src/libraries/IRremoteESP8266/src/ir_Lutron.cpp new file mode 100644 index 000000000..ecbf0151f --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Lutron.cpp @@ -0,0 +1,143 @@ +// Copyright 2018 David Conran + +/// @file +/// @brief Support for Lutron protocols. +/// @note The Lutron protocol uses a sort of Run Length encoding to encode +/// its data. There is no header or footer per-se. +/// As a mark is the first data we will notice, we always assume the First +/// bit of the technically 36-bit protocol is '1'. So it is assumed, and thus +/// we only care about the 35 bits of data. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/515 +/// @see http://www.lutron.com/TechnicalDocumentLibrary/048158.doc + +// Supports: +// Brand: Lutron, Model: SP-HT remote +// Brand: Lutron, Model: MIR-ITFS remote +// Brand: Lutron, Model: MIR-ITFS-LF remote +// Brand: Lutron, Model: MIR-ITFS-F remote + +#define __STDC_LIMIT_MACROS +#include +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kLutronTick = 2288; +const uint32_t kLutronGap = 150000; // Completely made up value. +const uint16_t kLutronDelta = 400; // +/- 300 usecs. + +#if SEND_LUTRON +/// Send a Lutron formatted message. +/// Status: Stable / Appears to be working for real devices. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note The protocol is really 36 bits long, but the first bit is always a 1. +/// So, assume the 1 and only have a normal payload of 35 bits. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/515 +void IRsend::sendLutron(uint64_t data, uint16_t nbits, uint16_t repeat) { + enableIROut(40000, 40); // 40Khz & 40% dutycycle. + for (uint16_t r = 0; r <= repeat; r++) { + mark(kLutronTick); // 1st bit is always '1'. + // Send the supplied data in MSB First order. + for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1) + if (data & mask) + mark(kLutronTick); // Send a 1 + else + space(kLutronTick); // Send a 0 + space(kLutronGap); // Inter-message gap. + } +} +#endif // SEND_LUTRON + +#if DECODE_LUTRON +/// Decode the supplied Lutron message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeLutron(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Technically the smallest number of entries for the smallest message is '1'. + // i.e. All the bits set to 1, would produce a single huge mark signal. + // So no minimum length check is required. + if (strict && nbits != kLutronBits) + return false; // Not strictly an Lutron message. + + uint64_t data = 0; + int16_t bitsSoFar = -1; + + if (nbits > sizeof(data) * 8) return false; // To large to store the data. + for (; bitsSoFar < nbits && offset < results->rawlen; offset++) { + uint16_t entry = results->rawbuf[offset]; + // It has to be large enough to qualify as a bit. + if (!matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) { + DPRINTLN("Entry too small. Aborting."); + return false; + } + // Keep reading bits of the same value until we run out. + while (entry != 0 && matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) { + bitsSoFar++; + DPRINT("Bit: "); + DPRINT(bitsSoFar); + if (offset % 2) { // Is Odd? + data = (data << 1) + 1; // Append a '1'. + DPRINTLN(" is a 1."); + } else { // Is it Even? + data <<= 1; // Append a '0'. + DPRINTLN(" is a 0."); + if (bitsSoFar == nbits && matchAtLeast(entry, kLutronGap)) + break; // We've likely reached the end of a message. + } + // Remove a bit length from the current entry. + entry = ::max(entry, (uint16_t)(kLutronTick / kRawTick)) - + kLutronTick / kRawTick; + } + if (offset % 2 && !match(entry, kLutronDelta, 0, kLutronDelta)) { + DPRINT("offset = "); + DPRINTLN(offset); + DPRINT("rawlen = "); + DPRINTLN(results->rawlen); + DPRINT("entry = "); + DPRINTLN(entry); + DPRINTLN("Odd Entry has too much left over. Aborting."); + return false; // Too much left over to be a good value. Reject it. + } + if (offset % 2 == 0 && offset <= results->rawlen - 1 && + !matchAtLeast(entry, kLutronDelta, 0, kLutronDelta)) { + DPRINT("offset = "); + DPRINTLN(offset); + DPRINT("rawlen = "); + DPRINTLN(results->rawlen); + DPRINT("entry = "); + DPRINTLN(entry); + DPRINTLN("Entry has too much left over. Aborting."); + return false; // Too much left over to be a good value. Reject it. + } + } + + // We got too many bits. + if (bitsSoFar > nbits || bitsSoFar < 0) { + DPRINTLN("Wrong number of bits found. Aborting."); + return false; + } + // If we got less bits than we were expecting, we need to pad with zeros + // until we get the correct number of bits. + if (bitsSoFar < nbits) data <<= (nbits - bitsSoFar); + + // Success + DPRINTLN("Lutron Success!"); + results->decode_type = LUTRON; + results->bits = bitsSoFar; + results->value = data ^ (1ULL << nbits); // Mask off the initial '1'. + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_LUTRON diff --git a/src/libraries/IRremoteESP8266/src/ir_MWM.cpp b/src/libraries/IRremoteESP8266/src/ir_MWM.cpp new file mode 100644 index 000000000..757696db6 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_MWM.cpp @@ -0,0 +1,197 @@ +// Copyright 2018 Brett T. Warden + +/// @file +/// @brief Disney Made With Magic (MWM) Support +/// derived from ir_Lasertag.cpp +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/557 + +// Supports: +// Brand: Disney, Model: Made With Magic (Glow With The Show) wand + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kMWMMinSamples = 6; // Msgs are >=3 bytes, bytes have >=2 + // samples +const uint16_t kMWMTick = 417; +const uint32_t kMWMMinGap = 30000; // Typical observed delay b/w commands +const uint8_t kMWMTolerance = 0; // Percentage error margin. +const uint16_t kMWMExcess = 0; // See kMarkExcess. +const uint16_t kMWMDelta = 150; // Use instead of Excess and Tolerance. +const uint8_t kMWMMaxWidth = 9; // Maximum number of successive bits at a + // single level - worst case +const int16_t kSpace = 1; +const int16_t kMark = 0; + +#if SEND_MWM +/// Send a MWM packet/message. +/// Status: Implemented. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol is 2400 bps serial, 1 start bit (mark), +/// 1 stop bit (space), no parity +void IRsend::sendMWM(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < 3) return; // Shortest possible message is 3 bytes + + // Set 38kHz IR carrier frequency & a 1/4 (25%) duty cycle. + // NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols. + enableIROut(38, 25); + + for (uint16_t r = 0; r <= repeat; r++) { + // Data + for (uint16_t i = 0; i < nbytes; i++) { + uint8_t byte = data[i]; + + // Start bit + mark(kMWMTick); + + // LSB first, space=1 + for (uint8_t mask = 0x1; mask; mask <<= 1) { + if (byte & mask) { // 1 + space(kMWMTick); + } else { // 0 + mark(kMWMTick); + } + } + // Stop bit + space(kMWMTick); + } + // Footer + space(kMWMMinGap); + } +} +#endif // SEND_MWM + +#if DECODE_MWM +/// Decode the supplied MWM message. +/// Status: Implemented. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note This protocol is 2400 bps serial, 1 start bit (mark), +/// 1 stop bit (space), no parity +bool IRrecv::decodeMWM(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + DPRINTLN("DEBUG: decodeMWM"); + + // Compliance + if (results->rawlen <= kMWMMinSamples + offset) { + DPRINTLN("DEBUG: decodeMWM: too few samples"); + return false; + } + + uint16_t used = 0; + uint64_t data = 0; + uint16_t frame_bits = 0; + uint16_t data_bits = 0; + + // No Header + + // Data + uint8_t bits_per_frame = 10; + for (; offset < results->rawlen && results->bits < 8 * kStateSizeMax; + frame_bits++) { + DPRINT("DEBUG: decodeMWM: offset = "); + DPRINTLN(offset); + int16_t level = getRClevel(results, &offset, &used, kMWMTick, kMWMTolerance, + kMWMExcess, kMWMDelta, kMWMMaxWidth); + if (level < 0) { + DPRINTLN("DEBUG: decodeMWM: getRClevel returned error"); + break; + } + switch (frame_bits % bits_per_frame) { + case 0: + // Start bit + if (level != kMark) { + DPRINTLN("DEBUG: decodeMWM: framing error - invalid start bit"); + goto done; + } + break; + case 9: + // Stop bit + if (level != kSpace) { + DPRINTLN("DEBUG: decodeMWM: framing error - invalid stop bit"); + return false; + } else { + DPRINT("DEBUG: decodeMWM: data_bits = "); + DPRINTLN(data_bits); + DPRINT("DEBUG: decodeMWM: Finished byte: "); + DPRINTLN(uint64ToString(data)); + results->state[data_bits / 8 - 1] = data & 0xFF; + results->bits = data_bits; + data = 0; + } + break; + default: + // Data bits + DPRINT("DEBUG: decodeMWM: Storing bit: "); + DPRINTLN((level == kSpace)); + // Transmission is LSB-first, space=1 + data |= ((level == kSpace)) << 8; + data >>= 1; + data_bits++; + break; + } + } + +done: + // Footer (None) + + // Compliance + DPRINT("DEBUG: decodeMWM: frame_bits = "); + DPRINTLN(frame_bits); + DPRINT("DEBUG: decodeMWM: data_bits = "); + DPRINTLN(data_bits); + if (data_bits < nbits) { + DPRINT("DEBUG: decodeMWM: too few bits; expected "); + DPRINTLN(nbits); + return false; // Less data than we expected. + } + + uint16_t payload_length = 0; + switch (results->state[0] & 0xf0) { + case 0x90: + case 0xf0: + // Normal commands + payload_length = results->state[0] & 0x0f; + DPRINT("DEBUG: decodeMWM: payload_length = "); + DPRINTLN(payload_length); + break; + default: + if (strict) { + // Show commands + if (results->state[0] != 0x55 && results->state[1] != 0xAA) { + return false; + } + } + break; + } + if (data_bits < (payload_length + 3) * 8) { + DPRINT("DEBUG: decodeMWM: too few bytes; expected "); + DPRINTLN((payload_length + 3)); + return false; + } + if (strict) { + if (payload_length && (data_bits > (payload_length + 3) * 8)) { + DPRINT("DEBUG: decodeMWM: too many bytes; expected "); + DPRINTLN((payload_length + 3)); + return false; + } + } + + // Success + results->decode_type = MWM; + results->repeat = false; + return true; +} +#endif // DECODE_MWM + +// vim: et:ts=2:sw=2 diff --git a/src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp b/src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp new file mode 100644 index 000000000..b459949cd --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp @@ -0,0 +1,154 @@ +// Copyright 2013 mpflaga +// Copyright 2015 kitlaan +// Copyright 2017 Jason kendall, David Conran + +/// @file +/// @brief Support for MagiQuest protocols. +/// @see https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp +/// @see https://github.com/mpflaga/Arduino-IRremote + +#include "ir_Magiquest.h" +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +#define IS_ZERO(m, s) (((m)*100 / ((m) + (s))) <= kMagiQuestZeroRatio) +#define IS_ONE(m, s) (((m)*100 / ((m) + (s))) >= kMagiQuestOneRatio) + +#if SEND_MAGIQUEST +/// Send a MagiQuest formatted message. +/// Status: Beta / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendMagiQuest(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(0, 0, // No Headers - Technically it's included in the data. + // i.e. 8 zeros. + kMagiQuestMarkOne, kMagiQuestSpaceOne, kMagiQuestMarkZero, + kMagiQuestSpaceZero, + 0, // No footer mark. + kMagiQuestGap, data, nbits, 36, true, repeat, 50); +} + +/// Encode a MagiQuest wand_id, and a magnitude into a single 64bit value. +/// (Only 48 bits of real data + 8 leading zero bits) +/// This is suitable for calling sendMagiQuest() with. +/// e.g. sendMagiQuest(encodeMagiQuest(wand_id, magnitude)) +/// @param[in] wand_id The value for the wand ID. +/// @param[in] magnitude The value for the magnitude +/// @return A code suitable for calling sendMagiQuest() with. +uint64_t IRsend::encodeMagiQuest(const uint32_t wand_id, + const uint16_t magnitude) { + uint64_t result = 0; + result = wand_id; + result <<= 16; + result |= magnitude; + // Shouldn't be needed, but ensure top 8/16 bit are zero. + result &= 0xFFFFFFFFFFFFULL; + return result; +} +#endif // SEND_MAGIQUEST + +#if DECODE_MAGIQUEST +/// Decode the supplied MagiQuest message. +/// Status: Beta / Should work. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note MagiQuest protocol appears to be a header of 8 'zero' bits, followed +/// by 32 bits of "wand ID" and finally 16 bits of "magnitude". +/// Even though we describe this protocol as 56 bits, it really only has +/// 48 bits of data that matter. +/// In transmission order, 8 zeros + 32 wand_id + 16 magnitude. +/// @see https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp +bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint16_t bits = 0; + uint64_t data = 0; + + if (results->rawlen < (2 * kMagiquestBits) + offset - 1) { + DPRINT("Not enough bits to be Magiquest - Rawlen: "); + DPRINT(results->rawlen); + DPRINT(" Expected: "); + DPRINTLN(2 * kMagiquestBits + offset - 1); + return false; + } + + // Compliance + if (strict && nbits != kMagiquestBits) return false; + + // Of six wands as datapoints, so far they all start with 8 ZEROs. + // For example, here is the data from two wands + // 00000000 00100011 01001100 00100110 00000010 00000010 00010111 + // 00000000 00100000 10001000 00110001 00000010 00000010 10110100 + + // Decode the (MARK + SPACE) bits + while (offset + 1 < results->rawlen && bits < nbits - 1) { + uint16_t mark = results->rawbuf[offset]; + uint16_t space = results->rawbuf[offset + 1]; + if (!matchMark(mark + space, kMagiQuestTotalUsec)) { + DPRINT("Not enough time to be Magiquest - Mark: "); + DPRINT(mark); + DPRINT(" Space: "); + DPRINT(space); + DPRINT(" Total: "); + DPRINT(mark + space); + DPRINT("Expected: "); + DPRINTLN(kMagiQuestTotalUsec); + return false; + } + + if (IS_ZERO(mark, space)) + data = (data << 1) | 0; + else if (IS_ONE(mark, space)) + data = (data << 1) | 1; + else + return false; + + bits++; + offset += 2; + + // Compliance + // The first 8 bits of this protocol are supposed to all be 0. + // Exit out early as it is never going to match. + if (strict && bits == 8 && data != 0) return false; + } + + // Last bit is special as the protocol ends with a SPACE, not a MARK. + // Grab the last MARK bit, assuming a good SPACE after it + if (offset < results->rawlen) { + uint16_t mark = results->rawbuf[offset]; + uint16_t space = (kMagiQuestTotalUsec / kRawTick) - mark; + + if (IS_ZERO(mark, space)) + data = (data << 1) | 0; + else if (IS_ONE(mark, space)) + data = (data << 1) | 1; + else + return false; + + bits++; + } + + if (bits != nbits) return false; + + if (strict) { + // The top 8 bits of the 56 bits needs to be 0x00 to be valid. + // i.e. bits 56 to 49 are all zero. + if ((data >> (nbits - 8)) != 0) return false; + } + + // Success + results->decode_type = MAGIQUEST; + results->bits = bits; + results->value = data; + results->address = data >> 16; // Wand ID + results->command = data & 0xFFFF; // Magnitude + return true; +} +#endif // DECODE_MAGIQUEST diff --git a/src/libraries/IRremoteESP8266/src/ir_Magiquest.h b/src/libraries/IRremoteESP8266/src/ir_Magiquest.h new file mode 100644 index 000000000..399904375 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Magiquest.h @@ -0,0 +1,43 @@ +// Copyright 2013 mpflaga +// Copyright 2015 kitlaan +// Copyright 2017 Jason kendall, David Conran + +/// @file +/// @brief Support for MagiQuest protocols. +/// @see https://github.com/kitlaan/Arduino-IRremote/blob/master/ir_Magiquest.cpp +/// @see https://github.com/mpflaga/Arduino-IRremote + +// Supports: +// Brand: MagiQuest, Model: Wand + +#ifndef IR_MAGIQUEST_H_ +#define IR_MAGIQUEST_H_ + +#define __STDC_LIMIT_MACROS +#include +#include "IRremoteESP8266.h" +#include "IRsend.h" + +/// MagiQuest packet is both Wand ID and magnitude of swish and flick +union magiquest { + uint64_t llword; + uint8_t byte[8]; + // uint16_t word[4]; + uint32_t lword[2]; + struct { + uint16_t magnitude; + uint32_t wand_id; + uint8_t padding; + uint8_t scrap; + } cmd; +}; + +const uint16_t kMagiQuestTotalUsec = 1150; +const uint8_t kMagiQuestZeroRatio = 30; // usually <= ~25% +const uint8_t kMagiQuestOneRatio = 38; // usually >= ~50% +const uint16_t kMagiQuestMarkZero = 280; +const uint16_t kMagiQuestSpaceZero = 850; +const uint16_t kMagiQuestMarkOne = 580; +const uint16_t kMagiQuestSpaceOne = 600; +const uint32_t kMagiQuestGap = kDefaultMessageGap; // Just a guess. +#endif // IR_MAGIQUEST_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Metz.cpp b/src/libraries/IRremoteESP8266/src/ir_Metz.cpp new file mode 100644 index 000000000..0dcc7dafa --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Metz.cpp @@ -0,0 +1,101 @@ +// Copyright 2020 David Conran (crankyoldgit) +/// @file +/// @brief Support for Metz protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1241 + +// Supports: +// Brand: Metz, Model: RM16 remote +// Brand: Metz, Model: RM17 remote +// Brand: Metz, Model: RM19 remote +// Brand: Metz, Model: CH610 TV + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants. +const uint16_t kMetzHdrMark = 880; ///< uSeconds. +const uint16_t kMetzHdrSpace = 2336; ///< uSeconds. +const uint16_t kMetzBitMark = 473; ///< uSeconds. +const uint16_t kMetzOneSpace = 1640; ///< uSeconds. +const uint16_t kMetzZeroSpace = 940; ///< uSeconds. +const uint16_t kMetzFreq = 38000; ///< Hz. +const uint8_t kMetzAddressBits = 3; +const uint8_t kMetzCommandBits = 6; + +#if SEND_METZ +/// Send a Metz formatted message. +/// Status: Beta / Needs testing against a real device. +/// @param[in] data containing the IR command. +/// @param[in] nbits Nr. of bits to send. usually kMetzBits +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendMetz(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kMetzHdrMark, kMetzHdrSpace, // Header + kMetzBitMark, kMetzOneSpace, // Data + kMetzBitMark, kMetzZeroSpace, + kMetzBitMark, kDefaultMessageGap, // Footer. + data, nbits, // Payload + kMetzFreq, true, repeat, kDutyDefault); +} + +/// Encode a Metz address, command, and toggle bits into a code suitable +/// for use with sendMetz(). +/// @param[in] address A 3-bit address value. +/// @param[in] command A 6-bit command value. +/// @param[in] toggle Should the toggle bit be set in the result? +/// @return A 19-bit value suitable for use with `sendMetz()`. +uint32_t IRsend::encodeMetz(const uint8_t address, const uint8_t command, + const bool toggle) { + return toggle << (2 * (kMetzAddressBits + kMetzCommandBits)) | + (address & 0x7) << (2 * kMetzCommandBits + kMetzAddressBits) | + (~address & 0x7) << (2 * kMetzCommandBits) | + (command & 0x3F) << kMetzCommandBits | + (~command & 0x3F); +} +#endif // SEND_METZ + +#if DECODE_METZ +/// Decode the supplied Metz message. +/// Status: BETA / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeMetz(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kMetzBits) return false; + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kMetzHdrMark, kMetzHdrSpace, // Header + kMetzBitMark, kMetzOneSpace, // Data + kMetzBitMark, kMetzZeroSpace, + kMetzBitMark, kDefaultMessageGap, // Footer + true, _tolerance, 0, true)) return false; + + uint16_t command = GETBITS64(data, kMetzCommandBits, kMetzCommandBits); + uint16_t address = GETBITS64(data, 2 * kMetzCommandBits + kMetzAddressBits, + kMetzAddressBits); + // Compliance + if (strict) { + if (command != invertBits(GETBITS64(data, 0, kMetzCommandBits), + kMetzCommandBits) || + address != invertBits(GETBITS64(data, 2 * kMetzCommandBits, + kMetzAddressBits), + kMetzAddressBits)) return false; + } + // Success + results->decode_type = decode_type_t::METZ; + results->bits = nbits; + results->value = data; + results->address = address; + results->command = command; + return true; +} +#endif // DECODE_METZ diff --git a/src/libraries/IRremoteESP8266/src/ir_Midea.cpp b/src/libraries/IRremoteESP8266/src/ir_Midea.cpp new file mode 100644 index 000000000..e3499281b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Midea.cpp @@ -0,0 +1,889 @@ +// Copyright 2017 bwze, crankyoldgit +/// @file +/// @brief Support for Midea protocols. +/// Midea added by crankyoldgit & bwze. +/// send: bwze/crankyoldgit, decode: crankyoldgit +/// @note SwingV has the function of an Ion Filter on Danby A/C units. +/// @see https://docs.google.com/spreadsheets/d/1TZh4jWrx4h9zzpYUI9aYXMl1fYOiqu-xVuOOMqagxrs/edit?usp=sharing +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1213 + +#include "ir_Midea.h" +#include "ir_NEC.h" +// #include +//#ifndef ARDUINO +#include "String.h" +//#endif +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using arduino::String; + +// Constants +const uint16_t kMideaTick = 80; +const uint16_t kMideaBitMarkTicks = 7; +const uint16_t kMideaBitMark = kMideaBitMarkTicks * kMideaTick; +const uint16_t kMideaOneSpaceTicks = 21; +const uint16_t kMideaOneSpace = kMideaOneSpaceTicks * kMideaTick; +const uint16_t kMideaZeroSpaceTicks = 7; +const uint16_t kMideaZeroSpace = kMideaZeroSpaceTicks * kMideaTick; +const uint16_t kMideaHdrMarkTicks = 56; +const uint16_t kMideaHdrMark = kMideaHdrMarkTicks * kMideaTick; +const uint16_t kMideaHdrSpaceTicks = 56; +const uint16_t kMideaHdrSpace = kMideaHdrSpaceTicks * kMideaTick; +const uint16_t kMideaMinGapTicks = + kMideaHdrMarkTicks + kMideaZeroSpaceTicks + kMideaBitMarkTicks; +const uint16_t kMideaMinGap = kMideaMinGapTicks * kMideaTick; +const uint8_t kMideaTolerance = 30; // Percent +const uint16_t kMidea24MinGap = 13000; ///< uSecs + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::addToggleToString; +using irutils::minsToString; + +#if SEND_MIDEA +/// Send a Midea message +/// Status: Alpha / Needs testing against a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendMidea(uint64_t data, uint16_t nbits, uint16_t repeat) { + if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8. + + // Set IR carrier frequency + enableIROut(38); + + for (uint16_t r = 0; r <= repeat; r++) { + // The protocol sends the message, then follows up with an entirely + // inverted payload. + for (size_t inner_loop = 0; inner_loop < 2; inner_loop++) { + // Header + mark(kMideaHdrMark); + space(kMideaHdrSpace); + // Data + // Break data into byte segments, starting at the Most Significant + // Byte. Each byte then being sent normal, then followed inverted. + for (uint16_t i = 8; i <= nbits; i += 8) { + // Grab a bytes worth of data. + uint8_t segment = (data >> (nbits - i)) & 0xFF; + sendData(kMideaBitMark, kMideaOneSpace, kMideaBitMark, kMideaZeroSpace, + segment, 8, true); + } + // Footer + mark(kMideaBitMark); + space(kMideaMinGap); // Pause before repeating + + // Invert the data for the 2nd phase of the message. + // As we get called twice in the inner loop, we will always revert + // to the original 'data' state. + data = ~data; + } + space(kDefaultMessageGap); + } +} +#endif // SEND_MIDEA + +// Code to emulate Midea A/C IR remote control unit. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRMideaAC::IRMideaAC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { this->stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRMideaAC::stateReset(void) { + // Power On, Mode Auto, Fan Auto, Temp = 25C/77F + _.remote_state = 0xA1826FFFFF62; + _CleanToggle = false; + _EconoToggle = false; + _8CHeatToggle = false; + _LightToggle = false; + _Quiet = _Quiet_prev = false; + _SwingVToggle = false; + _TurboToggle = false; +#if KAYSUN_AC + _SwingVStep = false; +#endif // KAYSUN_AC +} + +/// Set up hardware to be able to send a message. +void IRMideaAC::begin(void) { _irsend.begin(); } + +#if SEND_MIDEA +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMideaAC::send(const uint16_t repeat) { + _irsend.sendMidea(getRaw(), kMideaBits, repeat); + // Handle the toggle/special "one-off" settings if we need to. + if (_SwingVToggle && !isSwingVToggle()) + _irsend.sendMidea(kMideaACToggleSwingV, kMideaBits, repeat); + _SwingVToggle = false; +#if KAYSUN_AC + if (_SwingVStep && !isSwingVStep()) + _irsend.sendMidea(kMideaACSwingVStep, kMideaBits, repeat); + _SwingVStep = false; +#endif // KAYSUN_AC + if (_EconoToggle && !isEconoToggle()) + _irsend.sendMidea(kMideaACToggleEcono, kMideaBits, repeat); + _EconoToggle = false; + if (_TurboToggle && !isTurboToggle()) + _irsend.sendMidea(kMideaACToggleTurbo, kMideaBits, repeat); + _TurboToggle = false; + if (_LightToggle && !isLightToggle()) + _irsend.sendMidea(kMideaACToggleLight, kMideaBits, repeat); + _LightToggle = false; + if (getMode() <= kMideaACAuto) { // Only available in Cool, Dry, or Auto mode + if (_CleanToggle && !isCleanToggle()) + _irsend.sendMidea(kMideaACToggleSelfClean, kMideaBits, repeat); + _CleanToggle = false; + } else if (getMode() == kMideaACHeat) { // Only available in Heat mode + if (_8CHeatToggle && !is8CHeatToggle()) + _irsend.sendMidea(kMideaACToggle8CHeat, kMideaBits, repeat); + _8CHeatToggle = false; + } + if (_Quiet != _Quiet_prev) + _irsend.sendMidea(_Quiet ? kMideaACQuietOn : kMideaACQuietOff, + kMideaBits, repeat); + _Quiet_prev = _Quiet; +} +#endif // SEND_MIDEA + +/// Get a copy of the internal state/code for this protocol. +/// @return The code for this protocol based on the current internal state. +uint64_t IRMideaAC::getRaw(void) { + checksum(); // Ensure correct checksum before sending. + return _.remote_state; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +void IRMideaAC::setRaw(const uint64_t newState) { _.remote_state = newState; } + +/// Set the requested power state of the A/C to on. +void IRMideaAC::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMideaAC::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getPower(void) const { + return _.Power; +} + +/// Is the device currently using Celsius or the Fahrenheit temp scale? +/// @return true, the A/C unit uses Celsius natively, false, is Fahrenheit. +bool IRMideaAC::getUseCelsius(void) const { + return !_.useFahrenheit; +} + +/// Set the A/C unit to use Celsius natively. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setUseCelsius(const bool on) { + if (on == _.useFahrenheit) { // We need to change. + uint8_t native_temp = getTemp(!on); // Get the old native temp. + _.useFahrenheit = !on; // Cleared is on. + setTemp(native_temp, !on); // Reset temp using the old native temp. + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +/// @param[in] useCelsius true, use the Celsius temp scale. false, is Fahrenheit +void IRMideaAC::setTemp(const uint8_t temp, const bool useCelsius) { + uint8_t max_temp = kMideaACMaxTempF; + uint8_t min_temp = kMideaACMinTempF; + if (useCelsius) { + max_temp = kMideaACMaxTempC; + min_temp = kMideaACMinTempC; + } + uint8_t new_temp = ::min(max_temp, ::max(min_temp, temp)); + if (!_.useFahrenheit && !useCelsius) // Native is in C, new_temp is in F + new_temp = fahrenheitToCelsius(new_temp) - kMideaACMinTempC; + else if (_.useFahrenheit && useCelsius) // Native is in F, new_temp is in C + new_temp = celsiusToFahrenheit(new_temp) - kMideaACMinTempF; + else // Native and desired are the same units. + new_temp -= min_temp; + // Set the actual data. + _.Temp = new_temp; +} + +/// Get the current temperature setting. +/// @param[in] celsius true, the results are in Celsius. false, in Fahrenheit. +/// @return The current setting for temp. in the requested units/scale. +uint8_t IRMideaAC::getTemp(const bool celsius) const { + uint8_t temp = _.Temp; + if (!_.useFahrenheit) + temp += kMideaACMinTempC; + else + temp += kMideaACMinTempF; + if (celsius && _.useFahrenheit) temp = fahrenheitToCelsius(temp) + 0.5; + if (!celsius && !_.useFahrenheit) temp = celsiusToFahrenheit(temp); + return temp; +} + +/// Set the Sensor temperature. +/// @param[in] temp The temperature in degrees celsius. +/// @param[in] useCelsius true, use the Celsius temp scale. false, is Fahrenheit +/// @note Also known as FollowMe +void IRMideaAC::setSensorTemp(const uint8_t temp, const bool useCelsius) { + uint8_t max_temp = kMideaACMaxSensorTempF; + uint8_t min_temp = kMideaACMinSensorTempF; + if (useCelsius) { + max_temp = kMideaACMaxSensorTempC; + min_temp = kMideaACMinSensorTempC; + } + uint8_t new_temp = ::min(max_temp, ::max(min_temp, temp)); + if (!_.useFahrenheit && !useCelsius) // Native is in C, new_temp is in F + new_temp = fahrenheitToCelsius(new_temp) - kMideaACMinSensorTempC; + else if (_.useFahrenheit && useCelsius) // Native is in F, new_temp is in C + new_temp = celsiusToFahrenheit(new_temp) - kMideaACMinSensorTempF; + else // Native and desired are the same units. + new_temp -= min_temp; + // Set the actual data. + _.SensorTemp = new_temp + 1; + setEnableSensorTemp(true); +} + +/// Get the current Sensor temperature setting. +/// @param[in] celsius true, the results are in Celsius. false, in Fahrenheit. +/// @return The current setting for temp. in the requested units/scale. +/// @note Also known as FollowMe +uint8_t IRMideaAC::getSensorTemp(const bool celsius) const { + uint8_t temp = _.SensorTemp - 1; + if (!_.useFahrenheit) + temp += kMideaACMinSensorTempC; + else + temp += kMideaACMinSensorTempF; + if (celsius && _.useFahrenheit) temp = fahrenheitToCelsius(temp) + 0.5; + if (!celsius && !_.useFahrenheit) temp = celsiusToFahrenheit(temp); + return temp; +} + +/// Enable the remote's Sensor temperature. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Also known as FollowMe +void IRMideaAC::setEnableSensorTemp(const bool on) { + _.disableSensor = !on; + if (on) { + setType(kMideaACTypeFollow); + } else { + setType(kMideaACTypeCommand); + _.SensorTemp = kMideaACSensorTempOnTimerOff; // Apply special value if off. + } +} + +/// Is the remote temperature sensor enabled? +/// @return A boolean indicating if it is enabled or not. +/// @note Also known as FollowMe +bool IRMideaAC::getEnableSensorTemp(void) const { return !_.disableSensor; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. 1-3 set the speed, 0 for auto. +void IRMideaAC::setFan(const uint8_t fan) { + _.Fan = (fan > kMideaACFanHigh) ? kMideaACFanAuto : fan; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRMideaAC::getFan(void) const { + return _.Fan; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMideaAC::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMideaAC::setMode(const uint8_t mode) { + switch (mode) { + case kMideaACAuto: + case kMideaACCool: + case kMideaACHeat: + case kMideaACDry: + case kMideaACFan: + _.Mode = mode; + break; + default: + _.Mode = kMideaACAuto; + } +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setSleep(const bool on) { + _.Sleep = on; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getSleep(void) const { + return _.Sleep; +} + +/// Set the A/C to toggle the vertical swing toggle for the next send. +/// @note On Danby A/C units, this is associated with the Ion Filter instead. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setSwingVToggle(const bool on) { _SwingVToggle = on; } + +/// Is the current state a vertical swing toggle message? +/// @note On Danby A/C units, this is associated with the Ion Filter instead. +/// @return true, it is. false, it isn't. +bool IRMideaAC::isSwingVToggle(void) const { + return _.remote_state == kMideaACToggleSwingV; +} + +// Get the vertical swing toggle state of the A/C. +/// @note On Danby A/C units, this is associated with the Ion Filter instead. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getSwingVToggle(void) { + _SwingVToggle |= isSwingVToggle(); + return _SwingVToggle; +} + +#if KAYSUN_AC +/// Set the A/C to step the vertical swing for the next send. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setSwingVStep(const bool on) { _SwingVStep = on; } + +/// Is the current state a step vertical swing message? +/// @return true, it is. false, it isn't. +bool IRMideaAC::isSwingVStep(void) const { + return _.remote_state == kMideaACSwingVStep; +} + +// Get the step vertical swing state of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getSwingVStep(void) { + _SwingVStep |= isSwingVStep(); + return _SwingVStep; +} +#endif // KAYSUN_AC + +/// Set the A/C to toggle the Econo (energy saver) mode for the next send. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setEconoToggle(const bool on) { _EconoToggle = on; } + +/// Is the current state an Econo (energy saver) toggle message? +/// @return true, it is. false, it isn't. +bool IRMideaAC::isEconoToggle(void) const { + return _.remote_state == kMideaACToggleEcono; +} + +// Get the Econo (energy saver) toggle state of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getEconoToggle(void) { + _EconoToggle |= isEconoToggle(); + return _EconoToggle; +} + +/// Set the A/C to toggle the Turbo mode for the next send. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setTurboToggle(const bool on) { _TurboToggle = on; } + +/// Is the current state a Turbo toggle message? +/// @return true, it is. false, it isn't. +bool IRMideaAC::isTurboToggle(void) const { + return _.remote_state == kMideaACToggleTurbo; +} + +// Get the Turbo toggle state of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getTurboToggle(void) { + _TurboToggle |= isTurboToggle(); + return _TurboToggle; +} + +/// Set the A/C to toggle the Light (LED) mode for the next send. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setLightToggle(const bool on) { _LightToggle = on; } + +/// Is the current state a Light (LED) toggle message? +/// @return true, it is. false, it isn't. +bool IRMideaAC::isLightToggle(void) const { + return _.remote_state == kMideaACToggleLight; +} + +// Get the Light (LED) toggle state of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getLightToggle(void) { + _LightToggle |= isLightToggle(); + return _LightToggle; +} + +/// Is the current state a Self-Clean toggle message? +/// @return true, it is. false, it isn't. +bool IRMideaAC::isCleanToggle(void) const { + return _.remote_state == kMideaACToggleSelfClean; +} + +/// Set the A/C to toggle the Self Clean mode for the next send. +/// @note Only works in Cool, Dry, or Auto modes. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setCleanToggle(const bool on) { + _CleanToggle = on && getMode() <= kMideaACAuto; +} + +// Get the Self-Clean toggle state of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getCleanToggle(void) { + _CleanToggle |= isCleanToggle(); + return _CleanToggle; +} + +/// Is the current state a 8C Heat (Freeze Protect) toggle message? +/// @note Only works in Heat mode. +/// @return true, it is. false, it isn't. +bool IRMideaAC::is8CHeatToggle(void) const { + return _.remote_state == kMideaACToggle8CHeat; +} + +/// Set the A/C to toggle the 8C Heat (Freeze Protect) mode for the next send. +/// @note Only works in Heat mode. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::set8CHeatToggle(const bool on) { + _8CHeatToggle = on && getMode() == kMideaACHeat; +} + +// Get the 8C Heat (Freeze Protect) toggle state of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::get8CHeatToggle(void) { + _8CHeatToggle |= is8CHeatToggle(); + return _8CHeatToggle; +} + +/// Is the current state a Quiet(Silent) message? +/// @return true, it is. false, it isn't. +bool IRMideaAC::isQuiet(void) const { + return (_.remote_state == kMideaACQuietOff || + _.remote_state == kMideaACQuietOn); +} + +/// Set the Quiet (Silent) mode for the next send. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMideaAC::setQuiet(const bool on) { _Quiet = on; } + +/// Set the Quiet (Silent) mode for the next send. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @param[in] prev true, previously the setting was on. false, setting was off. +void IRMideaAC::setQuiet(const bool on, const bool prev) { + setQuiet(on); + _Quiet_prev = prev; +} + +// Get the Quiet (Silent) mode state of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMideaAC::getQuiet(void) const { + if (isQuiet()) + return _.remote_state == kMideaACQuietOn; + else + return _Quiet; +} + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @return The calculated checksum value. +uint8_t IRMideaAC::calcChecksum(const uint64_t state) { + uint8_t sum = 0; + uint64_t temp_state = state; + + for (uint8_t i = 0; i < 5; i++) { + temp_state >>= 8; + sum += reverseBits((temp_state & 0xFF), 8); + } + sum = 256 - sum; + return reverseBits(sum, 8); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The state to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRMideaAC::validChecksum(const uint64_t state) { + return GETBITS64(state, 0, 8) == calcChecksum(state); +} + +/// Calculate & set the checksum for the current internal state of the remote. +void IRMideaAC::checksum(void) { + // Stored the checksum value in the last byte. + _.Sum = calcChecksum(_.remote_state); +} + +/// Get the message type setting of the A/C message. +/// @return The message type setting. +uint8_t IRMideaAC::getType(void) const { return _.Type; } + +/// Set the message type setting of the A/C message. +/// @param[in] setting The desired message type setting. +void IRMideaAC::setType(const uint8_t setting) { + switch (setting) { + case kMideaACTypeFollow: + _.BeepDisable = false; + // FALL-THRU + case kMideaACTypeSpecial: + _.Type = setting; + break; + default: + _.Type = kMideaACTypeCommand; + _.BeepDisable = true; + } +} + +/// Is the OnTimer enabled? +/// @return true for yes, false for no. +bool IRMideaAC::isOnTimerEnabled(void) const { + return getType() == kMideaACTypeCommand && + _.SensorTemp != kMideaACSensorTempOnTimerOff; +} + +/// Get the value of the OnTimer is currently set to. +/// @return The number of minutes. +uint16_t IRMideaAC::getOnTimer(void) const { + return (_.SensorTemp >> 1) * 30 + 30; +} + +/// Set the value of the On Timer. +/// @param[in] mins The number of minutes for the timer. +/// @note Time will be rounded down to nearest 30 min as that is the resolution +/// of the actual device/protocol. +/// @note A value of less than 30 will disable the Timer. +/// @warning On Timer is incompatible with Sensor Temp/Follow Me messages. +/// Setting it will disable that mode/settings. +void IRMideaAC::setOnTimer(const uint16_t mins) { + setEnableSensorTemp(false); + uint8_t halfhours = ::min((uint16_t)(24 * 60), mins) / 30; + if (halfhours) + _.SensorTemp = ((halfhours - 1) << 1) | 1; + else + _.SensorTemp = kMideaACSensorTempOnTimerOff; +} + +/// Is the OffTimer enabled? +/// @return true for yes, false for no. +bool IRMideaAC::isOffTimerEnabled(void) const { + return _.OffTimer != kMideaACTimerOff; +} + +/// Get the value of the OffTimer is currently set to. +/// @return The number of minutes. +uint16_t IRMideaAC::getOffTimer(void) const { return _.OffTimer * 30 + 30; } + +/// Set the value of the Off Timer. +/// @param[in] mins The number of minutes for the timer. +/// @note Time will be rounded down to nearest 30 min as that is the resolution +/// of the actual device/protocol. +/// @note A value of less than 30 will disable the Timer. +void IRMideaAC::setOffTimer(const uint16_t mins) { + uint8_t halfhours = ::min((uint16_t)(24 * 60), mins) / 30; + if (halfhours) + _.OffTimer = halfhours - 1; + else + _.OffTimer = kMideaACTimerOff; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMideaAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kMideaACCool; + case stdAc::opmode_t::kHeat: return kMideaACHeat; + case stdAc::opmode_t::kDry: return kMideaACDry; + case stdAc::opmode_t::kFan: return kMideaACFan; + default: return kMideaACAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMideaAC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kMideaACFanLow; + case stdAc::fanspeed_t::kMedium: return kMideaACFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kMideaACFanHigh; + default: return kMideaACFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRMideaAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kMideaACCool: return stdAc::opmode_t::kCool; + case kMideaACHeat: return stdAc::opmode_t::kHeat; + case kMideaACDry: return stdAc::opmode_t::kDry; + case kMideaACFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRMideaAC::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kMideaACFanHigh: return stdAc::fanspeed_t::kMax; + case kMideaACFanMed: return stdAc::fanspeed_t::kMedium; + case kMideaACFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev A Ptr to the previous state. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMideaAC::toCommon(const stdAc::state_t *prev) { + stdAc::state_t result{}; + if (prev != NULL) { + result = *prev; + } else { + // Fixed/Not supported/Non-zero defaults. + result.protocol = decode_type_t::MIDEA; + result.model = -1; // No models used. + result.swingh = stdAc::swingh_t::kOff; + result.swingv = stdAc::swingv_t::kOff; + result.quiet = false; + result.turbo = false; + result.econo = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + } + if (isSwingVToggle()) { + result.swingv = (result.swingv != stdAc::swingv_t::kOff) ? + stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + return result; + } + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = !_.useFahrenheit; + result.degrees = getTemp(result.celsius); + result.sensorTemperature = getSensorTemp(result.celsius); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.sleep = _.Sleep ? 0 : -1; + result.econo = getEconoToggle(); + result.clean ^= getCleanToggle(); + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRMideaAC::toString(void) { + String result = ""; + const uint8_t message_type = getType(); + result.reserve(230); // Reserve some heap for the string to reduce fragging. + result += addIntToString(message_type, kTypeStr, false); + result += kSpaceLBraceStr; + switch (message_type) { + case kMideaACTypeCommand: result += kCommandStr; break; + case kMideaACTypeSpecial: result += kSpecialStr; break; + case kMideaACTypeFollow: result += kFollowStr; break; + default: result += kUnknownStr; + } + result += ')'; + if (message_type != kMideaACTypeSpecial) { + result += addBoolToString(_.Power, kPowerStr); + result += addModeToString(_.Mode, kMideaACAuto, kMideaACCool, + kMideaACHeat, kMideaACDry, kMideaACFan); + result += addBoolToString(!_.useFahrenheit, kCelsiusStr); + result += addTempToString(getTemp(true)); + result += '/'; + result += uint64ToString(getTemp(false)); + result += 'F'; + if (getEnableSensorTemp()) { + result += kCommaSpaceStr; + result += kSensorStr; + result += addTempToString(getSensorTemp(true), true, false); + result += '/'; + result += uint64ToString(getSensorTemp(false)); + result += 'F'; + } else { + result += addLabeledString( + isOnTimerEnabled() ? minsToString(getOnTimer()) : kOffStr, + kOnTimerStr); + } + result += addLabeledString( + isOffTimerEnabled() ? minsToString(getOffTimer()) : kOffStr, + kOffTimerStr); + result += addFanToString(_.Fan, kMideaACFanHigh, kMideaACFanLow, + kMideaACFanAuto, kMideaACFanAuto, kMideaACFanMed); + result += addBoolToString(_.Sleep, kSleepStr); + } + result += addToggleToString(getSwingVToggle(), kSwingVStr); +#if KAYSUN_AC + result += addBoolToString(getSwingVStep(), kStepStr); +#endif // KAYSUN_AC + result += addToggleToString(getEconoToggle(), kEconoStr); + result += addToggleToString(getTurboToggle(), kTurboStr); + result += addBoolToString(getQuiet(), kQuietStr); + result += addToggleToString(getLightToggle(), kLightStr); + result += addToggleToString(getCleanToggle(), kCleanStr); + result += addToggleToString(get8CHeatToggle(), k8CHeatStr); + return result; +} + +#if DECODE_MIDEA +/// Decode the supplied Midea message. +/// Status: Alpha / Needs testing against a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// Typically kHitachiAcBits, kHitachiAc1Bits, kHitachiAc2Bits, +/// kHitachiAc344Bits +/// @param[in] strict Flag indicating if we should perform strict matching. +bool IRrecv::decodeMidea(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint8_t min_nr_of_messages = 1; + if (strict) { + if (nbits != kMideaBits) return false; // Not strictly a MIDEA message. + min_nr_of_messages = 2; + } + + // The protocol sends the data normal + inverted, alternating on + // each byte. Hence twice the number of expected data bits. + if (results->rawlen < + min_nr_of_messages * (2 * nbits + kHeader + kFooter) - 1 + offset) + return false; // Can't possibly be a valid MIDEA message. + + uint64_t data = 0; + uint64_t inverted = 0; + + if (nbits > sizeof(data) * 8) + return false; // We can't possibly capture a Midea packet that big. + + for (uint8_t i = 0; i < min_nr_of_messages; i++) { + // Match Header + Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, i % 2 ? &inverted : &data, + results->rawlen - offset, nbits, + kMideaHdrMark, kMideaHdrSpace, + kMideaBitMark, kMideaOneSpace, + kMideaBitMark, kMideaZeroSpace, + kMideaBitMark, kMideaMinGap, + i % 2, // No "atleast" on 1st part, but yes on the 2nd. + kMideaTolerance); + if (!used) return false; + offset += used; + } + + // Compliance + if (strict) { + // Protocol requires a second message with all the data bits inverted. + // We should have checked we got a second message in the previous loop. + // Just need to check it's value is an inverted copy of the first message. + uint64_t mask = (1ULL << kMideaBits) - 1; + if ((data & mask) != ((inverted ^ mask) & mask)) return false; + if (!IRMideaAC::validChecksum(data)) return false; + } + + // Success + results->decode_type = MIDEA; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_MIDEA + +#if SEND_MIDEA24 +/// Send a Midea24 formatted message. +/// Status: STABLE / Confirmed working on a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1170 +/// @note This protocol is basically a 48-bit version of the NEC protocol with +/// alternate bytes inverted, thus only 24 bits of real data, and with at +/// least a single repeat. +/// @warning Can't be used beyond 32 bits. +void IRsend::sendMidea24(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + uint64_t newdata = 0; + // Construct the data into bye & inverted byte pairs. + for (int16_t i = nbits - 8; i >= 0; i -= 8) { + // Shuffle the data to be sent so far. + newdata <<= 16; + uint8_t next = GETBITS64(data, i, 8); + newdata |= ((next << 8) | (next ^ 0xFF)); + } + sendNEC(newdata, nbits * 2, repeat); +} +#endif // SEND_MIDEA24 + +#if DECODE_MIDEA24 +/// Decode the supplied Midea24 message. +/// Status: STABLE / Confirmed working on a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @note This protocol is basically a 48-bit version of the NEC protocol with +/// alternate bytes inverted, thus only 24 bits of real data. +/// @warning Can't be used beyond 32 bits. +bool IRrecv::decodeMidea24(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Not strictly a MIDEA24 message. + if (strict && nbits != kMidea24Bits) return false; + if (nbits > 32) return false; // Can't successfully match something that big. + + uint64_t longdata = 0; + if (!matchGeneric(results->rawbuf + offset, &longdata, + results->rawlen - offset, nbits * 2, + kNecHdrMark, kNecHdrSpace, + kNecBitMark, kNecOneSpace, + kNecBitMark, kNecZeroSpace, + kNecBitMark, kMidea24MinGap, true)) return false; + + // Build the result by checking every second byte is a complement(inversion) + // of the previous one. + uint32_t data = 0; + for (uint8_t i = nbits * 2; i >= 16;) { + // Shuffle the data collected so far. + data <<= 8; + i -= 8; + uint8_t current = GETBITS64(longdata, i, 8); + i -= 8; + uint8_t next = GETBITS64(longdata, i, 8); + // Check they are an inverted pair. + if (current != (next ^ 0xFF)) return false; // They are not, so abort. + data |= current; + } + + // Success + results->decode_type = decode_type_t::MIDEA24; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_MIDEA24 diff --git a/src/libraries/IRremoteESP8266/src/ir_Midea.h b/src/libraries/IRremoteESP8266/src/ir_Midea.h new file mode 100644 index 000000000..28b5496ef --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Midea.h @@ -0,0 +1,276 @@ +// Copyright 2017 David Conran + +/// @file +/// @brief Support for Midea protocols. +/// Midea added by crankyoldgit & bwze +/// @see https://docs.google.com/spreadsheets/d/1TZh4jWrx4h9zzpYUI9aYXMl1fYOiqu-xVuOOMqagxrs/edit?usp=sharing +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1342#issuecomment-733721085 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1733 + +// Supports: +// Brand: Pioneer System, Model: RYBO12GMFILCAD A/C (12K BTU) (MIDEA) +// Brand: Pioneer System, Model: RUBO18GMFILCAD A/C (18K BTU) (MIDEA) +// Brand: Pioneer System, Model: WS012GMFI22HLD A/C (12K BTU) (MIDEA) +// Brand: Pioneer System, Model: WS018GMFI22HLD A/C (12K BTU) (MIDEA) +// Brand: Pioneer System, Model: UB018GMFILCFHD A/C (12K BTU) (MIDEA) +// Brand: Pioneer System, Model: RG66B6(B)/BGEFU1 remote (MIDEA) +// Brand: Comfee, Model: MPD1-12CRN7 A/C (MIDEA) +// Brand: Kaysun, Model: Casual CF A/C (MIDEA) +// Brand: Keystone, Model: RG57H4(B)BGEF remote (MIDEA) +// Brand: MrCool, Model: RG57A6/BGEFU1 remote (MIDEA) +// Brand: Midea, Model: FS40-7AR Stand Fan (MIDEA24) +// Brand: Danby, Model: DAC080BGUWDB (MIDEA) +// Brand: Danby, Model: DAC100BGUWDB (MIDEA) +// Brand: Danby, Model: DAC120BGUWDB (MIDEA) +// Brand: Danby, Model: R09C/BCGE remote (MIDEA) +// Brand: Trotec, Model: TROTEC PAC 2100 X (MIDEA) +// Brand: Trotec, Model: TROTEC PAC 3900 X (MIDEA) +// Brand: Trotec, Model: RG57H(B)/BGE remote (MIDEA) +// Brand: Trotec, Model: RG57H3(B)/BGCEF-M remote (MIDEA) +// Brand: Lennox, Model: RG57A6/BGEFU1 remote (MIDEA) +// Brand: Lennox, Model: MWMA009S4-3P A/C (MIDEA) +// Brand: Lennox, Model: MWMA012S4-3P A/C (MIDEA) +// Brand: Lennox, Model: MCFA indoor split A/C (MIDEA) +// Brand: Lennox, Model: MCFB indoor split A/C (MIDEA) +// Brand: Lennox, Model: MMDA indoor split A/C (MIDEA) +// Brand: Lennox, Model: MMDB indoor split A/C (MIDEA) +// Brand: Lennox, Model: MWMA indoor split A/C (MIDEA) +// Brand: Lennox, Model: MWMB indoor split A/C (MIDEA) +// Brand: Lennox, Model: M22A indoor split A/C (MIDEA) +// Brand: Lennox, Model: M33A indoor split A/C (MIDEA) +// Brand: Lennox, Model: M33B indoor split A/C (MIDEA) + +#ifndef IR_MIDEA_H_ +#define IR_MIDEA_H_ + +#define __STDC_LIMIT_MACROS +#include +//#ifdef ARDUINO +#include "String.h" +//#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// @note +/// Compile-time model specific overrides. +/// Uncomment one of these if you have such a devices to better match your A/C. +/// It changes some of the special commands/settings. +// +// #define DANBY_DAC true +// #define KAYSUN_AC true + +/// @note Some Pioneer Systems have required a special bit to be set in order +/// for the A/C unit to accept the message. We don't currently understand what +/// this bit does. See the link for details of how to set this if needed. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1342#issuecomment-733721085 + +/// Native representation of a Midea A/C message. +union MideaProtocol{ + uint64_t remote_state; ///< The state in native IR code form + // only use 48bits + struct { + // Byte 0 + uint8_t Sum; + // Byte 1 (value=0xFF when not in use.) + // This byte gets dual usage as Sensor Temp and On Timer + // Depending on "Type" below. + // When in "OnTimer", the nr of half hours is stored with mask 0b01111110 + // i.e. + // uint8_t :1; + // uint8_t OnTimerHalfHours:6; + // uint8_t :1; + uint8_t SensorTemp:7; ///< Degrees or OnTimer. + uint8_t disableSensor:1; + // Byte 2 (value=0xFF when not in use.) + uint8_t :1; // 0b1 + uint8_t OffTimer:6; ///< Nr of Half hours. Off is 0b111111 + uint8_t BeepDisable:1; ///< 0 = no beep in follow me messages, 1 = beep. + // Byte 3 + uint8_t Temp:5; + uint8_t useFahrenheit:1; + uint8_t :0; + // Byte 4 + uint8_t Mode:3; + uint8_t Fan:2; + /// @todo Find out what this bit controls. + /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1342#issuecomment-733721085 + uint8_t :1; ///< Unknown, but set on _some_ Pioneer System A/Cs. + uint8_t Sleep:1; + uint8_t Power:1; + // Byte 5 + uint8_t Type:3; ///< Normal, Special, or FollowMe message type + uint8_t Header:5; ///< Typically 0b10100 + }; +}; + +// Constants +const uint8_t kMideaACMinTempF = 62; ///< Fahrenheit +const uint8_t kMideaACMaxTempF = 86; ///< Fahrenheit +const uint8_t kMideaACMinTempC = 17; ///< Celsius +const uint8_t kMideaACMaxTempC = 30; ///< Celsius +const uint8_t kMideaACMinSensorTempC = 0; ///< Celsius +const uint8_t kMideaACMaxSensorTempC = 37; ///< Celsius +const uint8_t kMideaACMinSensorTempF = 32; ///< Fahrenheit +const uint8_t kMideaACMaxSensorTempF = 99; ///< Fahrenheit (Guess only!) +const uint8_t kMideaACSensorTempOnTimerOff = 0b1111111; +const uint8_t kMideaACTimerOff = 0b111111; +const uint8_t kMideaACCool = 0; // 0b000 +const uint8_t kMideaACDry = 1; // 0b001 +const uint8_t kMideaACAuto = 2; // 0b010 +const uint8_t kMideaACHeat = 3; // 0b011 +const uint8_t kMideaACFan = 4; // 0b100 +const uint8_t kMideaACFanAuto = 0; // 0b00 +const uint8_t kMideaACFanLow = 1; // 0b01 +const uint8_t kMideaACFanMed = 2; // 0b10 +const uint8_t kMideaACFanHigh = 3; // 0b11 +#if KAYSUN_AC + // For Kaysun AC units, Toggle SwingV is 0xA202FFFFFF7E + const uint64_t kMideaACToggleSwingV = 0xA202FFFFFF7E; + const uint64_t kMideaACSwingVStep = 0xA201FFFFFF7C; +#else // KAYSUN_AC + const uint64_t kMideaACToggleSwingV = 0xA201FFFFFF7C; +#endif // KAYSUN_AC +#if DANBY_DAC + // For Danby DAC unit, the Ionizer toggle is the same as ToggleSwingV + // const uint64_t kMideaACToggleIonizer = 0xA201FFFFFF7C; + kSwingVToggleStr = kIonStr; +#endif // DANBY_DAC +const uint64_t kMideaACToggleEcono = 0xA202FFFFFF7E; +const uint64_t kMideaACToggleLight = 0xA208FFFFFF75; +const uint64_t kMideaACToggleTurbo = 0xA209FFFFFF74; +// Mode must be Auto, Cool, or Dry +const uint64_t kMideaACToggleSelfClean = 0xA20DFFFFFF70; +// 8C Heat AKA Freeze Protection +const uint64_t kMideaACToggle8CHeat = 0xA20FFFFFFF73; // Only in Heat +const uint64_t kMideaACQuietOn = 0xA212FFFFFF6E; +const uint64_t kMideaACQuietOff = 0xA213FFFFFF6F; + +const uint8_t kMideaACTypeCommand = 0b001; ///< Message type +const uint8_t kMideaACTypeSpecial = 0b010; ///< Message type +const uint8_t kMideaACTypeFollow = 0b100; ///< Message type + +// Legacy defines. (Deprecated) +#define MIDEA_AC_COOL kMideaACCool +#define MIDEA_AC_DRY kMideaACDry +#define MIDEA_AC_AUTO kMideaACAuto +#define MIDEA_AC_HEAT kMideaACHeat +#define MIDEA_AC_FAN kMideaACFan +#define MIDEA_AC_FAN_AUTO kMideaACFanAuto +#define MIDEA_AC_FAN_LOW kMideaACFanLow +#define MIDEA_AC_FAN_MED kMideaACFanMed +#define MIDEA_AC_FAN_HI kMideaACFanHigh +#define MIDEA_AC_POWER kMideaACPower +#define MIDEA_AC_SLEEP kMideaACSleep +#define MIDEA_AC_MIN_TEMP_F kMideaACMinTempF +#define MIDEA_AC_MAX_TEMP_F kMideaACMaxTempF +#define MIDEA_AC_MIN_TEMP_C kMideaACMinTempC +#define MIDEA_AC_MAX_TEMP_C kMideaACMaxTempC + +// Classes +/// Class for handling detailed Midea A/C messages. +/// @warning Consider this very alpha code. +class IRMideaAC { + public: + explicit IRMideaAC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_MIDEA + void send(const uint16_t repeat = kMideaMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_MIDEA + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + bool getUseCelsius(void) const; + void setUseCelsius(const bool celsius); + void setTemp(const uint8_t temp, const bool useCelsius = false); + uint8_t getTemp(const bool useCelsius = false) const; + void setSensorTemp(const uint8_t temp, const bool useCelsius = false); + uint8_t getSensorTemp(const bool useCelsius = false) const; + void setEnableSensorTemp(const bool on); + bool getEnableSensorTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setRaw(const uint64_t newState); + uint64_t getRaw(void); + static bool validChecksum(const uint64_t state); + void setSleep(const bool on); + bool getSleep(void) const; + bool isSwingVToggle(void) const; + void setSwingVToggle(const bool on); + bool getSwingVToggle(void); + #if KAYSUN_AC + bool isSwingVStep(void) const; + void setSwingVStep(const bool on); + bool getSwingVStep(void); + #endif // KAYSUN_AC + bool isEconoToggle(void) const; + void setEconoToggle(const bool on); + bool getEconoToggle(void); + bool isTurboToggle(void) const; + void setTurboToggle(const bool on); + bool getTurboToggle(void); + bool isLightToggle(void) const; + void setLightToggle(const bool on); + bool getLightToggle(void); + bool isCleanToggle(void) const; + void setCleanToggle(const bool on); + bool getCleanToggle(void); + bool is8CHeatToggle(void) const; + void set8CHeatToggle(const bool on); + bool get8CHeatToggle(void); + bool isQuiet(void) const; + void setQuiet(const bool on); + void setQuiet(const bool on, const bool prev); + bool getQuiet(void) const; + uint8_t getType(void) const; + bool isOnTimerEnabled(void) const; + uint16_t getOnTimer(void) const; + void setOnTimer(const uint16_t mins); + bool isOffTimerEnabled(void) const; + uint16_t getOffTimer(void) const; + void setOffTimer(const uint16_t mins); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL); + arduino::String toString(void); +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + MideaProtocol _; + bool _CleanToggle; + bool _EconoToggle; + bool _8CHeatToggle; + bool _LightToggle; + bool _Quiet; + bool _Quiet_prev; + bool _SwingVToggle; + #if KAYSUN_AC + bool _SwingVStep; + #endif // KAYSUN_AC + bool _TurboToggle; + void checksum(void); + static uint8_t calcChecksum(const uint64_t state); + void setType(const uint8_t type); +}; + +#endif // IR_MIDEA_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp b/src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp new file mode 100644 index 000000000..1d1f4899e --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp @@ -0,0 +1,113 @@ +// Copyright 2021 Victor Mukayev (vitos1k) +// Copyright 2021 David Conran (crankyoldgit) + +/// @file +/// @brief Support for the MilesTag2 IR protocol for LaserTag gaming +/// @see http://hosting.cmalton.me.uk/chrism/lasertag/MT2Proto.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1360 + +// Supports: +// Brand: Milestag2, Model: Various + +// TODO(vitos1k): This implementation would support only +// short SHOT packets(14bits) and MSGs = 24bits. Support +// for long MSGs > 24bits is TODO + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +// Shot packets have this bit as `0` +const uint16_t kMilesTag2ShotMask = 1 << (kMilesTag2ShotBits - 1); +// Msg packets have this bit as `1` +const uint32_t kMilesTag2MsgMask = 1 << (kMilesTag2MsgBits - 1); +const uint8_t kMilesTag2MsgTerminator = 0xE8; +const uint16_t kMilesTag2HdrMark = 2400; /// uSeconds. +const uint16_t kMilesTag2Space = 600; /// uSeconds. +const uint16_t kMilesTag2OneMark = 1200; /// uSeconds. +const uint16_t kMilesTag2ZeroMark = 600; /// uSeconds. +const uint16_t kMilesTag2RptLength = 32000; /// uSeconds. +const uint16_t kMilesTag2StdFreq = 38000; /// Hz. +const uint16_t kMilesTag2StdDuty = 25; /// Percentage. + +#if SEND_MILESTAG2 +/// Send a MilesTag2 formatted Shot/Msg packet. +/// Status: ALPHA / Probably works but needs testing with a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendMilestag2(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric( + kMilesTag2HdrMark, kMilesTag2Space, // Header + kMilesTag2OneMark, kMilesTag2Space, // 1 bit + kMilesTag2ZeroMark, kMilesTag2Space, // 0 bit + 0, // No footer mark + kMilesTag2RptLength, data, nbits, kMilesTag2StdFreq, true, // MSB First + repeat, kMilesTag2StdDuty); +} +#endif // SEND_MILESTAG2 + +#if DECODE_MILESTAG2 +/// Decode the supplied MilesTag2 message. +/// Status: ALPHA / Probably works but needs testing with a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1360 +bool IRrecv::decodeMilestag2(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint64_t data = 0; + // Header + Data + Optional Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kMilesTag2HdrMark, kMilesTag2Space, + kMilesTag2OneMark, kMilesTag2Space, + kMilesTag2ZeroMark, kMilesTag2Space, + 0, kMilesTag2RptLength, true)) return false; + + // Compliance + if (strict) { + switch (nbits) { + case kMilesTag2ShotBits: + // Is it a valid shot packet? + if (data & kMilesTag2ShotMask) return false; + break; + case kMilesTag2MsgBits: + // Is it a valid msg packet? i.e. Msg bit set & Terminator present. + if (!(data & kMilesTag2MsgMask) || + ((data & 0xFF) != kMilesTag2MsgTerminator)) + return false; + break; + default: + DPRINT("incorrect nbits:"); + DPRINTLN(nbits); + return false; // The request doesn't strictly match the protocol defn. + } + } + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = decode_type_t::MILESTAG2; + switch (nbits) { + case kMilesTag2ShotBits: + results->command = data & 0x3F; // Team & Damage + results->address = data >> 6; // Player ID. + break; + case kMilesTag2MsgBits: + results->command = (data >> 8) & 0xFF; // Message data + results->address = (data >> 16) & 0x7F; // Message ID + break; + default: + results->command = 0; + results->address = 0; + } + return true; +} +#endif // DECODE_MILESTAG2 diff --git a/src/libraries/IRremoteESP8266/src/ir_Mirage.cpp b/src/libraries/IRremoteESP8266/src/ir_Mirage.cpp new file mode 100644 index 000000000..a1ef38be3 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Mirage.cpp @@ -0,0 +1,851 @@ +// Copyright 2020-2021 David Conran (crankyoldgit) +/// @file +/// @brief Support for Mirage protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1289 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1573 + + +#include "ir_Mirage.h" +// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addSwingHToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::addToggleToString; +using irutils::minsToString; +using irutils::bcdToUint8; +using irutils::uint8ToBcd; +using irutils::sumNibbles; + +// Constants +const uint16_t kMirageHdrMark = 8360; ///< uSeconds +const uint16_t kMirageBitMark = 554; ///< uSeconds +const uint16_t kMirageHdrSpace = 4248; ///< uSeconds +const uint16_t kMirageOneSpace = 1592; ///< uSeconds +const uint16_t kMirageZeroSpace = 545; ///< uSeconds +const uint32_t kMirageGap = kDefaultMessageGap; ///< uSeconds (just a guess) +const uint16_t kMirageFreq = 38000; ///< Hz. (Just a guess) + +const uint8_t kMirageAcKKG29AC1PowerOn = 0b00; // 0 +const uint8_t kMirageAcKKG29AC1PowerOff = 0b11; // 3 + + +#if SEND_MIRAGE +/// Send a Mirage formatted message. +/// Status: STABLE / Reported as working. +/// @param[in] data An array of bytes containing the IR command. +/// @param[in] nbytes Nr. of bytes of data in the array. (>=kMirageStateLength) +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendMirage(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kMirageHdrMark, kMirageHdrSpace, + kMirageBitMark, kMirageOneSpace, + kMirageBitMark, kMirageZeroSpace, + kMirageBitMark, kMirageGap, + data, nbytes, kMirageFreq, false, // LSB + repeat, kDutyDefault); +} +#endif // SEND_MIRAGE + +#if DECODE_MIRAGE +/// Decode the supplied Mirage message. +/// Status: STABLE / Reported as working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeMirage(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kMirageBits) return false; // Compliance. + + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kMirageHdrMark, kMirageHdrSpace, + kMirageBitMark, kMirageOneSpace, + kMirageBitMark, kMirageZeroSpace, + kMirageBitMark, kMirageGap, true, + kUseDefTol, kMarkExcess, false)) return false; + // Compliance + if (strict && !IRMirageAc::validChecksum(results->state)) return false; + + // Success + results->decode_type = decode_type_t::MIRAGE; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} + +// Code to emulate Mirage A/C IR remote control unit. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRMirageAc::IRMirageAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRMirageAc::stateReset(void) { + // The state of the IR remote in IR code form. + static const uint8_t kReset[kMirageStateLength] = { + 0x56, 0x6C, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x42}; + setRaw(kReset); + _model = mirage_ac_remote_model_t::KKG9AC1; +} + +/// Set up hardware to be able to send a message. +void IRMirageAc::begin(void) { _irsend.begin(); } + +#if SEND_MITSUBISHI_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMirageAc::send(const uint16_t repeat) { + _irsend.sendMirage(getRaw(), kMirageStateLength, repeat); + // Reset any toggles after a send. + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + setCleanToggle(false); + setLight(false); // For this model (only), Light is a toggle. + break; + default: + break; + } +} +#endif // SEND_MITSUBISHI_AC + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRMirageAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] data A valid code for this protocol. +void IRMirageAc::setRaw(const uint8_t *data) { + memcpy(_.raw, data, kMirageStateLength); + _model = getModel(true); +} + +/// Guess the Mirage remote model from the supplied state code. +/// @param[in] state A valid state code for this protocol. +/// @return The model code. +/// @note This result isn't perfect. Both protocols can look the same but have +/// wildly different settings. +mirage_ac_remote_model_t IRMirageAc::getModel(const uint8_t *state) { + Mirage120Protocol p; + memcpy(p.raw, state, kMirageStateLength); + // Check for KKG29AC1 specific settings. + if (p.RecycleHeat || p.Filter || p.Sleep_Kkg29ac1 || p.CleanToggle || + p.IFeel || p.OffTimerEnable || p.OnTimerEnable) + return mirage_ac_remote_model_t::KKG29AC1; + // Check for things specific to KKG9AC1 + if ((p.Minutes || p.Seconds) || // Is part of the clock set? + // Are the timer times set, but not enabled? (enable check filtered above) + (p.OffTimerHours || p.OffTimerMins) || + (p.OnTimerHours || p.OnTimerMins)) + return mirage_ac_remote_model_t::KKG9AC1; + // As the above test has a 1 in 3600+ (for 1 second an hour) chance of a false + // negative in theory, we are going assume that anything left should be a + // KKG29AC1 model. + return mirage_ac_remote_model_t::KKG29AC1; // Default. +} + +/// Get the model code of the interal message state. +/// @param[in] useRaw If set, we try to get the model info from just the state. +/// @return The model code. +mirage_ac_remote_model_t IRMirageAc::getModel(const bool useRaw) const { + return useRaw ? getModel(_.raw) : _model; +} + +/// Set the model code of the interal message state. +/// @param[in] model The desired model to use for the settings. +void IRMirageAc::setModel(const mirage_ac_remote_model_t model) { + if (model != _model) { // Only change things if we need to. + // Save the old settings. + stdAc::state_t state = toCommon(); + const uint16_t ontimer = getOnTimer(); + const uint16_t offtimer = getOffTimer(); + const bool ifeel = getIFeel(); + const uint8_t sensor = getSensorTemp(); + // Change the model. + state.model = model; + // Restore/Convert the settings. + fromCommon(state); + setOnTimer(ontimer); + setOffTimer(offtimer); + setIFeel(ifeel); + setSensorTemp(sensor); + } +} + +/// Calculate and set the checksum values for the internal state. +void IRMirageAc::checksum(void) { _.Sum = calculateChecksum(_.raw); } + +/// Verify the checksum is valid for a given state. +/// @param[in] data The array to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRMirageAc::validChecksum(const uint8_t *data) { + return calculateChecksum(data) == data[kMirageStateLength - 1]; +} + +/// Calculate the checksum for a given state. +/// @param[in] data The value to calc the checksum of. +/// @return The calculated checksum value. +uint8_t IRMirageAc::calculateChecksum(const uint8_t *data) { + return sumNibbles(data, kMirageStateLength - 1); +} + +/// Set the requested power state of the A/C to on. +void IRMirageAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMirageAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setPower(bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Power = on ? kMirageAcKKG29AC1PowerOn : kMirageAcKKG29AC1PowerOff; + break; + default: + // In order to change the power setting, it seems must be less than + // kMirageAcPowerOff. kMirageAcPowerOff is larger than half of the + // possible value stored in the allocated bit space. + // Thus if the value is larger than kMirageAcPowerOff the power is off. + // Less than, then power is on. + // We can't just aribitarily add or subtract the value (which analysis + // indicates is how the power status changes. Very weird, I know!) as that + // is not an idempotent action, we must check if the addition or + // substraction is needed first. e.g. via getPower() + // i.e. If we added or subtracted twice, we would cause a wrap of the + // integer and not get the desired result. + if (on) + _.SwingAndPower -= getPower() ? 0 : kMirageAcPowerOff; + else + _.SwingAndPower += getPower() ? kMirageAcPowerOff : 0; + } +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMirageAc::getPower(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.Power == kMirageAcKKG29AC1PowerOn; + default: + return _.SwingAndPower < kMirageAcPowerOff; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMirageAc::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMirageAc::setMode(const uint8_t mode) { + switch (mode) { + case kMirageAcCool: + case kMirageAcDry: + case kMirageAcHeat: + case kMirageAcFan: + case kMirageAcRecycle: + _.Mode = mode; + // Reset turbo if we have to. + setTurbo(getTurbo()); + break; + default: // Default to cool mode for anything else. + setMode(kMirageAcCool); + } +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRMirageAc::setTemp(const uint8_t degrees) { + // Make sure we have desired temp in the correct range. + uint8_t celsius = ::max(degrees, kMirageAcMinTemp); + _.Temp = ::min(celsius, kMirageAcMaxTemp) + kMirageAcTempOffset; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRMirageAc::getTemp(void) const { return _.Temp - kMirageAcTempOffset; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRMirageAc::setFan(const uint8_t speed) { + _.Fan = (speed <= kMirageAcFanLow) ? speed : kMirageAcFanAuto; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRMirageAc::getFan(void) const { return _.Fan; } + +/// Change the Turbo setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setTurbo(bool on) { + const bool value = (on && (getMode() == kMirageAcCool)); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Turbo_Kkg29ac1 = value; + break; + default: + _.Turbo_Kkg9ac1 = value; + } +} + +/// Get the value of the current Turbo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMirageAc::getTurbo(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Turbo_Kkg29ac1; + default: return _.Turbo_Kkg9ac1; + } +} + +/// Change the Sleep setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setSleep(bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Sleep_Kkg29ac1 = on; + break; + default: + _.Sleep_Kkg9ac1 = on; + } +} + +/// Get the value of the current Sleep setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMirageAc::getSleep(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Sleep_Kkg29ac1; + default: return _.Sleep_Kkg9ac1; + } +} + +/// Change the Light/Display setting. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Light is a toggle on the KKG29AC1 model. +void IRMirageAc::setLight(bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.LightToggle_Kkg29ac1 = on; + break; + default: + _.Light_Kkg9ac1 = on; + } +} + +/// Get the value of the current Light/Display setting. +/// @return true, the setting is on. false, the setting is off. +/// @note Light is a toggle on the KKG29AC1 model. +bool IRMirageAc::getLight(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.LightToggle_Kkg29ac1; + default: return _.Light_Kkg9ac1; + } +} + +/// Get the clock time of the A/C unit. +/// @return Nr. of seconds past midnight. +uint32_t IRMirageAc::getClock(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return 0; + default: + return ((bcdToUint8(_.Hours) * 60) + bcdToUint8(_.Minutes)) * 60 + + bcdToUint8(_.Seconds); + } +} + +/// Set the clock time on the A/C unit. +/// @param[in] nr_of_seconds Nr. of seconds past midnight. +void IRMirageAc::setClock(const uint32_t nr_of_seconds) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Minutes = _.Seconds = 0; // No clock setting. Clear it just in case. + break; + default: + uint32_t remaining = ::min( + nr_of_seconds, (uint32_t)(24 * 60 * 60 - 1)); // Limit to 23:59:59. + _.Seconds = uint8ToBcd(remaining % 60); + remaining /= 60; + _.Minutes = uint8ToBcd(remaining % 60); + remaining /= 60; + _.Hours = uint8ToBcd(remaining); + } +} + +/// Set the Vertical Swing setting/position of the A/C. +/// @param[in] position The desired swing setting. +void IRMirageAc::setSwingV(const uint8_t position) { + switch (position) { + case kMirageAcSwingVOff: + case kMirageAcSwingVLowest: + case kMirageAcSwingVLow: + case kMirageAcSwingVMiddle: + case kMirageAcSwingVHigh: + case kMirageAcSwingVHighest: + case kMirageAcSwingVAuto: + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.SwingV = (position != kMirageAcSwingVOff); + break; + default: + const bool power = getPower(); + _.SwingAndPower = position; + // Power needs to be reapplied after overwriting SwingAndPower + setPower(power); + } + break; + default: // Default to Auto for anything else. + setSwingV(kMirageAcSwingVAuto); + } +} + +/// Get the Vertical Swing setting/position of the A/C. +/// @return The desired Vertical Swing setting/position. +uint8_t IRMirageAc::getSwingV(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.SwingV ? kMirageAcSwingVAuto : kMirageAcSwingVOff; + default: + return _.SwingAndPower - (getPower() ? 0 : kMirageAcPowerOff); + } +} + +/// Set the Horizontal Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setSwingH(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.SwingH = on; + break; + default: + break; + } +} + +/// Get the Horizontal Swing setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getSwingH(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.SwingH; + default: return false; + } +} + +/// Set the Quiet setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setQuiet(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Quiet = on; + break; + default: + break; + } +} + +/// Get the Quiet setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getQuiet(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Quiet; + default: return false; + } +} + +/// Set the CleanToggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setCleanToggle(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.CleanToggle = on; + break; + default: + break; + } +} + +/// Get the Clean Toggle setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getCleanToggle(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.CleanToggle; + default: return false; + } +} + +/// Set the Filter setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setFilter(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Filter = on; + break; + default: + break; + } +} + +/// Get the Filter setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getFilter(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Filter; + default: return false; + } +} + +/// Set the IFeel setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setIFeel(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.IFeel = on; + if (on) { + // If no previous sensor temp, default to currently desired temp. + if (!_.SensorTemp) _.SensorTemp = getTemp(); + } else { + _.SensorTemp = 0; // When turning it off, clear the Sensor Temp. + } + break; + default: + break; + } +} + +/// Get the IFeel setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getIFeel(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.IFeel; + default: return false; + } +} + +/// Set the Sensor Temp setting of the A/C's remote. +/// @param[in] degrees The desired sensor temp. in degrees celsius. +void IRMirageAc::setSensorTemp(const uint8_t degrees) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.SensorTemp = ::min(kMirageAcSensorTempMax, degrees) + + kMirageAcSensorTempOffset; + break; + default: + break; + } +} + +/// Get the Sensor Temp setting of the A/C's remote. +/// @return The current setting for the sensor temp. in degrees celsius. +uint16_t IRMirageAc::getSensorTemp(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.SensorTemp - kMirageAcSensorTempOffset; + default: + return false; + } +} + +/// Get the number of minutes the On Timer is currently set for. +/// @return Nr. of Minutes the timer is set for. 0, is the timer is not in use. +uint16_t IRMirageAc::getOnTimer(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.OnTimerEnable ? _.OnTimerHours * 60 + _.OnTimerMins : 0; + default: + return 0; + } +} + +/// Set the number of minutes for the On Timer. +/// @param[in] nr_of_mins How long to set the timer for. 0 disables the timer. +void IRMirageAc::setOnTimer(const uint16_t nr_of_mins) { + uint16_t mins = ::min(nr_of_mins, (uint16_t)(24 * 60)); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.OnTimerEnable = (mins > 0); + _.OnTimerHours = mins / 60; + _.OnTimerMins = mins % 60; + break; + default: + break; + } +} + +/// Get the number of minutes the Off Timer is currently set for. +/// @return Nr. of Minutes the timer is set for. 0, is the timer is not in use. +uint16_t IRMirageAc::getOffTimer(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.OffTimerEnable ? _.OffTimerHours * 60 + _.OffTimerMins : 0; + default: + return 0; + } +} + +/// Set the number of minutes for the Off Timer. +/// @param[in] nr_of_mins How long to set the timer for. 0 disables the timer. +void IRMirageAc::setOffTimer(const uint16_t nr_of_mins) { + uint16_t mins = ::min(nr_of_mins, (uint16_t)(24 * 60)); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.OffTimerEnable = (mins > 0); + _.OffTimerHours = mins / 60; + _.OffTimerMins = mins % 60; + break; + default: + break; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRMirageAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kMirageAcHeat: return stdAc::opmode_t::kHeat; + case kMirageAcDry: return stdAc::opmode_t::kDry; + case kMirageAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @param[in] model The model type to use to influence the conversion. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRMirageAc::toCommonFanSpeed(const uint8_t speed, + const mirage_ac_remote_model_t model) { + switch (model) { + case mirage_ac_remote_model_t::KKG29AC1: + switch (speed) { + case kMirageAcKKG29AC1FanHigh: return stdAc::fanspeed_t::kHigh; + case kMirageAcKKG29AC1FanMed: return stdAc::fanspeed_t::kMedium; + case kMirageAcKKG29AC1FanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } + break; + default: + switch (speed) { + case kMirageAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kMirageAcFanMed: return stdAc::fanspeed_t::kMedium; + case kMirageAcFanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMirageAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kHeat: return kMirageAcHeat; + case stdAc::opmode_t::kDry: return kMirageAcDry; + case stdAc::opmode_t::kFan: return kMirageAcFan; + default: return kMirageAcCool; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @param[in] model The model type to use to influence the conversion. +/// @return The native equivalent of the enum. +uint8_t IRMirageAc::convertFan(const stdAc::fanspeed_t speed, + const mirage_ac_remote_model_t model) { + uint8_t low; + uint8_t med; + switch (model) { + case mirage_ac_remote_model_t::KKG29AC1: + low = kMirageAcKKG29AC1FanLow; + med = kMirageAcKKG29AC1FanMed; + break; + default: + low = kMirageAcFanLow; + med = kMirageAcFanMed; + } + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return low; + case stdAc::fanspeed_t::kMedium: return med; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kMirageAcFanHigh; + default: return kMirageAcFanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMirageAc::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: return kMirageAcSwingVHighest; + case stdAc::swingv_t::kHigh: return kMirageAcSwingVHigh; + case stdAc::swingv_t::kMiddle: return kMirageAcSwingVMiddle; + case stdAc::swingv_t::kLow: return kMirageAcSwingVLow; + case stdAc::swingv_t::kLowest: return kMirageAcSwingVLowest; + case stdAc::swingv_t::kOff: return kMirageAcSwingVOff; + default: return kMirageAcSwingVAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRMirageAc::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kMirageAcSwingVHighest: return stdAc::swingv_t::kHighest; + case kMirageAcSwingVHigh: return stdAc::swingv_t::kHigh; + case kMirageAcSwingVMiddle: return stdAc::swingv_t::kMiddle; + case kMirageAcSwingVLow: return stdAc::swingv_t::kLow; + case kMirageAcSwingVLowest: return stdAc::swingv_t::kLowest; + case kMirageAcSwingVAuto: return stdAc::swingv_t::kAuto; + default: return stdAc::swingv_t::kOff; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMirageAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::MIRAGE; + result.model = _model; + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.sensorTemperature = getSensorTemp(); + result.fanspeed = toCommonFanSpeed(getFan(), _model); + result.swingv = toCommonSwingV(getSwingV()); + result.swingh = getSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + result.turbo = getTurbo(); + result.light = getLight(); + result.clean = getCleanToggle(); + result.filter = getFilter(); + result.sleep = getSleep() ? 0 : -1; + result.quiet = getQuiet(); + result.clock = getClock() / 60; + result.iFeel = getIFeel(); + // Not supported. + result.econo = false; + result.beep = false; + return result; +} + +/// Convert & set a stdAc::state_t to its equivalent internal settings. +/// @param[in] state The desired state in stdAc::state_t form. +void IRMirageAc::fromCommon(const stdAc::state_t state) { + stateReset(); + _model = (mirage_ac_remote_model_t)state.model; // Set directly to avoid loop + setPower(state.power); + setTemp(state.celsius ? state.degrees : fahrenheitToCelsius(state.degrees)); + setMode(convertMode(state.mode)); + setFan(convertFan(state.fanspeed, _model)); + setTurbo(state.turbo); + setSleep(state.sleep >= 0); + setLight(state.light); + setSwingV(convertSwingV(state.swingv)); + setSwingH(state.swingh != stdAc::swingh_t::kOff); + setQuiet(state.quiet); + setCleanToggle(state.clean); + setFilter(state.filter); + // setClock() expects seconds, not minutes. + setClock((state.clock > 0) ? state.clock * 60 : 0); + setIFeel(state.iFeel); + if (state.sensorTemperature != kNoTempValue) { + setSensorTemp(state.celsius ? state.sensorTemperature + : fahrenheitToCelsius(state.sensorTemperature)); + } + // Non-common settings. + setOnTimer(0); + setOffTimer(0); +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRMirageAc::toString(void) const { + String result = ""; + result.reserve(240); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::MIRAGE, _model, false); + result += addBoolToString(getPower(), kPowerStr); + result += addModeToString(_.Mode, 0xFF, kMirageAcCool, + kMirageAcHeat, kMirageAcDry, + kMirageAcFan); + result += addTempToString(getTemp()); + uint8_t fanlow; + uint8_t fanmed; + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + fanlow = kMirageAcKKG29AC1FanLow; + fanmed = kMirageAcKKG29AC1FanMed; + break; + default: // e.g. Model KKG9AC1 + fanlow = kMirageAcFanLow; + fanmed = kMirageAcFanMed; + } + result += addFanToString(_.Fan, kMirageAcFanHigh, fanlow, kMirageAcFanAuto, + kMirageAcFanAuto, fanmed); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getSleep(), kSleepStr); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + result += addBoolToString(_.Quiet, kQuietStr); + result += addToggleToString(getLight(), kLightStr); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addBoolToString(_.Filter, kFilterStr); + result += addToggleToString(_.CleanToggle, kCleanStr); + result += addLabeledString(getOnTimer() ? minsToString(getOnTimer()) + : kOffStr, + kOnTimerStr); + result += addLabeledString(getOffTimer() ? minsToString(getOffTimer()) + : kOffStr, + kOffTimerStr); + result += addBoolToString(_.IFeel, kIFeelStr); + if (_.IFeel) { + result += addIntToString(getSensorTemp(), kSensorTempStr); + result += 'C'; + } + break; + default: // e.g. Model KKG9AC1 + result += addBoolToString(getLight(), kLightStr); + result += addSwingVToString(getSwingV(), + kMirageAcSwingVAuto, + kMirageAcSwingVHighest, + kMirageAcSwingVHigh, + 0xFF, // Unused. + kMirageAcSwingVMiddle, + 0xFF, // Unused. + kMirageAcSwingVLow, + kMirageAcSwingVLowest, + kMirageAcSwingVOff, + 0xFF, 0xFF, 0xFF); // Unused. + result += addLabeledString(minsToString(getClock() / 60), kClockStr); + } + return result; +} +#endif // DECODE_MIRAGE diff --git a/src/libraries/IRremoteESP8266/src/ir_Mirage.h b/src/libraries/IRremoteESP8266/src/ir_Mirage.h new file mode 100644 index 000000000..e3388382b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Mirage.h @@ -0,0 +1,277 @@ +// Copyright 2020-2021 David Conran (crankyoldgit) +/// @file +/// @brief Support for Mirage protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1289 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1573 + + +// Supports: +// Brand: Mirage, Model: VLU series A/C +// Brand: Maxell, Model: MX-CH18CF A/C +// Brand: Maxell, Model: KKG9A-C1 remote +// Brand: Tronitechnik, Model: Reykir 9000 A/C +// Brand: Tronitechnik, Model: KKG29A-C1 remote + +#ifndef IR_MIRAGE_H_ +#define IR_MIRAGE_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Mirage 120-bit A/C message. +/// @see https://docs.google.com/spreadsheets/d/1Ucu9mOOIIJoWQjUJq_VCvwgV3EwKaRk8K2AuZgccYEk/edit#gid=0 +union Mirage120Protocol{ + uint8_t raw[kMirageStateLength]; ///< The state in code form. + struct { // Common + // Byte 0 + uint8_t Header :8; // Header. (0x56) + // Byte 1 + uint8_t Temp :8; // Celsius minus 0x5C. + // Byte 2 + uint8_t :8; // Unknown / Unused. + // Byte 3 + uint8_t :8; // Unknown / Unused. + // Byte 4 + uint8_t Fan :2; // Fan Speed. + uint8_t :2; // Unknown / Unused. + uint8_t Mode :4; // Cool, Heat, Dry, Fan, Recycle + // Byte 5 + uint8_t :8; + // Byte 6 + uint8_t :8; + // Byte 7 + uint8_t :8; + // Byte 8 + uint8_t :8; + // Byte 9 + uint8_t :8; + // Byte 10 + uint8_t :8; + // Byte 11 + uint8_t :8; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t :8; + // Byte 14 + uint8_t Sum :8; // Sum of all the previous nibbles. + }; + struct { // KKG9AC1 remote + // Byte 0 + uint8_t :8; // Header + // Byte 1 + uint8_t :8; // Temp + // Byte 2 + uint8_t :8; // Unknown / Unused. + // Byte 3 + uint8_t :3; // Unknown / Unused. + uint8_t Light_Kkg9ac1 :1; // Aka. Display. Seems linked to Sleep mode. + uint8_t :4; // Unknown / Unused. + // Byte 4 + uint8_t :8; // Fan & Mode + // Byte 5 + uint8_t :1; // Unknown + uint8_t SwingAndPower :7; + // Byte 6 + uint8_t :7; // Unknown / Unused. + uint8_t Sleep_Kkg9ac1 :1; // Sleep mode on or off. + // Byte 7 + uint8_t :3; // Unknown / Unused. + uint8_t Turbo_Kkg9ac1 :1; // Turbo mode on or off. Only works in Cool mode. + uint8_t :4; // Unknown / Unused. + // Byte 8 + uint8_t :8; // Unknown / Unused. + // Byte 9 + uint8_t :8; // Unknown / Unused. + // Byte 10 + uint8_t :8; // Unknown / Unused. + // Byte 11 + uint8_t Seconds :8; // Nr. of Seconds in BCD. + // Byte 12 + uint8_t Minutes :8; // Nr. of Minutes in BCD. + // Byte 13 + uint8_t Hours :8; // Nr. of Hours in BCD. + // Byte 14 + uint8_t :8; // Sum + }; + struct { // KKG29A-C1 remote + // Byte 0 + uint8_t :8; // Header + // Byte 1 + uint8_t :8; // Temp + // Byte 2 + uint8_t :8; + // Byte 3 + uint8_t Quiet :1; + uint8_t :7; + // Byte 4 + uint8_t :2; // Fan + uint8_t OffTimerEnable :1; + uint8_t OnTimerEnable :1; + uint8_t :3; // Mode + uint8_t :1; + // Byte 5 + uint8_t SwingH :1; + uint8_t SwingV :1; + uint8_t LightToggle_Kkg29ac1 :1; // Aka. Display Toggle. + uint8_t :3; + uint8_t Power :2; + // Byte 6 + uint8_t :1; + uint8_t Filter :1; // Aka. UVC + uint8_t :1; + uint8_t Sleep_Kkg29ac1 :1; // Sleep mode on or off. + uint8_t :2; + uint8_t RecycleHeat :1; + uint8_t :1; + // Byte 7 + uint8_t SensorTemp :6; // Temperature at the remote + uint8_t CleanToggle :1; + uint8_t IFeel :1; + // Byte 8 + uint8_t OnTimerHours :5; + uint8_t :2; + uint8_t Turbo_Kkg29ac1 :1; // Turbo mode on or off. + // Byte 9 + uint8_t OnTimerMins :6; + uint8_t :2; + // Byte 10 + uint8_t OffTimerHours :5; + uint8_t :3; + // Byte 11 + uint8_t OffTimerMins :6; + uint8_t :2; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t :8; + // Byte 14 + uint8_t :8; // Sum + }; +}; + +// Constants +const uint8_t kMirageAcHeat = 0b001; // 1 +const uint8_t kMirageAcCool = 0b010; // 2 +const uint8_t kMirageAcDry = 0b011; // 3 +const uint8_t kMirageAcRecycle = 0b100; // 4 +const uint8_t kMirageAcFan = 0b101; // 5 + +const uint8_t kMirageAcFanAuto = 0b00; // 0 +const uint8_t kMirageAcFanHigh = 0b01; // 1 +const uint8_t kMirageAcFanMed = 0b10; // 2 +const uint8_t kMirageAcFanLow = 0b11; // 3 +const uint8_t kMirageAcKKG29AC1FanAuto = 0b00; // 0 +const uint8_t kMirageAcKKG29AC1FanHigh = 0b01; // 1 +const uint8_t kMirageAcKKG29AC1FanLow = 0b10; // 2 +const uint8_t kMirageAcKKG29AC1FanMed = 0b11; // 3 + +const uint8_t kMirageAcMinTemp = 16; // 16C +const uint8_t kMirageAcMaxTemp = 32; // 32C +const uint8_t kMirageAcTempOffset = 0x5C; +const uint8_t kMirageAcSensorTempOffset = 20; +const uint8_t kMirageAcSensorTempMax = 43; // Celsius + +const uint8_t kMirageAcPowerOff = 0x5F; +const uint8_t kMirageAcSwingVOff = 0b0000; // 0 +const uint8_t kMirageAcSwingVLowest = 0b0011; // 3 +const uint8_t kMirageAcSwingVLow = 0b0101; // 5 +const uint8_t kMirageAcSwingVMiddle = 0b0111; // 7 +const uint8_t kMirageAcSwingVHigh = 0b1001; // 9 +const uint8_t kMirageAcSwingVHighest = 0b1011; // 11 +const uint8_t kMirageAcSwingVAuto = 0b1101; // 13 + + +/// Class for handling detailed Mirage 120-bit A/C messages. +/// @note Inspired and derived from the work done at: https://github.com/r45635/HVAC-IR-Control +/// @warning Consider this very alpha code. Seems to work, but not validated. +class IRMirageAc { + public: + explicit IRMirageAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_MIRAGE + void send(const uint16_t repeat = kMirageMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_MIRAGE + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t* data); + uint32_t getClock(void) const; + void setClock(const uint32_t nr_of_seconds); + void setTurbo(const bool on); + bool getTurbo(void) const; + void setLight(const bool on); + bool getLight(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setSwingV(const uint8_t position); + uint8_t getSwingV(void) const; + void setSwingH(const bool on); + bool getSwingH(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + void setCleanToggle(const bool on); + bool getCleanToggle(void) const; + void setFilter(const bool on); + bool getFilter(void) const; + void setIFeel(const bool on); + bool getIFeel(void) const; + void setSensorTemp(const uint8_t degrees); + uint16_t getSensorTemp(void) const; + uint16_t getOnTimer(void) const; + uint16_t getOffTimer(void) const; + void setOnTimer(const uint16_t nr_of_mins); + void setOffTimer(const uint16_t nr_of_mins); + mirage_ac_remote_model_t getModel(const bool useRaw = false) const; + void setModel(const mirage_ac_remote_model_t model); + static mirage_ac_remote_model_t getModel(const uint8_t *state); + static bool validChecksum(const uint8_t* data); + static uint8_t calculateChecksum(const uint8_t* data); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed, + const mirage_ac_remote_model_t model = mirage_ac_remote_model_t::KKG9AC1); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed, + const mirage_ac_remote_model_t model = mirage_ac_remote_model_t::KKG9AC1); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + stdAc::state_t toCommon(void) const; + void fromCommon(const stdAc::state_t state); + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Mirage120Protocol _; + mirage_ac_remote_model_t _model; + void checksum(void); +}; +#endif // IR_MIRAGE_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Mitsubishi.cpp b/src/libraries/IRremoteESP8266/src/ir_Mitsubishi.cpp new file mode 100644 index 000000000..6220ddfcd --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Mitsubishi.cpp @@ -0,0 +1,1728 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017-2021 David Conran +// Copyright 2019 Mark Kuchel +// Copyright 2018 Denes Varga + +/// @file +/// @brief Support for Mitsubishi protocols. +/// Mitsubishi (TV) decoding added from https://github.com/z3t0/Arduino-IRremote +/// Mitsubishi (TV) sending & Mitsubishi A/C support added by David Conran +/// @see GlobalCache's Control Tower's Mitsubishi TV data. +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/441 +/// @see https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L84 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/619 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/888 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1398 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399 +/// @see https://github.com/kuchel77 + +#include "ir_Mitsubishi.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "ir_Tcl.h" +#include "minmax.h" + +// Constants +// Mitsubishi TV +// period time is 1/33000Hz = 30.303 uSeconds (T) +const uint16_t kMitsubishiTick = 30; +const uint16_t kMitsubishiBitMarkTicks = 10; +const uint16_t kMitsubishiBitMark = kMitsubishiBitMarkTicks * kMitsubishiTick; +const uint16_t kMitsubishiOneSpaceTicks = 70; +const uint16_t kMitsubishiOneSpace = kMitsubishiOneSpaceTicks * kMitsubishiTick; +const uint16_t kMitsubishiZeroSpaceTicks = 30; +const uint16_t kMitsubishiZeroSpace = + kMitsubishiZeroSpaceTicks * kMitsubishiTick; +const uint16_t kMitsubishiMinCommandLengthTicks = 1786; +const uint16_t kMitsubishiMinCommandLength = + kMitsubishiMinCommandLengthTicks * kMitsubishiTick; +const uint16_t kMitsubishiMinGapTicks = 936; +const uint16_t kMitsubishiMinGap = kMitsubishiMinGapTicks * kMitsubishiTick; + +// Mitsubishi Projector (HC3000) +const uint16_t kMitsubishi2HdrMark = 8400; +const uint16_t kMitsubishi2HdrSpace = kMitsubishi2HdrMark / 2; +const uint16_t kMitsubishi2BitMark = 560; +const uint16_t kMitsubishi2ZeroSpace = 520; +const uint16_t kMitsubishi2OneSpace = kMitsubishi2ZeroSpace * 3; +const uint16_t kMitsubishi2MinGap = 28500; + +// Mitsubishi A/C +const uint16_t kMitsubishiAcHdrMark = 3400; +const uint16_t kMitsubishiAcHdrSpace = 1750; +const uint16_t kMitsubishiAcBitMark = 450; +const uint16_t kMitsubishiAcOneSpace = 1300; +const uint16_t kMitsubishiAcZeroSpace = 420; +const uint16_t kMitsubishiAcRptMark = 440; +const uint16_t kMitsubishiAcRptSpace = 15500; +const uint8_t kMitsubishiAcExtraTolerance = 5; + +// Mitsubishi 136 bit A/C +const uint16_t kMitsubishi136HdrMark = 3324; +const uint16_t kMitsubishi136HdrSpace = 1474; +const uint16_t kMitsubishi136BitMark = 467; +const uint16_t kMitsubishi136OneSpace = 1137; +const uint16_t kMitsubishi136ZeroSpace = 351; +const uint32_t kMitsubishi136Gap = kDefaultMessageGap; + +// Mitsubishi 112 bit A/C +const uint16_t kMitsubishi112HdrMark = 3450; +const uint16_t kMitsubishi112HdrSpace = 1696; +const uint16_t kMitsubishi112BitMark = 450; +const uint16_t kMitsubishi112OneSpace = 1250; +const uint16_t kMitsubishi112ZeroSpace = 385; +const uint32_t kMitsubishi112Gap = kDefaultMessageGap; +// Total tolerance percentage to use for matching the header mark. +const uint8_t kMitsubishi112HdrMarkTolerance = 5; + + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addSwingHToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::addTempFloatToString; +using irutils::minsToString; + +#if SEND_MITSUBISHI +/// Send the supplied Mitsubishi 16-bit message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol appears to have no header. +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp +/// @see GlobalCache's Control Tower's Mitsubishi TV data. +void IRsend::sendMitsubishi(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendGeneric(0, 0, // No Header + kMitsubishiBitMark, kMitsubishiOneSpace, kMitsubishiBitMark, + kMitsubishiZeroSpace, kMitsubishiBitMark, kMitsubishiMinGap, + kMitsubishiMinCommandLength, data, nbits, 33, true, repeat, 50); +} +#endif // SEND_MITSUBISHI + +#if DECODE_MITSUBISHI +/// Decode the supplied Mitsubishi 16-bit message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note This protocol appears to have no header. +/// @see GlobalCache's Control Tower's Mitsubishi TV data. +bool IRrecv::decodeMitsubishi(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kMitsubishiBits) + return false; // Request is out of spec. + + uint64_t data = 0; + + // Match Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + 0, 0, // No header + kMitsubishiBitMark, kMitsubishiOneSpace, + kMitsubishiBitMark, kMitsubishiZeroSpace, + kMitsubishiBitMark, kMitsubishiMinGap, + true, 30)) return false; + // Success + results->decode_type = MITSUBISHI; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_MITSUBISHI + +#if SEND_MITSUBISHI2 +/// Send a supplied second variant Mitsubishi 16-bit message. +/// Status: BETA / Probably works. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Based on a Mitsubishi HC3000 projector's remote. +/// This protocol appears to have a mandatory in-protocol repeat. +/// That is in *addition* to the entire message needing to be sent twice +/// for the device to accept the command. That is separate from the repeat. +/// i.e. Allegedly, the real remote requires the "Off" button pressed twice. +/// You will need to add a suitable gap yourself. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/441 +void IRsend::sendMitsubishi2(uint64_t data, uint16_t nbits, uint16_t repeat) { + for (uint16_t i = 0; i <= repeat; i++) { + // First half of the data. + sendGeneric(kMitsubishi2HdrMark, kMitsubishi2HdrSpace, kMitsubishi2BitMark, + kMitsubishi2OneSpace, kMitsubishi2BitMark, + kMitsubishi2ZeroSpace, kMitsubishi2BitMark, + kMitsubishi2HdrSpace, data >> (nbits / 2), nbits / 2, 33, true, + 0, 50); + // Second half of the data. + sendGeneric(0, 0, // No header for the second data block + kMitsubishi2BitMark, kMitsubishi2OneSpace, kMitsubishi2BitMark, + kMitsubishi2ZeroSpace, kMitsubishi2BitMark, kMitsubishi2MinGap, + data & ((1 << (nbits / 2)) - 1), nbits / 2, 33, true, 0, 50); + } +} +#endif // SEND_MITSUBISHI2 + +#if DECODE_MITSUBISHI2 +/// Decode the supplied second variation of a Mitsubishi 16-bit message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/441 +bool IRrecv::decodeMitsubishi2(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= 2 * nbits + kHeader + (kFooter * 2) - 1 + offset) + return false; // Shorter than shortest possibly expected. + if (strict && nbits != kMitsubishiBits) + return false; // Request is out of spec. + + results->value = 0; + + // Header + if (!matchMark(results->rawbuf[offset++], kMitsubishi2HdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kMitsubishi2HdrSpace)) + return false; + for (uint8_t i = 0; i < 2; i++) { + // Match Data + Footer + uint16_t used; + uint64_t data = 0; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits / 2, + 0, 0, // No header + kMitsubishi2BitMark, kMitsubishi2OneSpace, + kMitsubishi2BitMark, kMitsubishi2ZeroSpace, + kMitsubishi2BitMark, kMitsubishi2HdrSpace, + i % 2); + if (!used) return false; + offset += used; + results->value <<= (nbits / 2); + results->value |= data; + } + + // Success + results->decode_type = MITSUBISHI2; + results->bits = nbits; + results->address = GETBITS64(results->value, nbits / 2, nbits / 2); + results->command = GETBITS64(results->value, 0, nbits / 2); + return true; +} +#endif // DECODE_MITSUBISHI2 + +#if SEND_MITSUBISHI_AC +/// Send a Mitsubishi 144-bit A/C formatted message. (MITSUBISHI_AC) +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendMitsubishiAC(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kMitsubishiACStateLength) + return; // Not enough bytes to send a proper message. + + sendGeneric(kMitsubishiAcHdrMark, kMitsubishiAcHdrSpace, kMitsubishiAcBitMark, + kMitsubishiAcOneSpace, kMitsubishiAcBitMark, + kMitsubishiAcZeroSpace, kMitsubishiAcRptMark, + kMitsubishiAcRptSpace, data, nbytes, 38, false, repeat, 50); +} +#endif // SEND_MITSUBISHI_AC + +#if DECODE_MITSUBISHI_AC +/// Decode the supplied Mitsubish 144-bit A/C message. +/// Status: BETA / Probably works +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @see https://www.analysir.com/blog/2015/01/06/reverse-engineering-mitsubishi-ac-infrared-protocol/ +bool IRrecv::decodeMitsubishiAC(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + // Compliance + if (strict && nbits != kMitsubishiACBits) return false; // Out of spec. + // Do we need to look for a repeat? + const uint16_t expected_repeats = strict ? kMitsubishiACMinRepeat : kNoRepeat; + // Enough data? + if (results->rawlen <= (nbits * 2 + kHeader + kFooter) * + (expected_repeats + 1) + offset - 1) return false; + uint16_t save[kStateSizeMax]; + // Handle repeats if we need too. + for (uint16_t r = 0; r <= expected_repeats; r++) { + // Header + Data + Footer + uint16_t used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kMitsubishiAcHdrMark, kMitsubishiAcHdrSpace, + kMitsubishiAcBitMark, kMitsubishiAcOneSpace, + kMitsubishiAcBitMark, kMitsubishiAcZeroSpace, + kMitsubishiAcRptMark, kMitsubishiAcRptSpace, + r < expected_repeats, // At least? + _tolerance + kMitsubishiAcExtraTolerance, + 0, false); + if (!used) return false; // No match. + offset += used; + if (r) { // Is this a repeat? + // Repeats are expected to be exactly the same. + if (memcmp(save, results->state, nbits / 8) != 0) return false; + } else { // It is the first message. + // Compliance + if (strict) { + // Data signature check. + static const uint8_t signature[5] = {0x23, 0xCB, 0x26, 0x01, 0x00}; + if (memcmp(results->state, signature, 5) != 0) return false; + // Checksum verification. + if (!IRMitsubishiAC::validChecksum(results->state)) return false; + } + // Save a copy of the state to compare with. + memcpy(save, results->state, nbits / 8); + } + } + + // Success. + results->decode_type = MITSUBISHI_AC; + results->bits = nbits; + return true; +} +#endif // DECODE_MITSUBISHI_AC + +// Code to emulate Mitsubishi A/C IR remote control unit. +// Inspired and derived from the work done at: +// https://github.com/r45635/HVAC-IR-Control + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +/// @warning Consider this very alpha code. Seems to work, but not validated. +IRMitsubishiAC::IRMitsubishiAC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRMitsubishiAC::stateReset(void) { + // The state of the IR remote in IR code form. + // Known good state obtained from: + // https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L108 + static const uint8_t kReset[kMitsubishiACStateLength] = { + 0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x06, 0x30, 0x45, 0x67}; + setRaw(kReset); +} + +/// Set up hardware to be able to send a message. +void IRMitsubishiAC::begin(void) { _irsend.begin(); } + +#if SEND_MITSUBISHI_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMitsubishiAC::send(const uint16_t repeat) { + _irsend.sendMitsubishiAC(getRaw(), kMitsubishiACStateLength, repeat); +} +#endif // SEND_MITSUBISHI_AC + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRMitsubishiAC::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] data A valid code for this protocol. +void IRMitsubishiAC::setRaw(const uint8_t *data) { + memcpy(_.raw, data, kMitsubishiACStateLength); +} + +/// Calculate and set the checksum values for the internal state. +void IRMitsubishiAC::checksum(void) { + _.Sum = calculateChecksum(_.raw); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] data The array to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRMitsubishiAC::validChecksum(const uint8_t *data) { + return calculateChecksum(data) == data[kMitsubishiACStateLength - 1]; +} + +/// Calculate the checksum for a given state. +/// @param[in] data The value to calc the checksum of. +/// @return The calculated checksum value. +uint8_t IRMitsubishiAC::calculateChecksum(const uint8_t *data) { + return sumBytes(data, kMitsubishiACStateLength - 1); +} + +/// Set the requested power state of the A/C to on. +void IRMitsubishiAC::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMitsubishiAC::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiAC::setPower(bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiAC::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +/// @note The temperature resolution is 0.5 of a degree. +void IRMitsubishiAC::setTemp(const float degrees) { + // Make sure we have desired temp in the correct range. + float celsius = ::max(degrees, kMitsubishiAcMinTemp); + celsius = ::min(celsius, kMitsubishiAcMaxTemp); + // Convert to integer nr. of half degrees. + uint8_t nrHalfDegrees = celsius * 2; + // Do we have a half degree celsius? + _.HalfDegree = nrHalfDegrees & 1; + _.Temp = static_cast(nrHalfDegrees / 2 - kMitsubishiAcMinTemp); + // If temp is modified, iSave10C cannot be ON (because temp is then > 10C) + setISave10C(false); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +/// @note The temperature resolution is 0.5 of a degree. +float IRMitsubishiAC::getTemp(void) const { + return _.Temp + kMitsubishiAcMinTemp + (_.HalfDegree ? 0.5 : 0); +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. 0 is auto, 1-5 is speed, 6 is silent. +void IRMitsubishiAC::setFan(const uint8_t speed) { + uint8_t fan = speed; + // Bounds check + if (fan > kMitsubishiAcFanSilent) + fan = kMitsubishiAcFanMax; // Set the fan to maximum if out of range. + // Auto has a special bit. + _.FanAuto = (fan == kMitsubishiAcFanAuto); + if (fan >= kMitsubishiAcFanMax) + fan--; // There is no spoon^H^H^Heed 5 (max), pretend it doesn't exist. + _.Fan = fan; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRMitsubishiAC::getFan(void) const { + uint8_t fan = _.Fan; + if (fan == kMitsubishiAcFanMax) return kMitsubishiAcFanSilent; + return fan; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMitsubishiAC::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMitsubishiAC::setMode(const uint8_t mode) { + // If we get an unexpected mode, default to AUTO. + switch (mode) { + case kMitsubishiAcAuto: _.raw[8] = 0b00110000; break; + case kMitsubishiAcCool: _.raw[8] = 0b00110110; break; + case kMitsubishiAcDry: _.raw[8] = 0b00110010; break; + case kMitsubishiAcHeat: _.raw[8] = 0b00110000; break; + case kMitsubishiAcFan: _.raw[8] = 0b00110111; break; + default: + _.raw[8] = 0b00110000; + _.Mode = kMitsubishiAcAuto; + return; + } + _.Mode = mode; + // iSave10C can only be on in Heat mode. + if (mode != kMitsubishiAcHeat) { + setISave10C(false); + } +} + +/// Set the iSave10C (i-SAVE) mode of the A/C. +/// @param[in] state true, the setting is on. false, the setting is off. +/// @note Normal minimum temp is 16C; i-SAVE mode works as gate to enable AC +/// to use 10C as setting. However, when Remote control shows 10C, it still +/// emits 16C on the "Temp" bits, and instead it uses other bits to indicate +/// a target temp of 10C. +/// Slightly strange, but I guess it's to keep compatibility to systems +/// without i-SAVE. +/// i-SAVE only has this 10C functionality when the AC is already in Heat mode. +/// In all other modes, minimum temp is 16C. +/// I have found no other difference between normal Heat mode and i-SAVE +/// other than the ability to go to 10C. +/// In this implementation, i-SAVE mode is ONLY used to enable the AC +/// temperature setting to 10C. Therefore "Temp" is set to 16 disregarding +/// what the remote shows, and mode is set to Heat. +void IRMitsubishiAC::setISave10C(const bool state) { + if (state) setMode(kMitsubishiAcHeat); + if (state) setTemp(kMitsubishiAcMinTemp); + _.iSave10C = state; +} + +/// Get the iSave10C (i-SAVE) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiAC::getISave10C(void) const { + return _.iSave10C; +} + +/// Set the requested iSee mode. +/// @param[in] state requested iSee mode. +void IRMitsubishiAC::setISee(const bool state) { + _.ISee = state; +} + +/// Get the iSee mode of the A/C. +/// @return The iSee mode setting. +bool IRMitsubishiAC::getISee(void) const { + return _.ISee; +} + +/// Set the requested Ecocool mode. +/// @param[in] state requested Ecocool mode. +void IRMitsubishiAC::setEcocool(const bool state) { + _.Ecocool = state; +} + +/// Get the Ecocool mode of the A/C. +/// @return The Ecocool mode setting. +bool IRMitsubishiAC::getEcocool(void) const { + return _.Ecocool; +} + +/// Set the requested Absense Detect mode. +/// @param[in] state requested Absense Detect mode. +void IRMitsubishiAC::setAbsenseDetect(const bool state) { + _.AbsenseDetect = state; +} + +/// Get the Absense Detect mode of the A/C. +/// @return The Absense Detect mode setting. +bool IRMitsubishiAC::getAbsenseDetect(void) const { + return _.AbsenseDetect; +} + +/// Set the requested Direct/Indirect mode. Only works if I-See mode is ON. +/// @param[in] mode requested Direct/Indirect mode. +void IRMitsubishiAC::setDirectIndirect(const uint8_t mode) { + if (_.ISee) { + _.DirectIndirect = ::min(mode, kMitsubishiAcDirect); // bounds check + } else { + _.DirectIndirect = 0; + } +} + +/// Get the Direct/Indirect mode of the A/C. +/// @return The native mode setting. +uint8_t IRMitsubishiAC::getDirectIndirect(void) const { + return _.DirectIndirect; +} + +/// Set the requested Natural Flow mode. +/// @param[in] state requested Natural Flow mode. +void IRMitsubishiAC::setNaturalFlow(const bool state) { + _.NaturalFlow = state; +} + +/// Get the Natural Flow mode of the A/C. +/// @return The Natural Flow mode setting. +bool IRMitsubishiAC::getNaturalFlow(void) const { + return _.NaturalFlow; +} + + +/// Set the requested vane (Vertical Swing) operation mode of the a/c unit. +/// @note On some models, this represents the Right vertical vane. +/// @param[in] position The position/mode to set the vane to. +void IRMitsubishiAC::setVane(const uint8_t position) { + uint8_t pos = ::min(position, kMitsubishiAcVaneAutoMove); // bounds check + _.VaneBit = 1; + _.Vane = pos; +} + +/// Get the Vane (Vertical Swing) mode of the A/C. +/// @note On some models, this represents the Right vertical vane. +/// @return The native position/mode setting. +uint8_t IRMitsubishiAC::getVane(void) const { + return _.Vane; +} + +/// Set the requested Left Vane (Vertical Swing) operation mode of the a/c unit. +/// @param[in] position The position/mode to set the vane to. +void IRMitsubishiAC::setVaneLeft(const uint8_t position) { + _.VaneLeft = ::min(position, kMitsubishiAcVaneAutoMove); // bounds check +} + +/// Get the Left Vane (Vertical Swing) mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishiAC::getVaneLeft(void) const { return _.VaneLeft; } + +/// Set the requested wide-vane (Horizontal Swing) operation mode of the a/c. +/// @param[in] position The position/mode to set the wide vane to. +void IRMitsubishiAC::setWideVane(const uint8_t position) { + _.WideVane = ::min(position, kMitsubishiAcWideVaneAuto); +} + +/// Get the Wide Vane (Horizontal Swing) mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishiAC::getWideVane(void) const { + return _.WideVane; +} + +/// Get the clock time of the A/C unit. +/// @return Nr. of 10 minute increments past midnight. +/// @note 1 = 1/6 hour (10 minutes). e.g. 4pm = 48. +uint8_t IRMitsubishiAC::getClock(void) const { return _.Clock; } + +/// Set the clock time on the A/C unit. +/// @param[in] clock Nr. of 10 minute increments past midnight. +/// @note 1 = 1/6 hour (10 minutes). e.g. 6am = 36. +void IRMitsubishiAC::setClock(const uint8_t clock) { + _.Clock = clock; +} + +/// Get the desired start time of the A/C unit. +/// @return Nr. of 10 minute increments past midnight. +/// @note 1 = 1/6 hour (10 minutes). e.g. 4pm = 48. +uint8_t IRMitsubishiAC::getStartClock(void) const { return _.StartClock; } + +/// Set the desired start time of the A/C unit. +/// @param[in] clock Nr. of 10 minute increments past midnight. +/// @note 1 = 1/6 hour (10 minutes). e.g. 8pm = 120. +void IRMitsubishiAC::setStartClock(const uint8_t clock) { + _.StartClock = clock; +} + +/// Get the desired stop time of the A/C unit. +/// @return Nr. of 10 minute increments past midnight. +/// @note 1 = 1/6 hour (10 minutes). e.g. 10pm = 132. +uint8_t IRMitsubishiAC::getStopClock(void) const { return _.StopClock; } + +/// Set the desired stop time of the A/C unit. +/// @param[in] clock Nr. of 10 minute increments past midnight. +/// @note 1 = 1/6 hour (10 minutes). e.g. 10pm = 132. +void IRMitsubishiAC::setStopClock(const uint8_t clock) { + _.StopClock = clock; +} + +/// Get the timers active setting of the A/C. +/// @return The current timers enabled. +/// @note Possible values: kMitsubishiAcNoTimer, +/// kMitsubishiAcStartTimer, kMitsubishiAcStopTimer, +/// kMitsubishiAcStartStopTimer +uint8_t IRMitsubishiAC::getTimer(void) const { + return _.Timer; +} + +/// Set the timers active setting of the A/C. +/// @param[in] timer The timer code indicating which ones are active. +/// @note Possible values: kMitsubishiAcNoTimer, +/// kMitsubishiAcStartTimer, kMitsubishiAcStopTimer, +/// kMitsubishiAcStartStopTimer +void IRMitsubishiAC::setTimer(const uint8_t timer) { + _.Timer = timer; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kMitsubishiAcCool; + case stdAc::opmode_t::kHeat: return kMitsubishiAcHeat; + case stdAc::opmode_t::kDry: return kMitsubishiAcDry; + case stdAc::opmode_t::kFan: return kMitsubishiAcFan; + default: return kMitsubishiAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiAC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kMitsubishiAcFanSilent; + case stdAc::fanspeed_t::kLow: return kMitsubishiAcFanRealMax - 3; + case stdAc::fanspeed_t::kMedium: return kMitsubishiAcFanRealMax - 2; + case stdAc::fanspeed_t::kHigh: return kMitsubishiAcFanRealMax - 1; + case stdAc::fanspeed_t::kMax: return kMitsubishiAcFanRealMax; + default: return kMitsubishiAcFanAuto; + } +} + + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1401 +uint8_t IRMitsubishiAC::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: return kMitsubishiAcVaneHighest; + case stdAc::swingv_t::kHigh: return kMitsubishiAcVaneHigh; + case stdAc::swingv_t::kMiddle: return kMitsubishiAcVaneMiddle; + case stdAc::swingv_t::kLow: return kMitsubishiAcVaneLow; + case stdAc::swingv_t::kLowest: return kMitsubishiAcVaneLowest; + // These model Mitsubishi A/C have two automatic settings. + // 1. A typical up & down oscillation. (Native Swing) + // 2. The A/C determines where the best placement for the vanes, outside of + // user control. (Native Auto) + // Native "Swing" is what we consider "Auto" in stdAc. (Case 1) + case stdAc::swingv_t::kAuto: return kMitsubishiAcVaneSwing; + // Native "Auto" doesn't have a good match for this in stdAc. (Case 2) + // So we repurpose stdAc's "Off" (and anything else) to be Native Auto. + default: return kMitsubishiAcVaneAuto; + } +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiAC::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kLeftMax: return kMitsubishiAcWideVaneLeftMax; + case stdAc::swingh_t::kLeft: return kMitsubishiAcWideVaneLeft; + case stdAc::swingh_t::kMiddle: return kMitsubishiAcWideVaneMiddle; + case stdAc::swingh_t::kRight: return kMitsubishiAcWideVaneRight; + case stdAc::swingh_t::kRightMax: return kMitsubishiAcWideVaneRightMax; + case stdAc::swingh_t::kWide: return kMitsubishiAcWideVaneWide; + case stdAc::swingh_t::kAuto: return kMitsubishiAcWideVaneAuto; + default: return kMitsubishiAcWideVaneMiddle; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRMitsubishiAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kMitsubishiAcCool: return stdAc::opmode_t::kCool; + case kMitsubishiAcHeat: return stdAc::opmode_t::kHeat; + case kMitsubishiAcDry: return stdAc::opmode_t::kDry; + case kMitsubishiAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRMitsubishiAC::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kMitsubishiAcFanRealMax: return stdAc::fanspeed_t::kMax; + case kMitsubishiAcFanRealMax - 1: return stdAc::fanspeed_t::kHigh; + case kMitsubishiAcFanRealMax - 2: return stdAc::fanspeed_t::kMedium; + case kMitsubishiAcFanRealMax - 3: return stdAc::fanspeed_t::kLow; + case kMitsubishiAcFanSilent: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1401 +stdAc::swingv_t IRMitsubishiAC::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kMitsubishiAcVaneHighest: return stdAc::swingv_t::kHighest; + case kMitsubishiAcVaneHigh: return stdAc::swingv_t::kHigh; + case kMitsubishiAcVaneMiddle: return stdAc::swingv_t::kMiddle; + case kMitsubishiAcVaneLow: return stdAc::swingv_t::kLow; + case kMitsubishiAcVaneLowest: return stdAc::swingv_t::kLowest; + // These model Mitsubishi A/C have two automatic settings. + // 1. A typical up & down oscillation. (Native Swing) + // 2. The A/C determines where the best placement for the vanes, outside of + // user control. (Native Auto) + // Native "Auto" doesn't have a good match for this in stdAc. (Case 2) + // So we repurpose stdAc's "Off" to be Native Auto. + case kMitsubishiAcVaneAuto: return stdAc::swingv_t::kOff; + // Native "Swing" is what we consider "Auto" in stdAc. (Case 1) + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRMitsubishiAC::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kMitsubishiAcWideVaneLeftMax: return stdAc::swingh_t::kLeftMax; + case kMitsubishiAcWideVaneLeft: return stdAc::swingh_t::kLeft; + case kMitsubishiAcWideVaneMiddle: return stdAc::swingh_t::kMiddle; + case kMitsubishiAcWideVaneRight: return stdAc::swingh_t::kRight; + case kMitsubishiAcWideVaneRightMax: return stdAc::swingh_t::kRightMax; + case kMitsubishiAcWideVaneWide: return stdAc::swingh_t::kWide; + default: return stdAc::swingh_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMitsubishiAC::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::MITSUBISHI_AC; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = toCommonSwingV(_.Vane); + result.swingh = toCommonSwingH(_.WideVane); + result.quiet = getFan() == kMitsubishiAcFanSilent; + // Not supported. + result.turbo = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Change the Weekly Timer Enabled setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiAC::setWeeklyTimerEnabled(const bool on) { + _.WeeklyTimer = on; +} + +/// Get the value of the WeeklyTimer Enabled setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiAC::getWeeklyTimerEnabled(void) const { return _.WeeklyTimer; } + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRMitsubishiAC::toString(void) const { + String result = ""; + result.reserve(110); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kMitsubishiAcAuto, kMitsubishiAcCool, + kMitsubishiAcHeat, kMitsubishiAcDry, + kMitsubishiAcFan); + result += addTempFloatToString(getTemp()); + result += addFanToString(getFan(), kMitsubishiAcFanRealMax, + kMitsubishiAcFanRealMax - 3, + kMitsubishiAcFanAuto, kMitsubishiAcFanQuiet, + kMitsubishiAcFanRealMax - 2); + result += addSwingVToString(_.Vane, kMitsubishiAcVaneAuto, + kMitsubishiAcVaneHighest, kMitsubishiAcVaneHigh, + kMitsubishiAcVaneAuto, // Upper Middle unused. + kMitsubishiAcVaneMiddle, + kMitsubishiAcVaneAuto, // Lower Middle unused. + kMitsubishiAcVaneLow, kMitsubishiAcVaneLowest, + kMitsubishiAcVaneAuto, kMitsubishiAcVaneSwing, + // Below are unused. + kMitsubishiAcVaneAuto, kMitsubishiAcVaneAuto); + result += addSwingHToString(_.WideVane, kMitsubishiAcWideVaneAuto, + kMitsubishiAcWideVaneLeftMax, + kMitsubishiAcWideVaneLeft, + kMitsubishiAcWideVaneMiddle, + kMitsubishiAcWideVaneRight, + kMitsubishiAcWideVaneRightMax, + kMitsubishiAcWideVaneAuto, // Unused + kMitsubishiAcWideVaneAuto, // Unused + kMitsubishiAcWideVaneAuto, // Unused + kMitsubishiAcWideVaneAuto, // Unused + kMitsubishiAcWideVaneWide); + result += addLabeledString(minsToString(_.Clock * 10), kClockStr); + result += addLabeledString(minsToString(_.StartClock * 10), kOnTimerStr); + result += addLabeledString(minsToString(_.StopClock * 10), kOffTimerStr); + result += kCommaSpaceStr; + result += kTimerStr; + result += kColonSpaceStr; + switch (_.Timer) { + case kMitsubishiAcNoTimer: + result += '-'; + break; + case kMitsubishiAcStartTimer: + result += kStartStr; + break; + case kMitsubishiAcStopTimer: + result += kStopStr; + break; + case kMitsubishiAcStartStopTimer: + result += kStartStr; + result += '+'; + result += kStopStr; + break; + default: + result += F("? ("); + result += _.Timer; + result += ')'; + } + result += addBoolToString(_.WeeklyTimer, kWeeklyTimerStr); + result += addBoolToString(_.iSave10C, k10CHeatStr); + result += addBoolToString(_.ISee, kISeeStr); + result += addBoolToString(_.Ecocool, kEconoStr); + result += addBoolToString(_.AbsenseDetect, kAbsenseDetectStr); + result += addIntToString(_.DirectIndirect, kDirectIndirectModeStr); + result += addBoolToString(_.NaturalFlow, kFreshStr); + return result; +} + +#if SEND_MITSUBISHI136 +/// Send a Mitsubishi 136-bit A/C message. (MITSUBISHI136) +/// Status: BETA / Probably working. Needs to be tested against a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/888 +void IRsend::sendMitsubishi136(const unsigned char data[], + const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kMitsubishi136StateLength) + return; // Not enough bytes to send a proper message. + + sendGeneric(kMitsubishi136HdrMark, kMitsubishi136HdrSpace, + kMitsubishi136BitMark, kMitsubishi136OneSpace, + kMitsubishi136BitMark, kMitsubishi136ZeroSpace, + kMitsubishi136BitMark, kMitsubishi136Gap, + data, nbytes, 38, false, repeat, 50); +} +#endif // SEND_MITSUBISHI136 + +#if DECODE_MITSUBISHI136 +/// Decode the supplied Mitsubishi 136-bit A/C message. (MITSUBISHI136) +/// Status: STABLE / Reported as working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/888 +bool IRrecv::decodeMitsubishi136(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + if (nbits % 8 != 0) return false; // Not a multiple of an 8 bit byte. + if (strict) { // Do checks to see if it matches the spec. + if (nbits != kMitsubishi136Bits) return false; + } + uint16_t used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kMitsubishi136HdrMark, kMitsubishi136HdrSpace, + kMitsubishi136BitMark, kMitsubishi136OneSpace, + kMitsubishi136BitMark, kMitsubishi136ZeroSpace, + kMitsubishi136BitMark, kMitsubishi136Gap, + true, _tolerance, 0, false); + if (!used) return false; + if (strict) { + // Header validation: Codes start with 0x23CB26 + if (results->state[0] != 0x23 || results->state[1] != 0xCB || + results->state[2] != 0x26) return false; + if (!IRMitsubishi136::validChecksum(results->state, nbits / 8)) + return false; + } + results->decode_type = MITSUBISHI136; + results->bits = nbits; + return true; +} +#endif // DECODE_MITSUBISHI136 + +// Code to emulate Mitsubishi 136bit A/C IR remote control unit. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRMitsubishi136::IRMitsubishi136(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRMitsubishi136::stateReset(void) { + // The state of the IR remote in IR code form. + // Known good state obtained from: + // https://docs.google.com/spreadsheets/d/1f8EGfIbBUo2B-CzUFdrgKQprWakoYNKM80IKZN4KXQE/edit#gid=312397579&range=A10 + static const uint8_t kReset[kMitsubishi136StateLength] = { + 0x23, 0xCB, 0x26, 0x21, 0x00, 0x40, 0xC2, 0xC7, 0x04}; + memcpy(_.raw, kReset, kMitsubishi136StateLength); +} + +/// Calculate the checksum for the current internal state of the remote. +void IRMitsubishi136::checksum(void) { + for (uint8_t i = 0; i < 6; i++) + _.raw[kMitsubishi136PowerByte + 6 + i] = + ~_.raw[kMitsubishi136PowerByte + i]; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] data The array to verify the checksum of. +/// @param[in] len The length of the data array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRMitsubishi136::validChecksum(const uint8_t *data, const uint16_t len) { + if (len < kMitsubishi136StateLength) return false; + const uint16_t half = (len - kMitsubishi136PowerByte) / 2; + for (uint8_t i = 0; i < half; i++) { + // This variable is needed to avoid the warning: (known compiler issue) + // warning: comparison of promoted ~unsigned with unsigned [-Wsign-compare] + const uint8_t inverted = ~data[kMitsubishi136PowerByte + half + i]; + if (data[kMitsubishi136PowerByte + i] != inverted) return false; + } + return true; +} + +/// Set up hardware to be able to send a message. +void IRMitsubishi136::begin(void) { _irsend.begin(); } + +#if SEND_MITSUBISHI136 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMitsubishi136::send(const uint16_t repeat) { + _irsend.sendMitsubishi136(getRaw(), kMitsubishi136StateLength, repeat); +} +#endif // SEND_MITSUBISHI136 + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRMitsubishi136::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] data A valid code for this protocol. +void IRMitsubishi136::setRaw(const uint8_t *data) { + memcpy(_.raw, data, kMitsubishi136StateLength); +} + +/// Set the requested power state of the A/C to on. +void IRMitsubishi136::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMitsubishi136::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishi136::setPower(bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishi136::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRMitsubishi136::setTemp(const uint8_t degrees) { + uint8_t temp = ::max((uint8_t)kMitsubishi136MinTemp, degrees); + temp = ::min((uint8_t)kMitsubishi136MaxTemp, temp); + _.Temp = temp - kMitsubishiAcMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRMitsubishi136::getTemp(void) const { + return _.Temp + kMitsubishiAcMinTemp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRMitsubishi136::setFan(const uint8_t speed) { + _.Fan = ::min(speed, kMitsubishi136FanMax); +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRMitsubishi136::getFan(void) const { + return _.Fan; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMitsubishi136::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMitsubishi136::setMode(const uint8_t mode) { + // If we get an unexpected mode, default to AUTO. + switch (mode) { + case kMitsubishi136Fan: + case kMitsubishi136Cool: + case kMitsubishi136Heat: + case kMitsubishi136Auto: + case kMitsubishi136Dry: + _.Mode = mode; + break; + default: + _.Mode = kMitsubishi136Auto; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] position The position/mode to set the swing to. +void IRMitsubishi136::setSwingV(const uint8_t position) { + // If we get an unexpected mode, default to auto. + switch (position) { + case kMitsubishi136SwingVLowest: + case kMitsubishi136SwingVLow: + case kMitsubishi136SwingVHigh: + case kMitsubishi136SwingVHighest: + case kMitsubishi136SwingVAuto: + _.SwingV = position; + break; + default: + _.SwingV = kMitsubishi136SwingVAuto; + } +} + +/// Get the Vertical Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishi136::getSwingV(void) const { + return _.SwingV; +} + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishi136::setQuiet(bool on) { + if (on) setFan(kMitsubishi136FanQuiet); + else if (getQuiet()) setFan(kMitsubishi136FanLow); +} + + +/// Get the Quiet mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishi136::getQuiet(void) const { + return _.Fan == kMitsubishi136FanQuiet; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishi136::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kMitsubishi136Cool; + case stdAc::opmode_t::kHeat: return kMitsubishi136Heat; + case stdAc::opmode_t::kDry: return kMitsubishi136Dry; + case stdAc::opmode_t::kFan: return kMitsubishi136Fan; + default: return kMitsubishi136Auto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishi136::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kMitsubishi136FanMin; + case stdAc::fanspeed_t::kLow: return kMitsubishi136FanLow; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kMitsubishi136FanMax; + default: return kMitsubishi136FanMed; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishi136::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: return kMitsubishi136SwingVHighest; + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: return kMitsubishi136SwingVHigh; + case stdAc::swingv_t::kLow: return kMitsubishi136SwingVLow; + case stdAc::swingv_t::kLowest: return kMitsubishi136SwingVLowest; + default: return kMitsubishi136SwingVAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRMitsubishi136::toCommonMode(const uint8_t mode) { + switch (mode) { + case kMitsubishi136Cool: return stdAc::opmode_t::kCool; + case kMitsubishi136Heat: return stdAc::opmode_t::kHeat; + case kMitsubishi136Dry: return stdAc::opmode_t::kDry; + case kMitsubishi136Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRMitsubishi136::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kMitsubishi136FanMax: return stdAc::fanspeed_t::kMax; + case kMitsubishi136FanMed: return stdAc::fanspeed_t::kMedium; + case kMitsubishi136FanLow: return stdAc::fanspeed_t::kLow; + case kMitsubishi136FanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kMedium; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRMitsubishi136::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kMitsubishi136SwingVHighest: return stdAc::swingv_t::kHighest; + case kMitsubishi136SwingVHigh: return stdAc::swingv_t::kHigh; + case kMitsubishi136SwingVLow: return stdAc::swingv_t::kLow; + case kMitsubishi136SwingVLowest: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMitsubishi136::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::MITSUBISHI136; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = toCommonSwingV(_.SwingV); + result.quiet = getQuiet(); + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRMitsubishi136::toString(void) const { + String result = ""; + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kMitsubishi136Auto, kMitsubishi136Cool, + kMitsubishi136Heat, kMitsubishi136Dry, + kMitsubishi136Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kMitsubishi136FanMax, + kMitsubishi136FanLow, kMitsubishi136FanMax, + kMitsubishi136FanQuiet, kMitsubishi136FanMed); + result += addSwingVToString(_.SwingV, kMitsubishi136SwingVAuto, + kMitsubishi136SwingVHighest, + kMitsubishi136SwingVHigh, + kMitsubishi136SwingVAuto, // Unused + kMitsubishi136SwingVAuto, // Unused + kMitsubishi136SwingVAuto, // Unused + kMitsubishi136SwingVLow, + kMitsubishi136SwingVLow, + // Below are unused. + kMitsubishi136SwingVAuto, + kMitsubishi136SwingVAuto, + kMitsubishi136SwingVAuto, + kMitsubishi136SwingVAuto); + result += addBoolToString(getQuiet(), kQuietStr); + return result; +} + + +#if SEND_MITSUBISHI112 +/// Send a Mitsubishi 112-bit A/C formatted message. (MITSUBISHI112) +/// Status: Stable / Reported as working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947 +void IRsend::sendMitsubishi112(const unsigned char data[], + const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kMitsubishi112StateLength) + return; // Not enough bytes to send a proper message. + + sendGeneric(kMitsubishi112HdrMark, kMitsubishi112HdrSpace, + kMitsubishi112BitMark, kMitsubishi112OneSpace, + kMitsubishi112BitMark, kMitsubishi112ZeroSpace, + kMitsubishi112BitMark, kMitsubishi112Gap, + data, nbytes, 38, false, repeat, 50); +} +#endif // SEND_MITSUBISHI112 + +#if (DECODE_MITSUBISHI112 || DECODE_TCL112AC) +/// Decode the supplied Mitsubishi/TCL 112-bit A/C message. +/// (MITSUBISHI112, TCL112AC) +/// Status: STABLE / Reported as working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @note Note Mitsubishi112 & Tcl112Ac are basically the same protocol. +/// The only significant difference I can see is Mitsubishi112 has a +/// slightly longer header mark. We will use that to determine which +/// variant it should be. The other differences require full decoding and +/// only only with certain settings. +/// There are some other timing differences too, but the tolerances will +/// overlap. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/619 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947 +bool IRrecv::decodeMitsubishi112(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < (2 * nbits) + kHeader + kFooter - 1 + offset) + return false; + if (nbits % 8 != 0) return false; // Not a multiple of an 8 bit byte. + if (strict) { // Do checks to see if it matches the spec. + if (nbits != kMitsubishi112Bits && nbits != kTcl112AcBits) return false; + } + decode_type_t typeguess = decode_type_t::UNKNOWN; + uint16_t hdrspace; + uint16_t bitmark; + uint16_t onespace; + uint16_t zerospace; + uint32_t gap; + uint8_t tolerance = _tolerance; + + // Header +#if DECODE_MITSUBISHI112 + if (matchMark(results->rawbuf[offset], kMitsubishi112HdrMark, + kMitsubishi112HdrMarkTolerance, 0)) { + typeguess = decode_type_t::MITSUBISHI112; + hdrspace = kMitsubishi112HdrSpace; + bitmark = kMitsubishi112BitMark; + onespace = kMitsubishi112OneSpace; + zerospace = kMitsubishi112ZeroSpace; + gap = kMitsubishi112Gap; + } +#endif // DECODE_MITSUBISHI112 +#if (DECODE_TCL112AC || DECODE_TEKNOPOINT) + if (typeguess == decode_type_t::UNKNOWN && // We didn't match Mitsubishi112 + matchMark(results->rawbuf[offset], kTcl112AcHdrMark, + kTcl112AcHdrMarkTolerance, 0)) { + typeguess = decode_type_t::TCL112AC; + hdrspace = kTcl112AcHdrSpace; + bitmark = kTcl112AcBitMark; + onespace = kTcl112AcOneSpace; + zerospace = kTcl112AcZeroSpace; + gap = kTcl112AcGap; + tolerance += kTcl112AcTolerance; + } +#endif // (DECODE_TCL112AC || DECODE_TEKNOPOINT) + if (typeguess == decode_type_t::UNKNOWN) return false; // No header matched. + offset++; + + uint16_t used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + 0, // Skip the header as we matched it earlier. + hdrspace, bitmark, onespace, bitmark, zerospace, + bitmark, gap, + true, tolerance, 0, false); + if (!used) return false; + if (strict) { + // Header validation: Codes start with 0x23CB26 + if (results->state[0] != 0x23 || results->state[1] != 0xCB || + results->state[2] != 0x26) return false; + // TCL112 and MITSUBISHI112 share the exact same checksum. + if (!IRTcl112Ac::validChecksum(results->state, nbits / 8)) return false; + } + // Success + results->decode_type = typeguess; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_MITSUBISHI112 || DECODE_TCL112AC + +// Code to emulate Mitsubishi 112bit A/C IR remote control unit. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRMitsubishi112::IRMitsubishi112(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRMitsubishi112::stateReset(void) { + const uint8_t kReset[kMitsubishi112StateLength] = { + 0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x0B, 0x10, + 0x00, 0x00, 0x00, 0x30}; + setRaw(kReset); +} + +/// Calculate the checksum for the current internal state of the remote. +void IRMitsubishi112::checksum(void) { + _.Sum = IRTcl112Ac::calcChecksum(_.raw, kMitsubishi112StateLength); +} + +/// Set up hardware to be able to send a message. +void IRMitsubishi112::begin(void) { _irsend.begin(); } + +#if SEND_MITSUBISHI112 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMitsubishi112::send(const uint16_t repeat) { + _irsend.sendMitsubishi112(getRaw(), kMitsubishi112StateLength, repeat); +} +#endif // SEND_MITSUBISHI112 + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRMitsubishi112::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] data A valid code for this protocol. +void IRMitsubishi112::setRaw(const uint8_t *data) { + memcpy(_.raw, data, kMitsubishi112StateLength); +} + +/// Set the requested power state of the A/C to off. +void IRMitsubishi112::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMitsubishi112::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishi112::setPower(bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishi112::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRMitsubishi112::setTemp(const uint8_t degrees) { + uint8_t temp = ::max((uint8_t)kMitsubishi112MinTemp, degrees); + temp = ::min((uint8_t)kMitsubishi112MaxTemp, temp); + _.Temp = kMitsubishiAcMaxTemp - temp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRMitsubishi112::getTemp(void) const { + return kMitsubishiAcMaxTemp - _.Temp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRMitsubishi112::setFan(const uint8_t speed) { + switch (speed) { + case kMitsubishi112FanMin: + case kMitsubishi112FanLow: + case kMitsubishi112FanMed: + case kMitsubishi112FanMax: + _.Fan = speed; + break; + default: + _.Fan = kMitsubishi112FanMax; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRMitsubishi112::getFan(void) const { + return _.Fan; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMitsubishi112::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMitsubishi112::setMode(const uint8_t mode) { + // If we get an unexpected mode, default to AUTO. + switch (mode) { + // Note: No Fan Only mode. + case kMitsubishi112Cool: + case kMitsubishi112Heat: + case kMitsubishi112Auto: + case kMitsubishi112Dry: + _.Mode = mode; + break; + default: + _.Mode = kMitsubishi112Auto; + } +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] position The position/mode to set the swing to. +void IRMitsubishi112::setSwingV(const uint8_t position) { + // If we get an unexpected mode, default to auto. + switch (position) { + case kMitsubishi112SwingVLowest: + case kMitsubishi112SwingVLow: + case kMitsubishi112SwingVMiddle: + case kMitsubishi112SwingVHigh: + case kMitsubishi112SwingVHighest: + case kMitsubishi112SwingVAuto: + _.SwingV = position; + break; + default: + _.SwingV = kMitsubishi112SwingVAuto; + } +} + +/// Get the Vertical Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishi112::getSwingV(void) const { + return _.SwingV; +} + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] position The position/mode to set the swing to. +void IRMitsubishi112::setSwingH(const uint8_t position) { + // If we get an unexpected mode, default to auto. + switch (position) { + case kMitsubishi112SwingHLeftMax: + case kMitsubishi112SwingHLeft: + case kMitsubishi112SwingHMiddle: + case kMitsubishi112SwingHRight: + case kMitsubishi112SwingHRightMax: + case kMitsubishi112SwingHWide: + case kMitsubishi112SwingHAuto: + _.SwingH = position; + break; + default: + _.SwingH = kMitsubishi112SwingHAuto; + } +} + + +/// Get the Horizontal Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishi112::getSwingH(void) const { + return _.SwingH; +} + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note There is no true quiet setting on this A/C. +void IRMitsubishi112::setQuiet(bool on) { + if (on) + setFan(kMitsubishi112FanQuiet); + else if (getQuiet()) setFan(kMitsubishi112FanLow); +} + + +/// Get the Quiet mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +/// @note There is no true quiet setting on this A/C. +bool IRMitsubishi112::getQuiet(void) const { + return _.Fan == kMitsubishi112FanQuiet; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishi112::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kMitsubishi112Cool; + case stdAc::opmode_t::kHeat: return kMitsubishi112Heat; + case stdAc::opmode_t::kDry: return kMitsubishi112Dry; + // Note: No Fan Only mode. + default: return kMitsubishi112Auto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishi112::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kMitsubishi112FanMin; + case stdAc::fanspeed_t::kLow: return kMitsubishi112FanLow; + case stdAc::fanspeed_t::kMedium: return kMitsubishi112FanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kMitsubishi112FanMax; + default: return kMitsubishi112FanMed; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishi112::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: return kMitsubishi112SwingVHighest; + case stdAc::swingv_t::kHigh: return kMitsubishi112SwingVHigh; + case stdAc::swingv_t::kMiddle: return kMitsubishi112SwingVMiddle; + case stdAc::swingv_t::kLow: return kMitsubishi112SwingVLow; + case stdAc::swingv_t::kLowest: return kMitsubishi112SwingVLowest; + default: return kMitsubishi112SwingVAuto; + } +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishi112::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kLeftMax: return kMitsubishi112SwingHLeftMax; + case stdAc::swingh_t::kLeft: return kMitsubishi112SwingHLeft; + case stdAc::swingh_t::kMiddle: return kMitsubishi112SwingHMiddle; + case stdAc::swingh_t::kRight: return kMitsubishi112SwingHRight; + case stdAc::swingh_t::kRightMax: return kMitsubishi112SwingHRightMax; + case stdAc::swingh_t::kWide: return kMitsubishi112SwingHWide; + case stdAc::swingh_t::kAuto: return kMitsubishi112SwingHAuto; + default: return kMitsubishi112SwingHAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRMitsubishi112::toCommonMode(const uint8_t mode) { + switch (mode) { + case kMitsubishi112Cool: return stdAc::opmode_t::kCool; + case kMitsubishi112Heat: return stdAc::opmode_t::kHeat; + case kMitsubishi112Dry: return stdAc::opmode_t::kDry; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRMitsubishi112::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kMitsubishi112FanMax: return stdAc::fanspeed_t::kMax; + case kMitsubishi112FanMed: return stdAc::fanspeed_t::kMedium; + case kMitsubishi112FanLow: return stdAc::fanspeed_t::kLow; + case kMitsubishi112FanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kMedium; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRMitsubishi112::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kMitsubishi112SwingVHighest: return stdAc::swingv_t::kHighest; + case kMitsubishi112SwingVHigh: return stdAc::swingv_t::kHigh; + case kMitsubishi112SwingVMiddle: return stdAc::swingv_t::kMiddle; + case kMitsubishi112SwingVLow: return stdAc::swingv_t::kLow; + case kMitsubishi112SwingVLowest: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRMitsubishi112::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kMitsubishi112SwingHLeftMax: return stdAc::swingh_t::kLeftMax; + case kMitsubishi112SwingHLeft: return stdAc::swingh_t::kLeft; + case kMitsubishi112SwingHMiddle: return stdAc::swingh_t::kMiddle; + case kMitsubishi112SwingHRight: return stdAc::swingh_t::kRight; + case kMitsubishi112SwingHRightMax: return stdAc::swingh_t::kRightMax; + case kMitsubishi112SwingHWide: return stdAc::swingh_t::kWide; + default: return stdAc::swingh_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMitsubishi112::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::MITSUBISHI112; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = toCommonSwingV(_.SwingV); + result.swingh = toCommonSwingH(_.SwingH);; + result.quiet = getQuiet(); + // Not supported. + result.econo = false; // Need to figure this part from stdAc + result.clock = -1; + result.sleep = -1; + result.turbo = false; + result.clean = false; + result.filter = false; + result.light = false; + result.beep = false; + + + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRMitsubishi112::toString(void) const { + String result = ""; + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kMitsubishi112Auto, kMitsubishi112Cool, + kMitsubishi112Heat, kMitsubishi112Dry, + kMitsubishi112Auto); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kMitsubishi112FanMax, + kMitsubishi112FanLow, kMitsubishi112FanMax, + kMitsubishi112FanQuiet, kMitsubishi112FanMed); + result += addSwingVToString(_.SwingV, kMitsubishi112SwingVAuto, + kMitsubishi112SwingVHighest, + kMitsubishi112SwingVHigh, + kMitsubishi112SwingVAuto, // Upper Middle unused. + kMitsubishi112SwingVMiddle, + kMitsubishi112SwingVAuto, // Lower Middle unused. + kMitsubishi112SwingVLow, + kMitsubishi112SwingVLowest, + // Below are unused. + kMitsubishi112SwingVAuto, + kMitsubishi112SwingVAuto, + kMitsubishi112SwingVAuto, + kMitsubishi112SwingVAuto); + result += addSwingHToString(_.SwingH, kMitsubishi112SwingHAuto, + kMitsubishi112SwingHLeftMax, + kMitsubishi112SwingHLeft, + kMitsubishi112SwingHMiddle, + kMitsubishi112SwingHRight, + kMitsubishi112SwingHRightMax, + kMitsubishi112SwingHAuto, // Unused + kMitsubishi112SwingHAuto, // Unused + kMitsubishi112SwingHAuto, // Unused + kMitsubishi112SwingHAuto, // Unused + kMitsubishi112SwingHWide); + result += addBoolToString(getQuiet(), kQuietStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Mitsubishi.h b/src/libraries/IRremoteESP8266/src/ir_Mitsubishi.h new file mode 100644 index 000000000..ef2bc38d4 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Mitsubishi.h @@ -0,0 +1,456 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017-2021 David Conran +// Copyright 2019 Mark Kuchel + +/// @file +/// @brief Support for Mitsubishi protocols. +/// Mitsubishi (TV) decoding added from https://github.com/z3t0/Arduino-IRremote +/// Mitsubishi (TV) sending & Mitsubishi A/C support added by David Conran +/// @see GlobalCache's Control Tower's Mitsubishi TV data. +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/441 +/// @see https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L84 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/619 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/888 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1398 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399 +/// @see https://github.com/kuchel77 + +// Supports: +// Brand: Mitsubishi, Model: TV (MITSUBISHI) +// Brand: Mitsubishi, Model: HC3000 Projector (MITSUBISHI2) +// Brand: Mitsubishi, Model: MS-GK24VA A/C +// Brand: Mitsubishi, Model: KM14A 0179213 remote +// Brand: Mitsubishi Electric, Model: PEAD-RP71JAA Ducted A/C (MITSUBISHI136) +// Brand: Mitsubishi Electric, Model: 001CP T7WE10714 remote (MITSUBISHI136) +// Brand: Mitsubishi Electric, Model: MSH-A24WV A/C (MITSUBISHI112) +// Brand: Mitsubishi Electric, Model: MUH-A24WV A/C (MITSUBISHI112) +// Brand: Mitsubishi Electric, Model: KPOA remote (MITSUBISHI112) +// Brand: Mitsubishi Electric, Model: MLZ-RX5017AS A/C (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: SG153/M21EDF426 remote (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: MSZ-GV2519 A/C (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: RH151/M21ED6426 remote (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: MSZ-SF25VE3 A/C (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: SG15D remote (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: MSZ-ZW4017S A/C (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: MSZ-FHnnVE A/C (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: RH151 remote (MITSUBISHI_AC) +// Brand: Mitsubishi Electric, Model: PAR-FA32MA remote (MITSUBISHI136) + +#ifndef IR_MITSUBISHI_H_ +#define IR_MITSUBISHI_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Mitsubishi 144-bit A/C message. +union Mitsubishi144Protocol{ + uint8_t raw[kMitsubishiACStateLength]; ///< The state in code form. + struct { + // Byte 0~4 + uint8_t pad0[5]; + // Byte 5 + uint8_t :5; + uint8_t Power :1; + uint8_t :2; + // Byte 6 + uint8_t :3; + uint8_t Mode :3; + uint8_t ISee : 1; + uint8_t :1; + // Byte 7 + uint8_t Temp :4; + uint8_t HalfDegree :1; + uint8_t :3; + // Byte 8 + uint8_t :4; + uint8_t WideVane:4; // SwingH + // Byte 9 + uint8_t Fan :3; + uint8_t Vane :3; // SwingV or VaneRight + uint8_t VaneBit :1; + uint8_t FanAuto :1; + // Byte 10 + uint8_t Clock :8; + // Byte 11 + uint8_t StopClock :8; + // Byte 12 + uint8_t StartClock:8; + // Byte 13 + uint8_t Timer :3; + uint8_t WeeklyTimer :1; + uint8_t :4; + // Byte 14 + uint8_t :5; + uint8_t Ecocool :1; + uint8_t :2; + // Byte 15 + uint8_t DirectIndirect:2; + uint8_t AbsenseDetect :1; + uint8_t :2; + uint8_t iSave10C :1; // i-SAVE:mode=Heat & iSave=on AND 10C on remote + uint8_t :2; + // Byte 16 + uint8_t :1; + uint8_t NaturalFlow :1; + uint8_t :1; + uint8_t VaneLeft :3; // SwingV(Left) + uint8_t :2; + // Byte 17 + uint8_t Sum :8; + }; +}; + +// Constants +const uint8_t kMitsubishiAcAuto = 0b100; +const uint8_t kMitsubishiAcCool = 0b011; +const uint8_t kMitsubishiAcDry = 0b010; +const uint8_t kMitsubishiAcHeat = 0b001; +const uint8_t kMitsubishiAcFan = 0b111; +const uint8_t kMitsubishiAcFanAuto = 0; +const uint8_t kMitsubishiAcFanMax = 5; +const uint8_t kMitsubishiAcFanRealMax = 4; +const uint8_t kMitsubishiAcFanSilent = 6; +const uint8_t kMitsubishiAcFanQuiet = kMitsubishiAcFanSilent; +const float kMitsubishiAcMinTemp = 16.0; // 16C +const float kMitsubishiAcMaxTemp = 31.0; // 31C +const uint8_t kMitsubishiAcVaneAuto = 0b000; // Vanes move when AC wants to. +const uint8_t kMitsubishiAcVaneHighest = 0b001; +const uint8_t kMitsubishiAcVaneHigh = 0b010; +const uint8_t kMitsubishiAcVaneMiddle = 0b011; +const uint8_t kMitsubishiAcVaneLow = 0b100; +const uint8_t kMitsubishiAcVaneLowest = 0b101; +const uint8_t kMitsubishiAcVaneSwing = 0b111; // Vanes move all the time. +const uint8_t kMitsubishiAcVaneAutoMove = kMitsubishiAcVaneSwing; // Deprecated +const uint8_t kMitsubishiAcWideVaneLeftMax = 0b0001; // 1 +const uint8_t kMitsubishiAcWideVaneLeft = 0b0010; // 2 +const uint8_t kMitsubishiAcWideVaneMiddle = 0b0011; // 3 +const uint8_t kMitsubishiAcWideVaneRight = 0b0100; // 4 +const uint8_t kMitsubishiAcWideVaneRightMax = 0b0101; // 5 +const uint8_t kMitsubishiAcWideVaneWide = 0b0110; // 6 +const uint8_t kMitsubishiAcWideVaneAuto = 0b1000; // 8 +const uint8_t kMitsubishiAcDirectOff = 0b00; // Vanes move when AC wants to. +const uint8_t kMitsubishiAcIndirect = 0b01; +const uint8_t kMitsubishiAcDirect = 0b11; +const uint8_t kMitsubishiAcNoTimer = 0; +const uint8_t kMitsubishiAcStartTimer = 5; +const uint8_t kMitsubishiAcStopTimer = 3; +const uint8_t kMitsubishiAcStartStopTimer = 7; + +/// Native representation of a Mitsubishi 136-bit A/C message. +union Mitsubishi136Protocol{ + uint8_t raw[kMitsubishi136StateLength]; ///< The state in code form. + struct { + // Byte 0~4 + uint8_t pad[5]; + // Byte 5 + uint8_t :6; + uint8_t Power :1; + uint8_t :1; + // Byte 6 + uint8_t Mode :3; + uint8_t :1; + uint8_t Temp :4; + // Byte 7 + uint8_t :1; + uint8_t Fan :2; + uint8_t :1; + uint8_t SwingV :4; + }; +}; + +const uint8_t kMitsubishi136PowerByte = 5; +const uint8_t kMitsubishi136MinTemp = 17; // 17C +const uint8_t kMitsubishi136MaxTemp = 30; // 30C +const uint8_t kMitsubishi136Fan = 0b000; +const uint8_t kMitsubishi136Cool = 0b001; +const uint8_t kMitsubishi136Heat = 0b010; +const uint8_t kMitsubishi136Auto = 0b011; +const uint8_t kMitsubishi136Dry = 0b101; +const uint8_t kMitsubishi136SwingVLowest = 0b0000; +const uint8_t kMitsubishi136SwingVLow = 0b0001; +const uint8_t kMitsubishi136SwingVHigh = 0b0010; +const uint8_t kMitsubishi136SwingVHighest = 0b0011; +const uint8_t kMitsubishi136SwingVAuto = 0b1100; +const uint8_t kMitsubishi136FanMin = 0b00; +const uint8_t kMitsubishi136FanLow = 0b01; +const uint8_t kMitsubishi136FanMed = 0b10; +const uint8_t kMitsubishi136FanMax = 0b11; +const uint8_t kMitsubishi136FanQuiet = kMitsubishi136FanMin; + +/// Native representation of a Mitsubishi 112-bit A/C message. +union Mitsubishi112Protocol{ + uint8_t raw[kMitsubishi112StateLength]; ///< The state in code form. + struct { + // Byte 0~4 + uint8_t pad0[5]; + // Byte 5 + uint8_t :2; + uint8_t Power :1; + uint8_t :5; + // Byte 6 + uint8_t Mode :3; + uint8_t :5; + // Byte 7 + uint8_t Temp :4; + uint8_t :4; + // Byte 8 + uint8_t Fan :3; + uint8_t SwingV :3; + uint8_t :2; + // Byte 9~11 + uint8_t pad1[3]; + // Byte 12 + uint8_t :2; + uint8_t SwingH :4; + uint8_t :2; + // Byte 13 + uint8_t Sum :8; + }; +}; + +const uint8_t kMitsubishi112Cool = 0b011; +const uint8_t kMitsubishi112Heat = 0b001; +const uint8_t kMitsubishi112Auto = 0b111; +const uint8_t kMitsubishi112Dry = 0b010; + +const uint8_t kMitsubishi112MinTemp = 16; // 16C +const uint8_t kMitsubishi112MaxTemp = 31; // 31C + +const uint8_t kMitsubishi112FanMin = 0b010; +const uint8_t kMitsubishi112FanLow = 0b011; +const uint8_t kMitsubishi112FanMed = 0b101; +const uint8_t kMitsubishi112FanMax = 0b000; +const uint8_t kMitsubishi112FanQuiet = kMitsubishi112FanMin; +const uint8_t kMitsubishi112SwingVLowest = 0b101; +const uint8_t kMitsubishi112SwingVLow = 0b100; +const uint8_t kMitsubishi112SwingVMiddle = 0b011; +const uint8_t kMitsubishi112SwingVHigh = 0b010; +const uint8_t kMitsubishi112SwingVHighest = 0b001; +const uint8_t kMitsubishi112SwingVAuto = 0b111; + +const uint8_t kMitsubishi112SwingHLeftMax = 0b0001; +const uint8_t kMitsubishi112SwingHLeft = 0b0010; +const uint8_t kMitsubishi112SwingHMiddle = 0b0011; +const uint8_t kMitsubishi112SwingHRight = 0b0100; +const uint8_t kMitsubishi112SwingHRightMax = 0b0101; +const uint8_t kMitsubishi112SwingHWide = 0b1000; +const uint8_t kMitsubishi112SwingHAuto = 0b1100; + +// Legacy defines (Deprecated) +#define MITSUBISHI_AC_VANE_AUTO_MOVE kMitsubishiAcVaneAutoMove +#define MITSUBISHI_AC_VANE_AUTO kMitsubishiAcVaneAuto +#define MITSUBISHI_AC_MIN_TEMP kMitsubishiAcMinTemp +#define MITSUBISHI_AC_MAX_TEMP kMitsubishiAcMaxTemp +#define MITSUBISHI_AC_HEAT kMitsubishiAcHeat +#define MITSUBISHI_AC_FAN_SILENT kMitsubishiAcFanSilent +#define MITSUBISHI_AC_FAN_REAL_MAX kMitsubishiAcFanRealMax +#define MITSUBISHI_AC_FAN_MAX kMitsubishiAcFanMax +#define MITSUBISHI_AC_FAN_AUTO kMitsubishiAcFanAuto +#define MITSUBISHI_AC_DRY kMitsubishiAcDry +#define MITSUBISHI_AC_COOL kMitsubishiAcCool +#define MITSUBISHI_AC_AUTO kMitsubishiAcAuto + + +/// Class for handling detailed Mitsubishi 144-bit A/C messages. +/// @note Inspired and derived from the work done at: https://github.com/r45635/HVAC-IR-Control +/// @warning Consider this very alpha code. Seems to work, but not validated. +class IRMitsubishiAC { + public: + explicit IRMitsubishiAC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); + static bool validChecksum(const uint8_t* data); +#if SEND_MITSUBISHI_AC + void send(const uint16_t repeat = kMitsubishiACMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_MITSUBISHI_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const float degrees); + float getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setISave10C(const bool state); + bool getISave10C(void) const; + void setISee(const bool state); + bool getISee(void) const; + void setDirectIndirect(const uint8_t position); + uint8_t getDirectIndirect(void) const; + void setEcocool(const bool state); + bool getEcocool(void) const; + void setAbsenseDetect(const bool state); + bool getAbsenseDetect(void) const; + void setNaturalFlow(const bool state); + bool getNaturalFlow(void) const; + void setVane(const uint8_t position); // controls RIGHT vane on some models + uint8_t getVane(void) const; + void setVaneLeft(const uint8_t position); + uint8_t getVaneLeft(void) const; + void setWideVane(const uint8_t position); + uint8_t getWideVane(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t* data); + uint8_t getClock(void) const; + void setClock(const uint8_t clock); + uint8_t getStartClock(void) const; + void setStartClock(const uint8_t clock); + uint8_t getStopClock(void) const; + void setStopClock(const uint8_t clock); + uint8_t getTimer(void) const; + void setTimer(const uint8_t timer); + bool getWeeklyTimerEnabled(void) const; + void setWeeklyTimerEnabled(const bool on); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Mitsubishi144Protocol _; + void checksum(void); + static uint8_t calculateChecksum(const uint8_t* data); +}; + +/// Class for handling detailed Mitsubishi 136-bit A/C messages. +class IRMitsubishi136 { + public: + explicit IRMitsubishi136(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_MITSUBISHI136 + void send(const uint16_t repeat = kMitsubishi136MinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_MITSUBISHI136 + void begin(void); + static bool validChecksum(const uint8_t* data, + const uint16_t len = kMitsubishi136StateLength); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingV(const uint8_t position); + uint8_t getSwingV(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t* data); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Mitsubishi136Protocol _; + void checksum(void); +}; + +/// Class for handling detailed Mitsubishi 122-bit A/C messages. +class IRMitsubishi112 { + public: + explicit IRMitsubishi112(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_MITSUBISHI112 + void send(const uint16_t repeat = kMitsubishi112MinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_MITSUBISHI112 + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwingV(const uint8_t position); + uint8_t getSwingV(void) const; + void setSwingH(const uint8_t position); + uint8_t getSwingH(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t* data); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Mitsubishi112Protocol _; + void checksum(void); +}; + +#endif // IR_MITSUBISHI_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.cpp b/src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.cpp new file mode 100644 index 000000000..a89c31b0e --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.cpp @@ -0,0 +1,1051 @@ +// Copyright 2019 David Conran + +/// @file +/// @brief Support for Mitsubishi Heavy Industry protocols. +/// Code to emulate Mitsubishi Heavy Industries A/C IR remote control units. +/// @note This code was *heavily* influenced by ToniA's great work & code, +/// but it has been written from scratch. +/// Nothing was copied other than constants and message analysis. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/660 +/// @see https://github.com/ToniA/Raw-IR-decoder-for-Arduino/blob/master/MitsubishiHeavy.cpp +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/MitsubishiHeavyHeatpumpIR.cpp + +#include "ir_MitsubishiHeavy.h" +// #include +#include +#include "IRremoteESP8266.h" +#include "IRtext.h" +#include "IRutils.h" +#ifndef ARDUINO +//#include +#endif +#include "minmax.h" + +// Constants +const uint16_t kMitsubishiHeavyHdrMark = 3140; +const uint16_t kMitsubishiHeavyHdrSpace = 1630; +const uint16_t kMitsubishiHeavyBitMark = 370; +const uint16_t kMitsubishiHeavyOneSpace = 420; +const uint16_t kMitsubishiHeavyZeroSpace = 1220; +const uint32_t kMitsubishiHeavyGap = kDefaultMessageGap; // Just a guess. + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addSwingHToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::checkInvertedBytePairs; +using irutils::invertBytePairs; + +#if SEND_MITSUBISHIHEAVY +/// Send a MitsubishiHeavy 88-bit A/C message. +/// Status: BETA / Appears to be working. Needs testing against a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendMitsubishiHeavy88(const unsigned char data[], + const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kMitsubishiHeavy88StateLength) + return; // Not enough bytes to send a proper message. + sendGeneric(kMitsubishiHeavyHdrMark, kMitsubishiHeavyHdrSpace, + kMitsubishiHeavyBitMark, kMitsubishiHeavyOneSpace, + kMitsubishiHeavyBitMark, kMitsubishiHeavyZeroSpace, + kMitsubishiHeavyBitMark, kMitsubishiHeavyGap, + data, nbytes, 38000, false, repeat, kDutyDefault); +} + +/// Send a MitsubishiHeavy 152-bit A/C message. +/// Status: BETA / Appears to be working. Needs testing against a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendMitsubishiHeavy152(const unsigned char data[], + const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kMitsubishiHeavy152StateLength) + return; // Not enough bytes to send a proper message. + sendMitsubishiHeavy88(data, nbytes, repeat); +} +#endif // SEND_MITSUBISHIHEAVY + +// Class for decoding and constructing MitsubishiHeavy152 AC messages. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRMitsubishiHeavy152Ac::IRMitsubishiHeavy152Ac(const uint16_t pin, + const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRMitsubishiHeavy152Ac::begin(void) { _irsend.begin(); } + +#if SEND_MITSUBISHIHEAVY +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMitsubishiHeavy152Ac::send(const uint16_t repeat) { + _irsend.sendMitsubishiHeavy152(getRaw(), kMitsubishiHeavy152StateLength, + repeat); +} +#endif // SEND_MITSUBISHIHEAVY + +/// Reset the state of the remote to a known good state/sequence. +void IRMitsubishiHeavy152Ac::stateReset(void) { + memcpy(_.raw, kMitsubishiHeavyZmsSig, kMitsubishiHeavySigLength); + for (uint8_t i = kMitsubishiHeavySigLength; + i < kMitsubishiHeavy152StateLength - 3; i += 2) _.raw[i] = 0; + _.raw[17] = 0x80; +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRMitsubishiHeavy152Ac::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] data A valid code for this protocol. +void IRMitsubishiHeavy152Ac::setRaw(const uint8_t *data) { + memcpy(_.raw, data, kMitsubishiHeavy152StateLength); +} + +/// Set the requested power state of the A/C to on. +void IRMitsubishiHeavy152Ac::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMitsubishiHeavy152Ac::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRMitsubishiHeavy152Ac::setTemp(const uint8_t temp) { + uint8_t newtemp = temp; + newtemp = ::min(newtemp, kMitsubishiHeavyMaxTemp); + newtemp = ::max(newtemp, kMitsubishiHeavyMinTemp); + _.Temp = newtemp - kMitsubishiHeavyMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRMitsubishiHeavy152Ac::getTemp(void) const { + return _.Temp + kMitsubishiHeavyMinTemp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRMitsubishiHeavy152Ac::setFan(const uint8_t speed) { + uint8_t newspeed = speed; + switch (speed) { + case kMitsubishiHeavy152FanLow: + case kMitsubishiHeavy152FanMed: + case kMitsubishiHeavy152FanHigh: + case kMitsubishiHeavy152FanMax: + case kMitsubishiHeavy152FanEcono: + case kMitsubishiHeavy152FanTurbo: break; + default: newspeed = kMitsubishiHeavy152FanAuto; + } + _.Fan = newspeed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRMitsubishiHeavy152Ac::getFan(void) const { + return _.Fan; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMitsubishiHeavy152Ac::setMode(const uint8_t mode) { + uint8_t newmode = mode; + switch (mode) { + case kMitsubishiHeavyCool: + case kMitsubishiHeavyDry: + case kMitsubishiHeavyFan: + case kMitsubishiHeavyHeat: + break; + default: + newmode = kMitsubishiHeavyAuto; + } + _.Mode = newmode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMitsubishiHeavy152Ac::getMode(void) const { + return _.Mode; +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] pos The position/mode to set the swing to. +void IRMitsubishiHeavy152Ac::setSwingVertical(const uint8_t pos) { + _.SwingV = ::min(pos, kMitsubishiHeavy152SwingVOff); +} + +/// Get the Vertical Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishiHeavy152Ac::getSwingVertical(void) const { + return _.SwingV; +} + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] pos The position/mode to set the swing to. +void IRMitsubishiHeavy152Ac::setSwingHorizontal(const uint8_t pos) { + _.SwingH = ::min(pos, kMitsubishiHeavy152SwingHOff); +} + +/// Get the Horizontal Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishiHeavy152Ac::getSwingHorizontal(void) const { + return _.SwingH; +} + +/// Set the Night (Sleep) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::setNight(const bool on) { + _.Night = on; +} + +/// Get the Night (Sleep) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::getNight(void) const { + return _.Night; +} + +/// Set the 3D mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::set3D(const bool on) { + if (on) + { _.Three = 1; _.D = 1; } + else + { _.Three = 0; _.D = 0; } +} + +/// Get the 3D mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::get3D(void) const { + return _.Three && _.D; +} + +/// Set the Silent (Quiet) mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::setSilent(const bool on) { + _.Silent = on; +} + +/// Get the Silent (Quiet) mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::getSilent(void) const { + return _.Silent; +} + +/// Set the Filter mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::setFilter(const bool on) { + _.Filter = on; +} + +/// Get the Filter mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::getFilter(void) const { + return _.Filter; +} + +/// Set the Clean mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::setClean(const bool on) { + _.Filter = on; + _.Clean = on; +} + +/// Get the Clean mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::getClean(void) const { + return _.Clean && _.Filter; +} + +/// Set the Turbo mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::setTurbo(const bool on) { + if (on) + setFan(kMitsubishiHeavy152FanTurbo); + else if (getTurbo()) setFan(kMitsubishiHeavy152FanAuto); +} + +/// Get the Turbo mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::getTurbo(void) const { + return _.Fan == kMitsubishiHeavy152FanTurbo; +} + +/// Set the Economical mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy152Ac::setEcono(const bool on) { + if (on) + setFan(kMitsubishiHeavy152FanEcono); + else if (getEcono()) setFan(kMitsubishiHeavy152FanAuto); +} + +/// Get the Economical mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy152Ac::getEcono(void) const { + return _.Fan == kMitsubishiHeavy152FanEcono; +} + +/// Verify the given state has a ZM-S signature. +/// @param[in] state A ptr to a state to be checked. +/// @return true, the check passed. Otherwise, false. +bool IRMitsubishiHeavy152Ac::checkZmsSig(const uint8_t *state) { + for (uint8_t i = 0; i < kMitsubishiHeavySigLength; i++) + if (state[i] != kMitsubishiHeavyZmsSig[i]) return false; + return true; +} + +/// Calculate the checksum for the current internal state of the remote. +/// Note: Technically it has no checksum, but does have inverted byte pairs. +void IRMitsubishiHeavy152Ac::checksum(void) { + const uint8_t kOffset = kMitsubishiHeavySigLength - 2; + invertBytePairs(_.raw + kOffset, kMitsubishiHeavy152StateLength - kOffset); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +/// Note: Technically it has no checksum, but does have inverted byte pairs. +bool IRMitsubishiHeavy152Ac::validChecksum(const uint8_t *state, + const uint16_t length) { + // Assume anything too short is fine. + if (length < kMitsubishiHeavySigLength) return true; + const uint8_t kOffset = kMitsubishiHeavySigLength - 2; + return checkInvertedBytePairs(state + kOffset, length - kOffset); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy152Ac::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kMitsubishiHeavyCool; + case stdAc::opmode_t::kHeat: return kMitsubishiHeavyHeat; + case stdAc::opmode_t::kDry: return kMitsubishiHeavyDry; + case stdAc::opmode_t::kFan: return kMitsubishiHeavyFan; + default: return kMitsubishiHeavyAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy152Ac::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + // Assumes Econo is slower than Low. + case stdAc::fanspeed_t::kMin: return kMitsubishiHeavy152FanEcono; + case stdAc::fanspeed_t::kLow: return kMitsubishiHeavy152FanLow; + case stdAc::fanspeed_t::kMedium: return kMitsubishiHeavy152FanMed; + case stdAc::fanspeed_t::kHigh: return kMitsubishiHeavy152FanHigh; + case stdAc::fanspeed_t::kMax: return kMitsubishiHeavy152FanMax; + default: return kMitsubishiHeavy152FanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy152Ac::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kAuto: return kMitsubishiHeavy152SwingVAuto; + case stdAc::swingv_t::kHighest: return kMitsubishiHeavy152SwingVHighest; + case stdAc::swingv_t::kHigh: return kMitsubishiHeavy152SwingVHigh; + case stdAc::swingv_t::kMiddle: return kMitsubishiHeavy152SwingVMiddle; + case stdAc::swingv_t::kLow: return kMitsubishiHeavy152SwingVLow; + case stdAc::swingv_t::kLowest: return kMitsubishiHeavy152SwingVLowest; + default: return kMitsubishiHeavy152SwingVOff; + } +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy152Ac::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kAuto: return kMitsubishiHeavy152SwingHAuto; + case stdAc::swingh_t::kLeftMax: return kMitsubishiHeavy152SwingHLeftMax; + case stdAc::swingh_t::kLeft: return kMitsubishiHeavy152SwingHLeft; + case stdAc::swingh_t::kMiddle: return kMitsubishiHeavy152SwingHMiddle; + case stdAc::swingh_t::kRight: return kMitsubishiHeavy152SwingHRight; + case stdAc::swingh_t::kRightMax: return kMitsubishiHeavy152SwingHRightMax; + default: return kMitsubishiHeavy152SwingHOff; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRMitsubishiHeavy152Ac::toCommonMode(const uint8_t mode) { + switch (mode) { + case kMitsubishiHeavyCool: return stdAc::opmode_t::kCool; + case kMitsubishiHeavyHeat: return stdAc::opmode_t::kHeat; + case kMitsubishiHeavyDry: return stdAc::opmode_t::kDry; + case kMitsubishiHeavyFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRMitsubishiHeavy152Ac::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kMitsubishiHeavy152FanMax: return stdAc::fanspeed_t::kMax; + case kMitsubishiHeavy152FanHigh: return stdAc::fanspeed_t::kHigh; + case kMitsubishiHeavy152FanMed: return stdAc::fanspeed_t::kMedium; + case kMitsubishiHeavy152FanLow: return stdAc::fanspeed_t::kLow; + case kMitsubishiHeavy152FanEcono: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRMitsubishiHeavy152Ac::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kMitsubishiHeavy152SwingHLeftMax: return stdAc::swingh_t::kLeftMax; + case kMitsubishiHeavy152SwingHLeft: return stdAc::swingh_t::kLeft; + case kMitsubishiHeavy152SwingHMiddle: return stdAc::swingh_t::kMiddle; + case kMitsubishiHeavy152SwingHRight: return stdAc::swingh_t::kRight; + case kMitsubishiHeavy152SwingHRightMax: return stdAc::swingh_t::kRightMax; + case kMitsubishiHeavy152SwingHOff: return stdAc::swingh_t::kOff; + default: return stdAc::swingh_t::kAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRMitsubishiHeavy152Ac::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kMitsubishiHeavy152SwingVHighest: return stdAc::swingv_t::kHighest; + case kMitsubishiHeavy152SwingVHigh: return stdAc::swingv_t::kHigh; + case kMitsubishiHeavy152SwingVMiddle: return stdAc::swingv_t::kMiddle; + case kMitsubishiHeavy152SwingVLow: return stdAc::swingv_t::kLow; + case kMitsubishiHeavy152SwingVLowest: return stdAc::swingv_t::kLowest; + case kMitsubishiHeavy152SwingVOff: return stdAc::swingv_t::kOff; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMitsubishiHeavy152Ac::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::MITSUBISHI_HEAVY_152; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = toCommonSwingV(_.SwingV); + result.swingh = toCommonSwingH(_.SwingH); + result.turbo = getTurbo(); + result.econo = getEcono(); + result.clean = getClean(); + result.quiet = _.Silent; + result.filter = _.Filter; + result.sleep = _.Night ? 0 : -1; + // Not supported. + result.light = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRMitsubishiHeavy152Ac::toString(void) const { + String result = ""; + result.reserve(180); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kMitsubishiHeavyAuto, + kMitsubishiHeavyCool, kMitsubishiHeavyHeat, + kMitsubishiHeavyDry, kMitsubishiHeavyFan); + result += addTempToString(getTemp()); + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + switch (_.Fan) { + case kMitsubishiHeavy152FanAuto: + result += kAutoStr; + break; + case kMitsubishiHeavy152FanHigh: + result += kHighStr; + break; + case kMitsubishiHeavy152FanLow: + result += kLowStr; + break; + case kMitsubishiHeavy152FanMed: + result += kMedStr; + break; + case kMitsubishiHeavy152FanMax: + result += kMaxStr; + break; + case kMitsubishiHeavy152FanEcono: + result += kEconoStr; + break; + case kMitsubishiHeavy152FanTurbo: + result += kTurboStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addSwingVToString(_.SwingV, kMitsubishiHeavy152SwingVAuto, + kMitsubishiHeavy152SwingVHighest, + kMitsubishiHeavy152SwingVHigh, + kMitsubishiHeavy152SwingVAuto, // UpperMid unused + kMitsubishiHeavy152SwingVMiddle, + kMitsubishiHeavy152SwingVAuto, // LowerMid unused + kMitsubishiHeavy152SwingVLow, + kMitsubishiHeavy152SwingVLowest, + kMitsubishiHeavy152SwingVOff, + // Below are unused. + kMitsubishiHeavy152SwingVAuto, + kMitsubishiHeavy152SwingVAuto, + kMitsubishiHeavy152SwingVAuto); + result += addSwingHToString(_.SwingH, kMitsubishiHeavy152SwingHAuto, + kMitsubishiHeavy152SwingHLeftMax, + kMitsubishiHeavy152SwingHLeft, + kMitsubishiHeavy152SwingHMiddle, + kMitsubishiHeavy152SwingHRight, + kMitsubishiHeavy152SwingHRightMax, + kMitsubishiHeavy152SwingHOff, + kMitsubishiHeavy152SwingHLeftRight, + kMitsubishiHeavy152SwingHRightLeft, + // Below are unused. + kMitsubishiHeavy152SwingHAuto, + kMitsubishiHeavy152SwingHAuto); + result += addBoolToString(_.Silent, kSilentStr); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getEcono(), kEconoStr); + result += addBoolToString(_.Night, kNightStr); + result += addBoolToString(_.Filter, kFilterStr); + result += addBoolToString(get3D(), k3DStr); + result += addBoolToString(getClean(), kCleanStr); + return result; +} + + +// Class for decoding and constructing MitsubishiHeavy88 AC messages. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRMitsubishiHeavy88Ac::IRMitsubishiHeavy88Ac(const uint16_t pin, + const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRMitsubishiHeavy88Ac::begin(void) { _irsend.begin(); } + +#if SEND_MITSUBISHIHEAVY +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMitsubishiHeavy88Ac::send(const uint16_t repeat) { + _irsend.sendMitsubishiHeavy88(getRaw(), kMitsubishiHeavy88StateLength, + repeat); +} +#endif // SEND_MITSUBISHIHEAVY + +/// Reset the state of the remote to a known good state/sequence. +void IRMitsubishiHeavy88Ac::stateReset(void) { + memcpy(_.raw, kMitsubishiHeavyZjsSig, kMitsubishiHeavySigLength); + for (uint8_t i = kMitsubishiHeavySigLength; i < kMitsubishiHeavy88StateLength; + i++) _.raw[i] = 0; +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRMitsubishiHeavy88Ac::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] data A valid code for this protocol. +void IRMitsubishiHeavy88Ac::setRaw(const uint8_t *data) { + memcpy(_.raw, data, kMitsubishiHeavy88StateLength); +} + +/// Set the requested power state of the A/C to on. +void IRMitsubishiHeavy88Ac::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMitsubishiHeavy88Ac::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy88Ac::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy88Ac::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRMitsubishiHeavy88Ac::setTemp(const uint8_t temp) { + uint8_t newtemp = temp; + newtemp = ::min(newtemp, kMitsubishiHeavyMaxTemp); + newtemp = ::max(newtemp, kMitsubishiHeavyMinTemp); + _.Temp = newtemp - kMitsubishiHeavyMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRMitsubishiHeavy88Ac::getTemp(void) const { + return _.Temp + kMitsubishiHeavyMinTemp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRMitsubishiHeavy88Ac::setFan(const uint8_t speed) { + uint8_t newspeed = speed; + switch (speed) { + case kMitsubishiHeavy88FanLow: + case kMitsubishiHeavy88FanMed: + case kMitsubishiHeavy88FanHigh: + case kMitsubishiHeavy88FanTurbo: + case kMitsubishiHeavy88FanEcono: break; + default: newspeed = kMitsubishiHeavy88FanAuto; + } + _.Fan = newspeed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRMitsubishiHeavy88Ac::getFan(void) const { + return _.Fan; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMitsubishiHeavy88Ac::setMode(const uint8_t mode) { + uint8_t newmode = mode; + switch (mode) { + case kMitsubishiHeavyCool: + case kMitsubishiHeavyDry: + case kMitsubishiHeavyFan: + case kMitsubishiHeavyHeat: + break; + default: + newmode = kMitsubishiHeavyAuto; + } + _.Mode = newmode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMitsubishiHeavy88Ac::getMode(void) const { + return _.Mode; +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] pos The position/mode to set the swing to. +void IRMitsubishiHeavy88Ac::setSwingVertical(const uint8_t pos) { + uint8_t newpos; + switch (pos) { + case kMitsubishiHeavy88SwingVAuto: + case kMitsubishiHeavy88SwingVHighest: + case kMitsubishiHeavy88SwingVHigh: + case kMitsubishiHeavy88SwingVMiddle: + case kMitsubishiHeavy88SwingVLow: + case kMitsubishiHeavy88SwingVLowest: newpos = pos; break; + default: newpos = kMitsubishiHeavy88SwingVOff; + } + _.SwingV5 = newpos; + _.SwingV7 = (newpos >> kMitsubishiHeavy88SwingVByte5Size); +} + +/// Get the Vertical Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishiHeavy88Ac::getSwingVertical(void) const { + return _.SwingV5 | (_.SwingV7 << kMitsubishiHeavy88SwingVByte5Size); +} + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] pos The position/mode to set the swing to. +void IRMitsubishiHeavy88Ac::setSwingHorizontal(const uint8_t pos) { + uint8_t newpos; + switch (pos) { + case kMitsubishiHeavy88SwingHAuto: + case kMitsubishiHeavy88SwingHLeftMax: + case kMitsubishiHeavy88SwingHLeft: + case kMitsubishiHeavy88SwingHMiddle: + case kMitsubishiHeavy88SwingHRight: + case kMitsubishiHeavy88SwingHRightMax: + case kMitsubishiHeavy88SwingHLeftRight: + case kMitsubishiHeavy88SwingHRightLeft: + case kMitsubishiHeavy88SwingH3D: newpos = pos; break; + default: newpos = kMitsubishiHeavy88SwingHOff; + } + _.SwingH1 = newpos; + _.SwingH2 = (newpos >> kMitsubishiHeavy88SwingHSize); +} + +/// Get the Horizontal Swing mode of the A/C. +/// @return The native position/mode setting. +uint8_t IRMitsubishiHeavy88Ac::getSwingHorizontal(void) const { + return _.SwingH1 | (_.SwingH2 << kMitsubishiHeavy88SwingHSize); +} + +/// Set the Turbo mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy88Ac::setTurbo(const bool on) { + if (on) + setFan(kMitsubishiHeavy88FanTurbo); + else if (getTurbo()) setFan(kMitsubishiHeavy88FanAuto); +} + +/// Get the Turbo mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy88Ac::getTurbo(void) const { + return _.Fan == kMitsubishiHeavy88FanTurbo; +} + +/// Set the Economical mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy88Ac::setEcono(const bool on) { + if (on) + setFan(kMitsubishiHeavy88FanEcono); + else if (getEcono()) setFan(kMitsubishiHeavy88FanAuto); +} + +/// Get the Economical mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy88Ac::getEcono(void) const { + return _.Fan == kMitsubishiHeavy88FanEcono; +} + +/// Set the 3D mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy88Ac::set3D(const bool on) { + if (on) + setSwingHorizontal(kMitsubishiHeavy88SwingH3D); + else if (get3D()) + setSwingHorizontal(kMitsubishiHeavy88SwingHOff); +} + +/// Get the 3D mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy88Ac::get3D(void) const { + return getSwingHorizontal() == kMitsubishiHeavy88SwingH3D; +} + +/// Set the Clean mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMitsubishiHeavy88Ac::setClean(const bool on) { + _.Clean = on; +} + +/// Get the Clean mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRMitsubishiHeavy88Ac::getClean(void) const { + return _.Clean; +} + +/// Verify the given state has a ZJ-S signature. +/// @param[in] state A ptr to a state to be checked. +/// @return true, the check passed. Otherwise, false. +bool IRMitsubishiHeavy88Ac::checkZjsSig(const uint8_t *state) { + for (uint8_t i = 0; i < kMitsubishiHeavySigLength; i++) + if (state[i] != kMitsubishiHeavyZjsSig[i]) return false; + return true; +} + +/// Calculate the checksum for the current internal state of the remote. +/// Note: Technically it has no checksum, but does have inverted byte pairs. +void IRMitsubishiHeavy88Ac::checksum(void) { + const uint8_t kOffset = kMitsubishiHeavySigLength - 2; + invertBytePairs(_.raw + kOffset, kMitsubishiHeavy88StateLength - kOffset); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +/// Note: Technically it has no checksum, but does have inverted byte pairs. +bool IRMitsubishiHeavy88Ac::validChecksum(const uint8_t *state, + const uint16_t length) { + return IRMitsubishiHeavy152Ac::validChecksum(state, length); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy88Ac::convertMode(const stdAc::opmode_t mode) { + return IRMitsubishiHeavy152Ac::convertMode(mode); +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy88Ac::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + // Assumes Econo is slower than Low. + case stdAc::fanspeed_t::kMin: return kMitsubishiHeavy88FanEcono; + case stdAc::fanspeed_t::kLow: return kMitsubishiHeavy88FanLow; + case stdAc::fanspeed_t::kMedium: return kMitsubishiHeavy88FanMed; + case stdAc::fanspeed_t::kHigh: return kMitsubishiHeavy88FanHigh; + case stdAc::fanspeed_t::kMax: return kMitsubishiHeavy88FanTurbo; + default: return kMitsubishiHeavy88FanAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy88Ac::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kAuto: return kMitsubishiHeavy88SwingVAuto; + case stdAc::swingv_t::kHighest: return kMitsubishiHeavy88SwingVHighest; + case stdAc::swingv_t::kHigh: return kMitsubishiHeavy88SwingVHigh; + case stdAc::swingv_t::kMiddle: return kMitsubishiHeavy88SwingVMiddle; + case stdAc::swingv_t::kLow: return kMitsubishiHeavy88SwingVLow; + case stdAc::swingv_t::kLowest: return kMitsubishiHeavy88SwingVLowest; + default: return kMitsubishiHeavy88SwingVOff; + } +} + +/// Convert a stdAc::swingh_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRMitsubishiHeavy88Ac::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kAuto: return kMitsubishiHeavy88SwingHAuto; + case stdAc::swingh_t::kLeftMax: return kMitsubishiHeavy88SwingHLeftMax; + case stdAc::swingh_t::kLeft: return kMitsubishiHeavy88SwingHLeft; + case stdAc::swingh_t::kMiddle: return kMitsubishiHeavy88SwingHMiddle; + case stdAc::swingh_t::kRight: return kMitsubishiHeavy88SwingHRight; + case stdAc::swingh_t::kRightMax: return kMitsubishiHeavy88SwingHRightMax; + default: return kMitsubishiHeavy88SwingHOff; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRMitsubishiHeavy88Ac::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kMitsubishiHeavy88FanTurbo: return stdAc::fanspeed_t::kMax; + case kMitsubishiHeavy88FanHigh: return stdAc::fanspeed_t::kHigh; + case kMitsubishiHeavy88FanMed: return stdAc::fanspeed_t::kMedium; + case kMitsubishiHeavy88FanLow: return stdAc::fanspeed_t::kLow; + case kMitsubishiHeavy88FanEcono: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRMitsubishiHeavy88Ac::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kMitsubishiHeavy88SwingHLeftMax: return stdAc::swingh_t::kLeftMax; + case kMitsubishiHeavy88SwingHLeft: return stdAc::swingh_t::kLeft; + case kMitsubishiHeavy88SwingHMiddle: return stdAc::swingh_t::kMiddle; + case kMitsubishiHeavy88SwingHRight: return stdAc::swingh_t::kRight; + case kMitsubishiHeavy88SwingHRightMax: return stdAc::swingh_t::kRightMax; + case kMitsubishiHeavy88SwingHOff: return stdAc::swingh_t::kOff; + default: return stdAc::swingh_t::kAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRMitsubishiHeavy88Ac::toCommonSwingV(const uint8_t pos) { + switch (pos) { + case kMitsubishiHeavy88SwingVHighest: return stdAc::swingv_t::kHighest; + case kMitsubishiHeavy88SwingVHigh: return stdAc::swingv_t::kHigh; + case kMitsubishiHeavy88SwingVMiddle: return stdAc::swingv_t::kMiddle; + case kMitsubishiHeavy88SwingVLow: return stdAc::swingv_t::kLow; + case kMitsubishiHeavy88SwingVLowest: return stdAc::swingv_t::kLowest; + case kMitsubishiHeavy88SwingVOff: return stdAc::swingv_t::kOff; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMitsubishiHeavy88Ac::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::MITSUBISHI_HEAVY_88; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = IRMitsubishiHeavy152Ac::toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = toCommonSwingV(getSwingVertical()); + result.swingh = toCommonSwingH(getSwingHorizontal()); + result.turbo = getTurbo(); + result.econo = getEcono(); + result.clean = _.Clean; + // Not supported. + result.quiet = false; + result.filter = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRMitsubishiHeavy88Ac::toString(void) const { + String result = ""; + result.reserve(140); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kMitsubishiHeavyAuto, + kMitsubishiHeavyCool, kMitsubishiHeavyHeat, + kMitsubishiHeavyDry, kMitsubishiHeavyFan); + result += addTempToString(getTemp()); + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + switch (_.Fan) { + case kMitsubishiHeavy88FanAuto: + result += kAutoStr; + break; + case kMitsubishiHeavy88FanHigh: + result += kHighStr; + break; + case kMitsubishiHeavy88FanLow: + result += kLowStr; + break; + case kMitsubishiHeavy88FanMed: + result += kMedStr; + break; + case kMitsubishiHeavy88FanEcono: + result += kEconoStr; + break; + case kMitsubishiHeavy88FanTurbo: + result += kTurboStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addSwingVToString(getSwingVertical(), kMitsubishiHeavy88SwingVAuto, + kMitsubishiHeavy88SwingVHighest, + kMitsubishiHeavy88SwingVHigh, + kMitsubishiHeavy88SwingVAuto, // UpperMid unused + kMitsubishiHeavy88SwingVMiddle, + kMitsubishiHeavy88SwingVAuto, // LowerMid unused + kMitsubishiHeavy88SwingVLow, + kMitsubishiHeavy88SwingVLowest, + kMitsubishiHeavy88SwingVOff, + // Below are unused. + kMitsubishiHeavy88SwingVAuto, + kMitsubishiHeavy88SwingVAuto, + kMitsubishiHeavy88SwingVAuto); + result += addSwingHToString(getSwingHorizontal(), + kMitsubishiHeavy88SwingHAuto, + kMitsubishiHeavy88SwingHLeftMax, + kMitsubishiHeavy88SwingHLeft, + kMitsubishiHeavy88SwingHMiddle, + kMitsubishiHeavy88SwingHRight, + kMitsubishiHeavy88SwingHRightMax, + kMitsubishiHeavy88SwingHOff, + kMitsubishiHeavy88SwingHLeftRight, + kMitsubishiHeavy88SwingHRightLeft, + kMitsubishiHeavy88SwingH3D, + // Below are unused. + kMitsubishiHeavy88SwingHAuto); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getEcono(), kEconoStr); + result += addBoolToString(get3D(), k3DStr); + result += addBoolToString(_.Clean, kCleanStr); + return result; +} + +#if DECODE_MITSUBISHIHEAVY +/// Decode the supplied Mitsubishi Heavy Industries A/C message. +/// Status: BETA / Appears to be working. Needs testing against a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// Typically kMitsubishiHeavy88Bits or kMitsubishiHeavy152Bits (def). +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeMitsubishiHeavy(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict) { + switch (nbits) { + case kMitsubishiHeavy88Bits: + case kMitsubishiHeavy152Bits: + break; + default: + return false; // Not what is expected + } + } + + uint16_t used; + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kMitsubishiHeavyHdrMark, kMitsubishiHeavyHdrSpace, + kMitsubishiHeavyBitMark, kMitsubishiHeavyOneSpace, + kMitsubishiHeavyBitMark, kMitsubishiHeavyZeroSpace, + kMitsubishiHeavyBitMark, kMitsubishiHeavyGap, true, + _tolerance, 0, false); + if (used == 0) return false; + offset += used; + // Compliance + switch (nbits) { + case kMitsubishiHeavy88Bits: + if (strict && !(IRMitsubishiHeavy88Ac::checkZjsSig(results->state) && + IRMitsubishiHeavy88Ac::validChecksum(results->state))) + return false; + results->decode_type = MITSUBISHI_HEAVY_88; + break; + case kMitsubishiHeavy152Bits: + if (strict && !(IRMitsubishiHeavy152Ac::checkZmsSig(results->state) && + IRMitsubishiHeavy152Ac::validChecksum(results->state))) + return false; + results->decode_type = MITSUBISHI_HEAVY_152; + break; + default: + return false; + } + + // Success + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_MITSUBISHIHEAVY diff --git a/src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.h b/src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.h new file mode 100644 index 000000000..702558788 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_MitsubishiHeavy.h @@ -0,0 +1,346 @@ +// Copyright 2019 David Conran + +/// @file +/// @brief Support for Mitsubishi Heavy Industry protocols. +/// Code to emulate Mitsubishi Heavy Industries A/C IR remote control units. +/// @note This code was *heavily* influenced by ToniA's great work & code, +/// but it has been written from scratch. +/// Nothing was copied other than constants and message analysis. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/660 +/// @see https://github.com/ToniA/Raw-IR-decoder-for-Arduino/blob/master/MitsubishiHeavy.cpp +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/MitsubishiHeavyHeatpumpIR.cpp + +// Supports: +// Brand: Mitsubishi Heavy Industries, Model: RLA502A700B remote (152 bit) +// Brand: Mitsubishi Heavy Industries, Model: SRKxxZM-S A/C (152 bit) +// Brand: Mitsubishi Heavy Industries, Model: SRKxxZMXA-S A/C (152 bit) +// Brand: Mitsubishi Heavy Industries, Model: RKX502A001C remote (88 bit) +// Brand: Mitsubishi Heavy Industries, Model: SRKxxZJ-S A/C (88 bit) + +#ifndef IR_MITSUBISHIHEAVY_H_ +#define IR_MITSUBISHIHEAVY_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Mitsubishi Heavy 152-bit A/C message. +union Mitsubishi152Protocol{ + uint8_t raw[kMitsubishiHeavy152StateLength]; ///< State in code form + struct { + // Byte 0~4 + uint8_t Sig[5]; + // Byte 5 + uint8_t Mode :3; + uint8_t Power :1; + uint8_t :1; + uint8_t Clean :1; + uint8_t Filter:1; + uint8_t :1; + // Byte 6 + uint8_t :8; + // Byte 7 + uint8_t Temp :4; + uint8_t :4; + // Byte 8 + uint8_t :8; + // Byte 9 + uint8_t Fan :4; + uint8_t :4; + // Byte 10 + uint8_t :8; + // Byte 11 + uint8_t :1; + uint8_t Three :1; + uint8_t :2; + uint8_t D :1; // binding with "Three" + uint8_t SwingV :3; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t SwingH :4; + uint8_t :4; + // Byte 14 + uint8_t :8; + // Byte 15 + uint8_t :6; + uint8_t Night :1; + uint8_t Silent :1; + }; +}; + +// Constants. +const uint8_t kMitsubishiHeavySigLength = 5; + +// ZMS (152 bit) +const uint8_t kMitsubishiHeavyZmsSig[kMitsubishiHeavySigLength] = { + 0xAD, 0x51, 0x3C, 0xE5, 0x1A}; + +const uint8_t kMitsubishiHeavyAuto = 0; // 0b000 +const uint8_t kMitsubishiHeavyCool = 1; // 0b001 +const uint8_t kMitsubishiHeavyDry = 2; // 0b010 +const uint8_t kMitsubishiHeavyFan = 3; // 0b011 +const uint8_t kMitsubishiHeavyHeat = 4; // 0b100 + +const uint8_t kMitsubishiHeavyMinTemp = 17; // 17C +const uint8_t kMitsubishiHeavyMaxTemp = 31; // 31C + +const uint8_t kMitsubishiHeavy152FanAuto = 0x0; // 0b0000 +const uint8_t kMitsubishiHeavy152FanLow = 0x1; // 0b0001 +const uint8_t kMitsubishiHeavy152FanMed = 0x2; // 0b0010 +const uint8_t kMitsubishiHeavy152FanHigh = 0x3; // 0b0011 +const uint8_t kMitsubishiHeavy152FanMax = 0x4; // 0b0100 +const uint8_t kMitsubishiHeavy152FanEcono = 0x6; // 0b0110 +const uint8_t kMitsubishiHeavy152FanTurbo = 0x8; // 0b1000 + +const uint8_t kMitsubishiHeavy152SwingVAuto = 0; // 0b000 +const uint8_t kMitsubishiHeavy152SwingVHighest = 1; // 0b001 +const uint8_t kMitsubishiHeavy152SwingVHigh = 2; // 0b010 +const uint8_t kMitsubishiHeavy152SwingVMiddle = 3; // 0b011 +const uint8_t kMitsubishiHeavy152SwingVLow = 4; // 0b100 +const uint8_t kMitsubishiHeavy152SwingVLowest = 5; // 0b101 +const uint8_t kMitsubishiHeavy152SwingVOff = 6; // 0b110 + +const uint8_t kMitsubishiHeavy152SwingHAuto = 0; // 0b0000 +const uint8_t kMitsubishiHeavy152SwingHLeftMax = 1; // 0b0001 +const uint8_t kMitsubishiHeavy152SwingHLeft = 2; // 0b0010 +const uint8_t kMitsubishiHeavy152SwingHMiddle = 3; // 0b0011 +const uint8_t kMitsubishiHeavy152SwingHRight = 4; // 0b0100 +const uint8_t kMitsubishiHeavy152SwingHRightMax = 5; // 0b0101 +const uint8_t kMitsubishiHeavy152SwingHRightLeft = 6; // 0b0110 +const uint8_t kMitsubishiHeavy152SwingHLeftRight = 7; // 0b0111 +const uint8_t kMitsubishiHeavy152SwingHOff = 8; // 0b1000 + +/// Native representation of a Mitsubishi Heavy 88-bit A/C message. +union Mitsubishi88Protocol{ + uint8_t raw[kMitsubishiHeavy88StateLength]; ///< State in code form + struct { + // Byte 0~4 + uint8_t Sig[5]; + // Byte 5 + uint8_t :1; + uint8_t SwingV5 :1; + uint8_t SwingH1 :2; + uint8_t :1; + uint8_t Clean :1; + uint8_t SwingH2 :2; + // Byte 6 + uint8_t :8; + // Byte 7 + uint8_t :3; + uint8_t SwingV7 :2; + uint8_t Fan :3; + // Byte 8 + uint8_t :8; + // Byte 9 + uint8_t Mode :3; + uint8_t Power :1; + uint8_t Temp :4; + }; +}; + +// ZJS (88 bit) +const uint8_t kMitsubishiHeavyZjsSig[kMitsubishiHeavySigLength] = { + 0xAD, 0x51, 0x3C, 0xD9, 0x26}; + +const uint8_t kMitsubishiHeavy88SwingHSize = 2; // Bits (per offset) +const uint8_t kMitsubishiHeavy88SwingHOff = 0b0000; +const uint8_t kMitsubishiHeavy88SwingHAuto = 0b1000; +const uint8_t kMitsubishiHeavy88SwingHLeftMax = 0b0001; +const uint8_t kMitsubishiHeavy88SwingHLeft = 0b0101; +const uint8_t kMitsubishiHeavy88SwingHMiddle = 0b1001; +const uint8_t kMitsubishiHeavy88SwingHRight = 0b1101; +const uint8_t kMitsubishiHeavy88SwingHRightMax = 0b0010; +const uint8_t kMitsubishiHeavy88SwingHRightLeft = 0b1010; +const uint8_t kMitsubishiHeavy88SwingHLeftRight = 0b0110; +const uint8_t kMitsubishiHeavy88SwingH3D = 0b1110; + +const uint8_t kMitsubishiHeavy88FanAuto = 0; // 0b000 +const uint8_t kMitsubishiHeavy88FanLow = 2; // 0b010 +const uint8_t kMitsubishiHeavy88FanMed = 3; // 0b011 +const uint8_t kMitsubishiHeavy88FanHigh = 4; // 0b100 +const uint8_t kMitsubishiHeavy88FanTurbo = 6; // 0b110 +const uint8_t kMitsubishiHeavy88FanEcono = 7; // 0b111 +const uint8_t kMitsubishiHeavy88SwingVByte5Size = 1; + + // Mask 0b111 +const uint8_t kMitsubishiHeavy88SwingVOff = 0b000; // 0 +const uint8_t kMitsubishiHeavy88SwingVAuto = 0b100; // 4 +const uint8_t kMitsubishiHeavy88SwingVHighest = 0b110; // 6 +const uint8_t kMitsubishiHeavy88SwingVHigh = 0b001; // 1 +const uint8_t kMitsubishiHeavy88SwingVMiddle = 0b011; // 3 +const uint8_t kMitsubishiHeavy88SwingVLow = 0b101; // 5 +const uint8_t kMitsubishiHeavy88SwingVLowest = 0b111; // 7 + + +// Classes + +/// Class for handling detailed Mitsubishi Heavy 152-bit A/C messages. +class IRMitsubishiHeavy152Ac { + public: + explicit IRMitsubishiHeavy152Ac(const uint16_t pin, + const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_MITSUBISHIHEAVY + void send(const uint16_t repeat = kMitsubishiHeavy152MinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_MITSUBISHIHEAVY + void begin(void); + void on(void); + void off(void); + + void setPower(const bool on); + bool getPower(void) const; + + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + + void setSwingVertical(const uint8_t pos); + uint8_t getSwingVertical(void) const; + void setSwingHorizontal(const uint8_t pos); + uint8_t getSwingHorizontal(void) const; + + void setNight(const bool on); + bool getNight(void) const; + + void set3D(const bool on); + bool get3D(void) const; + + void setSilent(const bool on); + bool getSilent(void) const; + + void setFilter(const bool on); + bool getFilter(void) const; + + void setClean(const bool on); + bool getClean(void) const; + + void setTurbo(const bool on); + bool getTurbo(void) const; + + void setEcono(const bool on); + bool getEcono(void) const; + + uint8_t* getRaw(void); + void setRaw(const uint8_t* data); + + static bool checkZmsSig(const uint8_t *state); + static bool validChecksum( + const uint8_t *state, + const uint16_t length = kMitsubishiHeavy152StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Mitsubishi152Protocol _; + void checksum(void); +}; + +/// Class for handling detailed Mitsubishi Heavy 88-bit A/C messages. +class IRMitsubishiHeavy88Ac { + public: + explicit IRMitsubishiHeavy88Ac(const uint16_t pin, + const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_MITSUBISHIHEAVY + void send(const uint16_t repeat = kMitsubishiHeavy88MinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_MITSUBISHIHEAVY + void begin(void); + void on(void); + void off(void); + + void setPower(const bool on); + bool getPower(void) const; + + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + + void setSwingVertical(const uint8_t pos); + uint8_t getSwingVertical(void) const; + void setSwingHorizontal(const uint8_t pos); + uint8_t getSwingHorizontal(void) const; + + void setTurbo(const bool on); + bool getTurbo(void) const; + + void setEcono(const bool on); + bool getEcono(void) const; + + void set3D(const bool on); + bool get3D(void) const; + + void setClean(const bool on); + bool getClean(void) const; + + uint8_t* getRaw(void); + void setRaw(const uint8_t* data); + + static bool checkZjsSig(const uint8_t *state); + static bool validChecksum( + const uint8_t *state, + const uint16_t length = kMitsubishiHeavy88StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Mitsubishi88Protocol _; + void checksum(void); +}; +#endif // IR_MITSUBISHIHEAVY_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Multibrackets.cpp b/src/libraries/IRremoteESP8266/src/ir_Multibrackets.cpp new file mode 100644 index 000000000..2367b49bc --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Multibrackets.cpp @@ -0,0 +1,115 @@ +// Copyright 2020 David Conran + +/// @file +/// @brief Support for Multibrackets protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1103 +/// @see http://info.multibrackets.com/data/common/manuals/4500_code.pdf + +// Supports: +// Brand: Multibrackets, Model: Motorized Swing mount large - 4500 + +#include "IRrecv.h" +#include "IRsend.h" + +const uint16_t kMultibracketsTick = 5000; // uSeconds +const uint16_t kMultibracketsHdrMark = 3 * kMultibracketsTick; // uSeconds +const uint16_t kMultibracketsFooterSpace = 6 * kMultibracketsTick; // uSeconds +const uint8_t kMultibracketsTolerance = 5; // Percent +const uint16_t kMultibracketsFreq = 38000; // Hertz + +#if SEND_MULTIBRACKETS +/// Send a Multibrackets formatted message. +/// Status: BETA / Appears to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendMultibrackets(uint64_t data, uint16_t nbits, uint16_t repeat) { + enableIROut(kMultibracketsFreq); + for (uint16_t r = 0; r <= repeat; r++) { + uint16_t bits = nbits; + // Header + mark(kMultibracketsHdrMark); + // Data + // Send 0's until we get down to a bit size we can actually manage. + while (bits > sizeof(data) * 8) { + space(kMultibracketsTick); + bits--; + } + // Send the supplied data. + for (uint64_t mask = 1ULL << (bits - 1); mask; mask >>= 1) + if (data & mask) // Send a 1 + mark(kMultibracketsTick); + else // Send a 0 + space(kMultibracketsTick); + // Footer + space(kMultibracketsFooterSpace); + } +} +#endif // SEND_MULTIBRACKETS + +#if DECODE_MULTIBRACKETS +/// Decode the Multibrackets message. +/// Status: BETA / Appears to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeMultibrackets(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance + if (strict && nbits != kMultibracketsBits) + return false; // Doesn't match our protocol defn. + + // Check there is enough unprocessed buffer left. + if (results->rawlen < offset) return false; + + // Header + int32_t remaining = *(results->rawbuf + offset); + if (!matchAtLeast(remaining, kMultibracketsHdrMark, kMultibracketsTolerance)) + return false; + remaining -= (kMultibracketsHdrMark / kRawTick); // Remove the header. + + // We are done with the header. Onto the data. + bool bit = true; + uint16_t bitsSoFar = 0; + uint64_t data = 0; + // Keep going till we run out of message or expected bits. + while (offset <= results->rawlen && bitsSoFar < nbits) { + // Have we finished processing this rawbuf value yet? + if (remaining <= 0) { // No more possible "bits" left in this value. + // Invert the bit for next time, and move along the rawbuf. + bit = !bit; + offset++; + // Load the next data point if there is one. + if (offset <= results->rawlen) remaining = *(results->rawbuf + offset); + } else { // Look for more bits in this entry. + if (matchAtLeast(remaining, kMultibracketsTick, + kMultibracketsTolerance)) { // There is! + data <<= 1; + data += bit; + bitsSoFar++; + } + remaining -= (kMultibracketsTick / kRawTick); // Remove the "bit". + } + } + + // Compliance + if (bitsSoFar != nbits) return false; + + // Footer + if (results->rawlen <= offset && !matchAtLeast(*(results->rawbuf + offset), + kMultibracketsFooterSpace, + kMultibracketsTolerance)) + return false; + + // Success + results->decode_type = decode_type_t::MULTIBRACKETS; + results->value = data; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_MULTIBRACKETS diff --git a/src/libraries/IRremoteESP8266/src/ir_NEC.cpp b/src/libraries/IRremoteESP8266/src/ir_NEC.cpp new file mode 100644 index 000000000..3b4cc939d --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_NEC.cpp @@ -0,0 +1,140 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017 David Conran + +/// @file +/// @brief Support for NEC (Renesas) protocols. +/// NEC originally added from https://github.com/shirriff/Arduino-IRremote/ +/// @see http://www.sbprojects.net/knowledge/ir/nec.php + +#define __STDC_LIMIT_MACROS +#include "ir_NEC.h" +#include +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// This protocol is used by a lot of other protocols, hence the long list. +#if (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO || \ + SEND_MIDEA24) + +/// Send a raw NEC(Renesas) formatted message. +/// Status: STABLE / Known working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol appears to have no header. +/// @see http://www.sbprojects.net/knowledge/ir/nec.php +void IRsend::sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendGeneric(kNecHdrMark, kNecHdrSpace, kNecBitMark, kNecOneSpace, kNecBitMark, + kNecZeroSpace, kNecBitMark, kNecMinGap, kNecMinCommandLength, + data, nbits, 38, true, 0, // Repeats are handled later. + 33); + // Optional command repeat sequence. + if (repeat) + sendGeneric(kNecHdrMark, kNecRptSpace, 0, 0, 0, 0, // No actual data sent. + kNecBitMark, kNecMinGap, kNecMinCommandLength, 0, + 0, // No data to be sent. + 38, true, repeat - 1, // We've already sent a one message. + 33); +} + +/// Calculate the raw NEC data based on address and command. +/// Status: STABLE / Expected to work. +/// @param[in] address An address value. +/// @param[in] command An 8-bit command value. +/// @return A raw 32-bit NEC message suitable for use with `sendNEC()`. +/// @see http://www.sbprojects.net/knowledge/ir/nec.php +uint32_t IRsend::encodeNEC(uint16_t address, uint16_t command) { + command &= 0xFF; // We only want the least significant byte of command. + // sendNEC() sends MSB first, but protocol says this is LSB first. + command = reverseBits(command, 8); + command = (command << 8) + (command ^ 0xFF); // Calculate the new command. + if (address > 0xFF) { // Is it Extended NEC? + address = reverseBits(address, 16); + return ((address << 16) + command); // Extended. + } else { + address = reverseBits(address, 8); + return (address << 24) + ((address ^ 0xFF) << 16) + command; // Normal. + } +} +#endif // (SEND_NEC || SEND_SHERWOOD || SEND_AIWA_RC_T501 || SEND_SANYO || + // SEND_MIDEA24) + +// This protocol is used by a lot of other protocols, hence the long list. +#if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || DECODE_SANYO) +/// Decode the supplied NEC (Renesas) message. +/// Status: STABLE / Known good. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note NEC protocol has three variants/forms. +/// Normal: an 8 bit address & an 8 bit command in 32 bit data form. +/// i.e. address + inverted(address) + command + inverted(command) +/// Extended: a 16 bit address & an 8 bit command in 32 bit data form. +/// i.e. address + command + inverted(command) +/// Repeat: a 0-bit code. i.e. No data bits. Just the header + footer. +/// @see http://www.sbprojects.net/knowledge/ir/nec.php +bool IRrecv::decodeNEC(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < kNecRptLength + offset - 1) + return false; // Can't possibly be a valid NEC message. + if (strict && nbits != kNECBits) + return false; // Not strictly an NEC message. + + uint64_t data = 0; + + // Header - All NEC messages have this Header Mark. + if (!matchMark(results->rawbuf[offset++], kNecHdrMark)) return false; + // Check if it is a repeat code. + if (matchSpace(results->rawbuf[offset], kNecRptSpace) && + matchMark(results->rawbuf[offset + 1], kNecBitMark) && + (offset + 2 <= results->rawlen || + matchAtLeast(results->rawbuf[offset + 2], kNecMinGap))) { + results->value = kRepeat; + results->decode_type = NEC; + results->bits = 0; + results->address = 0; + results->command = 0; + results->repeat = true; + return true; + } + + // Match Header (cont.) + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + 0, kNecHdrSpace, + kNecBitMark, kNecOneSpace, + kNecBitMark, kNecZeroSpace, + kNecBitMark, kNecMinGap, true)) return false; + // Compliance + // Calculate command and optionally enforce integrity checking. + uint8_t command = (data & 0xFF00) >> 8; + // Command is sent twice, once as plain and then inverted. + if ((command ^ 0xFF) != (data & 0xFF)) { + if (strict) return false; // Command integrity failed. + command = 0; // The command value isn't valid, so default to zero. + } + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = NEC; + // NEC command and address are technically in LSB first order so the + // final versions have to be reversed. + results->command = reverseBits(command, 8); + // Normal NEC protocol has an 8 bit address sent, followed by it inverted. + uint8_t address = (data & 0xFF000000) >> 24; + uint8_t address_inverted = (data & 0x00FF0000) >> 16; + if (address == (address_inverted ^ 0xFF)) + // Inverted, so it is normal NEC protocol. + results->address = reverseBits(address, 8); + else // Not inverted, so must be Extended NEC protocol, thus 16 bit address. + results->address = reverseBits((data >> 16) & UINT16_MAX, 16); + return true; +} +#endif // (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || + // DECODE_SANYO) diff --git a/src/libraries/IRremoteESP8266/src/ir_NEC.h b/src/libraries/IRremoteESP8266/src/ir_NEC.h new file mode 100644 index 000000000..c9b0a6ec1 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_NEC.h @@ -0,0 +1,80 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017, 2018 David Conran + +/// @file +/// @brief Support for NEC (Renesas) protocols. +/// NEC originally added from https://github.com/shirriff/Arduino-IRremote/ +/// @see http://www.sbprojects.net/knowledge/ir/nec.php + +// Supports: +// Brand: Yamaha, Model: RAV561 remote +// Brand: Yamaha, Model: RXV585B A/V Receiver +// Brand: Aloka, Model: SleepyLights LED Lamp +// Brand: Toshiba, Model: 42TL838 LCD TV +// Brand: Duux, Model: Blizzard Smart 10K / DXMA04 A/C +// Brand: Duux, Model: YJ-A081 TR Remote +// Brand: Silan Microelectronics, Model: SC6121-001 IC +// Brand: BBK, Model: SP550S 5.1 sound system +// Brand: Tanix, Model: TX3 mini Android TV Box + +#ifndef IR_NEC_H_ +#define IR_NEC_H_ + +#include +#include "IRremoteESP8266.h" + +// Constants +const uint16_t kNecTick = 560; +const uint16_t kNecHdrMarkTicks = 16; +const uint16_t kNecHdrMark = kNecHdrMarkTicks * kNecTick; +const uint16_t kNecHdrSpaceTicks = 8; +const uint16_t kNecHdrSpace = kNecHdrSpaceTicks * kNecTick; +const uint16_t kNecBitMarkTicks = 1; +const uint16_t kNecBitMark = kNecBitMarkTicks * kNecTick; +const uint16_t kNecOneSpaceTicks = 3; +const uint16_t kNecOneSpace = kNecOneSpaceTicks * kNecTick; +const uint16_t kNecZeroSpaceTicks = 1; +const uint16_t kNecZeroSpace = kNecZeroSpaceTicks * kNecTick; +const uint16_t kNecRptSpaceTicks = 4; +const uint16_t kNecRptSpace = kNecRptSpaceTicks * kNecTick; +const uint16_t kNecRptLength = 4; +const uint16_t kNecMinCommandLengthTicks = 193; +const uint32_t kNecMinCommandLength = kNecMinCommandLengthTicks * kNecTick; +const uint32_t kNecMinGap = + kNecMinCommandLength - + (kNecHdrMark + kNecHdrSpace + kNECBits * (kNecBitMark + kNecOneSpace) + + kNecBitMark); +const uint16_t kNecMinGapTicks = + kNecMinCommandLengthTicks - + (kNecHdrMarkTicks + kNecHdrSpaceTicks + + kNECBits * (kNecBitMarkTicks + kNecOneSpaceTicks) + kNecBitMarkTicks); + +// IR codes and structure for kids ALOKA SleepyLights LED Lamp. +// https://aloka-designs.com/ +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1004 +// +// May be useful for someone wanting to control the lamp. +// +// The lamp is toggled On and Off with the same power button. +// The colour, when selected, is the brightest and there are 4 levels of +// brightness that decrease on each send of the colour. A fifth send of the +// colour resets to brightest again. +// +// Remote buttons defined left to right, top line to bottom line on the remote. +const uint32_t kAlokaPower = 0xFF609F; +const uint32_t kAlokaLedWhite = 0xFF906F; +const uint32_t kAlokaLedGreen = 0xFF9867; +const uint32_t kAlokaLedBlue = 0xFFD827; +const uint32_t kAlokaLedPinkRed = 0xFF8877; +const uint32_t kAlokaLedRed = 0xFFA857; +const uint32_t kAlokaLedLightGreen = 0xFFE817; +const uint32_t kAlokaLedMidBlue = 0xFF48B7; +const uint32_t kAlokaLedPink = 0xFF6897; +const uint32_t kAlokaLedOrange = 0xFFB24D; +const uint32_t kAlokaLedYellow = 0xFF00FF; +const uint32_t kAlokaNightFade = 0xFF50AF; +const uint32_t kAlokaNightTimer = 0xFF7887; +const uint32_t kAlokaLedRainbow = 0xFF708F; +// Didn't have a better description for it... +const uint32_t kAlokaLedTreeGrow = 0xFF58A7; +#endif // IR_NEC_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Neoclima.cpp b/src/libraries/IRremoteESP8266/src/ir_Neoclima.cpp new file mode 100644 index 000000000..ba49b3277 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Neoclima.cpp @@ -0,0 +1,609 @@ +// Copyright 2019-2020 David Conran + +/// @file +/// @brief Support for Neoclima protocols. +/// Analysis by crankyoldgit, AndreyShpilevoy, & griffisc306 +/// Code by crankyoldgit +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/764 +/// @see https://drive.google.com/file/d/1kjYk4zS9NQcMQhFkak-L4mp4UuaAIesW/view +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1260 + +#include "ir_Neoclima.h" +// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kNeoclimaHdrMark = 6112; +const uint16_t kNeoclimaHdrSpace = 7391; +const uint16_t kNeoclimaBitMark = 537; +const uint16_t kNeoclimaOneSpace = 1651; +const uint16_t kNeoclimaZeroSpace = 571; +const uint32_t kNeoclimaMinGap = kDefaultMessageGap; + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; + +#if SEND_NEOCLIMA +/// Send a Neoclima message. +/// Status: STABLE / Known to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendNeoclima(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + // Set IR carrier frequency + enableIROut(38); + + for (uint16_t i = 0; i <= repeat; i++) { + sendGeneric(kNeoclimaHdrMark, kNeoclimaHdrSpace, + kNeoclimaBitMark, kNeoclimaOneSpace, + kNeoclimaBitMark, kNeoclimaZeroSpace, + kNeoclimaBitMark, kNeoclimaHdrSpace, + data, nbytes, 38000, false, 0, // Repeats are already handled. + 50); + // Extra footer. + mark(kNeoclimaBitMark); + space(kNeoclimaMinGap); + } +} +#endif // SEND_NEOCLIMA + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRNeoclimaAc::IRNeoclimaAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { + stateReset(); +} + +/// Reset the state of the remote to a known good state/sequence. +void IRNeoclimaAc::stateReset(void) { + static const uint8_t kReset[kNeoclimaStateLength] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x2A, 0xA5}; + setRaw(kReset); +} + +/// Set up hardware to be able to send a message. +void IRNeoclimaAc::begin(void) { _irsend.begin(); } + +/// Calculate the checksum for a given state. +/// @param[in] state The array to calc the checksum of. +/// @param[in] length The length/size of the array. +/// @return The calculated checksum value. +uint8_t IRNeoclimaAc::calcChecksum(const uint8_t state[], + const uint16_t length) { + if (length == 0) return state[0]; + return sumBytes(state, length - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRNeoclimaAc::validChecksum(const uint8_t state[], const uint16_t length) { + if (length < 2) + return true; // No checksum to compare with. Assume okay. + return (state[length - 1] == calcChecksum(state, length)); +} + +/// Calculate & update the checksum for the internal state. +/// @param[in] length The length/size of the internal state. +void IRNeoclimaAc::checksum(uint16_t length) { + if (length < 2) return; + _.Sum = calcChecksum(_.raw, length); +} + +#if SEND_NEOCLIMA +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRNeoclimaAc::send(const uint16_t repeat) { + _irsend.sendNeoclima(getRaw(), kNeoclimaStateLength, repeat); +} +#endif // SEND_NEOCLIMA + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRNeoclimaAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length/size of the new_code array. +void IRNeoclimaAc::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kNeoclimaStateLength)); +} + +/// Set the Button/Command pressed setting of the A/C. +/// @param[in] button The value of the button/command that was pressed. +void IRNeoclimaAc::setButton(const uint8_t button) { + switch (button) { + case kNeoclimaButtonPower: + case kNeoclimaButtonMode: + case kNeoclimaButtonTempUp: + case kNeoclimaButtonTempDown: + case kNeoclimaButtonSwing: + case kNeoclimaButtonFanSpeed: + case kNeoclimaButtonAirFlow: + case kNeoclimaButtonHold: + case kNeoclimaButtonSleep: + case kNeoclimaButtonLight: + case kNeoclimaButtonEye: + case kNeoclimaButtonFollow: + case kNeoclimaButtonIon: + case kNeoclimaButtonFresh: + case kNeoclimaButton8CHeat: + case kNeoclimaButtonTurbo: + case kNeoclimaButtonEcono: + case kNeoclimaButtonTempUnit: + _.Button = button; + break; + default: + _.Button = kNeoclimaButtonPower; + } +} + +/// Get the Button/Command setting of the A/C. +/// @return The value of the button/command that was pressed. +uint8_t IRNeoclimaAc::getButton(void) const { + return _.Button; +} + +/// Set the requested power state of the A/C to on. +void IRNeoclimaAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRNeoclimaAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setPower(const bool on) { + _.Button = kNeoclimaButtonPower; + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getPower(void) const { + return _.Power; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRNeoclimaAc::setMode(const uint8_t mode) { + switch (mode) { + case kNeoclimaDry: + // In this mode fan speed always LOW + setFan(kNeoclimaFanLow); + // FALL THRU + case kNeoclimaAuto: + case kNeoclimaCool: + case kNeoclimaFan: + case kNeoclimaHeat: + _.Mode = mode; + _.Button = kNeoclimaButtonMode; + break; + default: + // If we get an unexpected mode, default to AUTO. + _.Mode = kNeoclimaAuto; + _.Button = kNeoclimaButtonMode; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRNeoclimaAc::getMode(void) const { + return _.Mode; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRNeoclimaAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kNeoclimaCool; + case stdAc::opmode_t::kHeat: return kNeoclimaHeat; + case stdAc::opmode_t::kDry: return kNeoclimaDry; + case stdAc::opmode_t::kFan: return kNeoclimaFan; + default: return kNeoclimaAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRNeoclimaAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kNeoclimaCool: return stdAc::opmode_t::kCool; + case kNeoclimaHeat: return stdAc::opmode_t::kHeat; + case kNeoclimaDry: return stdAc::opmode_t::kDry; + case kNeoclimaFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +/// @param[in] celsius Use Fahrenheit (false) or Celsius (true). +void IRNeoclimaAc::setTemp(const uint8_t temp, const bool celsius) { + uint8_t oldtemp = getTemp(); + _.UseFah = !celsius; + const uint8_t min_temp = celsius ? kNeoclimaMinTempC : kNeoclimaMinTempF; + const uint8_t max_temp = celsius ? kNeoclimaMaxTempC : kNeoclimaMaxTempF; + const uint8_t newtemp = ::min(max_temp, ::max(min_temp, temp)); + if (oldtemp > newtemp) + _.Button = kNeoclimaButtonTempDown; + else if (newtemp > oldtemp) + _.Button = kNeoclimaButtonTempUp; + _.Temp = newtemp - min_temp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees. +/// @note The units of the temperature (F/C) is determined by `getTempUnits()`. +uint8_t IRNeoclimaAc::getTemp(void) const { + const uint8_t min_temp = getTempUnits() ? kNeoclimaMinTempC + : kNeoclimaMinTempF; + return _.Temp + min_temp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. 0-3, 0 is auto, 1-3 is the speed +void IRNeoclimaAc::setFan(const uint8_t speed) { + _.Button = kNeoclimaButtonFanSpeed; + if (_.Mode == kNeoclimaDry) { // Dry mode only allows low speed. + _.Fan = kNeoclimaFanLow; + return; + } + switch (speed) { + case kNeoclimaFanAuto: + case kNeoclimaFanHigh: + case kNeoclimaFanMed: + case kNeoclimaFanLow: + _.Fan = speed; + break; + default: + // If we get an unexpected speed, default to Auto. + _.Fan = kNeoclimaFanAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRNeoclimaAc::getFan(void) const { + return _.Fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRNeoclimaAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kNeoclimaFanLow; + case stdAc::fanspeed_t::kMedium: return kNeoclimaFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kNeoclimaFanHigh; + default: return kNeoclimaFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRNeoclimaAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kNeoclimaFanHigh: return stdAc::fanspeed_t::kMax; + case kNeoclimaFanMed: return stdAc::fanspeed_t::kMedium; + case kNeoclimaFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setSleep(const bool on) { + _.Button = kNeoclimaButtonSleep; + _.Sleep = on; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getSleep(void) const { + return _.Sleep; +} + +/// Set the vertical swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setSwingV(const bool on) { + _.Button = kNeoclimaButtonSwing; + _.SwingV = (on ? kNeoclimaSwingVOn : kNeoclimaSwingVOff); +} + +/// Get the vertical swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getSwingV(void) const { + return _.SwingV == kNeoclimaSwingVOn; +} + +/// Set the horizontal swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setSwingH(const bool on) { + _.Button = kNeoclimaButtonAirFlow; + _.SwingH = !on; // Cleared when `on` +} + +/// Get the horizontal swing (Air Flow) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getSwingH(void) const { + return !_.SwingH; +} + +/// Set the Turbo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setTurbo(const bool on) { + _.Button = kNeoclimaButtonTurbo; + _.Turbo = on; +} + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getTurbo(void) const { + return _.Turbo; +} + +/// Set the Economy (Energy Saver) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setEcono(const bool on) { + _.Button = kNeoclimaButtonEcono; + _.Econo = on; +} + +/// Get the Economy (Energy Saver) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getEcono(void) const { + return _.Econo; +} + +/// Set the Fresh (air) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setFresh(const bool on) { + _.Button = kNeoclimaButtonFresh; + _.Fresh = on; +} + +/// Get the Fresh (air) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getFresh(void) const { + return _.Fresh; +} + +/// Set the Hold setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setHold(const bool on) { + _.Button = kNeoclimaButtonHold; + _.Hold = on; +} + +/// Get the Hold setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getHold(void) const { + return _.Hold; +} + +/// Set the Ion (filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setIon(const bool on) { + _.Button = kNeoclimaButtonIon; + _.Ion = on; +} + +/// Get the Ion (filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getIon(void) const { + return _.Ion; +} + +/// Set the Light(LED display) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setLight(const bool on) { + _.Button = kNeoclimaButtonLight; + _.Light = on; +} + +/// Get the Light (LED display) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getLight(void) const { + return _.Light; +} + +/// Set the 8°C Heat setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note This feature maintains the room temperature steadily at 8°C and +/// prevents the room from freezing by activating the heating operation +/// automatically when nobody is at home over a longer period during severe +/// winter. +void IRNeoclimaAc::set8CHeat(const bool on) { + _.Button = kNeoclimaButton8CHeat; + _.CHeat = on; +} + +/// Get the 8°C Heat setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::get8CHeat(void) const { + return _.CHeat; +} + +/// Set the Eye (Sensor) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRNeoclimaAc::setEye(const bool on) { + _.Button = kNeoclimaButtonEye; + _.Eye = on; +} + +/// Get the Eye (Sensor) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getEye(void) const { + return _.Eye; +} + +/// Is the A/C unit using Fahrenheit or Celsius for temperature units. +/// @return false, Fahrenheit. true, Celsius. +bool IRNeoclimaAc::getTempUnits(void) const { + return !_.UseFah; +} + +/* DISABLED + TODO(someone): Work out why "on" is either 0x5D or 0x5F +void IRNeoclimaAc::setFollow(const bool on) { + setButton(kNeoclimaButtonFollow); + if (on) + remote_state[8] = kNeoclimaFollowMe; + else + remote_state[8] = 0; +} +*/ + +/// Get the Follow Me setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRNeoclimaAc::getFollow(void) const { + return (_.Follow & kNeoclimaFollowMe) == kNeoclimaFollowMe; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRNeoclimaAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::NEOCLIMA; + result.model = -1; // No models used. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = getTempUnits(); + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = getSwingV() ? stdAc::swingv_t::kAuto + : stdAc::swingv_t::kOff; + result.swingh = getSwingH() ? stdAc::swingh_t::kAuto + : stdAc::swingh_t::kOff; + result.turbo = _.Turbo; + result.econo = _.Econo; + result.light = _.Light; + result.filter = _.Ion; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRNeoclimaAc::toString(void) const { + String result = ""; + result.reserve(110); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kNeoclimaAuto, kNeoclimaCool, + kNeoclimaHeat, kNeoclimaDry, kNeoclimaFan); + result += addTempToString(getTemp(), getTempUnits()); + result += addFanToString(_.Fan, kNeoclimaFanHigh, kNeoclimaFanLow, + kNeoclimaFanAuto, kNeoclimaFanAuto, kNeoclimaFanMed); + result += addBoolToString(getSwingV(), kSwingVStr); + result += addBoolToString(getSwingH(), kSwingHStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addBoolToString(_.Hold, kHoldStr); + result += addBoolToString(_.Ion, kIonStr); + result += addBoolToString(_.Eye, kEyeStr); + result += addBoolToString(_.Light, kLightStr); + result += addBoolToString(getFollow(), kFollowStr); + result += addBoolToString(_.CHeat, k8CHeatStr); + result += addBoolToString(_.Fresh, kFreshStr); + result += addIntToString(_.Button, kButtonStr); + result += kSpaceLBraceStr; + switch (_.Button) { + case kNeoclimaButtonPower: result += kPowerStr; break; + case kNeoclimaButtonMode: result += kModeStr; break; + case kNeoclimaButtonTempUp: result += kTempUpStr; break; + case kNeoclimaButtonTempDown: result += kTempDownStr; break; + case kNeoclimaButtonSwing: result += kSwingStr; break; + case kNeoclimaButtonFanSpeed: result += kFanStr; break; + case kNeoclimaButtonAirFlow: result += kAirFlowStr; break; + case kNeoclimaButtonHold: result += kHoldStr; break; + case kNeoclimaButtonSleep: result += kSleepStr; break; + case kNeoclimaButtonLight: result += kLightStr; break; + case kNeoclimaButtonEye: result += kEyeStr; break; + case kNeoclimaButtonFollow: result += kFollowStr; break; + case kNeoclimaButtonIon: result += kIonStr; break; + case kNeoclimaButtonFresh: result += kFreshStr; break; + case kNeoclimaButton8CHeat: result += k8CHeatStr; break; + case kNeoclimaButtonTurbo: result += kTurboStr; break; + case kNeoclimaButtonEcono: result += kEconoStr; break; + case kNeoclimaButtonTempUnit: result += kCelsiusFahrenheitStr; break; + default: result += kUnknownStr; + } + result += ')'; + return result; +} + +#if DECODE_NEOCLIMA +/// Decode the supplied Neoclima message. +/// Status: STABLE / Known working +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeNeoclima(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance + if (strict && nbits != kNeoclimaBits) + return false; // Incorrect nr. of bits per spec. + + // Match Main Header + Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kNeoclimaHdrMark, kNeoclimaHdrSpace, + kNeoclimaBitMark, kNeoclimaOneSpace, + kNeoclimaBitMark, kNeoclimaZeroSpace, + kNeoclimaBitMark, kNeoclimaHdrSpace, false, + _tolerance, 0, false); + if (!used) return false; + offset += used; + // Extra footer. + uint64_t unused; + if (!matchGeneric(results->rawbuf + offset, &unused, + results->rawlen - offset, 0, 0, 0, 0, 0, 0, 0, + kNeoclimaBitMark, kNeoclimaHdrSpace, true)) return false; + + // Compliance + if (strict) { + // Check we got a valid checksum. + if (!IRNeoclimaAc::validChecksum(results->state, nbits / 8)) return false; + } + + // Success + results->decode_type = decode_type_t::NEOCLIMA; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_NEOCLIMA diff --git a/src/libraries/IRremoteESP8266/src/ir_Neoclima.h b/src/libraries/IRremoteESP8266/src/ir_Neoclima.h new file mode 100644 index 000000000..5284d4087 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Neoclima.h @@ -0,0 +1,198 @@ +// Copyright 2019 David Conran + +/// @file +/// @brief Support for Neoclima protocols. +/// Analysis by crankyoldgit & AndreyShpilevoy +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/764 +/// @see https://drive.google.com/file/d/1kjYk4zS9NQcMQhFkak-L4mp4UuaAIesW/view + +// Supports: +// Brand: Neoclima, Model: NS-09AHTI A/C +// Brand: Neoclima, Model: ZH/TY-01 remote +// Brand: Soleus Air, Model: TTWM1-10-01 A/C +// Brand: Soleus Air, Model: ZCF/TL-05 remote + +#ifndef IR_NEOCLIMA_H_ +#define IR_NEOCLIMA_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Neoclima A/C message. +union NeoclimaProtocol { + uint8_t raw[kNeoclimaStateLength]; ///< State of the remote in code. + struct { + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t :1; + uint8_t CHeat :1; + uint8_t Ion :1; + uint8_t :5; + // Byte 2 + uint8_t :8; + // Byte 3 + uint8_t Light :1; + uint8_t :1; + uint8_t Hold :1; + uint8_t Turbo :1; + uint8_t Econo :1; + uint8_t :1; + uint8_t Eye :1; + uint8_t :1; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t Button :5; + uint8_t :2; + uint8_t Fresh :1; + // Byte 6 + uint8_t :8; + // Byte 7 + uint8_t Sleep :1; + uint8_t Power :1; + uint8_t SwingV :2; + uint8_t SwingH :1; + uint8_t Fan :2; + uint8_t UseFah :1; + // Byte 8 + uint8_t Follow :8; + // Byte 9 + uint8_t Temp :5; + uint8_t Mode :3; + // Byte 10 + uint8_t :8; + // Byte 11 + uint8_t Sum :8; + }; +}; + +// Constants + +const uint8_t kNeoclimaButtonPower = 0x00; +const uint8_t kNeoclimaButtonMode = 0x01; +const uint8_t kNeoclimaButtonTempUp = 0x02; +const uint8_t kNeoclimaButtonTempDown = 0x03; +const uint8_t kNeoclimaButtonSwing = 0x04; +const uint8_t kNeoclimaButtonFanSpeed = 0x05; +const uint8_t kNeoclimaButtonAirFlow = 0x07; +const uint8_t kNeoclimaButtonHold = 0x08; +const uint8_t kNeoclimaButtonSleep = 0x09; +const uint8_t kNeoclimaButtonTurbo = 0x0A; +const uint8_t kNeoclimaButtonLight = 0x0B; +const uint8_t kNeoclimaButtonEcono = 0x0D; +const uint8_t kNeoclimaButtonEye = 0x0E; +const uint8_t kNeoclimaButtonFollow = 0x13; +const uint8_t kNeoclimaButtonIon = 0x14; +const uint8_t kNeoclimaButtonFresh = 0x15; +const uint8_t kNeoclimaButton8CHeat = 0x1D; +const uint8_t kNeoclimaButtonTempUnit = 0x1E; + +const uint8_t kNeoclimaSwingVOn = 0b01; +const uint8_t kNeoclimaSwingVOff = 0b10; +const uint8_t kNeoclimaFanAuto = 0b00; +const uint8_t kNeoclimaFanHigh = 0b01; +const uint8_t kNeoclimaFanMed = 0b10; +const uint8_t kNeoclimaFanLow = 0b11; + +const uint8_t kNeoclimaFollowMe = 0x5D; // Also 0x5F + +const uint8_t kNeoclimaMinTempC = 16; // 16C +const uint8_t kNeoclimaMaxTempC = 32; // 32C +const uint8_t kNeoclimaMinTempF = 61; // 61F +const uint8_t kNeoclimaMaxTempF = 90; // 90F +const uint8_t kNeoclimaAuto = 0b000; +const uint8_t kNeoclimaCool = 0b001; +const uint8_t kNeoclimaDry = 0b010; +const uint8_t kNeoclimaFan = 0b011; +const uint8_t kNeoclimaHeat = 0b100; + +// Classes +/// Class for handling detailed Neoclima A/C messages. +class IRNeoclimaAc { + public: + explicit IRNeoclimaAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_NEOCLIMA + void send(const uint16_t repeat = kNeoclimaMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_NEOCLIMA + void begin(void); + void setButton(const uint8_t button); + uint8_t getButton(void) const; + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setTemp(const uint8_t temp, const bool celsius = true); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + void setSwingH(const bool on); + bool getSwingH(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setFresh(const bool on); + bool getFresh(void) const; + void setHold(const bool on); + bool getHold(void) const; + void setIon(const bool on); + bool getIon(void) const; + void setLight(const bool on); + bool getLight(void) const; + void set8CHeat(const bool on); + bool get8CHeat(void) const; + void setEye(const bool on); + bool getEye(void) const; + bool getTempUnits(void) const; + // DISABLED: See TODO in ir_Neoclima.cpp + // void setFollow(const bool on); + bool getFollow(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kNeoclimaStateLength); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kNeoclimaStateLength); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kNeoclimaStateLength); + String toString(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + NeoclimaProtocol _; + void checksum(const uint16_t length = kNeoclimaStateLength); +}; + +#endif // IR_NEOCLIMA_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Nikai.cpp b/src/libraries/IRremoteESP8266/src/ir_Nikai.cpp new file mode 100644 index 000000000..ee49c301b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Nikai.cpp @@ -0,0 +1,74 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017 David Conran + +/// @file +/// @brief Nikai +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/309 + +// Supports: +// Brand: Nikai, Model: Unknown LCD TV + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kNikaiTick = 500; +const uint16_t kNikaiHdrMarkTicks = 8; +const uint16_t kNikaiHdrMark = kNikaiHdrMarkTicks * kNikaiTick; +const uint16_t kNikaiHdrSpaceTicks = 8; +const uint16_t kNikaiHdrSpace = kNikaiHdrSpaceTicks * kNikaiTick; +const uint16_t kNikaiBitMarkTicks = 1; +const uint16_t kNikaiBitMark = kNikaiBitMarkTicks * kNikaiTick; +const uint16_t kNikaiOneSpaceTicks = 2; +const uint16_t kNikaiOneSpace = kNikaiOneSpaceTicks * kNikaiTick; +const uint16_t kNikaiZeroSpaceTicks = 4; +const uint16_t kNikaiZeroSpace = kNikaiZeroSpaceTicks * kNikaiTick; +const uint16_t kNikaiMinGapTicks = 17; +const uint16_t kNikaiMinGap = kNikaiMinGapTicks * kNikaiTick; + +#if SEND_NIKAI +/// Send a Nikai formatted message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendNikai(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendGeneric(kNikaiHdrMark, kNikaiHdrSpace, kNikaiBitMark, kNikaiOneSpace, + kNikaiBitMark, kNikaiZeroSpace, kNikaiBitMark, kNikaiMinGap, data, + nbits, 38, true, repeat, 33); +} +#endif // SEND_NIKAI + +#if DECODE_NIKAI +/// Decode the supplied Nikai message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +bool IRrecv::decodeNikai(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kNikaiBits) + return false; // We expect Nikai to be a certain sized message. + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kNikaiHdrMark, kNikaiHdrSpace, + kNikaiBitMark, kNikaiOneSpace, + kNikaiBitMark, kNikaiZeroSpace, + kNikaiBitMark, kNikaiMinGap, true)) return false; + // Success + results->bits = nbits; + results->value = data; + results->decode_type = NIKAI; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_NIKAI diff --git a/src/libraries/IRremoteESP8266/src/ir_Panasonic.cpp b/src/libraries/IRremoteESP8266/src/ir_Panasonic.cpp new file mode 100644 index 000000000..03e1e4e6a --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Panasonic.cpp @@ -0,0 +1,1345 @@ +// Copyright 2015 Kristian Lauszus +// Copyright 2017, 2018 David Conran + +/// @file +/// @brief Support for Panasonic protocols. +/// Panasonic protocol originally added by Kristian Lauszus +/// (Thanks to zenwheel and other people at the original blog post) +/// @see Panasonic https://github.com/z3t0/Arduino-IRremote +/// @see http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?2615 +/// @see Panasonic A/C support heavily influenced by https://github.com/ToniA/ESPEasy/blob/HeatpumpIR/lib/HeatpumpIR/PanasonicHeatpumpIR.cpp +/// Panasonic A/C Clock & Timer support: +/// Reverse Engineering by MikkelTb +/// Code by crankyoldgit + +#include "ir_Panasonic.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "String.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using arduino::String; + +// Constants +/// @see http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?26152 +const uint16_t kPanasonicHdrMark = 3456; ///< uSeconds. +const uint16_t kPanasonicHdrSpace = 1728; ///< uSeconds. +const uint16_t kPanasonicBitMark = 432; ///< uSeconds. +const uint16_t kPanasonicOneSpace = 1296; ///< uSeconds. +const uint16_t kPanasonicZeroSpace = 432; ///< uSeconds. +const uint32_t kPanasonicMinCommandLength = 163296; ///< uSeconds. +const uint16_t kPanasonicEndGap = 5000; ///< uSeconds. See #245 +const uint32_t kPanasonicMinGap = 74736; ///< uSeconds. + +const uint16_t kPanasonicAcSectionGap = 10000; ///< uSeconds. +const uint16_t kPanasonicAcSection1Length = 8; +const uint32_t kPanasonicAcMessageGap = kDefaultMessageGap; // Just a guess. + +const uint16_t kPanasonicAc32HdrMark = 3543; ///< uSeconds. +const uint16_t kPanasonicAc32BitMark = 920; ///< uSeconds. +const uint16_t kPanasonicAc32HdrSpace = 3450; ///< uSeconds. +const uint16_t kPanasonicAc32OneSpace = 2575; ///< uSeconds. +const uint16_t kPanasonicAc32ZeroSpace = 828; ///< uSeconds. +const uint16_t kPanasonicAc32SectionGap = 13946; ///< uSeconds. +const uint8_t kPanasonicAc32Sections = 2; +const uint8_t kPanasonicAc32BlocksPerSection = 2; + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addSwingHToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::minsToString; +using irutils::setBit; +using irutils::setBits; + +// Used by Denon as well. +#if (SEND_PANASONIC || SEND_DENON) +/// Send a Panasonic formatted message. +/// Status: STABLE / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol is a modified version of Kaseikyo. +/// @note Use this method if you want to send the results of `decodePanasonic`. +void IRsend::sendPanasonic64(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, + kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, + kPanasonicBitMark, kPanasonicMinGap, kPanasonicMinCommandLength, + data, nbits, kPanasonicFreq, true, repeat, 50); +} + +/// Send a Panasonic formatted message. +/// Status: STABLE, but DEPRECATED +/// @deprecated This is only for legacy use only, please use `sendPanasonic64()` +/// instead. +/// @param[in] address The 16-bit manufacturer code. +/// @param[in] data The 32-bit data portion of the message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This protocol is a modified version of Kaseikyo. +void IRsend::sendPanasonic(const uint16_t address, const uint32_t data, + const uint16_t nbits, const uint16_t repeat) { + sendPanasonic64(((uint64_t)address << 32) | (uint64_t)data, nbits, repeat); +} + +/// Calculate the raw Panasonic data based on device, subdevice, & function. +/// Status: STABLE / Should be working. +/// @param[in] manufacturer A 16-bit manufacturer code. e.g. 0x4004 is Panasonic +/// @param[in] device An 8-bit code. +/// @param[in] subdevice An 8-bit code. +/// @param[in] function An 8-bit code. +/// @return A value suitable for use with `sendPanasonic64()`. +/// @note Panasonic 48-bit protocol is a modified version of Kaseikyo. +/// @see http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?2615 +uint64_t IRsend::encodePanasonic(const uint16_t manufacturer, + const uint8_t device, + const uint8_t subdevice, + const uint8_t function) { + uint8_t checksum = device ^ subdevice ^ function; + return (((uint64_t)manufacturer << 32) | ((uint64_t)device << 24) | + ((uint64_t)subdevice << 16) | ((uint64_t)function << 8) | checksum); +} +#endif // (SEND_PANASONIC || SEND_DENON) + +// Used by Denon as well. +#if (DECODE_PANASONIC || DECODE_DENON) +/// Decode the supplied Panasonic message. +/// Status: STABLE / Should be working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] manufacturer A 16-bit manufacturer code. e.g. 0x4004 is Panasonic +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @warning Results to be used with `sendPanasonic64()`, not `sendPanasonic()`. +/// @note Panasonic 48-bit protocol is a modified version of Kaseikyo. +/// @see http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?2615 +/// @see http://www.hifi-remote.com/wiki/index.php?title=Panasonic +bool IRrecv::decodePanasonic(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict, + const uint32_t manufacturer) { + if (strict && nbits != kPanasonicBits) + return false; // Request is out of spec. + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kPanasonicHdrMark, kPanasonicHdrSpace, + kPanasonicBitMark, kPanasonicOneSpace, + kPanasonicBitMark, kPanasonicZeroSpace, + kPanasonicBitMark, kPanasonicEndGap, true)) return false; + // Compliance + uint32_t address = data >> 32; + uint32_t command = data; + if (strict) { + if (address != manufacturer) // Verify the Manufacturer code. + return false; + // Verify the checksum. + uint8_t checksumOrig = data; + uint8_t checksumCalc = (data >> 24) ^ (data >> 16) ^ (data >> 8); + if (checksumOrig != checksumCalc) return false; + } + + // Success + results->value = data; + results->address = address; + results->command = command; + results->decode_type = decode_type_t::PANASONIC; + results->bits = nbits; + return true; +} +#endif // (DECODE_PANASONIC || DECODE_DENON) + +#if SEND_PANASONIC_AC +/// Send a Panasonic A/C message. +/// Status: STABLE / Work with real device(s). +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendPanasonicAC(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kPanasonicAcSection1Length) return; + for (uint16_t r = 0; r <= repeat; r++) { + // First section. (8 bytes) + sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, + kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, + kPanasonicBitMark, kPanasonicAcSectionGap, data, + kPanasonicAcSection1Length, kPanasonicFreq, false, 0, 50); + // First section. (The rest of the data bytes) + sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, + kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, + kPanasonicBitMark, kPanasonicAcMessageGap, + data + kPanasonicAcSection1Length, + nbytes - kPanasonicAcSection1Length, kPanasonicFreq, false, 0, + 50); + } +} +#endif // SEND_PANASONIC_AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRPanasonicAc::IRPanasonicAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRPanasonicAc::stateReset(void) { + memcpy(remote_state, kPanasonicKnownGoodState, kPanasonicAcStateLength); + _temp = 25; // An initial saved desired temp. Completely made up. + _swingh = kPanasonicAcSwingHMiddle; // A similar made up value for H Swing. +} + +/// Set up hardware to be able to send a message. +void IRPanasonicAc::begin(void) { _irsend.begin(); } + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRPanasonicAc::validChecksum(const uint8_t *state, const uint16_t length) { + if (length < 2) return false; // 1 byte of data can't have a checksum. + return (state[length - 1] == + sumBytes(state, length - 1, kPanasonicAcChecksumInit)); +} + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @param[in] length The size/length of the state. +/// @return The calculated checksum value. +uint8_t IRPanasonicAc::calcChecksum(const uint8_t *state, + const uint16_t length) { + return sumBytes(state, length - 1, kPanasonicAcChecksumInit); +} + +/// Calculate and set the checksum values for the internal state. +/// @param[in] length The size/length of the state. +void IRPanasonicAc::fixChecksum(const uint16_t length) { + remote_state[length - 1] = calcChecksum(remote_state, length); +} + +#if SEND_PANASONIC_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRPanasonicAc::send(const uint16_t repeat) { + _irsend.sendPanasonicAC(getRaw(), kPanasonicAcStateLength, repeat); +} +#endif // SEND_PANASONIC_AC + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRPanasonicAc::setModel(const panasonic_ac_remote_model_t model) { + switch (model) { + case panasonic_ac_remote_model_t::kPanasonicDke: + case panasonic_ac_remote_model_t::kPanasonicJke: + case panasonic_ac_remote_model_t::kPanasonicLke: + case panasonic_ac_remote_model_t::kPanasonicNke: + case panasonic_ac_remote_model_t::kPanasonicCkp: + case panasonic_ac_remote_model_t::kPanasonicRkr: break; + // Only proceed if we know what to do. + default: return; + } + // clear & set the various bits and bytes. + remote_state[13] &= 0xF0; + remote_state[17] = 0x00; + remote_state[21] &= 0b11101111; + remote_state[23] = 0x81; + remote_state[25] = 0x00; + + switch (model) { + case kPanasonicLke: + remote_state[13] |= 0x02; + remote_state[17] = 0x06; + break; + case kPanasonicDke: + remote_state[23] = 0x01; + remote_state[25] = 0x06; + // Has to be done last as setSwingHorizontal has model check built-in + setSwingHorizontal(_swingh); + break; + case kPanasonicNke: + remote_state[17] = 0x06; + break; + case kPanasonicJke: + break; + case kPanasonicCkp: + remote_state[21] |= 0x10; + remote_state[23] = 0x01; + break; + case kPanasonicRkr: + remote_state[13] |= 0x08; + remote_state[23] = 0x89; + default: + break; + } + // Reset the Ion filter. + setIon(getIon()); +} + +/// Get/Detect the model of the A/C. +/// @return The enum of the compatible model. +panasonic_ac_remote_model_t IRPanasonicAc::getModel(void) { + if (remote_state[23] == 0x89) return kPanasonicRkr; + if (remote_state[17] == 0x00) { + if ((remote_state[21] & 0x10) && (remote_state[23] & 0x01)) + return panasonic_ac_remote_model_t::kPanasonicCkp; + if (remote_state[23] & 0x80) + return panasonic_ac_remote_model_t::kPanasonicJke; + } + if (remote_state[17] == 0x06 && (remote_state[13] & 0x0F) == 0x02) + return panasonic_ac_remote_model_t::kPanasonicLke; + if (remote_state[23] == 0x01) + return panasonic_ac_remote_model_t::kPanasonicDke; + if (remote_state[17] == 0x06) + return panasonic_ac_remote_model_t::kPanasonicNke; + return panasonic_ac_remote_model_t::kPanasonicUnknown; // Default +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRPanasonicAc::getRaw(void) { + fixChecksum(); + return remote_state; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRPanasonicAc::setRaw(const uint8_t state[]) { + memcpy(remote_state, state, kPanasonicAcStateLength); +} + +/// Control the power state of the A/C unit. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @warning For CKP models, the remote has no memory of the power state the A/C +/// unit should be in. For those models setting this on/true will toggle the +/// power state of the Panasonic A/C unit with the next message. +/// e.g. If the A/C unit is already on, setPower(true) will turn it off. +/// If the A/C unit is already off, setPower(true) will turn it on. +/// `setPower(false)` will leave the A/C power state as it was. +/// For all other models, setPower(true) should set the internal state to +/// turn it on, and setPower(false) should turn it off. +void IRPanasonicAc::setPower(const bool on) { + setBit(&remote_state[13], kPanasonicAcPowerOffset, on); +} + +/// Get the A/C power state of the remote. +/// @return true, the setting is on. false, the setting is off. +/// @warning Except for CKP models, where it returns if the power state will be +/// toggled on the A/C unit when the next message is sent. +bool IRPanasonicAc::getPower(void) { + return GETBIT8(remote_state[13], kPanasonicAcPowerOffset); +} + +/// Change the power setting to On. +void IRPanasonicAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRPanasonicAc::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRPanasonicAc::getMode(void) { + return GETBITS8(remote_state[13], kHighNibble, kModeBitsSize); +} + +/// Set the operating mode of the A/C. +/// @param[in] desired The desired operating mode. +void IRPanasonicAc::setMode(const uint8_t desired) { + uint8_t mode = kPanasonicAcAuto; // Default to Auto mode. + switch (desired) { + case kPanasonicAcFan: + // Allegedly Fan mode has a temperature of 27. + setTemp(kPanasonicAcFanModeTemp, false); + mode = desired; + break; + case kPanasonicAcAuto: + case kPanasonicAcCool: + case kPanasonicAcHeat: + case kPanasonicAcDry: + mode = desired; + // Set the temp to the saved temp, just incase our previous mode was Fan. + setTemp(_temp); + break; + } + remote_state[13] &= 0x0F; // Clear the previous mode bits. + setBits(&remote_state[13], kHighNibble, kModeBitsSize, mode); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRPanasonicAc::getTemp(void) { + return GETBITS8(remote_state[14], kPanasonicAcTempOffset, + kPanasonicAcTempSize); +} + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +/// @param[in] remember: A flag for the class to remember the temperature. +/// @note Automatically safely limits the temp to the operating range supported. +void IRPanasonicAc::setTemp(const uint8_t celsius, const bool remember) { + uint8_t temperature; + temperature = ::max(celsius, kPanasonicAcMinTemp); + temperature = ::min(temperature, kPanasonicAcMaxTemp); + if (remember) _temp = temperature; + setBits(&remote_state[14], kPanasonicAcTempOffset, kPanasonicAcTempSize, + temperature); +} + +/// Get the current vertical swing setting. +/// @return The current position it is set to. +uint8_t IRPanasonicAc::getSwingVertical(void) { + return GETBITS8(remote_state[16], kLowNibble, kNibbleSize); +} + +/// Control the vertical swing setting. +/// @param[in] desired_elevation The position to set the vertical swing to. +void IRPanasonicAc::setSwingVertical(const uint8_t desired_elevation) { + uint8_t elevation = desired_elevation; + if (elevation != kPanasonicAcSwingVAuto) { + elevation = ::max(elevation, kPanasonicAcSwingVHighest); + elevation = ::min(elevation, kPanasonicAcSwingVLowest); + } + setBits(&remote_state[16], kLowNibble, kNibbleSize, elevation); +} + +/// Get the current horizontal swing setting. +/// @return The current position it is set to. +uint8_t IRPanasonicAc::getSwingHorizontal(void) { + return GETBITS8(remote_state[17], kLowNibble, kNibbleSize); +} + +/// Control the horizontal swing setting. +/// @param[in] desired_direction The position to set the horizontal swing to. +void IRPanasonicAc::setSwingHorizontal(const uint8_t desired_direction) { + switch (desired_direction) { + case kPanasonicAcSwingHAuto: + case kPanasonicAcSwingHMiddle: + case kPanasonicAcSwingHFullLeft: + case kPanasonicAcSwingHLeft: + case kPanasonicAcSwingHRight: + case kPanasonicAcSwingHFullRight: break; + // Ignore anything that isn't valid. + default: return; + } + _swingh = desired_direction; // Store the direction for later. + uint8_t direction = desired_direction; + switch (getModel()) { + case kPanasonicDke: + case kPanasonicRkr: + break; + case kPanasonicNke: + case kPanasonicLke: + direction = kPanasonicAcSwingHMiddle; + break; + default: // Ignore everything else. + return; + } + setBits(&remote_state[17], kLowNibble, kNibbleSize, direction); +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRPanasonicAc::setFan(const uint8_t speed) { + switch (speed) { + case kPanasonicAcFanMin: + case kPanasonicAcFanLow: + case kPanasonicAcFanMed: + case kPanasonicAcFanHigh: + case kPanasonicAcFanMax: + case kPanasonicAcFanAuto: + setBits(&remote_state[16], kHighNibble, kNibbleSize, + speed + kPanasonicAcFanDelta); + break; + default: setFan(kPanasonicAcFanAuto); + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRPanasonicAc::getFan(void) { + return GETBITS8(remote_state[16], kHighNibble, kNibbleSize) - + kPanasonicAcFanDelta; +} + +/// Get the Quiet setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRPanasonicAc::getQuiet(void) { + switch (getModel()) { + case kPanasonicRkr: + case kPanasonicCkp: + return GETBIT8(remote_state[21], kPanasonicAcQuietCkpOffset); + default: + return GETBIT8(remote_state[21], kPanasonicAcQuietOffset); + } +} + +/// Set the Quiet setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRPanasonicAc::setQuiet(const bool on) { + uint8_t offset; + switch (getModel()) { + case kPanasonicRkr: + case kPanasonicCkp: offset = kPanasonicAcQuietCkpOffset; break; + default: offset = kPanasonicAcQuietOffset; + } + if (on) setPowerful(false); // Powerful is mutually exclusive. + setBit(&remote_state[21], offset, on); +} + +/// Get the Powerful (Turbo) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRPanasonicAc::getPowerful(void) { + switch (getModel()) { + case kPanasonicRkr: + case kPanasonicCkp: + return GETBIT8(remote_state[21], kPanasonicAcPowerfulCkpOffset); + default: + return GETBIT8(remote_state[21], kPanasonicAcPowerfulOffset); + } +} + +/// Set the Powerful (Turbo) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRPanasonicAc::setPowerful(const bool on) { + uint8_t offset; + switch (getModel()) { + case kPanasonicRkr: + case kPanasonicCkp: offset = kPanasonicAcPowerfulCkpOffset; break; + default: offset = kPanasonicAcPowerfulOffset; + } + + if (on) setQuiet(false); // Quiet is mutually exclusive. + setBit(&remote_state[21], offset, on); +} + +/// Convert standard (military/24hr) time to nr. of minutes since midnight. +/// @param[in] hours The hours component of the time. +/// @param[in] mins The minutes component of the time. +/// @return The nr of minutes since midnight. +uint16_t IRPanasonicAc::encodeTime(const uint8_t hours, const uint8_t mins) { + return ::min(hours, (uint8_t)23) * 60 + ::min(mins, (uint8_t)59); +} + +/// Get the time from a given pointer location. +/// @param[in] ptr A pointer to a time location in a state. +/// @return The time expressed as nr. of minutes past midnight. +/// @note Internal use only. +uint16_t IRPanasonicAc::_getTime(const uint8_t ptr[]) { + uint16_t result = (GETBITS8( + ptr[1], kLowNibble, kPanasonicAcTimeOverflowSize) << + (kPanasonicAcTimeSize - kPanasonicAcTimeOverflowSize)) + ptr[0]; + if (result == kPanasonicAcTimeSpecial) return 0; + return result; +} + +/// Get the current clock time value. +/// @return The time expressed as nr. of minutes past midnight. +uint16_t IRPanasonicAc::getClock(void) { return _getTime(&remote_state[24]); } + +/// Set the time at a given pointer location. +/// @param[in, out] ptr A pointer to a time location in a state. +/// @param[in] mins_since_midnight The time as nr. of minutes past midnight. +/// @param[in] round_down Do we round to the nearest 10 minute mark? +/// @note Internal use only. +void IRPanasonicAc::_setTime(uint8_t * const ptr, + const uint16_t mins_since_midnight, + const bool round_down) { + uint16_t corrected = ::min(mins_since_midnight, kPanasonicAcTimeMax); + if (round_down) corrected -= corrected % 10; + if (mins_since_midnight == kPanasonicAcTimeSpecial) + corrected = kPanasonicAcTimeSpecial; + ptr[0] = corrected; + setBits(&ptr[1], kLowNibble, kPanasonicAcTimeOverflowSize, + corrected >> (kPanasonicAcTimeSize - kPanasonicAcTimeOverflowSize)); +} + +/// Set the current clock time value. +/// @param[in] mins_since_midnight The time as nr. of minutes past midnight. +void IRPanasonicAc::setClock(const uint16_t mins_since_midnight) { + _setTime(&remote_state[24], mins_since_midnight, false); +} + +/// Get the On Timer time value. +/// @return The time expressed as nr. of minutes past midnight. +uint16_t IRPanasonicAc::getOnTimer(void) { return _getTime(&remote_state[18]); } + +/// Set/Enable the On Timer. +/// @param[in] mins_since_midnight The time as nr. of minutes past midnight. +/// @param[in] enable Do we enable the timer or not? +void IRPanasonicAc::setOnTimer(const uint16_t mins_since_midnight, + const bool enable) { + // Set the timer flag. + setBit(&remote_state[13], kPanasonicAcOnTimerOffset, enable); + // Store the time. + _setTime(&remote_state[18], mins_since_midnight, true); +} + +/// Cancel the On Timer. +void IRPanasonicAc::cancelOnTimer(void) { setOnTimer(0, false); } + +/// Check if the On Timer is Enabled. +/// @return true, the setting is on. false, the setting is off. +bool IRPanasonicAc::isOnTimerEnabled(void) { + return GETBIT8(remote_state[13], kPanasonicAcOnTimerOffset); +} + +/// Get the Off Timer time value. +/// @return The time expressed as nr. of minutes past midnight. +uint16_t IRPanasonicAc::getOffTimer(void) { + uint16_t result = (GETBITS8(remote_state[20], 0, 7) << kNibbleSize) | + GETBITS8(remote_state[19], kHighNibble, kNibbleSize); + if (result == kPanasonicAcTimeSpecial) return 0; + return result; +} + +/// Set/Enable the Off Timer. +/// @param[in] mins_since_midnight The time as nr. of minutes past midnight. +/// @param[in] enable Do we enable the timer or not? +void IRPanasonicAc::setOffTimer(const uint16_t mins_since_midnight, + const bool enable) { + // Ensure its on a 10 minute boundary and no overflow. + uint16_t corrected = ::min(mins_since_midnight, kPanasonicAcTimeMax); + corrected -= corrected % 10; + if (mins_since_midnight == kPanasonicAcTimeSpecial) + corrected = kPanasonicAcTimeSpecial; + // Set the timer flag. + setBit(&remote_state[13], kPanasonicAcOffTimerOffset, enable); + // Store the time. + setBits(&remote_state[19], kHighNibble, kNibbleSize, corrected); + setBits(&remote_state[20], 0, 7, corrected >> kNibbleSize); +} + +/// Cancel the Off Timer. +void IRPanasonicAc::cancelOffTimer(void) { setOffTimer(0, false); } + +/// Check if the Off Timer is Enabled. +/// @return true, the setting is on. false, the setting is off. +bool IRPanasonicAc::isOffTimerEnabled(void) { + return GETBIT8(remote_state[13], kPanasonicAcOffTimerOffset); +} + +/// Get the Ion (filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRPanasonicAc::getIon(void) { + switch (getModel()) { + case kPanasonicDke: + return GETBIT8(remote_state[kPanasonicAcIonFilterByte], + kPanasonicAcIonFilterOffset); + default: + return false; + } +} + +/// Set the Ion (filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRPanasonicAc::setIon(const bool on) { + if (getModel() == kPanasonicDke) + setBit(&remote_state[kPanasonicAcIonFilterByte], + kPanasonicAcIonFilterOffset, on); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRPanasonicAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kPanasonicAcCool; + case stdAc::opmode_t::kHeat: return kPanasonicAcHeat; + case stdAc::opmode_t::kDry: return kPanasonicAcDry; + case stdAc::opmode_t::kFan: return kPanasonicAcFan; + default: return kPanasonicAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRPanasonicAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kPanasonicAcFanMin; + case stdAc::fanspeed_t::kLow: return kPanasonicAcFanLow; + case stdAc::fanspeed_t::kMedium: return kPanasonicAcFanMed; + case stdAc::fanspeed_t::kHigh: return kPanasonicAcFanHigh; + case stdAc::fanspeed_t::kMax: return kPanasonicAcFanMax; + default: return kPanasonicAcFanAuto; + } +} + +/// Convert a standard A/C vertical swing into its native setting. +/// @param[in] position A stdAc::swingv_t position to convert. +/// @return The equivalent native horizontal swing position. +uint8_t IRPanasonicAc::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: + case stdAc::swingv_t::kLow: + case stdAc::swingv_t::kLowest: return (uint8_t)position; + default: return kPanasonicAcSwingVAuto; + } +} + +/// Convert a standard A/C horizontal swing into its native setting. +/// @param[in] position A stdAc::swingh_t position to convert. +/// @return The equivalent native horizontal swing position. +uint8_t IRPanasonicAc::convertSwingH(const stdAc::swingh_t position) { + switch (position) { + case stdAc::swingh_t::kLeftMax: return kPanasonicAcSwingHFullLeft; + case stdAc::swingh_t::kLeft: return kPanasonicAcSwingHLeft; + case stdAc::swingh_t::kMiddle: return kPanasonicAcSwingHMiddle; + case stdAc::swingh_t::kRight: return kPanasonicAcSwingHRight; + case stdAc::swingh_t::kRightMax: return kPanasonicAcSwingHFullRight; + default: return kPanasonicAcSwingHAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRPanasonicAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kPanasonicAcCool: return stdAc::opmode_t::kCool; + case kPanasonicAcHeat: return stdAc::opmode_t::kHeat; + case kPanasonicAcDry: return stdAc::opmode_t::kDry; + case kPanasonicAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRPanasonicAc::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kPanasonicAcFanMax: return stdAc::fanspeed_t::kMax; + case kPanasonicAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kPanasonicAcFanMed: return stdAc::fanspeed_t::kMedium; + case kPanasonicAcFanLow: return stdAc::fanspeed_t::kLow; + case kPanasonicAcFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a native horizontal swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common horizontal swing position. +stdAc::swingh_t IRPanasonicAc::toCommonSwingH(const uint8_t pos) { + switch (pos) { + case kPanasonicAcSwingHFullLeft: return stdAc::swingh_t::kLeftMax; + case kPanasonicAcSwingHLeft: return stdAc::swingh_t::kLeft; + case kPanasonicAcSwingHMiddle: return stdAc::swingh_t::kMiddle; + case kPanasonicAcSwingHRight: return stdAc::swingh_t::kRight; + case kPanasonicAcSwingHFullRight: return stdAc::swingh_t::kRightMax; + default: return stdAc::swingh_t::kAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRPanasonicAc::toCommonSwingV(const uint8_t pos) { + if (pos >= kPanasonicAcSwingVHighest && pos <= kPanasonicAcSwingVLowest) + return (stdAc::swingv_t)pos; + else + return stdAc::swingv_t::kAuto; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRPanasonicAc::toCommon(void) { + stdAc::state_t result{}; + result.protocol = decode_type_t::PANASONIC_AC; + result.model = getModel(); + result.power = getPower(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = toCommonSwingV(getSwingVertical()); + result.swingh = toCommonSwingH(getSwingHorizontal()); + result.quiet = getQuiet(); + result.turbo = getPowerful(); + result.filter = getIon(); + // Not supported. + result.econo = false; + result.clean = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRPanasonicAc::toString(void) { + String result = ""; + result.reserve(180); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::PANASONIC_AC, getModel(), false); + result += addBoolToString(getPower(), kPowerStr); + result += addModeToString(getMode(), kPanasonicAcAuto, kPanasonicAcCool, + kPanasonicAcHeat, kPanasonicAcDry, kPanasonicAcFan); + result += addTempToString(getTemp()); + result += addFanToString(getFan(), kPanasonicAcFanHigh, kPanasonicAcFanLow, + kPanasonicAcFanAuto, kPanasonicAcFanMin, + kPanasonicAcFanMed, kPanasonicAcFanMax); + result += addSwingVToString(getSwingVertical(), kPanasonicAcSwingVAuto, + kPanasonicAcSwingVHighest, + kPanasonicAcSwingVHigh, + kPanasonicAcSwingVAuto, // Upper Middle is unused + kPanasonicAcSwingVMiddle, + kPanasonicAcSwingVAuto, // Lower Middle is unused + kPanasonicAcSwingVLow, + kPanasonicAcSwingVLowest, + // Below are unused. + kPanasonicAcSwingVAuto, + kPanasonicAcSwingVAuto, + kPanasonicAcSwingVAuto, + kPanasonicAcSwingVAuto); + switch (getModel()) { + case kPanasonicJke: + case kPanasonicCkp: + break; // No Horizontal Swing support. + default: + result += addSwingHToString(getSwingHorizontal(), kPanasonicAcSwingHAuto, + kPanasonicAcSwingHFullLeft, + kPanasonicAcSwingHLeft, + kPanasonicAcSwingHMiddle, + kPanasonicAcSwingHRight, + kPanasonicAcSwingHFullRight, + // Below are unused. + kPanasonicAcSwingHAuto, + kPanasonicAcSwingHAuto, + kPanasonicAcSwingHAuto, + kPanasonicAcSwingHAuto, + kPanasonicAcSwingHAuto); + } + result += addBoolToString(getQuiet(), kQuietStr); + result += addBoolToString(getPowerful(), kPowerfulStr); + if (getModel() == kPanasonicDke) + result += addBoolToString(getIon(), kIonStr); + result += addLabeledString(minsToString(getClock()), kClockStr); + result += addLabeledString( + isOnTimerEnabled() ? minsToString(getOnTimer()) : kOffStr, + kOnTimerStr); + result += addLabeledString( + isOffTimerEnabled() ? minsToString(getOffTimer()) : kOffStr, + kOffTimerStr); + return result; +} + +#if DECODE_PANASONIC_AC +/// Decode the supplied Panasonic AC message. +/// Status: STABLE / Works with real device(s). +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodePanasonicAC(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint8_t min_nr_of_messages = 1; + if (strict) { + if (nbits != kPanasonicAcBits && nbits != kPanasonicAcShortBits) + return false; // Not strictly a PANASONIC_AC message. + } + + if (results->rawlen <= + min_nr_of_messages * (2 * nbits + kHeader + kFooter) - 1 + offset) + return false; // Can't possibly be a valid PANASONIC_AC message. + + // Match Header + Data #1 + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, kPanasonicAcSection1Length * 8, + kPanasonicHdrMark, kPanasonicHdrSpace, + kPanasonicBitMark, kPanasonicOneSpace, + kPanasonicBitMark, kPanasonicZeroSpace, + kPanasonicBitMark, kPanasonicAcSectionGap, false, + kPanasonicAcTolerance, kPanasonicAcExcess, false); + if (!used) return false; + offset += used; + + // Match Header + Data #2 + Footer + if (!matchGeneric(results->rawbuf + offset, + results->state + kPanasonicAcSection1Length, + results->rawlen - offset, + nbits - kPanasonicAcSection1Length * 8, + kPanasonicHdrMark, kPanasonicHdrSpace, + kPanasonicBitMark, kPanasonicOneSpace, + kPanasonicBitMark, kPanasonicZeroSpace, + kPanasonicBitMark, kPanasonicAcMessageGap, true, + kPanasonicAcTolerance, kPanasonicAcExcess, false)) + return false; + // Compliance + if (strict) { + // Check the signatures of the section blocks. They start with 0x02& 0x20. + if (results->state[0] != 0x02 || results->state[1] != 0x20 || + results->state[8] != 0x02 || results->state[9] != 0x20) + return false; + if (!IRPanasonicAc::validChecksum(results->state, nbits / 8)) return false; + } + + // Success + results->decode_type = decode_type_t::PANASONIC_AC; + results->bits = nbits; + return true; +} +#endif // DECODE_PANASONIC_AC + +#if SEND_PANASONIC_AC32 +/// Send a Panasonic AC 32/16bit formatted message. +/// Status: STABLE / Confirmed working. +/// @param[in] data containing the IR command. +/// @param[in] nbits Nr. of bits to send. Usually kPanasonicAc32Bits +/// @param[in] repeat Nr. of times the message is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1307 +void IRsend::sendPanasonicAC32(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + uint16_t section_bits; + uint16_t sections; + uint16_t blocks; + // Calculate the section, block, and bit sizes based on the requested bit size + if (nbits > kPanasonicAc32Bits / 2) { // A long message + section_bits = nbits / kPanasonicAc32Sections; + sections = kPanasonicAc32Sections; + blocks = kPanasonicAc32BlocksPerSection; + } else { // A short message + section_bits = nbits; + sections = kPanasonicAc32Sections - 1; + blocks = kPanasonicAc32BlocksPerSection + 1; + } + for (uint16_t r = 0; r <= repeat; r++) { + for (uint8_t section = 0; section < sections; section++) { + uint64_t section_data; + section_data = GETBITS64(data, section_bits * (sections - section - 1), + section_bits); + + // Duplicate bytes in the data. + uint64_t expanded_data = 0; + for (uint8_t i = 0; i < sizeof(expanded_data); i++) { + const uint8_t first_byte = section_data >> 56; + for (uint8_t i = 0; i < 2; i++) + expanded_data = (expanded_data << 8) | first_byte; + section_data <<= 8; + } + // Two data blocks per section (i.e. 1 + a repeat) + sendGeneric(kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace, // Header + kPanasonicAc32BitMark, kPanasonicAc32OneSpace, // Data + kPanasonicAc32BitMark, kPanasonicAc32ZeroSpace, + 0, 0, // No Footer + expanded_data, section_bits * 2, kPanasonicFreq, false, + blocks - 1, // Repeat + 50); + // Section Footer + sendGeneric(kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace, // Header + 0, 0, 0, 0, // No Data + kPanasonicAc32BitMark, kPanasonicAc32SectionGap, // Footer + data, 0, // No data (bits) + kPanasonicFreq, true, 0, 50); + } + } +} +#endif // SEND_PANASONIC_AC32 + +#if DECODE_PANASONIC_AC32 +/// Decode the supplied Panasonic AC 32/16bit message. +/// Status: STABLE / Confirmed working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// Typically: kPanasonicAc32Bits or kPanasonicAc32Bits/2 +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1307 +/// @note Protocol has two known configurations: +/// (long) +/// Two sections of identical 32 bit data block pairs. ie. (32+32)+(32+32)=128 +/// or +/// (short) +/// A single section of 3 x identical 32 bit data blocks i.e. (32+32+32)=96 +/// Each data block also has a pair of 8 bits repeated identical bits. +/// e.g. (8+8)+(8+8)=32 +/// +/// So each long version really only has 32 unique bits, and the short version +/// really only has 16 unique bits. +bool IRrecv::decodePanasonicAC32(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && (nbits != kPanasonicAc32Bits && + nbits != kPanasonicAc32Bits / 2)) + return false; // Not strictly a valid bit size. + + // Determine if this is a long or a short message we are looking for. + const bool is_long = (nbits > kPanasonicAc32Bits / 2); + const uint16_t min_length = is_long ? + kPanasonicAc32Sections * kPanasonicAc32BlocksPerSection * + ((2 * nbits) + kHeader + kFooter) - 1 + offset : + (kPanasonicAc32BlocksPerSection + 1) * ((4 * nbits) + kHeader) + + kFooter - 1 + offset; + + if (results->rawlen < min_length) + return false; // Can't possibly be a valid message. + + // Calculate the parameters for the decode based on it's length. + uint16_t sections; + uint16_t blocks_per_section; + if (is_long) { + sections = kPanasonicAc32Sections; + blocks_per_section = kPanasonicAc32BlocksPerSection; + } else { + sections = kPanasonicAc32Sections - 1; + blocks_per_section = kPanasonicAc32BlocksPerSection + 1; + } + const uint16_t bits_per_block = nbits / sections; + + uint64_t data = 0; + uint64_t section_data = 0; + uint32_t prev_section_data; + + // Match all the expected data blocks. + for (uint16_t block = 0; + block < sections * blocks_per_section; + block++) { + prev_section_data = section_data; + uint16_t used = matchGeneric(results->rawbuf + offset, §ion_data, + results->rawlen - offset, bits_per_block * 2, + kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace, + kPanasonicAc32BitMark, kPanasonicAc32OneSpace, + kPanasonicAc32BitMark, kPanasonicAc32ZeroSpace, + 0, 0, // No Footer + false, kUseDefTol, kMarkExcess, false); + if (!used) return false; + offset += used; + // Is it the first block of the section? + if (block % blocks_per_section == 0) { + // The protocol repeats each byte twice, so to shrink the code we + // remove the duplicate bytes in the collected data. We only need to do + // this for the first block in a section. + uint64_t shrunk_data = 0; + uint64_t data_copy = section_data; + for (uint8_t i = 0; i < sizeof(data_copy); i += 2) { + const uint8_t first_byte = GETBITS64(data_copy, + (sizeof(data_copy) - 1) * 8, 8); + shrunk_data = (shrunk_data << 8) | first_byte; + // Compliance + if (strict) { + // Every second byte must be a duplicate of the previous. + const uint8_t next_byte = GETBITS64(data_copy, + (sizeof(data_copy) - 2) * 8, 8); + if (first_byte != next_byte) return false; + } + data_copy <<= 16; + } + // Keep the data from the first of the block in the section. + data = (data << bits_per_block) | shrunk_data; + } else { // Not the first block in a section. + // Compliance + if (strict) + // Compare the data from the blocks in pairs. + if (section_data != prev_section_data) return false; + // Look for the section footer at the end of the blocks. + if ((block + 1) % blocks_per_section == 0) { + uint64_t junk; + used = matchGeneric(results->rawbuf + offset, &junk, + results->rawlen - offset, 0, + // Header + kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace, + // No Data + 0, 0, + 0, 0, + // Footer + kPanasonicAc32BitMark, kPanasonicAc32SectionGap, + true); + if (!used) return false; + offset += used; + } + } + } + + // Success + results->value = data; + results->decode_type = decode_type_t::PANASONIC_AC32; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_PANASONIC_AC32 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRPanasonicAc32::IRPanasonicAc32(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +#if SEND_PANASONIC_AC32 +/// Send the current internal state as IR messages. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRPanasonicAc32::send(const uint16_t repeat) { + _irsend.sendPanasonicAC32(getRaw(), kPanasonicAc32Bits, repeat); +} +#endif // SEND_PANASONIC_AC32 + +/// Set up hardware to be able to send a message. +void IRPanasonicAc32::begin(void) { _irsend.begin(); } + +/// Get a copy of the internal state/code for this protocol. +/// @return The code for this protocol based on the current internal state. +uint32_t IRPanasonicAc32::getRaw(void) const { return _.raw; } + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRPanasonicAc32::setRaw(const uint32_t state) { _.raw = state; } + +/// Reset the state of the remote to a known good state/sequence. +void IRPanasonicAc32::stateReset(void) { setRaw(kPanasonicAc32KnownGood); } + +/// Set the Power Toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRPanasonicAc32::setPowerToggle(const bool on) { _.PowerToggle = !on; } + +/// Get the Power Toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRPanasonicAc32::getPowerToggle(void) const { return !_.PowerToggle; } + +/// Set the desired temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRPanasonicAc32::setTemp(const uint8_t degrees) { + uint8_t temp = ::max((uint8_t)kPanasonicAcMinTemp, degrees); + temp = ::min((uint8_t)kPanasonicAcMaxTemp, temp); + _.Temp = temp - (kPanasonicAcMinTemp - 1); +} + +/// Get the current desired temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRPanasonicAc32::getTemp(void) const { + return _.Temp + (kPanasonicAcMinTemp - 1); +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRPanasonicAc32::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +void IRPanasonicAc32::setMode(const uint8_t mode) { + switch (mode) { + case kPanasonicAc32Auto: + case kPanasonicAc32Cool: + case kPanasonicAc32Dry: + case kPanasonicAc32Heat: + case kPanasonicAc32Fan: + _.Mode = mode; + break; + default: _.Mode = kPanasonicAc32Auto; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRPanasonicAc32::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kPanasonicAc32Cool; + case stdAc::opmode_t::kHeat: return kPanasonicAc32Heat; + case stdAc::opmode_t::kDry: return kPanasonicAc32Dry; + case stdAc::opmode_t::kFan: return kPanasonicAc32Fan; + default: return kPanasonicAc32Auto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRPanasonicAc32::toCommonMode(const uint8_t mode) { + switch (mode) { + case kPanasonicAc32Cool: return stdAc::opmode_t::kCool; + case kPanasonicAc32Heat: return stdAc::opmode_t::kHeat; + case kPanasonicAc32Dry: return stdAc::opmode_t::kDry; + case kPanasonicAc32Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRPanasonicAc32::setFan(const uint8_t speed) { + switch (speed) { + case kPanasonicAc32FanMin: + case kPanasonicAc32FanLow: + case kPanasonicAc32FanMed: + case kPanasonicAc32FanHigh: + case kPanasonicAc32FanMax: + case kPanasonicAc32FanAuto: + _.Fan = speed; + break; + default: _.Fan = kPanasonicAc32FanAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRPanasonicAc32::getFan(void) const { return _.Fan; } + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRPanasonicAc32::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kPanasonicAc32FanMax: return stdAc::fanspeed_t::kMax; + case kPanasonicAc32FanHigh: return stdAc::fanspeed_t::kHigh; + case kPanasonicAc32FanMed: return stdAc::fanspeed_t::kMedium; + case kPanasonicAc32FanLow: return stdAc::fanspeed_t::kLow; + case kPanasonicAc32FanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRPanasonicAc32::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kPanasonicAc32FanMin; + case stdAc::fanspeed_t::kLow: return kPanasonicAc32FanLow; + case stdAc::fanspeed_t::kMedium: return kPanasonicAc32FanMed; + case stdAc::fanspeed_t::kHigh: return kPanasonicAc32FanHigh; + case stdAc::fanspeed_t::kMax: return kPanasonicAc32FanMax; + default: return kPanasonicAc32FanAuto; + } +} + +/// Get the current horizontal swing setting. +/// @return The current position it is set to. +bool IRPanasonicAc32::getSwingHorizontal(void) const { return _.SwingH; } + +/// Control the horizontal swing setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRPanasonicAc32::setSwingHorizontal(const bool on) { _.SwingH = on; } + +/// Get the current vertical swing setting. +/// @return The current position it is set to. +uint8_t IRPanasonicAc32::getSwingVertical(void) const { return _.SwingV; } + +/// Control the vertical swing setting. +/// @param[in] pos The position to set the vertical swing to. +void IRPanasonicAc32::setSwingVertical(const uint8_t pos) { + uint8_t elevation = pos; + if (elevation != kPanasonicAc32SwingVAuto) { + elevation = ::max(elevation, kPanasonicAcSwingVHighest); + elevation = ::min(elevation, kPanasonicAcSwingVLowest); + } + _.SwingV = elevation; +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRPanasonicAc32::toCommonSwingV(const uint8_t pos) { + return IRPanasonicAc::toCommonSwingV(pos); +} + +/// Convert a standard A/C vertical swing into its native setting. +/// @param[in] position A stdAc::swingv_t position to convert. +/// @return The equivalent native horizontal swing position. +uint8_t IRPanasonicAc32::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: + case stdAc::swingv_t::kLow: + case stdAc::swingv_t::kLowest: return (uint8_t)position; + default: return kPanasonicAc32SwingVAuto; + } +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRPanasonicAc32::toString(void) const { + String result = ""; + result.reserve(110); + result += addBoolToString(getPowerToggle(), kPowerToggleStr, false); + result += addModeToString(_.Mode, kPanasonicAc32Auto, kPanasonicAc32Cool, + kPanasonicAc32Heat, kPanasonicAc32Dry, + kPanasonicAc32Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kPanasonicAc32FanHigh, kPanasonicAc32FanLow, + kPanasonicAc32FanAuto, kPanasonicAc32FanMin, + kPanasonicAc32FanMed, kPanasonicAc32FanMax); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addSwingVToString(getSwingVertical(), + kPanasonicAc32SwingVAuto, + kPanasonicAcSwingVHighest, + kPanasonicAcSwingVHigh, + kPanasonicAc32SwingVAuto, // Upper Middle unused + kPanasonicAcSwingVMiddle, + kPanasonicAc32SwingVAuto, // Lower Middle unused + kPanasonicAcSwingVLow, + kPanasonicAcSwingVLowest, + // Below are unused. + kPanasonicAc32SwingVAuto, + kPanasonicAc32SwingVAuto, + kPanasonicAc32SwingVAuto, + kPanasonicAc32SwingVAuto); + return result; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRPanasonicAc32::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.power = false; + } + result.protocol = decode_type_t::PANASONIC_AC32; + result.model = -1; + if (getPowerToggle()) result.power = !result.power; + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = toCommonSwingV(getSwingVertical()); + result.swingh = getSwingHorizontal() ? stdAc::swingh_t::kAuto + : stdAc::swingh_t::kOff; + // Not supported. + result.quiet = false; + result.turbo = false; + result.filter = false; + result.econo = false; + result.clean = false; + result.light = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Panasonic.h b/src/libraries/IRremoteESP8266/src/ir_Panasonic.h new file mode 100644 index 000000000..48a3b296b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Panasonic.h @@ -0,0 +1,271 @@ +// Copyright 2018 David Conran + +/// @file +/// @brief Support for Panasonic protocols. +/// @see Panasonic A/C support heavily influenced by https://github.com/ToniA/ESPEasy/blob/HeatpumpIR/lib/HeatpumpIR/PanasonicHeatpumpIR.cpp + +// Supports: +// Brand: Panasonic, Model: TV (PANASONIC) +// Brand: Panasonic, Model: NKE series A/C (PANASONIC_AC NKE/2) +// Brand: Panasonic, Model: DKE series A/C (PANASONIC_AC DKE/3) +// Brand: Panasonic, Model: DKW series A/C (PANASONIC_AC DKE/3) +// Brand: Panasonic, Model: PKR series A/C (PANASONIC_AC DKE/3) +// Brand: Panasonic, Model: JKE series A/C (PANASONIC_AC JKE/4) +// Brand: Panasonic, Model: CKP series A/C (PANASONIC_AC CKP/5) +// Brand: Panasonic, Model: RKR series A/C (PANASONIC_AC RKR/6) +// Brand: Panasonic, Model: CS-ME10CKPG A/C (PANASONIC_AC CKP/5) +// Brand: Panasonic, Model: CS-ME12CKPG A/C (PANASONIC_AC CKP/5) +// Brand: Panasonic, Model: CS-ME14CKPG A/C (PANASONIC_AC CKP/5) +// Brand: Panasonic, Model: CS-E7PKR A/C (PANASONIC_AC DKE/2) +// Brand: Panasonic, Model: CS-Z9RKR A/C (PANASONIC_AC RKR/6) +// Brand: Panasonic, Model: CS-Z24RKR A/C (PANASONIC_AC RKR/6) +// Brand: Panasonic, Model: CS-YW9MKD A/C (PANASONIC_AC JKE/4) +// Brand: Panasonic, Model: A75C2311 remote (PANASONIC_AC CKP/5) +// Brand: Panasonic, Model: A75C2616-1 remote (PANASONIC_AC DKE/3) +// Brand: Panasonic, Model: A75C3704 remote (PANASONIC_AC DKE/3) +// Brand: Panasonic, Model: A75C3747 remote (PANASONIC_AC JKE/4) +// Brand: Panasonic, Model: CS-E9CKP series A/C (PANASONIC_AC32) +// Brand: Panasonic, Model: A75C2295 remote (PANASONIC_AC32) +// Brand: Panasonic, Model: A75C4762 remote (PANASONIC_AC RKR/6) + +#ifndef IR_PANASONIC_H_ +#define IR_PANASONIC_H_ + +#define __STDC_LIMIT_MACROS +#include +//#ifdef ARDUINO +#include "String.h" +//#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +// Constants +const uint16_t kPanasonicFreq = 36700; +const uint16_t kPanasonicAcExcess = 0; +// Much higher than usual. See issue #540. +const uint16_t kPanasonicAcTolerance = 40; + +const uint8_t kPanasonicAcAuto = 0; // 0b000 +const uint8_t kPanasonicAcDry = 2; // 0b010 +const uint8_t kPanasonicAcCool = 3; // 0b011 +const uint8_t kPanasonicAcHeat = 4; // 0b010 +const uint8_t kPanasonicAcFan = 6; // 0b110 +const uint8_t kPanasonicAcFanMin = 0; +const uint8_t kPanasonicAcFanLow = 1; +const uint8_t kPanasonicAcFanMed = 2; +const uint8_t kPanasonicAcFanHigh = 3; +const uint8_t kPanasonicAcFanMax = 4; +const uint8_t kPanasonicAcFanAuto = 7; +const uint8_t kPanasonicAcFanDelta = 3; +const uint8_t kPanasonicAcPowerOffset = 0; +const uint8_t kPanasonicAcTempOffset = 1; // Bits +const uint8_t kPanasonicAcTempSize = 5; // Bits +const uint8_t kPanasonicAcMinTemp = 16; // Celsius +const uint8_t kPanasonicAcMaxTemp = 30; // Celsius +const uint8_t kPanasonicAcFanModeTemp = 27; // Celsius +const uint8_t kPanasonicAcQuietOffset = 0; +const uint8_t kPanasonicAcPowerfulOffset = 5; // 0b100000 +// CKP & RKR models have Powerful and Quiet bits swapped. +const uint8_t kPanasonicAcQuietCkpOffset = kPanasonicAcPowerfulOffset; +const uint8_t kPanasonicAcPowerfulCkpOffset = kPanasonicAcQuietOffset; +const uint8_t kPanasonicAcSwingVHighest = 0x1; // 0b0001 +const uint8_t kPanasonicAcSwingVHigh = 0x2; // 0b0010 +const uint8_t kPanasonicAcSwingVMiddle = 0x3; // 0b0011 +const uint8_t kPanasonicAcSwingVLow = 0x4; // 0b0100 +const uint8_t kPanasonicAcSwingVLowest = 0x5; // 0b0101 +const uint8_t kPanasonicAcSwingVAuto = 0xF; // 0b1111 + +const uint8_t kPanasonicAcSwingHMiddle = 0x6; // 0b0110 +const uint8_t kPanasonicAcSwingHFullLeft = 0x9; // 0b1001 +const uint8_t kPanasonicAcSwingHLeft = 0xA; // 0b1010 +const uint8_t kPanasonicAcSwingHRight = 0xB; // 0b1011 +const uint8_t kPanasonicAcSwingHFullRight = 0xC; // 0b1100 +const uint8_t kPanasonicAcSwingHAuto = 0xD; // 0b1101 +const uint8_t kPanasonicAcChecksumInit = 0xF4; +const uint8_t kPanasonicAcOnTimerOffset = 1; +const uint8_t kPanasonicAcOffTimerOffset = 2; +const uint8_t kPanasonicAcTimeSize = 11; // Bits +const uint8_t kPanasonicAcTimeOverflowSize = 3; // Bits +const uint16_t kPanasonicAcTimeMax = 23 * 60 + 59; // Mins since midnight. +const uint16_t kPanasonicAcTimeSpecial = 0x600; + +const uint8_t kPanasonicAcIonFilterByte = 22; // Byte +const uint8_t kPanasonicAcIonFilterOffset = 0; // Bit + +const uint8_t kPanasonicKnownGoodState[kPanasonicAcStateLength] = { + 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02, + 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x0E, 0xE0, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00}; + +/// Class for handling detailed Panasonic A/C messages. +class IRPanasonicAc { + public: + explicit IRPanasonicAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_PANASONIC_AC + void send(const uint16_t repeat = kPanasonicAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_PANASONIC_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void); + void setTemp(const uint8_t temp, const bool remember = true); + uint8_t getTemp(void); + void setFan(const uint8_t fan); + uint8_t getFan(void); + void setMode(const uint8_t mode); + uint8_t getMode(void); + void setRaw(const uint8_t state[]); + uint8_t *getRaw(void); + static bool validChecksum(const uint8_t *state, + const uint16_t length = kPanasonicAcStateLength); + static uint8_t calcChecksum(const uint8_t *state, + const uint16_t length = kPanasonicAcStateLength); + void setQuiet(const bool on); + bool getQuiet(void); + void setPowerful(const bool on); + bool getPowerful(void); + void setIon(const bool on); + bool getIon(void); + void setModel(const panasonic_ac_remote_model_t model); + panasonic_ac_remote_model_t getModel(void); + void setSwingVertical(const uint8_t elevation); + uint8_t getSwingVertical(void); + void setSwingHorizontal(const uint8_t direction); + uint8_t getSwingHorizontal(void); + static uint16_t encodeTime(const uint8_t hours, const uint8_t mins); + uint16_t getClock(void); + void setClock(const uint16_t mins_since_midnight); + uint16_t getOnTimer(void); + void setOnTimer(const uint16_t mins_since_midnight, const bool enable = true); + void cancelOnTimer(void); + bool isOnTimerEnabled(void); + uint16_t getOffTimer(void); + void setOffTimer(const uint16_t mins_since_midnight, + const bool enable = true); + void cancelOffTimer(void); + bool isOffTimerEnabled(void); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + stdAc::state_t toCommon(void); + arduino::String toString(void); +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + uint8_t remote_state[kPanasonicAcStateLength]; ///< The state in code form. + uint8_t _swingh; + uint8_t _temp; + void fixChecksum(const uint16_t length = kPanasonicAcStateLength); + static uint16_t _getTime(const uint8_t ptr[]); + static void _setTime(uint8_t * const ptr, const uint16_t mins_since_midnight, + const bool round_down); +}; + +/// Native representation of a Panasonic 32-bit A/C message. +union PanasonicAc32Protocol { + uint32_t raw; ///< The state in IR code form. + struct { + // Byte 0 + uint8_t :3; + uint8_t SwingH :1; + uint8_t SwingV :3; + uint8_t :1; ///< Always appears to be set. (1) + // Byte 1 + uint8_t :8; // Always seems to be 0x36. + // Byte 2 + uint8_t Temp :4; + uint8_t Fan :4; + // Byte 3 + uint8_t Mode :3; + uint8_t PowerToggle :1; // 0 means toggle, 1 = keep the same. + uint8_t :4; + }; +}; + +const uint8_t kPanasonicAc32Fan = 1; // 0b001 +const uint8_t kPanasonicAc32Cool = 2; // 0b010 +const uint8_t kPanasonicAc32Dry = 3; // 0b011 +const uint8_t kPanasonicAc32Heat = 4; // 0b010 +const uint8_t kPanasonicAc32Auto = 6; // 0b110 + +const uint8_t kPanasonicAc32FanMin = 2; +const uint8_t kPanasonicAc32FanLow = 3; +const uint8_t kPanasonicAc32FanMed = 4; +const uint8_t kPanasonicAc32FanHigh = 5; +const uint8_t kPanasonicAc32FanMax = 6; +const uint8_t kPanasonicAc32FanAuto = 0xF; +const uint8_t kPanasonicAc32SwingVAuto = 0x7; // 0b111 +const uint32_t kPanasonicAc32KnownGood = 0x0AF136FC; ///< Cool, Auto, 16C + +/// Class for handling detailed Panasonic 32bit A/C messages. +class IRPanasonicAc32 { + public: + explicit IRPanasonicAc32(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_PANASONIC_AC32 + void send(const uint16_t repeat = kPanasonicAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_PANASONIC_AC32 + void begin(void); + void setPowerToggle(const bool on); + bool getPowerToggle(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setRaw(const uint32_t state); + uint32_t getRaw(void) const; + void setSwingVertical(const uint8_t pos); + uint8_t getSwingVertical(void) const; + void setSwingHorizontal(const bool on); + bool getSwingHorizontal(void) const; + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + PanasonicAc32Protocol _; ///< The state in code form. +}; + +#endif // IR_PANASONIC_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Pioneer.cpp b/src/libraries/IRremoteESP8266/src/ir_Pioneer.cpp new file mode 100644 index 000000000..d2087107a --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Pioneer.cpp @@ -0,0 +1,143 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017, 2018 David Conran +// Copyright 2018 Kamil Palczewski +// Copyright 2019 s-hadinger + +/// @file +/// @brief Pioneer remote emulation +/// @see http://www.adrian-kingston.com/IRFormatPioneer.htm +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/547 +/// @see https://www.pioneerelectronics.com/PUSA/Support/Home-Entertainment-Custom-Install/IR+Codes/A+V+Receivers +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1220 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1749#issuecomment-1028122645 + +// Supports: +// Brand: Pioneer, Model: AV Receivers +// Brand: Pioneer, Model: VSX-324 AV Receiver +// Brand: Pioneer, Model: AXD7690 Remote + +#define __STDC_LIMIT_MACROS +#include +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1220 +const uint16_t kPioneerTick = 534; ///< uSeconds. +const uint16_t kPioneerHdrMark = 8506; ///< uSeconds. +const uint16_t kPioneerHdrSpace = 4191; ///< uSeconds. +const uint16_t kPioneerBitMark = 568; ///< uSeconds. +const uint16_t kPioneerOneSpace = 1542; ///< uSeconds. +const uint16_t kPioneerZeroSpace = 487; ///< uSeconds. +const uint32_t kPioneerMinCommandLength = 84906; ///< uSeconds. +const uint32_t kPioneerMinGap = 25181; ///< uSeconds. + +#if SEND_PIONEER +/// Send a raw Pioneer formatted message. +/// Status: STABLE / Expected to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendPioneer(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + // If nbits is to big, abort. + if (nbits > sizeof(data) * 8) return; + for (uint16_t r = 0; r <= repeat; r++) { + // don't use NEC repeat but repeat the whole sequence + if (nbits > 32) { + sendGeneric(kPioneerHdrMark, kPioneerHdrSpace, + kPioneerBitMark, kPioneerOneSpace, + kPioneerBitMark, kPioneerZeroSpace, + kPioneerBitMark, kPioneerMinGap, + kPioneerMinCommandLength, + data >> 32, nbits - 32, 40, true, 0, 33); + } + sendGeneric(kPioneerHdrMark, kPioneerHdrSpace, + kPioneerBitMark, kPioneerOneSpace, + kPioneerBitMark, kPioneerZeroSpace, + kPioneerBitMark, kPioneerMinGap, + kPioneerMinCommandLength, + data, nbits > 32 ? 32 : nbits, 40, true, 0, 33); + } +} + +/// Calculate the raw Pioneer data code based on two NEC sub-codes +/// Status: STABLE / Expected to work. +/// @param[in] address A 16-bit "published" NEC value. +/// @param[in] command A 16-bit "published" NEC value. +/// @return A raw 64-bit Pioneer message code for use with `sendPioneer()`` +/// @note Address & Command can be take from a decode result OR from the +/// spreadsheets located at: +/// https://www.pioneerelectronics.com/PUSA/Support/Home-Entertainment-Custom-Install/IR+Codes/A+V+Receivers +/// where the first part is considered the address, +/// and the second the command. +/// e.g. +/// "A556+AF20" is an Address of 0xA556 & a Command of 0xAF20. +/// @note If the Address is 0, use it like the following: +/// `irsend.sendPioneer(irsend.encodePioneer(0, 0xAA1C), 32, 1);` or +/// `irsend.sendPioneer(irsend.encodePioneer(0xAA1C, 0xAA1C), 64, 0);` +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1749#issuecomment-1028122645 +uint64_t IRsend::encodePioneer(const uint16_t address, const uint16_t command) { + return (((uint64_t)encodeNEC(address >> 8, address & 0xFF)) << 32) | + encodeNEC(command >> 8, command & 0xFF); +} +#endif // SEND_PIONEER + +#if DECODE_PIONEER +/// Decode the supplied Pioneer message. +/// Status: STABLE / Should be working. (Self decodes & real examples) +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodePioneer(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1 + offset) + return false; // Can't possibly be a valid Pioneer message. + if (strict && nbits != kPioneerBits) + return false; // Not strictly an Pioneer message. + + uint64_t data = 0; + results->value = 0; + for (uint16_t section = 0; section < 2; section++) { + // Match Header + Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits / 2, + kPioneerHdrMark, kPioneerHdrSpace, + kPioneerBitMark, kPioneerOneSpace, + kPioneerBitMark, kPioneerZeroSpace, + kPioneerBitMark, kPioneerMinGap, true); + if (!used) return false; + offset += used; + uint8_t command = data >> 8; + uint8_t command_inverted = data; + uint8_t address = data >> 24; + uint8_t address_inverted = data >> 16; + // Compliance + if (strict) { + if (command != (command_inverted ^ 0xFF)) + return false; // Command integrity failed. + if (address != (address_inverted ^ 0xFF)) + return false; // Address integrity failed. + } + results->value = (results->value << (nbits / 2)) + data; + // NEC-like commands and addresses are technically in LSB first order so the + // final versions have to be reversed. + uint16_t code = reverseBits((command << 8) + address, 16); + if (section) + results->command = code; + else + results->address = code; + } + + // Success + results->bits = nbits; + results->decode_type = PIONEER; + return true; +} +#endif // DECODE_PIONEER diff --git a/src/libraries/IRremoteESP8266/src/ir_Pronto.cpp b/src/libraries/IRremoteESP8266/src/ir_Pronto.cpp new file mode 100644 index 000000000..df4cf25ac --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Pronto.cpp @@ -0,0 +1,107 @@ +// Copyright 2017 David Conran + +/// @file +/// @brief Pronto code message generation +/// @see http://www.etcwiki.org/wiki/Pronto_Infrared_Format +/// @see http://www.remotecentral.com/features/irdisp2.htm +/// @see http://harctoolbox.org/Glossary.html#ProntoSemantics +/// @see https://irdb.globalcache.com/ + +// Supports: +// Brand: Pronto, Model: Pronto Hex + +// #include +#include "IRsend.h" + +// Constants +const float kProntoFreqFactor = 0.241246; +const uint16_t kProntoTypeOffset = 0; +const uint16_t kProntoFreqOffset = 1; +const uint16_t kProntoSeq1LenOffset = 2; +const uint16_t kProntoSeq2LenOffset = 3; +const uint16_t kProntoDataOffset = 4; + +#if SEND_PRONTO +/// Send a Pronto Code formatted message. +/// Status: STABLE / Known working. +/// @param[in] data An array of uint16_t containing the pronto codes. +/// @param[in] len Nr. of entries in the data[] array. +/// @param[in] repeat Nr. of times to repeat the message. +/// @note Pronto codes are typically represented in hexadecimal. +/// You will need to convert the code to an array of integers, and calculate +/// it's length. +/// e.g. +/// @code +/// A Sony 20 bit DVD remote command. +/// "0000 0067 0000 0015 0060 0018 0018 0018 0030 0018 0030 0018 0030 0018 +/// 0018 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0030 0018 +/// 0030 0018 0030 0018 0018 0018 0018 0018 0030 0018 0018 0018 0018 0018 +/// 0030 0018 0018 03f6" +/// @endcode +/// converts to: +/// @code{.cpp} +/// uint16_t prontoCode[46] = { +/// 0x0000, 0x0067, 0x0000, 0x0015, +/// 0x0060, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, 0x0030, 0x0018, +/// 0x0030, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018, +/// 0x0018, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, +/// 0x0030, 0x0018, 0x0030, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, +/// 0x0030, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0030, 0x0018, +/// 0x0018, 0x03f6}; +/// // Send the Pronto(Sony) code. Repeat twice as Sony's require that. +/// sendPronto(prontoCode, 46, kSonyMinRepeat); +/// @endcode +/// @see http://www.etcwiki.org/wiki/Pronto_Infrared_Format +/// @see http://www.remotecentral.com/features/irdisp2.htm +void IRsend::sendPronto(uint16_t data[], uint16_t len, uint16_t repeat) { + // Check we have enough data to work out what to send. + if (len < kProntoMinLength) return; + + // We only know how to deal with 'raw' pronto codes types. Reject all others. + if (data[kProntoTypeOffset] != 0) return; + + // Pronto frequency is in Hz. + uint16_t hz = + (uint16_t)(1000000U / (data[kProntoFreqOffset] * kProntoFreqFactor)); + enableIROut(hz); + + // Grab the length of the two sequences. + uint16_t seq_1_len = data[kProntoSeq1LenOffset] * 2; + uint16_t seq_2_len = data[kProntoSeq2LenOffset] * 2; + // Calculate where each sequence starts in the buffer. + uint16_t seq_1_start = kProntoDataOffset; + uint16_t seq_2_start = kProntoDataOffset + seq_1_len; + + uint32_t periodic_time_x10 = calcUSecPeriod(hz / 10, false); + + // Normal (1st sequence) case. + // Is there a first (normal) sequence to send? + if (seq_1_len > 0) { + // Check we have enough data to send the complete first sequence. + if (seq_1_len + seq_1_start > len) return; + // Send the contents of the 1st sequence. + for (uint16_t i = seq_1_start; i < seq_1_start + seq_1_len; i += 2) { + mark((data[i] * periodic_time_x10) / 10); + space((data[i + 1] * periodic_time_x10) / 10); + } + } else { + // There was no first sequence to send, it is implied that we have to send + // the 2nd/repeat sequence an additional time. i.e. At least once. + repeat++; + } + + // Repeat (2nd sequence) case. + // Is there a second (repeat) sequence to be sent? + if (seq_2_len > 0) { + // Check we have enough data to send the complete second sequence. + if (seq_2_len + seq_2_start > len) return; + + // Send the contents of the 2nd sequence. + for (uint16_t r = 0; r < repeat; r++) + for (uint16_t i = seq_2_start; i < seq_2_start + seq_2_len; i += 2) { + mark((data[i] * periodic_time_x10) / 10); + space((data[i + 1] * periodic_time_x10) / 10); + } + } +} +#endif // SEND_PRONTO diff --git a/src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp b/src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp new file mode 100644 index 000000000..e522a370f --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp @@ -0,0 +1,454 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017 David Conran + +/// @file +/// @brief RC-5 & RC-6 support +/// RC-5 & RC-6 support added from https://github.com/z3t0/Arduino-IRremote +/// RC-5X support added by David Conran +/// @see https://en.wikipedia.org/wiki/RC-5 +/// @see http://www.sbprojects.net/knowledge/ir/rc5.php +/// @see https://en.wikipedia.org/wiki/Manchester_code +/// @see https://en.wikipedia.org/wiki/RC-6 +/// @see https://www.sbprojects.net/knowledge/ir/rc6.php +/// @see http://www.pcbheaven.com/userpages/The_Philips_RC6_Protocol/ +/// @see http://www.righto.com/2010/12/64-bit-rc6-codes-arduino-and-xbox.html + +// Supports: +// Brand: Philips, Model: Standard RC-5 (RC5) +// Brand: Philips, Model: RC-5X (RC5X) +// Brand: Philips, Model: Standard RC-6 (RC6) + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtimer.h" +#include "IRutils.h" +#include "minmax.h" +// Constants +// RC-5/RC-5X +const uint16_t kRc5T1 = 889; +const uint32_t kRc5MinCommandLength = 113778; +const uint32_t kRc5MinGap = kRc5MinCommandLength - kRC5RawBits * (2 * kRc5T1); +const uint16_t kRc5ToggleMask = 0x800; // The 12th bit. +const uint16_t kRc5SamplesMin = 11; + +// RC-6 +const uint16_t kRc6Tick = 444; +const uint16_t kRc6HdrMarkTicks = 6; +const uint16_t kRc6HdrMark = kRc6HdrMarkTicks * kRc6Tick; +const uint16_t kRc6HdrSpaceTicks = 2; +const uint16_t kRc6HdrSpace = kRc6HdrSpaceTicks * kRc6Tick; +const uint16_t kRc6RptLengthTicks = 187; +const uint32_t kRc6RptLength = kRc6RptLengthTicks * kRc6Tick; +const uint32_t kRc6ToggleMask = 0x10000UL; // The 17th bit. +const uint16_t kRc6_36ToggleMask = 0x8000; // The 16th bit. + +// Common (getRClevel()) +const int16_t kMark = 0; +const int16_t kSpace = 1; + +#if SEND_RC5 +/// Send a Philips RC-5/RC-5X packet. +/// Status: RC-5 (stable), RC-5X (alpha) +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Caller needs to take care of flipping the toggle bit. +/// That bit differentiates between key press & key release. +/// For RC-5 it is the MSB of the data. +/// For RC-5X it is the 2nd MSB of the data. +/// @todo Testing of the RC-5X components. +void IRsend::sendRC5(const uint64_t data, uint16_t nbits, + const uint16_t repeat) { + if (nbits > sizeof(data) * 8) return; // We can't send something that big. + bool skipSpace = true; + bool field_bit = true; + // Set 36kHz IR carrier frequency & a 1/4 (25%) duty cycle. + enableIROut(36, 25); + + if (nbits >= kRC5XBits) { // Is this a RC-5X message? + // field bit is the inverted MSB of RC-5X data. + field_bit = ((data >> (nbits - 1)) ^ 1) & 1; + nbits--; + } + + IRtimer usecTimer = IRtimer(); + for (uint16_t i = 0; i <= repeat; i++) { + usecTimer.reset(); + + // Header + // First start bit (0x1). space, then mark. + if (skipSpace) + skipSpace = false; // First time through, we assume the leading space(). + else + space(kRc5T1); + mark(kRc5T1); + // Field/Second start bit. + if (field_bit) { // Send a 1. Normal for RC-5. + space(kRc5T1); + mark(kRc5T1); + } else { // Send a 0. Special case for RC-5X. Means 7th command bit is 1. + mark(kRc5T1); + space(kRc5T1); + } + + // Data + for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1) + if (data & mask) { // 1 + space(kRc5T1); // 1 is space, then mark. + mark(kRc5T1); + } else { // 0 + mark(kRc5T1); // 0 is mark, then space. + space(kRc5T1); + } + // Footer + space(::max(kRc5MinGap, kRc5MinCommandLength - usecTimer.elapsed())); + } +} + +/// Encode a Philips RC-5 data message. +/// Status: Beta / Should be working. +/// @param[in] address The 5-bit address value for the message. +/// @param[in] command The 6-bit command value for the message. +/// @param[in] key_released Indicate if the remote key has been released. +/// @return A message suitable for use in sendRC5(). +uint16_t IRsend::encodeRC5(const uint8_t address, const uint8_t command, + const bool key_released) { + return (key_released << (kRC5Bits - 1)) | ((address & 0x1f) << 6) | + (command & 0x3F); +} + +/// Encode a Philips RC-5X data message. +/// Status: Beta / Should be working. +/// @param[in] address The 5-bit address value for the message. +/// @param[in] command The 7-bit command value for the message. +/// @param[in] key_released Indicate if the remote key has been released. +/// @return A message suitable for use in sendRC5(). +uint16_t IRsend::encodeRC5X(const uint8_t address, const uint8_t command, + const bool key_released) { + // The 2nd start/field bit (MSB of the return value) is the value of the 7th + // command bit. + bool s2 = (command >> 6) & 1; + return ((uint16_t)s2 << (kRC5XBits - 1)) | + encodeRC5(address, command, key_released); +} + +/// Flip the toggle bit of a Philips RC-5/RC-5X data message. +/// Used to indicate a change of remote button's state. +/// Status: STABLE. +/// @param[in] data The existing RC-5/RC-5X message. +/// @return A data message suitable for use in sendRC5() with the toggle bit +/// flipped. +uint64_t IRsend::toggleRC5(const uint64_t data) { + return data ^ kRc5ToggleMask; +} +#endif // SEND_RC5 + +#if SEND_RC6 +/// Flip the toggle bit of a Philips RC-6 data message. +/// Used to indicate a change of remote button's state. +/// Status: STABLE / Should work fine. +/// @param[in] data The existing RC-6 message. +/// @param [in] nbits Nr. of bits in the RC-6 protocol. +/// @return A data message suitable for use in sendRC6() with the toggle bit +/// flipped. +/// @note For RC-6 (20-bits), it is the 17th least significant bit. +/// @note For RC-6 (36-bits/Xbox-360), it is the 16th least significant bit. +uint64_t IRsend::toggleRC6(const uint64_t data, const uint16_t nbits) { + if (nbits == kRC6_36Bits) return data ^ kRc6_36ToggleMask; + return data ^ kRc6ToggleMask; +} + +/// Encode a Philips RC-6 data message. +/// Status: Beta / Should be working. +/// @param[in] address The address (aka. control) value for the message. +/// Includes the field/mode/toggle bits. +/// @param[in] command The 8-bit command value for the message. +/// (aka. information) +/// @param[in] mode Which protocol to use. +/// Defined by nr. of bits in the protocol. +/// @return A data message suitable for use in `sendRC6()`. +uint64_t IRsend::encodeRC6(const uint32_t address, const uint8_t command, + const uint16_t mode) { + switch (mode) { + case kRC6Mode0Bits: + return ((address & 0xFFF) << 8) | (command & 0xFF); + case kRC6_36Bits: + return ((uint64_t)(address & 0xFFFFFFF) << 8) | (command & 0xFF); + default: + return 0; + } +} + +/// Send a Philips RC-6 packet. +/// Status: Stable. +/// @note Caller needs to take care of flipping the toggle bit (The 4th Most +/// Significant Bit). That bit differentiates between key press & key release. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendRC6(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + // Check we can send the number of bits requested. + if (nbits > sizeof(data) * 8) return; + // Set 36kHz IR carrier frequency & a 1/3 (33%) duty cycle. + enableIROut(36, 33); + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kRc6HdrMark); + space(kRc6HdrSpace); + // Start bit. + mark(kRc6Tick); // mark, then space == 0x1. + space(kRc6Tick); + // Data + uint16_t bitTime; + for (uint64_t i = 1, mask = 1ULL << (nbits - 1); mask; i++, mask >>= 1) { + if (i == 4) // The fourth bit we send is a "double width trailer bit". + bitTime = 2 * kRc6Tick; // double-wide trailer bit + else + bitTime = kRc6Tick; // Normal bit + if (data & mask) { // 1 + mark(bitTime); + space(bitTime); + } else { // 0 + space(bitTime); + mark(bitTime); + } + } + // Footer + space(kRc6RptLength); + } +} +#endif // SEND_RC6 + +#if (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG) +/// Gets one undecoded level at a time from the raw buffer. +/// The RC5/6 decoding is easier if the data is broken into time intervals. +/// E.g. if the buffer has MARK for 2 time intervals and SPACE for 1, +/// successive calls to getRClevel will return MARK, MARK, SPACE. +/// offset and used are updated to keep track of the current position. +/// @param[in,out] results Ptr to the data to decode and where to store the +/// decode result. +/// @param[in,out] offset Ptr to the currect offset to the rawbuf. +/// @param[in,out] used Ptr to the current used counter. +/// @param[in] bitTime Time interval of single bit in microseconds. +/// @param[in] tolerance Percent tolerance to be used in matching. +/// @param[in] excess Extra useconds to add to Marks & removed from Spaces. +/// @param[in] delta A non-scaling (+/-) error margin (in useconds). +/// @param[in] maxwidth Maximum number of successive levels to find in a single +/// level (default is 3) +/// @return MARK, SPACE, or -1 for error. +/// (The measured time interval is not a multiple of t1.) +/// @see https://en.wikipedia.org/wiki/Manchester_code +int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset, + uint16_t *used, const uint16_t bitTime, + const uint8_t tolerance, const int16_t excess, + const uint16_t delta, const uint8_t maxwidth) { + DPRINT("DEBUG: getRClevel: offset = "); + DPRINTLN(uint64ToString(*offset)); + DPRINT("DEBUG: getRClevel: rawlen = "); + DPRINTLN(uint64ToString(results->rawlen)); + if (*offset >= results->rawlen) { + DPRINTLN("DEBUG: getRClevel: SPACE, past end of rawbuf"); + return kSpace; // After end of recorded buffer, assume SPACE. + } + uint16_t width = results->rawbuf[*offset]; + // If the value of offset is odd, it's a MARK. Even, it's a SPACE. + uint16_t val = ((*offset) % 2) ? kMark : kSpace; + // Check to see if we have hit an inter-message gap (> 20ms). + if (val == kSpace && + (width > 20000 - delta || width > maxwidth * bitTime + delta)) { + DPRINTLN("DEBUG: getRClevel: SPACE, hit end of mesg gap."); + return kSpace; + } + int16_t correction = (val == kMark) ? excess : -excess; + + // Calculate the look-ahead for our current position in the buffer. + uint16_t avail; + // Note: We want to match in greedy order as the other way leads to + // mismatches due to overlaps induced by the correction and tolerance + // values. + for (avail = maxwidth; avail > 0; avail--) { + if (match(width, avail * bitTime + correction, tolerance, delta)) { + break; + } + } + if (!avail) { + DPRINTLN("DEBUG: getRClevel: Unexpected width. Exiting."); + return -1; // The width is not what we expected. + } + + (*used)++; // Count another one of the avail slots as used. + if (*used >= avail) { // Are we out of look-ahead/avail slots? + // Yes, so reset the used counter, and move the offset ahead. + *used = 0; + (*offset)++; + } + if (val == kMark) { + DPRINTLN("DEBUG: getRClevel: MARK"); + } else { + DPRINTLN("DEBUG: getRClevel: SPACE"); + } + + return val; +} +#endif // (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG) + +#if DECODE_RC5 +/// Decode the supplied RC-5/RC5X message. +/// Status: RC-5 (stable), RC-5X (alpha) +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note The 'toggle' bit is included as the 6th (MSB) address bit, the MSB of +/// data, & in the count of bits decoded. +/// @todo Serious testing of the RC-5X and strict aspects needs to be done. +bool IRrecv::decodeRC5(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= kRc5SamplesMin + kHeader - 1 + offset) return false; + + // Compliance + if (strict && nbits != kRC5Bits && nbits != kRC5XBits) + return false; // It's neither RC-5 or RC-5X. + + uint16_t used = 0; + bool is_rc5x = false; + uint64_t data = 0; + + // Header + // Get start bit #1. + if (getRClevel(results, &offset, &used, kRc5T1) != kMark) return false; + // Get field/start bit #2 (inverted bit-7 of the command if RC-5X protocol) + uint16_t actual_bits = 1; + int16_t levelA = getRClevel(results, &offset, &used, kRc5T1); + int16_t levelB = getRClevel(results, &offset, &used, kRc5T1); + if (levelA == kSpace && levelB == kMark) { // Matched a 1. + is_rc5x = false; + } else if (levelA == kMark && levelB == kSpace) { // Matched a 0. + if (nbits <= kRC5Bits) return false; // Field bit must be '1' for RC5. + is_rc5x = true; + data = 1; + } else { + return false; // Not what we expected. + } + + // Data + for (; offset < results->rawlen; actual_bits++) { + int16_t levelA = getRClevel(results, &offset, &used, kRc5T1); + int16_t levelB = getRClevel(results, &offset, &used, kRc5T1); + if (levelA == kSpace && levelB == kMark) + data = (data << 1) | 1; // 1 + else if (levelA == kMark && levelB == kSpace) + data <<= 1; // 0 + else + break; + } + // Footer (None) + + // Compliance + if (actual_bits < nbits) return false; // Less data than we expected. + if (strict && actual_bits != kRC5Bits && actual_bits != kRC5XBits) + return false; + + // Success + results->value = data; + results->address = (data >> 6) & 0x1F; + results->command = data & 0x3F; + results->repeat = false; + if (is_rc5x) { + results->decode_type = RC5X; + results->command |= ((uint32_t)is_rc5x) << 6; + } else { + results->decode_type = RC5; + actual_bits--; // RC5 doesn't count the field bit as data. + } + results->bits = actual_bits; + return true; +} +#endif // DECODE_RC5 + +#if DECODE_RC6 +/// Decode the supplied RC6 message. +/// Status: Stable. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @todo Testing of the strict compliance aspects. +bool IRrecv::decodeRC6(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= kHeader + 2 + 4 + offset) + // Up to the double-wide T bit. + return false; // Smaller than absolute smallest possible RC6 message. + + if (strict) { // Compliance + // Unlike typical protocols, the ability to have mark+space, and space+mark + // as data bits means it is possible to only have nbits of entries for the + // data portion, rather than the typically required 2 * nbits. + // Also due to potential melding with the start bit, we can only count + // the start bit as 1, instead of a more typical 2 value. The header still + // remains as normal. + if (results->rawlen <= nbits + kHeader + 1 + offset) + return false; // Don't have enough entries/samples to be valid. + switch (nbits) { + case kRC6Mode0Bits: + case kRC6_36Bits: + break; + default: + return false; // Asking for the wrong number of bits. + } + } + + // Header + if (!matchMark(results->rawbuf[offset], kRc6HdrMark)) return false; + // Calculate how long the common tick time is based on the header mark. + uint32_t tick = results->rawbuf[offset++] * kRawTick / kRc6HdrMarkTicks; + if (!matchSpace(results->rawbuf[offset++], kRc6HdrSpaceTicks * tick)) + return false; + + uint16_t used = 0; + + // Get the start bit. e.g. 1. + if (getRClevel(results, &offset, &used, tick) != kMark) return false; + if (getRClevel(results, &offset, &used, tick) != kSpace) return false; + + uint16_t actual_bits; + uint64_t data = 0; + + // Data (Warning: Here be dragons^Wpointers!!) + for (actual_bits = 0; offset < results->rawlen; actual_bits++) { + int16_t levelA, levelB; // Next two levels + levelA = getRClevel(results, &offset, &used, tick); + // T bit is double wide; make sure second half matches + if (actual_bits == 3 && levelA != getRClevel(results, &offset, &used, tick)) + return false; + levelB = getRClevel(results, &offset, &used, tick); + // T bit is double wide; make sure second half matches + if (actual_bits == 3 && levelB != getRClevel(results, &offset, &used, tick)) + return false; + if (levelA == kMark && levelB == kSpace) // reversed compared to RC5 + data = (data << 1) | 1; // 1 + else if (levelA == kSpace && levelB == kMark) + data <<= 1; // 0 + else + break; + } + + // More compliance + if (strict && actual_bits != nbits) + return false; // Actual nr. of bits didn't match expected. + + // Success + results->decode_type = RC6; + results->bits = actual_bits; + results->value = data; + results->address = data >> 8; + results->command = data & 0xFF; + return true; +} +#endif // DECODE_RC6 diff --git a/src/libraries/IRremoteESP8266/src/ir_RCMM.cpp b/src/libraries/IRremoteESP8266/src/ir_RCMM.cpp new file mode 100644 index 000000000..ea7310ddc --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_RCMM.cpp @@ -0,0 +1,165 @@ +// Copyright 2017 David Conran + +/// @file +/// @brief Support for the Phillips RC-MM protocol. +/// @see http://www.sbprojects.net/knowledge/ir/rcmm.php + +// Supports: +// Brand: Microsoft, Model: XBOX 360 + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtimer.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kRcmmTick = 28; // Technically it would be 27.777* +const uint16_t kRcmmHdrMarkTicks = 15; +const uint16_t kRcmmHdrMark = 416; +const uint16_t kRcmmHdrSpaceTicks = 10; +const uint16_t kRcmmHdrSpace = 277; +const uint16_t kRcmmBitMarkTicks = 6; +const uint16_t kRcmmBitMark = 166; +const uint16_t kRcmmBitSpace0Ticks = 10; +const uint16_t kRcmmBitSpace0 = 277; +const uint16_t kRcmmBitSpace1Ticks = 16; +const uint16_t kRcmmBitSpace1 = 444; +const uint16_t kRcmmBitSpace2Ticks = 22; +const uint16_t kRcmmBitSpace2 = 611; +const uint16_t kRcmmBitSpace3Ticks = 28; +const uint16_t kRcmmBitSpace3 = 777; +const uint16_t kRcmmRptLengthTicks = 992; +const uint32_t kRcmmRptLength = 27778; +const uint16_t kRcmmMinGapTicks = 120; +const uint32_t kRcmmMinGap = 3360; +// Use a tolerance of +/-10% when matching some data spaces. +const uint8_t kRcmmTolerance = 10; +const uint16_t kRcmmExcess = 50; + +#if SEND_RCMM +/// Send a Philips RC-MM packet. +/// Status: STABLE / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendRCMM(uint64_t data, uint16_t nbits, uint16_t repeat) { + // Set 36kHz IR carrier frequency & a 1/3 (33%) duty cycle. + enableIROut(36, 33); + IRtimer usecs = IRtimer(); + + for (uint16_t r = 0; r <= repeat; r++) { + usecs.reset(); + // Header + mark(kRcmmHdrMark); + space(kRcmmHdrSpace); + // Data + uint64_t mask = 0b11ULL << (nbits - 2); + // RC-MM sends data 2 bits at a time. + for (int32_t i = nbits; i > 0; i -= 2) { + mark(kRcmmBitMark); + // Grab the next Most Significant Bits to send. + switch ((data & mask) >> (i - 2)) { + case 0b00: + space(kRcmmBitSpace0); + break; + case 0b01: + space(kRcmmBitSpace1); + break; + case 0b10: + space(kRcmmBitSpace2); + break; + case 0b11: + space(kRcmmBitSpace3); + break; + } + mask >>= 2; + } + // Footer + mark(kRcmmBitMark); + // Protocol requires us to wait at least kRcmmRptLength usecs from the + // start or kRcmmMinGap usecs. + space(::max(kRcmmRptLength - usecs.elapsed(), kRcmmMinGap)); + } +} +#endif // SEND_RCMM + +#if DECODE_RCMM +/// Decode a Philips RC-MM packet (between 12 & 32 bits) if possible. +/// Status: STABLE / Should be working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeRCMM(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint64_t data = 0; + + if (results->rawlen <= 4 + offset - 1) + return false; // Not enough entries to ever be RCMM. + + // Calc the maximum size in bits, the message can be, or that we can accept. + int16_t maxBitSize = + ::min((uint16_t)results->rawlen - 5, (uint16_t)sizeof(data) * 8); + // Compliance + if (strict) { + // Technically the spec says bit sizes should be 12 xor 24. however + // 32 bits has been seen from a device. We are going to assume + // 12 <= bits <= 32 is the 'required' bit length for the spec. + if (maxBitSize < 12 || maxBitSize > 32) return false; + if (maxBitSize < nbits) + return false; // Short cut, we can never reach the expected nr. of bits. + } + // Header decode + if (!matchMark(results->rawbuf[offset], kRcmmHdrMark)) return false; + // Calculate how long the common tick time is based on the header mark. + uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kRcmmHdrMarkTicks; + if (!matchSpace(results->rawbuf[offset], kRcmmHdrSpace)) return false; + // Calculate how long the common tick time is based on the header space. + uint32_t s_tick = results->rawbuf[offset++] * kRawTick / kRcmmHdrSpaceTicks; + + // Data decode + // RC-MM has two bits of data per mark/space pair. + uint16_t actualBits; + for (actualBits = 0; actualBits < maxBitSize; actualBits += 2, offset++) { + if (!match(results->rawbuf[offset++], kRcmmBitMarkTicks * m_tick)) + return false; + + data <<= 2; + // Use non-default tolerance & excess for matching some of the spaces as the + // defaults are too generous and causes mis-matches in some cases. + if (match(results->rawbuf[offset], kRcmmBitSpace0Ticks * s_tick)) + data += 0; + else if (match(results->rawbuf[offset], kRcmmBitSpace1Ticks * s_tick)) + data += 1; + else if (match(results->rawbuf[offset], kRcmmBitSpace2Ticks * s_tick, + kRcmmTolerance)) + data += 2; + else if (match(results->rawbuf[offset], kRcmmBitSpace3Ticks * s_tick, + kRcmmTolerance)) + data += 3; + else + return false; + } + // Footer decode + if (!match(results->rawbuf[offset++], kRcmmBitMarkTicks * m_tick)) + return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kRcmmMinGapTicks * s_tick)) + return false; + + // Compliance + if (strict && actualBits != nbits) return false; + + // Success + results->value = data; + results->decode_type = RCMM; + results->bits = actualBits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_RCMM diff --git a/src/libraries/IRremoteESP8266/src/ir_Rhoss.cpp b/src/libraries/IRremoteESP8266/src/ir_Rhoss.cpp new file mode 100644 index 000000000..5137159e6 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Rhoss.cpp @@ -0,0 +1,365 @@ +// Copyright 2021 Tom Rosenback + +/// @file +/// @brief Support for Rhoss protocols. + +#include "ir_Rhoss.h" +// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +const uint16_t kRhossHdrMark = 3042; +const uint16_t kRhossHdrSpace = 4248; +const uint16_t kRhossBitMark = 648; +const uint16_t kRhossOneSpace = 1545; +const uint16_t kRhossZeroSpace = 457; +const uint32_t kRhossGap = kDefaultMessageGap; +const uint16_t kRhossFreq = 38; + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; + +#if SEND_RHOSS +/// Send a Rhoss HVAC formatted message. +/// Status: STABLE / Reported as working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendRhoss(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + // Check if we have enough bytes to send a proper message. + if (nbytes < kRhossStateLength) return; + + // We always send a message, even for repeat=0, hence '<= repeat'. + for (uint16_t r = 0; r <= repeat; r++) { + sendGeneric(kRhossHdrMark, kRhossHdrSpace, kRhossBitMark, + kRhossOneSpace, kRhossBitMark, kRhossZeroSpace, + kRhossBitMark, kRhossZeroSpace, + data, nbytes, kRhossFreq, false, 0, kDutyDefault); + mark(kRhossBitMark); + // Gap + space(kRhossGap); + } +} +#endif // SEND_RHOSS + +#if DECODE_RHOSS +/// Decode the supplied Rhoss formatted message. +/// Status: STABLE / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +bool IRrecv::decodeRhoss(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kRhossBits) return false; + + if (results->rawlen <= 2 * nbits + kHeader + kFooter - 1 + offset) { + return false; // Can't possibly be a valid Rhoss message. + } + + uint16_t used; + // Header + Data Block (96 bits) + Footer + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, kRhossBits, + kRhossHdrMark, kRhossHdrSpace, + kRhossBitMark, kRhossOneSpace, + kRhossBitMark, kRhossZeroSpace, + kRhossBitMark, kRhossZeroSpace, + false, kUseDefTol, kMarkExcess, false); + + if (!used) return false; + offset += used; + + // Footer (Part 2) + if (!matchMark(results->rawbuf[offset++], kRhossBitMark)) { + return false; + } + + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kRhossGap)) { + return false; + } + + if (strict && !IRRhossAc::validChecksum(results->state)) return false; + + // Success + results->decode_type = decode_type_t::RHOSS; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} + +#endif // DECODE_RHOSS + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRRhossAc::IRRhossAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { this->stateReset(); } + +/// Set up hardware to be able to send a message. +void IRRhossAc::begin(void) { _irsend.begin(); } + +#if SEND_RHOSS +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRRhossAc::send(const uint16_t repeat) { + _irsend.sendRhoss(getRaw(), kRhossStateLength, repeat); +} +#endif // SEND_RHOSS + +/// Calculate the checksum for the supplied state. +/// @param[in] state The source state to generate the checksum from. +/// @param[in] length Length of the supplied state to checksum. +/// @return The checksum value. +uint8_t IRRhossAc::calcChecksum(const uint8_t state[], const uint16_t length) { + return sumBytes(state, length - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The size of the state. +/// @return A boolean indicating if it's checksum is valid. +bool IRRhossAc::validChecksum(const uint8_t state[], const uint16_t length) { + return (state[length - 1] == IRRhossAc::calcChecksum(state, length)); +} + +/// Update the checksum value for the internal state. +void IRRhossAc::checksum(void) { + _.Sum = IRRhossAc::calcChecksum(_.raw, kRhossStateLength); + _.raw[kRhossStateLength - 1] = _.Sum; +} + +/// Reset the internals of the object to a known good state. +void IRRhossAc::stateReset(void) { + for (uint8_t i = 1; i < kRhossStateLength; i++) _.raw[i] = 0x0; + _.raw[0] = 0xAA; + _.raw[2] = 0x60; + _.raw[6] = 0x54; + _.Power = kRhossDefaultPower; + _.Fan = kRhossDefaultFan; + _.Mode = kRhossDefaultMode; + _.Swing = kRhossDefaultSwing; + _.Temp = kRhossDefaultTemp - kRhossTempMin; +} + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A PTR to the internal state. +uint8_t* IRRhossAc::getRaw(void) { + checksum(); // Ensure correct bit array before returning + return _.raw; +} + +/// Set the raw state of the object. +/// @param[state] state The raw state from the native IR message. +void IRRhossAc::setRaw(const uint8_t state[]) { + memcpy(_.raw, state, kRhossStateLength); +} + +/// Set the internal state to have the power on. +void IRRhossAc::on(void) { setPower(true); } + +/// Set the internal state to have the power off. +void IRRhossAc::off(void) { setPower(false); } + +/// Set the internal state to have the desired power. +/// @param[in] on The desired power state. +void IRRhossAc::setPower(const bool on) { + _.Power = (on ? kRhossPowerOn : kRhossPowerOff); +} + +/// Get the power setting from the internal state. +/// @return A boolean indicating the power setting. +bool IRRhossAc::getPower(void) const { + return _.Power == kRhossPowerOn; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRRhossAc::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kRhossTempMin, degrees); + _.Temp = ::min(kRhossTempMax, temp) - kRhossTempMin; +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRRhossAc::getTemp(void) const { + return _.Temp + kRhossTempMin; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRRhossAc::setFan(const uint8_t speed) { + switch (speed) { + case kRhossFanAuto: + case kRhossFanMin: + case kRhossFanMed: + case kRhossFanMax: + _.Fan = speed; + break; + default: + _.Fan = kRhossFanAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRRhossAc::getFan(void) const { + return _.Fan; +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] state true, the Swing is on. false, the Swing is off. +void IRRhossAc::setSwing(const bool state) { + _.Swing = state; +} + +/// Get the Vertical Swing speed of the A/C. +/// @return The native swing speed setting. +uint8_t IRRhossAc::getSwing(void) const { + return _.Swing; +} + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRRhossAc::getMode(void) const { + return _.Mode; +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRRhossAc::setMode(const uint8_t mode) { + switch (mode) { + case kRhossModeFan: + case kRhossModeCool: + case kRhossModeDry: + case kRhossModeHeat: + case kRhossModeAuto: + _.Mode = mode; + return; + default: + _.Mode = kRhossDefaultMode; + break; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRRhossAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kRhossModeCool; + case stdAc::opmode_t::kHeat: + return kRhossModeHeat; + case stdAc::opmode_t::kDry: + return kRhossModeDry; + case stdAc::opmode_t::kFan: + return kRhossModeFan; + case stdAc::opmode_t::kAuto: + return kRhossModeAuto; + default: + return kRhossDefaultMode; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRRhossAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kRhossFanMin; + case stdAc::fanspeed_t::kMedium: + return kRhossFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kRhossFanMax; + default: + return kRhossDefaultFan; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRRhossAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kRhossModeCool: return stdAc::opmode_t::kCool; + case kRhossModeHeat: return stdAc::opmode_t::kHeat; + case kRhossModeDry: return stdAc::opmode_t::kDry; + case kRhossModeFan: return stdAc::opmode_t::kFan; + case kRhossModeAuto: return stdAc::opmode_t::kAuto; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRRhossAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kRhossFanMax: return stdAc::fanspeed_t::kMax; + case kRhossFanMed: return stdAc::fanspeed_t::kMedium; + case kRhossFanMin: return stdAc::fanspeed_t::kMin; + case kRhossFanAuto: + default: + return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRRhossAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::RHOSS; + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = _.Temp; + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.Swing ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + // Not supported. + result.model = -1; + result.turbo = false; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRRhossAc::toString(void) const { + String result = ""; + result.reserve(70); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(getMode(), kRhossModeAuto, kRhossModeCool, + kRhossModeHeat, kRhossModeDry, kRhossModeFan); + result += addTempToString(getTemp()); + result += addFanToString(getFan(), kRhossFanMax, kRhossFanMin, + kRhossFanAuto, kRhossFanAuto, + kRhossFanMed); + result += addBoolToString(getSwing(), kSwingVStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Rhoss.h b/src/libraries/IRremoteESP8266/src/ir_Rhoss.h new file mode 100644 index 000000000..515f70f86 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Rhoss.h @@ -0,0 +1,145 @@ +// Copyright 2021 Tom Rosenback + +/// @file +/// @brief Support for Rhoss A/C protocol +// Supports: +// Brand: Rhoss, Model: Idrowall MPCV 20-30-35-40 + +#ifndef IR_RHOSS_H_ +#define IR_RHOSS_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + + +/// Native representation of a Rhoss A/C message. +union RhossProtocol{ + uint8_t raw[kRhossStateLength]; // The state of the IR remote. + struct { + // Byte 0 + uint8_t :8; // Typically 0xAA + // Byte 1 + uint8_t Temp :4; + uint8_t :4; // Typically 0x0 + // Byte 2 + uint8_t :8; // Typically 0x60 + // Byte 3 + uint8_t :8; // Typically 0x0 + // Byte 4 + uint8_t Fan :2; + uint8_t :2; // Typically 0x0 + uint8_t Mode :4; + // Byte 5 + uint8_t Swing :1; + uint8_t :5; // Typically 0x0 + uint8_t Power :2; + // Byte 6 + uint8_t :8; // Typically 0x54 + // Byte 7 + uint8_t :8; // Typically 0x0 + // Byte 8 + uint8_t :8; // Typically 0x0 + // Byte 9 + uint8_t :8; // Typically 0x0 + // Byte 10 + uint8_t :8; // Typically 0x0 + // Byte 11 + uint8_t Sum :8; + }; +}; + +// Constants + +// Fan Control +const uint8_t kRhossFanAuto = 0b00; +const uint8_t kRhossFanMin = 0b01; +const uint8_t kRhossFanMed = 0b10; +const uint8_t kRhossFanMax = 0b11; +// Modes +const uint8_t kRhossModeHeat = 0b0001; +const uint8_t kRhossModeCool = 0b0010; +const uint8_t kRhossModeDry = 0b0011; +const uint8_t kRhossModeFan = 0b0100; +const uint8_t kRhossModeAuto = 0b0101; + +// Temperature +const uint8_t kRhossTempMin = 16; // Celsius +const uint8_t kRhossTempMax = 30; // Celsius + +// Power +const uint8_t kRhossPowerOn = 0b10; // 0x2 +const uint8_t kRhossPowerOff = 0b01; // 0x1 + +// Swing +const uint8_t kRhossSwingOn = 0b1; // 0x1 +const uint8_t kRhossSwingOff = 0b0; // 0x0 + +const uint8_t kRhossDefaultFan = kRhossFanAuto; +const uint8_t kRhossDefaultMode = kRhossModeCool; +const uint8_t kRhossDefaultTemp = 21; // Celsius +const bool kRhossDefaultPower = false; +const bool kRhossDefaultSwing = false; + +// Classes + +/// Class for handling detailed Rhoss A/C messages. +class IRRhossAc { + public: + explicit IRRhossAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + + void stateReset(); +#if SEND_RHOSS + void send(const uint16_t repeat = kRhossDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_RHOSS + void begin(); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kRhossStateLength); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kRhossStateLength); + void setPower(const bool state); + bool getPower(void) const; + void on(void); + void off(void); + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setSwing(const bool state); + uint8_t getSwing(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t state[]); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; +#else + /// @cond IGNORE + IRsendTest _irsend; + /// @endcond +#endif + RhossProtocol _; + void checksum(void); +}; +#endif // IR_RHOSS_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Samsung.cpp b/src/libraries/IRremoteESP8266/src/ir_Samsung.cpp new file mode 100644 index 000000000..1cfc5e8df --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Samsung.cpp @@ -0,0 +1,993 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017-2021 David Conran +/// @file +/// @brief Support for Samsung protocols. +/// Samsung originally added from https://github.com/shirriff/Arduino-IRremote/ +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/505 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062 +/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538 (Checksum) + +#include "ir_Samsung.h" +//// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kSamsungTick = 560; +const uint16_t kSamsungHdrMarkTicks = 8; +const uint16_t kSamsungHdrMark = kSamsungHdrMarkTicks * kSamsungTick; +const uint16_t kSamsungHdrSpaceTicks = 8; +const uint16_t kSamsungHdrSpace = kSamsungHdrSpaceTicks * kSamsungTick; +const uint16_t kSamsungBitMarkTicks = 1; +const uint16_t kSamsungBitMark = kSamsungBitMarkTicks * kSamsungTick; +const uint16_t kSamsungOneSpaceTicks = 3; +const uint16_t kSamsungOneSpace = kSamsungOneSpaceTicks * kSamsungTick; +const uint16_t kSamsungZeroSpaceTicks = 1; +const uint16_t kSamsungZeroSpace = kSamsungZeroSpaceTicks * kSamsungTick; +const uint16_t kSamsungRptSpaceTicks = 4; +const uint16_t kSamsungRptSpace = kSamsungRptSpaceTicks * kSamsungTick; +const uint16_t kSamsungMinMessageLengthTicks = 193; +const uint32_t kSamsungMinMessageLength = + kSamsungMinMessageLengthTicks * kSamsungTick; +const uint16_t kSamsungMinGapTicks = + kSamsungMinMessageLengthTicks - + (kSamsungHdrMarkTicks + kSamsungHdrSpaceTicks + + kSamsungBits * (kSamsungBitMarkTicks + kSamsungOneSpaceTicks) + + kSamsungBitMarkTicks); +const uint32_t kSamsungMinGap = kSamsungMinGapTicks * kSamsungTick; + +const uint16_t kSamsungAcHdrMark = 690; +const uint16_t kSamsungAcHdrSpace = 17844; +const uint8_t kSamsungAcSections = 2; +const uint16_t kSamsungAcSectionMark = 3086; +const uint16_t kSamsungAcSectionSpace = 8864; +const uint16_t kSamsungAcSectionGap = 2886; +const uint16_t kSamsungAcBitMark = 586; +const uint16_t kSamsungAcOneSpace = 1432; +const uint16_t kSamsungAcZeroSpace = 436; + +// Data from https://github.com/crankyoldgit/IRremoteESP8266/issues/1220 +// Values calculated based on the average of ten messages. +const uint16_t kSamsung36HdrMark = 4515; /// < uSeconds +const uint16_t kSamsung36HdrSpace = 4438; /// < uSeconds +const uint16_t kSamsung36BitMark = 512; /// < uSeconds +const uint16_t kSamsung36OneSpace = 1468; /// < uSeconds +const uint16_t kSamsung36ZeroSpace = 490; /// < uSeconds + +// _.Swing +const uint8_t kSamsungAcSwingV = 0b010; +const uint8_t kSamsungAcSwingH = 0b011; +const uint8_t kSamsungAcSwingBoth = 0b100; +const uint8_t kSamsungAcSwingOff = 0b111; +// _.FanSpecial +const uint8_t kSamsungAcFanSpecialOff = 0b000; +const uint8_t kSamsungAcPowerfulOn = 0b011; +const uint8_t kSamsungAcBreezeOn = 0b101; +const uint8_t kSamsungAcEconoOn = 0b111; + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::addToggleToString; +using irutils::minsToString; + +#if SEND_SAMSUNG +/// Send a 32-bit Samsung formatted message. +/// Status: STABLE / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf +/// @note Samsung has a separate message to indicate a repeat, like NEC does. +/// @todo Confirm that is actually how Samsung sends a repeat. +/// The refdoc doesn't indicate it is true. +void IRsend::sendSAMSUNG(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kSamsungHdrMark, kSamsungHdrSpace, kSamsungBitMark, + kSamsungOneSpace, kSamsungBitMark, kSamsungZeroSpace, + kSamsungBitMark, kSamsungMinGap, kSamsungMinMessageLength, data, + nbits, 38, true, repeat, 33); +} + +/// Construct a raw Samsung message from the supplied customer(address) & +/// command. +/// Status: STABLE / Should be working. +/// @param[in] customer The customer code. (aka. Address) +/// @param[in] command The command code. +/// @return A raw 32-bit Samsung message suitable for `sendSAMSUNG()`. +uint32_t IRsend::encodeSAMSUNG(const uint8_t customer, const uint8_t command) { + uint8_t revcustomer = reverseBits(customer, sizeof(customer) * 8); + uint8_t revcommand = reverseBits(command, sizeof(command) * 8); + return ((revcommand ^ 0xFF) | (revcommand << 8) | (revcustomer << 16) | + (revcustomer << 24)); +} +#endif + +#if DECODE_SAMSUNG +/// Decode the supplied Samsung 32-bit message. +/// Status: STABLE +/// @note Samsung messages whilst 32 bits in size, only contain 16 bits of +/// distinct data. e.g. In transmition order: +/// customer_byte + customer_byte(same) + address_byte + invert(address_byte) +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note LG 32bit protocol appears near identical to the Samsung protocol. +/// They differ on their compliance criteria and how they repeat. +/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf +bool IRrecv::decodeSAMSUNG(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kSamsungBits) + return false; // We expect Samsung to be 32 bits of message. + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kSamsungHdrMark, kSamsungHdrSpace, + kSamsungBitMark, kSamsungOneSpace, + kSamsungBitMark, kSamsungZeroSpace, + kSamsungBitMark, kSamsungMinGap, true)) return false; + // Compliance + // According to the spec, the customer (address) code is the first 8 + // transmitted bits. It's then repeated. Check for that. + uint8_t address = data >> 24; + if (strict && address != ((data >> 16) & 0xFF)) return false; + // Spec says the command code is the 3rd block of transmitted 8-bits, + // followed by the inverted command code. + uint8_t command = (data & 0xFF00) >> 8; + if (strict && command != ((data & 0xFF) ^ 0xFF)) return false; + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = SAMSUNG; + // command & address need to be reversed as they are transmitted LSB first, + results->command = reverseBits(command, sizeof(command) * 8); + results->address = reverseBits(address, sizeof(address) * 8); + return true; +} +#endif + +#if SEND_SAMSUNG36 +/// Send a Samsung 36-bit formatted message. +/// Status: STABLE / Works on real devices. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621 +void IRsend::sendSamsung36(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + if (nbits < 16) return; // To small to send. + for (uint16_t r = 0; r <= repeat; r++) { + // Block #1 (16 bits) + sendGeneric(kSamsung36HdrMark, kSamsung36HdrSpace, + kSamsung36BitMark, kSamsung36OneSpace, + kSamsung36BitMark, kSamsung36ZeroSpace, + kSamsung36BitMark, kSamsung36HdrSpace, + data >> (nbits - 16), 16, 38, true, 0, kDutyDefault); + // Block #2 (The rest, typically 20 bits) + sendGeneric(0, 0, // No header + kSamsung36BitMark, kSamsung36OneSpace, + kSamsung36BitMark, kSamsung36ZeroSpace, + kSamsung36BitMark, kSamsungMinGap, // Gap is just a guess. + // Mask off the rest of the bits. + data & ((1ULL << (nbits - 16)) - 1), + nbits - 16, 38, true, 0, kDutyDefault); + } +} +#endif // SEND_SAMSUNG36 + +#if DECODE_SAMSUNG36 +/// Decode the supplied Samsung36 message. +/// Status: STABLE / Expected to work. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621 +bool IRrecv::decodeSamsung36(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter * 2 - 1 + offset) + return false; // Can't possibly be a valid Samsung message. + // We need to be looking for > 16 bits to make sense. + if (nbits <= 16) return false; + if (strict && nbits != kSamsung36Bits) + return false; // We expect nbits to be 36 bits of message. + + uint64_t data = 0; + + // Match Header + Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, 16, + kSamsung36HdrMark, kSamsung36HdrSpace, + kSamsung36BitMark, kSamsung36OneSpace, + kSamsung36BitMark, kSamsung36ZeroSpace, + kSamsung36BitMark, kSamsung36HdrSpace, false); + if (!used) return false; + offset += used; + // Data (Block #2) + uint64_t data2 = 0; + if (!matchGeneric(results->rawbuf + offset, &data2, + results->rawlen - offset, nbits - 16, + 0, 0, + kSamsung36BitMark, kSamsung36OneSpace, + kSamsung36BitMark, kSamsung36ZeroSpace, + kSamsung36BitMark, kSamsungMinGap, true)) return false; + data <<= (nbits - 16); + data += data2; + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = SAMSUNG36; + results->command = data & ((1ULL << (nbits - 16)) - 1); + results->address = data >> (nbits - 16); + return true; +} +#endif // DECODE_SAMSUNG36 + +#if SEND_SAMSUNG_AC +/// Send a Samsung A/C message. +/// Status: Stable / Known working. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/505 +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendSamsungAC(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kSamsungAcStateLength && nbytes % kSamsungAcSectionLength) + return; // Not an appropriate number of bytes to send a proper message. + + enableIROut(38); + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kSamsungAcHdrMark); + space(kSamsungAcHdrSpace); + // Send in 7 byte sections. + for (uint16_t offset = 0; offset < nbytes; + offset += kSamsungAcSectionLength) { + sendGeneric(kSamsungAcSectionMark, kSamsungAcSectionSpace, + kSamsungAcBitMark, kSamsungAcOneSpace, kSamsungAcBitMark, + kSamsungAcZeroSpace, kSamsungAcBitMark, kSamsungAcSectionGap, + data + offset, kSamsungAcSectionLength, // 7 bytes == 56 bits + 38000, false, 0, 50); // Send in LSBF order + } + // Complete made up guess at inter-message gap. + space(kDefaultMessageGap - kSamsungAcSectionGap); + } +} +#endif // SEND_SAMSUNG_AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRSamsungAc::IRSamsungAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { + stateReset(); +} + +/// Reset the internal state of the emulation. +/// @param[in] extended A flag indicating if force sending a special extended +/// message with the first `send()` call. +/// @param[in] initialPower Set the initial power state. True, on. False, off. +void IRSamsungAc::stateReset(const bool extended, const bool initialPower) { + static const uint8_t kReset[kSamsungAcExtendedStateLength] = { + 0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0, + 0x01, 0x02, 0xAE, 0x71, 0x00, 0x15, 0xF0}; + memcpy(_.raw, kReset, kSamsungAcExtendedStateLength); + _forceextended = extended; + _lastsentpowerstate = initialPower; + setPower(initialPower); + _OnTimerEnable = false; + _OffTimerEnable = false; + _Sleep = false; + _lastSleep = false; + _OnTimer = _OffTimer = _lastOnTimer = _lastOffTimer = 0; +} + +/// Set up hardware to be able to send a message. +void IRSamsungAc::begin(void) { _irsend.begin(); } + +/// Get the existing checksum for a given state section. +/// @param[in] section The array to extract the checksum from. +/// @return The existing checksum value. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947 +uint8_t IRSamsungAc::getSectionChecksum(const uint8_t *section) { + return ((GETBITS8(*(section + 2), kLowNibble, kNibbleSize) << kNibbleSize) + + GETBITS8(*(section + 1), kHighNibble, kNibbleSize)); +} + +/// Calculate the checksum for a given state section. +/// @param[in] section The array to calc the checksum of. +/// @return The calculated checksum value. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947 +uint8_t IRSamsungAc::calcSectionChecksum(const uint8_t *section) { + uint8_t sum = 0; + + sum += countBits(*section, 8); // Include the entire first byte + // The lower half of the second byte. + sum += countBits(GETBITS8(*(section + 1), kLowNibble, kNibbleSize), 8); + // The upper half of the third byte. + sum += countBits(GETBITS8(*(section + 2), kHighNibble, kNibbleSize), 8); + // The next 4 bytes. + sum += countBits(section + 3, 4); + // Bitwise invert the result. + return sum ^ UINT8_MAX; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRSamsungAc::validChecksum(const uint8_t state[], const uint16_t length) { + bool result = true; + const uint16_t maxlength = + (length > kSamsungAcExtendedStateLength) ? kSamsungAcExtendedStateLength + : length; + for (uint16_t offset = 0; + offset + kSamsungAcSectionLength <= maxlength; + offset += kSamsungAcSectionLength) + result &= (getSectionChecksum(state + offset) == + calcSectionChecksum(state + offset)); + return result; +} + +/// Update the checksum for the internal state. +void IRSamsungAc::checksum(void) { + uint8_t sectionsum = calcSectionChecksum(_.raw); + _.Sum1Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize); + _.Sum1Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize); + sectionsum = calcSectionChecksum(_.raw + kSamsungAcSectionLength); + _.Sum2Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize); + _.Sum2Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize); + sectionsum = calcSectionChecksum(_.raw + kSamsungAcSectionLength * 2); + _.Sum3Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize); + _.Sum3Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize); +} + +#if SEND_SAMSUNG_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +/// @note Use for most function/mode/settings changes to the unit. +/// i.e. When the device is already running. +void IRSamsungAc::send(const uint16_t repeat) { + // Do we need to send a special (extended) message? + if (getPower() != _lastsentpowerstate || _forceextended || + (_lastOnTimer != _OnTimer) || (_lastOffTimer != _OffTimer) || + (_Sleep != _lastSleep)) // We do. + sendExtended(repeat); + else // No, it's just a normal message. + _irsend.sendSamsungAC(getRaw(), kSamsungAcStateLength, repeat); +} + +/// Send the extended current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +/// @note Samsung A/C requires an extended length message when you want to +/// change the power operating mode, Timers, or Sleep setting of the A/C unit. +void IRSamsungAc::sendExtended(const uint16_t repeat) { + _lastsentpowerstate = getPower(); // Remember the last power state sent. + _lastOnTimer = _OnTimer; + _lastOffTimer = _OffTimer; + static const uint8_t extended_middle_section[kSamsungAcSectionLength] = { + 0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00}; + // Copy/convert the internal state to an extended state by + // copying the second section to the third section, and inserting the extended + // middle (second) section. + memcpy(_.raw + 2 * kSamsungAcSectionLength, + _.raw + kSamsungAcSectionLength, + kSamsungAcSectionLength); + memcpy(_.raw + kSamsungAcSectionLength, extended_middle_section, + kSamsungAcSectionLength); + _setOnTimer(); + _setSleepTimer(); // This also sets any Off Timer if needed too. + // Send it. + _irsend.sendSamsungAC(getRaw(), kSamsungAcExtendedStateLength, repeat); + // Now revert it by copying the third section over the second section. + memcpy(_.raw + kSamsungAcSectionLength, + _.raw + 2 * kSamsungAcSectionLength, + kSamsungAcSectionLength); + + _forceextended = false; // It has now been sent, so clear the flag if set. +} + +/// Send the special extended "On" message as the library can't seem to +/// reproduce this message automatically. +/// @param[in] repeat Nr. of times the message will be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/604#issuecomment-475020036 +void IRSamsungAc::sendOn(const uint16_t repeat) { + const uint8_t extended_state[kSamsungAcExtendedStateLength] = { + 0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0, + 0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xE2, 0xFE, 0x71, 0x80, 0x11, 0xF0}; + _irsend.sendSamsungAC(extended_state, kSamsungAcExtendedStateLength, repeat); + _lastsentpowerstate = true; // On +} + +/// Send the special extended "Off" message as the library can't seem to +/// reproduce this message automatically. +/// @param[in] repeat Nr. of times the message will be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/604#issuecomment-475020036 +void IRSamsungAc::sendOff(const uint16_t repeat) { + const uint8_t extended_state[kSamsungAcExtendedStateLength] = { + 0x02, 0xB2, 0x0F, 0x00, 0x00, 0x00, 0xC0, + 0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0xFF, 0x71, 0x80, 0x11, 0xC0}; + _irsend.sendSamsungAC(extended_state, kSamsungAcExtendedStateLength, repeat); + _lastsentpowerstate = false; // Off +} +#endif // SEND_SAMSUNG_AC + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRSamsungAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length/size of the new_code array. +void IRSamsungAc::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, + kSamsungAcExtendedStateLength)); + // Shrink the extended state into a normal state. + if (length > kSamsungAcStateLength) { + _OnTimerEnable = _.OnTimerEnable; + _OffTimerEnable = _.OffTimerEnable; + _Sleep = _.Sleep5 && _.Sleep12; + _OnTimer = _getOnTimer(); + _OffTimer = _getOffTimer(); + for (uint8_t i = kSamsungAcStateLength; i < length; i++) + _.raw[i - kSamsungAcSectionLength] = _.raw[i]; + } +} + +/// Set the requested power state of the A/C to on. +void IRSamsungAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRSamsungAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setPower(const bool on) { + _.Power1 = _.Power2 = (on ? 0b11 : 0b00); +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getPower(void) const { + return _.Power1 == 0b11 && _.Power2 == 0b11; +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRSamsungAc::setTemp(const uint8_t temp) { + uint8_t newtemp = ::max(kSamsungAcMinTemp, temp); + newtemp = ::min(kSamsungAcMaxTemp, newtemp); + _.Temp = newtemp - kSamsungAcMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRSamsungAc::getTemp(void) const { + return _.Temp + kSamsungAcMinTemp; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRSamsungAc::setMode(const uint8_t mode) { + // If we get an unexpected mode, default to AUTO. + uint8_t newmode = mode; + if (newmode > kSamsungAcHeat) newmode = kSamsungAcAuto; + _.Mode = newmode; + + // Auto mode has a special fan setting valid only in auto mode. + if (newmode == kSamsungAcAuto) { + _.Fan = kSamsungAcFanAuto2; + } else { + // Non-Auto can't have this fan setting + if (_.Fan == kSamsungAcFanAuto2) + _.Fan = kSamsungAcFanAuto; // Default to something safe. + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRSamsungAc::getMode(void) const { + return _.Mode; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRSamsungAc::setFan(const uint8_t speed) { + switch (speed) { + case kSamsungAcFanAuto: + case kSamsungAcFanLow: + case kSamsungAcFanMed: + case kSamsungAcFanHigh: + case kSamsungAcFanTurbo: + if (_.Mode == kSamsungAcAuto) return; // Not valid in Auto mode. + break; + case kSamsungAcFanAuto2: // Special fan setting for when in Auto mode. + if (_.Mode != kSamsungAcAuto) return; + break; + default: + return; + } + _.Fan = speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRSamsungAc::getFan(void) const { + return _.Fan; +} + +/// Get the vertical swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getSwing(void) const { + switch (_.Swing) { + case kSamsungAcSwingV: + case kSamsungAcSwingBoth: return true; + default: return false; + } +} + +/// Set the vertical swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setSwing(const bool on) { + switch (_.Swing) { + case kSamsungAcSwingBoth: + case kSamsungAcSwingH: + _.Swing = on ? kSamsungAcSwingBoth : kSamsungAcSwingH; + break; + default: + _.Swing = on ? kSamsungAcSwingV : kSamsungAcSwingOff; + } +} + +/// Get the horizontal swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getSwingH(void) const { + switch (_.Swing) { + case kSamsungAcSwingH: + case kSamsungAcSwingBoth: return true; + default: return false; + } +} + +/// Set the horizontal swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setSwingH(const bool on) { + switch (_.Swing) { + case kSamsungAcSwingV: + case kSamsungAcSwingBoth: + _.Swing = on ? kSamsungAcSwingBoth : kSamsungAcSwingV; + break; + default: + _.Swing = on ? kSamsungAcSwingH : kSamsungAcSwingOff; + } +} + +/// Get the Beep toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getBeep(void) const { return _.BeepToggle; } + +/// Set the Beep toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setBeep(const bool on) { _.BeepToggle = on; } + +/// Get the Clean toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getClean(void) const { + return _.CleanToggle10 && _.CleanToggle11; +} + +/// Set the Clean toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setClean(const bool on) { + _.CleanToggle10 = on; + _.CleanToggle11 = on; +} + +/// Get the Quiet setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getQuiet(void) const { return _.Quiet; } + +/// Set the Quiet setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setQuiet(const bool on) { + _.Quiet = on; + if (on) { + // Quiet mode seems to set fan speed to auto. + setFan(kSamsungAcFanAuto); + setPowerful(false); // Quiet 'on' is mutually exclusive to Powerful. + } +} + +/// Get the Powerful (Turbo) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getPowerful(void) const { + return (_.FanSpecial == kSamsungAcPowerfulOn) && + (_.Fan == kSamsungAcFanTurbo); +} + +/// Set the Powerful (Turbo) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setPowerful(const bool on) { + uint8_t off_value = (getBreeze() || getEcono()) ? _.FanSpecial + : kSamsungAcFanSpecialOff; + _.FanSpecial = (on ? kSamsungAcPowerfulOn : off_value); + if (on) { + // Powerful mode sets fan speed to Turbo. + setFan(kSamsungAcFanTurbo); + setQuiet(false); // Powerful 'on' is mutually exclusive to Quiet. + } +} + +/// Are the vanes closed over the fan outlet, to stop direct wind? Aka. WindFree +/// @return true, the setting is on. false, the setting is off. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062 +bool IRSamsungAc::getBreeze(void) const { + return (_.FanSpecial == kSamsungAcBreezeOn) && + (_.Fan == kSamsungAcFanAuto && !getSwing()); +} + +/// Closes the vanes over the fan outlet, to stop direct wind. Aka. WindFree +/// @param[in] on true, the setting is on. false, the setting is off. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062 +void IRSamsungAc::setBreeze(const bool on) { + const uint8_t off_value = (getPowerful() || + getEcono()) ? _.FanSpecial + : kSamsungAcFanSpecialOff; + _.FanSpecial = (on ? kSamsungAcBreezeOn : off_value); + if (on) { + setFan(kSamsungAcFanAuto); + setSwing(false); + } +} + +/// Get the current Economy (Eco) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getEcono(void) const { + return (_.FanSpecial == kSamsungAcEconoOn) && + (_.Fan == kSamsungAcFanAuto && getSwing()); +} + +/// Set the current Economy (Eco) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setEcono(const bool on) { + const uint8_t off_value = (getBreeze() || + getPowerful()) ? _.FanSpecial + : kSamsungAcFanSpecialOff; + _.FanSpecial = (on ? kSamsungAcEconoOn : off_value); + if (on) { + setFan(kSamsungAcFanAuto); + setSwing(true); + } +} + +/// Get the Display (Light/LED) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getDisplay(void) const { return _.Display; } + +/// Set the Display (Light/LED) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setDisplay(const bool on) { _.Display = on; } + +/// Get the Ion (Filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSamsungAc::getIon(void) const { return _.Ion; } + +/// Set the Ion (Filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSamsungAc::setIon(const bool on) { _.Ion = on; } + +/// Get the On Timer setting of the A/C from a raw extended state. +/// @return The Nr. of minutes the On Timer is set for. +uint16_t IRSamsungAc::_getOnTimer(void) const { + if (_.OnTimeDay) return 24 * 60; + return (_.OnTimeHrs2 * 2 + _.OnTimeHrs1) * 60 + _.OnTimeMins * 10; +} + +/// Set the current On Timer value of the A/C into the raw extended state. +void IRSamsungAc::_setOnTimer(void) { + _.OnTimerEnable = _OnTimerEnable = (_OnTimer > 0); + _.OnTimeDay = (_OnTimer >= 24 * 60); + if (_.OnTimeDay) { + _.OnTimeHrs2 = _.OnTimeHrs1 = _.OnTimeMins = 0; + return; + } + _.OnTimeMins = (_OnTimer % 60) / 10; + const uint8_t hours = _OnTimer / 60; + _.OnTimeHrs1 = hours & 0b1; + _.OnTimeHrs2 = hours >> 1; +} + +/// Get the Off Timer setting of the A/C from a raw extended state. +/// @return The Nr. of minutes the Off Timer is set for. +uint16_t IRSamsungAc::_getOffTimer(void) const { + if (_.OffTimeDay) return 24 * 60; + return (_.OffTimeHrs2 * 2 + _.OffTimeHrs1) * 60 + _.OffTimeMins * 10; +} + +/// Set the current Off Timer value of the A/C into the raw extended state. +void IRSamsungAc::_setOffTimer(void) { + _.OffTimerEnable = _OffTimerEnable = (_OffTimer > 0); + _.OffTimeDay = (_OffTimer >= 24 * 60); + if (_.OffTimeDay) { + _.OffTimeHrs2 = _.OffTimeHrs1 = _.OffTimeMins = 0; + return; + } + _.OffTimeMins = (_OffTimer % 60) / 10; + const uint8_t hours = _OffTimer / 60; + _.OffTimeHrs1 = hours & 0b1; + _.OffTimeHrs2 = hours >> 1; +} + +// Set the current Sleep Timer value of the A/C into the raw extended state. +void IRSamsungAc::_setSleepTimer(void) { + _setOffTimer(); + // The Sleep mode/timer should only be engaged if an off time has been set. + _.Sleep5 = _Sleep && _OffTimerEnable; + _.Sleep12 = _.Sleep5; +} + +/// Get the On Timer setting of the A/C. +/// @return The Nr. of minutes the On Timer is set for. +uint16_t IRSamsungAc::getOnTimer(void) const { return _OnTimer; } + +/// Get the Off Timer setting of the A/C. +/// @return The Nr. of minutes the Off Timer is set for. +/// @note Sleep & Off Timer share the same timer. +uint16_t IRSamsungAc::getOffTimer(void) const { + return _Sleep ? 0 : _OffTimer; +} + +/// Get the Sleep Timer setting of the A/C. +/// @return The Nr. of minutes the Off Timer is set for. +/// @note Sleep & Off Timer share the same timer. +uint16_t IRSamsungAc::getSleepTimer(void) const { + return _Sleep ? _OffTimer : 0; +} + +#define TIMER_RESOLUTION(mins) \ + (((::min((mins), (uint16_t)(24 * 60))) / 10) * 10) + +/// Set the On Timer value of the A/C. +/// @param[in] nr_of_mins The number of minutes the timer should be. +/// @note The timer time only has a resolution of 10 mins. +/// @note Setting the On Timer active will cancel the Sleep timer/setting. +void IRSamsungAc::setOnTimer(const uint16_t nr_of_mins) { + // Limit to one day, and round down to nearest 10 min increment. + _OnTimer = TIMER_RESOLUTION(nr_of_mins); + _OnTimerEnable = _OnTimer > 0; + if (_OnTimer) _Sleep = false; +} + +/// Set the Off Timer value of the A/C. +/// @param[in] nr_of_mins The number of minutes the timer should be. +/// @note The timer time only has a resolution of 10 mins. +/// @note Setting the Off Timer active will cancel the Sleep timer/setting. +void IRSamsungAc::setOffTimer(const uint16_t nr_of_mins) { + // Limit to one day, and round down to nearest 10 min increment. + _OffTimer = TIMER_RESOLUTION(nr_of_mins); + _OffTimerEnable = _OffTimer > 0; + if (_OffTimer) _Sleep = false; +} + +/// Set the Sleep Timer value of the A/C. +/// @param[in] nr_of_mins The number of minutes the timer should be. +/// @note The timer time only has a resolution of 10 mins. +/// @note Sleep timer acts as an Off timer, and cancels any On Timer. +void IRSamsungAc::setSleepTimer(const uint16_t nr_of_mins) { + // Limit to one day, and round down to nearest 10 min increment. + _OffTimer = TIMER_RESOLUTION(nr_of_mins); + if (_OffTimer) setOnTimer(0); // Clear the on timer if set. + _Sleep = _OffTimer > 0; + _OffTimerEnable = _Sleep; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSamsungAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kSamsungAcCool; + case stdAc::opmode_t::kHeat: return kSamsungAcHeat; + case stdAc::opmode_t::kDry: return kSamsungAcDry; + case stdAc::opmode_t::kFan: return kSamsungAcFan; + default: return kSamsungAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSamsungAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kSamsungAcFanLow; + case stdAc::fanspeed_t::kMedium: return kSamsungAcFanMed; + case stdAc::fanspeed_t::kHigh: return kSamsungAcFanHigh; + case stdAc::fanspeed_t::kMax: return kSamsungAcFanTurbo; + default: return kSamsungAcFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRSamsungAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kSamsungAcCool: return stdAc::opmode_t::kCool; + case kSamsungAcHeat: return stdAc::opmode_t::kHeat; + case kSamsungAcDry: return stdAc::opmode_t::kDry; + case kSamsungAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRSamsungAc::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kSamsungAcFanTurbo: return stdAc::fanspeed_t::kMax; + case kSamsungAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kSamsungAcFanMed: return stdAc::fanspeed_t::kMedium; + case kSamsungAcFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRSamsungAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::SAMSUNG_AC; + result.model = -1; // Not supported. + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = getSwing() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.swingh = getSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + result.quiet = getQuiet(); + result.turbo = getPowerful(); + result.econo = getEcono(); + result.clean = getClean(); + result.beep = _.BeepToggle; + result.light = _.Display; + result.filter = _.Ion; + result.sleep = _Sleep ? getSleepTimer() : -1; + // Not supported. + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRSamsungAc::toString(void) const { + String result = ""; + result.reserve(230); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kSamsungAcAuto, kSamsungAcCool, + kSamsungAcHeat, kSamsungAcDry, + kSamsungAcFan); + result += addTempToString(getTemp()); + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + switch (_.Fan) { + case kSamsungAcFanAuto: + case kSamsungAcFanAuto2: + result += kAutoStr; + break; + case kSamsungAcFanLow: + result += kLowStr; + break; + case kSamsungAcFanMed: + result += kMedStr; + break; + case kSamsungAcFanHigh: + result += kHighStr; + break; + case kSamsungAcFanTurbo: + result += kTurboStr; + break; + default: + result += kUnknownStr; + break; + } + result += ')'; + result += addBoolToString(getSwing(), kSwingVStr); + result += addBoolToString(getSwingH(), kSwingHStr); + result += addToggleToString(_.BeepToggle, kBeepStr); + result += addToggleToString(getClean(), kCleanStr); + result += addBoolToString(getQuiet(), kQuietStr); + result += addBoolToString(getPowerful(), kPowerfulStr); + result += addBoolToString(getEcono(), kEconoStr); + result += addBoolToString(getBreeze(), kBreezeStr); + result += addBoolToString(_.Display, kLightStr); + result += addBoolToString(_.Ion, kIonStr); + if (_OnTimerEnable) + result += addLabeledString(minsToString(_OnTimer), kOnTimerStr); + if (_OffTimerEnable) + result += addLabeledString(minsToString(_OffTimer), + _Sleep ? kSleepTimerStr : kOffTimerStr); + return result; +} + +#if DECODE_SAMSUNG_AC +/// Decode the supplied Samsung A/C message. +/// Status: Stable / Known to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/505 +bool IRrecv::decodeSamsungAC(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader * 3 + kFooter * 2 - 1 + offset) + return false; // Can't possibly be a valid Samsung A/C message. + if (nbits != kSamsungAcBits && nbits != kSamsungAcExtendedBits) return false; + + // Message Header + if (!matchMark(results->rawbuf[offset++], kSamsungAcBitMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kSamsungAcHdrSpace)) return false; + // Section(s) + for (uint16_t pos = 0; pos <= (nbits / 8) - kSamsungAcSectionLength; + pos += kSamsungAcSectionLength) { + uint16_t used; + // Section Header + Section Data (7 bytes) + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, kSamsungAcSectionLength * 8, + kSamsungAcSectionMark, kSamsungAcSectionSpace, + kSamsungAcBitMark, kSamsungAcOneSpace, + kSamsungAcBitMark, kSamsungAcZeroSpace, + kSamsungAcBitMark, kSamsungAcSectionGap, + pos + kSamsungAcSectionLength >= nbits / 8, + _tolerance, 0, false); + if (used == 0) return false; + offset += used; + } + // Compliance + if (strict) { + // Is the checksum valid? + if (!IRSamsungAc::validChecksum(results->state, nbits / 8)) { + DPRINTLN("DEBUG: Checksum failed!"); + return false; + } + } + // Success + results->decode_type = SAMSUNG_AC; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_SAMSUNG_AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Samsung.h b/src/libraries/IRremoteESP8266/src/ir_Samsung.h new file mode 100644 index 000000000..e0e8b34d5 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Samsung.h @@ -0,0 +1,280 @@ +// Copyright 2018-2021 David Conran +/// @file +/// @brief Support for Samsung protocols. +/// Samsung originally added from https://github.com/shirriff/Arduino-IRremote/ +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/505 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/621 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1062 +/// @see http://elektrolab.wz.cz/katalog/samsung_protocol.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538 (Checksum) +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1277 (Timers) + +// Supports: +// Brand: Samsung, Model: UA55H6300 TV (SAMSUNG) +// Brand: Samsung, Model: BN59-01178B TV remote (SAMSUNG) +// Brand: Samsung, Model: UE40K5510AUXRU TV (SAMSUNG) +// Brand: Samsung, Model: DB63-03556X003 remote +// Brand: Samsung, Model: DB93-16761C remote +// Brand: Samsung, Model: IEC-R03 remote +// Brand: Samsung, Model: AK59-00167A Bluray remote (SAMSUNG36) +// Brand: Samsung, Model: AH59-02692E Soundbar remote (SAMSUNG36) +// Brand: Samsung, Model: HW-J551 Soundbar (SAMSUNG36) +// Brand: Samsung, Model: AR09FSSDAWKNFA A/C (SAMSUNG_AC) +// Brand: Samsung, Model: AR09HSFSBWKN A/C (SAMSUNG_AC) +// Brand: Samsung, Model: AR12KSFPEWQNET A/C (SAMSUNG_AC) +// Brand: Samsung, Model: AR12HSSDBWKNEU A/C (SAMSUNG_AC) +// Brand: Samsung, Model: AR12NXCXAWKXEU A/C (SAMSUNG_AC) +// Brand: Samsung, Model: AR12TXEAAWKNEU A/C (SAMSUNG_AC) +// Brand: Samsung, Model: DB93-14195A remote (SAMSUNG_AC) +// Brand: Samsung, Model: DB96-24901C remote (SAMSUNG_AC) + +#ifndef IR_SAMSUNG_H_ +#define IR_SAMSUNG_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Samsung A/C message. +union SamsungProtocol{ + uint8_t raw[kSamsungAcExtendedStateLength]; ///< State in code form. + struct { // Standard message map + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t :4; + uint8_t :4; // Sum1Lower + // Byte 2 + uint8_t :4; // Sum1Upper + uint8_t :4; + // Byte 3 + uint8_t :8; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t :4; + uint8_t Sleep5 :1; + uint8_t Quiet :1; + uint8_t :2; + // Byte 6 + uint8_t :4; + uint8_t Power1 :2; + uint8_t :2; + // Byte 7 + uint8_t :8; + // Byte 8 + uint8_t :4; + uint8_t :4; // Sum2Lower + // Byte 9 + uint8_t :4; // Sum1Upper + uint8_t Swing :3; + uint8_t :1; + // Byte 10 + uint8_t :1; + uint8_t FanSpecial :3; // Powerful, Breeze/WindFree, Econo + uint8_t Display :1; + uint8_t :2; + uint8_t CleanToggle10 :1; + // Byte 11 + uint8_t Ion :1; + uint8_t CleanToggle11 :1; + uint8_t :2; + uint8_t Temp :4; + // Byte 12 + uint8_t :1; + uint8_t Fan :3; + uint8_t Mode :3; + uint8_t :1; + // Byte 13 + uint8_t :2; + uint8_t BeepToggle :1; + uint8_t :1; + uint8_t Power2 :2; + uint8_t :2; + }; + struct { // Extended message map + // 1st Section + // Byte 0 + uint8_t :8; + // Byte 1 + uint8_t :4; + uint8_t Sum1Lower :4; + // Byte 2 + uint8_t Sum1Upper :4; + uint8_t :4; + // Byte 3 + uint8_t :8; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t :8; + // Byte 6 + uint8_t :8; + // 2nd Section + // Byte 7 + uint8_t :8; + // Byte 8 + uint8_t :4; + uint8_t Sum2Lower :4; + // Byte 9 + uint8_t Sum2Upper :4; + uint8_t OffTimeMins :3; // In units of 10's of mins + uint8_t OffTimeHrs1 :1; // LSB of the number of hours. + // Byte 10 + uint8_t OffTimeHrs2 :4; // MSBs of the number of hours. + uint8_t OnTimeMins :3; // In units of 10's of mins + uint8_t OnTimeHrs1 :1; // LSB of the number of hours. + // Byte 11 + uint8_t OnTimeHrs2 :4; // MSBs of the number of hours. + uint8_t :4; + // Byte 12 + uint8_t OffTimeDay :1; + uint8_t OnTimerEnable :1; + uint8_t OffTimerEnable :1; + uint8_t Sleep12 :1; + uint8_t OnTimeDay :1; + uint8_t :3; + // Byte 13 + uint8_t :8; + // 3rd Section + // Byte 14 + uint8_t :8; + // Byte 15 + uint8_t :4; + uint8_t Sum3Lower :4; + // Byte 16 + uint8_t Sum3Upper :4; + uint8_t :4; + // Byte 17 + uint8_t :8; + // Byte 18 + uint8_t :8; + // Byte 19 + uint8_t :8; + // Byte 20 + uint8_t :8; + }; +}; + +// Constants +const uint8_t kSamsungAcMinTemp = 16; // C Mask 0b11110000 +const uint8_t kSamsungAcMaxTemp = 30; // C Mask 0b11110000 +const uint8_t kSamsungAcAutoTemp = 25; // C Mask 0b11110000 +const uint8_t kSamsungAcAuto = 0; +const uint8_t kSamsungAcCool = 1; +const uint8_t kSamsungAcDry = 2; +const uint8_t kSamsungAcFan = 3; +const uint8_t kSamsungAcHeat = 4; +const uint8_t kSamsungAcFanAuto = 0; +const uint8_t kSamsungAcFanLow = 2; +const uint8_t kSamsungAcFanMed = 4; +const uint8_t kSamsungAcFanHigh = 5; +const uint8_t kSamsungAcFanAuto2 = 6; +const uint8_t kSamsungAcFanTurbo = 7; +const uint16_t kSamsungAcSectionLength = 7; +const uint64_t kSamsungAcPowerSection = 0x1D20F00000000; + +// Classes +/// Class for handling detailed Samsung A/C messages. +class IRSamsungAc { + public: + explicit IRSamsungAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(const bool extended = true, const bool initialPower = true); +#if SEND_SAMSUNG_AC + void send(const uint16_t repeat = kSamsungAcDefaultRepeat); + void sendExtended(const uint16_t repeat = kSamsungAcDefaultRepeat); + void sendOn(const uint16_t repeat = kSamsungAcDefaultRepeat); + void sendOff(const uint16_t repeat = kSamsungAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_SAMSUNG_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwing(const bool on); + bool getSwing(void) const; + void setSwingH(const bool on); + bool getSwingH(void) const; + void setBeep(const bool on); + bool getBeep(void) const; + void setClean(const bool on); + bool getClean(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + void setPowerful(const bool on); + bool getPowerful(void) const; + void setBreeze(const bool on); + bool getBreeze(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setDisplay(const bool on); + bool getDisplay(void) const; + void setIon(const bool on); + bool getIon(void) const; + uint16_t getOnTimer(void) const; + void setOnTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void) const; + void setOffTimer(const uint16_t nr_of_mins); + uint16_t getSleepTimer(void) const; + void setSleepTimer(const uint16_t nr_of_mins); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kSamsungAcStateLength); + static uint8_t calcSectionChecksum(const uint8_t *section); + static uint8_t getSectionChecksum(const uint8_t *section); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kSamsungAcStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + SamsungProtocol _; + bool _forceextended; ///< Flag to know when we need to send an extended mesg. + bool _lastsentpowerstate; + bool _OnTimerEnable; + bool _OffTimerEnable; + bool _Sleep; + bool _lastSleep; + uint16_t _OnTimer; + uint16_t _OffTimer; + uint16_t _lastOnTimer; + uint16_t _lastOffTimer; + void checksum(void); + uint16_t _getOnTimer(void) const; + uint16_t _getOffTimer(void) const; + void _setOnTimer(void); + void _setOffTimer(void); + void _setSleepTimer(void); +}; + +#endif // IR_SAMSUNG_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Sanyo.cpp b/src/libraries/IRremoteESP8266/src/ir_Sanyo.cpp new file mode 100644 index 000000000..167e2a8b0 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Sanyo.cpp @@ -0,0 +1,1047 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2016 marcosamarinho +// Copyright 2017-2021 David Conran + +/// @file +/// @brief Support for Sanyo protocols. +/// Sanyo LC7461 support originally by marcosamarinho +/// Sanyo SA 8650B originally added from +/// https://github.com/shirriff/Arduino-IRremote/ +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Sanyo.cpp +/// @see http://pdf.datasheetcatalog.com/datasheet/sanyo/LC7461.pdf +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Sanyo.cpp +/// @see http://slydiman.narod.ru/scr/kb/sanyo.htm +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1211 +/// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?usp=sharing +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 + +#include "ir_Sanyo.h" +// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::minsToString; +using irutils::sumNibbles; + +// Constants +// Sanyo SA 8650B +const uint16_t kSanyoSa8650bHdrMark = 3500; // seen range 3500 +const uint16_t kSanyoSa8650bHdrSpace = 950; // seen 950 +const uint16_t kSanyoSa8650bOneMark = 2400; // seen 2400 +const uint16_t kSanyoSa8650bZeroMark = 700; // seen 700 +// usually see 713 - not using ticks as get number wrapround +const uint16_t kSanyoSa8650bDoubleSpaceUsecs = 800; +const uint16_t kSanyoSa8650bRptLength = 45000; + +// Sanyo LC7461 +const uint16_t kSanyoLc7461AddressMask = (1 << kSanyoLC7461AddressBits) - 1; +const uint16_t kSanyoLc7461CommandMask = (1 << kSanyoLC7461CommandBits) - 1; +const uint16_t kSanyoLc7461HdrMark = 9000; +const uint16_t kSanyoLc7461HdrSpace = 4500; +const uint16_t kSanyoLc7461BitMark = 560; // 1T +const uint16_t kSanyoLc7461OneSpace = 1690; // 3T +const uint16_t kSanyoLc7461ZeroSpace = 560; // 1T +const uint32_t kSanyoLc7461MinCommandLength = 108000; + +const uint16_t kSanyoLc7461MinGap = + kSanyoLc7461MinCommandLength - + (kSanyoLc7461HdrMark + kSanyoLc7461HdrSpace + + kSanyoLC7461Bits * (kSanyoLc7461BitMark + + (kSanyoLc7461OneSpace + kSanyoLc7461ZeroSpace) / 2) + + kSanyoLc7461BitMark); + +const uint16_t kSanyoAcHdrMark = 8500; ///< uSeconds +const uint16_t kSanyoAcHdrSpace = 4200; ///< uSeconds +const uint16_t kSanyoAcBitMark = 500; ///< uSeconds +const uint16_t kSanyoAcOneSpace = 1600; ///< uSeconds +const uint16_t kSanyoAcZeroSpace = 550; ///< uSeconds +const uint32_t kSanyoAcGap = kDefaultMessageGap; ///< uSeconds (Guess only) +const uint16_t kSanyoAcFreq = 38000; ///< Hz. (Guess only) + +const uint16_t kSanyoAc88HdrMark = 5400; ///< uSeconds +const uint16_t kSanyoAc88HdrSpace = 2000; ///< uSeconds +const uint16_t kSanyoAc88BitMark = 500; ///< uSeconds +const uint16_t kSanyoAc88OneSpace = 1500; ///< uSeconds +const uint16_t kSanyoAc88ZeroSpace = 750; ///< uSeconds +const uint32_t kSanyoAc88Gap = 3675; ///< uSeconds +const uint16_t kSanyoAc88Freq = 38000; ///< Hz. (Guess only) +const uint8_t kSanyoAc88ExtraTolerance = 5; /// (%) Extra tolerance to use. + +const uint16_t kSanyoAc152HdrMark = 3300; ///< uSeconds +const uint16_t kSanyoAc152BitMark = 440; ///< uSeconds +const uint16_t kSanyoAc152HdrSpace = 1725; ///< uSeconds +const uint16_t kSanyoAc152OneSpace = 1290; ///< uSeconds +const uint16_t kSanyoAc152ZeroSpace = 405; ///< uSeconds +const uint16_t kSanyoAc152Freq = 38000; ///< Hz. (Guess only) +const uint8_t kSanyoAc152ExtraTolerance = 13; /// (%) Extra tolerance to use. + +#if SEND_SANYO +/// Construct a Sanyo LC7461 message. +/// @param[in] address The 13 bit value of the address(Custom) portion of the +/// protocol. +/// @param[in] command The 8 bit value of the command(Key) portion of the +/// protocol. +/// @return An uint64_t with the encoded raw 42 bit Sanyo LC7461 data value. +/// @note This protocol uses the NEC protocol timings. However, data is +/// formatted as : address(13 bits), !address, command(8 bits), !command. +/// According with LIRC, this protocol is used on Sanyo, Aiwa and Chinon +uint64_t IRsend::encodeSanyoLC7461(uint16_t address, uint8_t command) { + // Mask our input values to ensure the correct bit sizes. + address &= kSanyoLc7461AddressMask; + command &= kSanyoLc7461CommandMask; + + uint64_t data = address; + address ^= kSanyoLc7461AddressMask; // Invert the 13 LSBs. + // Append the now inverted address. + data = (data << kSanyoLC7461AddressBits) | address; + // Append the command. + data = (data << kSanyoLC7461CommandBits) | command; + command ^= kSanyoLc7461CommandMask; // Invert the command. + // Append the now inverted command. + data = (data << kSanyoLC7461CommandBits) | command; + + return data; +} + +/// Send a Sanyo LC7461 message. +/// Status: BETA / Probably works. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Based on \@marcosamarinho's work. +/// This protocol uses the NEC protocol timings. However, data is +/// formatted as : address(13 bits), !address, command (8 bits), !command. +/// According with LIRC, this protocol is used on Sanyo, Aiwa and Chinon +/// Information for this protocol is available at the Sanyo LC7461 datasheet. +/// Repeats are performed similar to the NEC method of sending a special +/// repeat message, rather than duplicating the entire message. +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Sanyo.cpp +/// @see http://pdf.datasheetcatalog.com/datasheet/sanyo/LC7461.pdf +void IRsend::sendSanyoLC7461(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + // This protocol appears to be another 42-bit variant of the NEC protocol. + sendNEC(data, nbits, repeat); +} +#endif // SEND_SANYO + +#if DECODE_SANYO +/// Decode the supplied SANYO LC7461 message. +/// Status: BETA / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note Based on \@marcosamarinho's work. +/// This protocol uses the NEC protocol. However, data is +/// formatted as : address(13 bits), !address, command (8 bits), !command. +/// According with LIRC, this protocol is used on Sanyo, Aiwa and Chinon +/// Information for this protocol is available at the Sanyo LC7461 datasheet. +/// @see http://slydiman.narod.ru/scr/kb/sanyo.htm +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Sanyo.cpp +/// @see http://pdf.datasheetcatalog.com/datasheet/sanyo/LC7461.pdf +bool IRrecv::decodeSanyoLC7461(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kSanyoLC7461Bits) + return false; // Not strictly in spec. + // This protocol is basically a 42-bit variant of the NEC protocol. + if (!decodeNEC(results, offset, nbits, false)) + return false; // Didn't match a NEC format (without strict) + + // Bits 30 to 42+. + uint16_t address = + results->value >> (kSanyoLC7461Bits - kSanyoLC7461AddressBits); + // Bits 9 to 16. + uint8_t command = + (results->value >> kSanyoLC7461CommandBits) & kSanyoLc7461CommandMask; + // Compliance + if (strict) { + if (results->bits != nbits) return false; + // Bits 17 to 29. + uint16_t inverted_address = + (results->value >> (kSanyoLC7461CommandBits * 2)) & + kSanyoLc7461AddressMask; + // Bits 1-8. + uint8_t inverted_command = results->value & kSanyoLc7461CommandMask; + if ((address ^ kSanyoLc7461AddressMask) != inverted_address) + return false; // Address integrity check failed. + if ((command ^ kSanyoLc7461CommandMask) != inverted_command) + return false; // Command integrity check failed. + } + + // Success + results->decode_type = SANYO_LC7461; + results->address = address; + results->command = command; + return true; +} + +/* NOTE: Disabled due to poor quality. +/// Decode the supplied Sanyo SA 8650B message. +/// Status: Depricated. +/// @depricated Disabled due to poor quality. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @warning This decoder looks like rubbish. Only keeping it for compatibility +/// with the Arduino IRremote library. Seriously, don't trust it. +/// If someone has a device that this is supposed to be for, please log an +/// Issue on github with a rawData dump please. We should probably remove it. +/// We think this is a Sanyo decoder - serial = SA 8650B +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Sanyo.cpp +bool IRrecv::decodeSanyo(decode_results *results, uint16_t nbits, bool strict) { + if (results->rawlen < 2 * nbits + kHeader - 1) + return false; // Shorter than shortest possible. + if (strict && nbits != kSanyoSA8650BBits) + return false; // Doesn't match the spec. + + uint16_t offset = 0; + + // TODO(crankyoldgit): This repeat code looks like garbage, it should never + // match or if it does, it won't be reliable. We should probably just + // remove it. + if (results->rawbuf[offset++] < kSanyoSa8650bDoubleSpaceUsecs) { + results->bits = 0; + results->value = kRepeat; + results->decode_type = SANYO; + results->address = 0; + results->command = 0; + results->repeat = true; + return true; + } + + // Header + if (!matchMark(results->rawbuf[offset++], kSanyoSa8650bHdrMark)) + return false; + // NOTE: These next two lines look very wrong. Treat as suspect. + if (!matchMark(results->rawbuf[offset++], kSanyoSa8650bHdrMark)) + return false; + // Data + uint64_t data = 0; + while (offset + 1 < results->rawlen) { + if (!matchSpace(results->rawbuf[offset], kSanyoSa8650bHdrSpace)) + break; + offset++; + if (matchMark(results->rawbuf[offset], kSanyoSa8650bOneMark)) + data = (data << 1) | 1; // 1 + else if (matchMark(results->rawbuf[offset], kSanyoSa8650bZeroMark)) + data <<= 1; // 0 + else + return false; + offset++; + } + + if (strict && kSanyoSA8650BBits > (offset - 1U) / 2U) + return false; + + // Success + results->bits = (offset - 1) / 2; + results->decode_type = SANYO; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +*/ +#endif // DECODE_SANYO + + +#if SEND_SANYO_AC +/// Send a SanyoAc formatted message. +/// Status: STABLE / Reported as working. +/// @param[in] data An array of bytes containing the IR command. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// @param[in] repeat Nr. of times the message is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1211 +void IRsend::sendSanyoAc(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // Header + Data + Footer + sendGeneric(kSanyoAcHdrMark, kSanyoAcHdrSpace, + kSanyoAcBitMark, kSanyoAcOneSpace, + kSanyoAcBitMark, kSanyoAcZeroSpace, + kSanyoAcBitMark, kSanyoAcGap, + data, nbytes, kSanyoAcFreq, false, repeat, kDutyDefault); +} +#endif // SEND_SANYO_AC + +#if DECODE_SANYO_AC +/// Decode the supplied SanyoAc message. +/// Status: STABLE / Reported as working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1211 +bool IRrecv::decodeSanyoAc(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kSanyoAcBits) + return false; + + // Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kSanyoAcHdrMark, kSanyoAcHdrSpace, + kSanyoAcBitMark, kSanyoAcOneSpace, + kSanyoAcBitMark, kSanyoAcZeroSpace, + kSanyoAcBitMark, kSanyoAcGap, + true, kUseDefTol, kMarkExcess, false)) return false; + // Compliance + if (strict) + if (!IRSanyoAc::validChecksum(results->state, nbits / 8)) return false; + + // Success + results->decode_type = decode_type_t::SANYO_AC; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_SANYO_AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRSanyoAc::IRSanyoAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known state/sequence. +void IRSanyoAc::stateReset(void) { + static const uint8_t kReset[kSanyoAcStateLength] = { + 0x6A, 0x6D, 0x51, 0x00, 0x10, 0x45, 0x00, 0x00, 0x33}; + memcpy(_.raw, kReset, kSanyoAcStateLength); +} + +/// Set up hardware to be able to send a message. +void IRSanyoAc::begin(void) { _irsend.begin(); } + +#if SEND_SANYO_AC +/// Send the current internal state as IR messages. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRSanyoAc::send(const uint16_t repeat) { + _irsend.sendSanyoAc(getRaw(), kSanyoAcStateLength, repeat); +} +#endif // SEND_SANYO_AC + +/// Get a PTR to the internal state/code for this protocol with all integrity +/// checks passing. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRSanyoAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +void IRSanyoAc::setRaw(const uint8_t newState[]) { + memcpy(_.raw, newState, kSanyoAcStateLength); +} + +/// Calculate the checksum for a given state. +/// @param[in] state The array to calc the checksum of. +/// @param[in] length The length/size of the array. +/// @return The calculated checksum value. +uint8_t IRSanyoAc::calcChecksum(const uint8_t state[], + const uint16_t length) { + return length ? sumNibbles(state, length - 1) : 0; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRSanyoAc::validChecksum(const uint8_t state[], const uint16_t length) { + return length && state[length - 1] == IRSanyoAc::calcChecksum(state, length); +} + +/// Calculate & set the checksum for the current internal state of the remote. +void IRSanyoAc::checksum(void) { + // Stored the checksum value in the last byte. + _.Sum = calcChecksum(_.raw); +} + + +/// Set the requested power state of the A/C to on. +void IRSanyoAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRSanyoAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc::setPower(const bool on) { + _.Power = (on ? kSanyoAcPowerOn : kSanyoAcPowerOff); +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc::getPower(void) const { + return _.Power == kSanyoAcPowerOn; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRSanyoAc::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +void IRSanyoAc::setMode(const uint8_t mode) { + switch (mode) { + case kSanyoAcAuto: + case kSanyoAcCool: + case kSanyoAcDry: + case kSanyoAcHeat: + _.Mode = mode; + break; + default: _.Mode = kSanyoAcAuto; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSanyoAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kSanyoAcCool; + case stdAc::opmode_t::kHeat: return kSanyoAcHeat; + case stdAc::opmode_t::kDry: return kSanyoAcDry; + default: return kSanyoAcAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRSanyoAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kSanyoAcCool: return stdAc::opmode_t::kCool; + case kSanyoAcHeat: return stdAc::opmode_t::kHeat; + case kSanyoAcDry: return stdAc::opmode_t::kDry; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the desired temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRSanyoAc::setTemp(const uint8_t degrees) { + uint8_t temp = ::max((uint8_t)kSanyoAcTempMin, degrees); + temp = ::min((uint8_t)kSanyoAcTempMax, temp); + _.Temp = temp - kSanyoAcTempDelta; +} + +/// Get the current desired temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRSanyoAc::getTemp(void) const { + return _.Temp + kSanyoAcTempDelta; +} + +/// Set the sensor temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRSanyoAc::setSensorTemp(const uint8_t degrees) { + uint8_t temp = ::max((uint8_t)kSanyoAcTempMin, degrees); + temp = ::min((uint8_t)kSanyoAcTempMax, temp); + _.SensorTemp = temp - kSanyoAcTempDelta; +} + +/// Get the current sensor temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRSanyoAc::getSensorTemp(void) const { + return _.SensorTemp + kSanyoAcTempDelta; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRSanyoAc::setFan(const uint8_t speed) { + _.Fan = speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRSanyoAc::getFan(void) const { + return _.Fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSanyoAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kSanyoAcFanLow; + case stdAc::fanspeed_t::kMedium: return kSanyoAcFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kSanyoAcFanHigh; + default: return kSanyoAcFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRSanyoAc::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kSanyoAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kSanyoAcFanMedium: return stdAc::fanspeed_t::kMedium; + case kSanyoAcFanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Get the vertical swing setting of the A/C. +/// @return The current swing mode setting. +uint8_t IRSanyoAc::getSwingV(void) const { + return _.SwingV; +} + +/// Set the vertical swing setting of the A/C. +/// @param[in] setting The value of the desired setting. +void IRSanyoAc::setSwingV(const uint8_t setting) { + if (setting == kSanyoAcSwingVAuto || + (setting >= kSanyoAcSwingVLowest && setting <= kSanyoAcSwingVHighest)) + _.SwingV = setting; + else + _.SwingV = kSanyoAcSwingVAuto; +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSanyoAc::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: return kSanyoAcSwingVHighest; + case stdAc::swingv_t::kHigh: return kSanyoAcSwingVHigh; + case stdAc::swingv_t::kMiddle: return kSanyoAcSwingVUpperMiddle; + case stdAc::swingv_t::kLow: return kSanyoAcSwingVLow; + case stdAc::swingv_t::kLowest: return kSanyoAcSwingVLowest; + default: return kSanyoAcSwingVAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] setting A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRSanyoAc::toCommonSwingV(const uint8_t setting) { + switch (setting) { + case kSanyoAcSwingVHighest: return stdAc::swingv_t::kHighest; + case kSanyoAcSwingVHigh: return stdAc::swingv_t::kHigh; + case kSanyoAcSwingVUpperMiddle: + case kSanyoAcSwingVLowerMiddle: return stdAc::swingv_t::kMiddle; + case kSanyoAcSwingVLow: return stdAc::swingv_t::kLow; + case kSanyoAcSwingVLowest: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kAuto; + } +} + +/// Set the Sleep (Night Setback) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc::setSleep(const bool on) { + _.Sleep = on; +} + +/// Get the Sleep (Night Setback) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc::getSleep(void) const { + return _.Sleep; +} + +/// Set the Sensor Location setting of the A/C. +/// i.e. Where the ambient temperature is measured. +/// @param[in] location true is Unit/Wall, false is Remote/Room. +void IRSanyoAc::setSensor(const bool location) { + _.Sensor = location; +} + +/// Get the Sensor Location setting of the A/C. +/// i.e. Where the ambient temperature is measured. +/// @return true is Unit/Wall, false is Remote/Room. +bool IRSanyoAc::getSensor(void) const { + return _.Sensor; +} + +/// Set the Beep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc::setBeep(const bool on) { + _.Beep = on; +} + +/// Get the Beep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc::getBeep(void) const { + return _.Beep; +} + +/// Get the nr of minutes the Off Timer is set to. +/// @return The timer time expressed as the number of minutes. +/// A value of 0 means the Off Timer is off/disabled. +/// @note The internal precission has a resolution of 1 hour. +uint16_t IRSanyoAc::getOffTimer(void) const { + if (_.OffTimer) + return _.OffHour * 60; + else + return 0; +} + +/// Set the nr of minutes for the Off Timer. +/// @param[in] mins The timer time expressed as nr. of minutes. +/// A value of 0 means the Off Timer is off/disabled. +/// @note The internal precission has a resolution of 1 hour. +void IRSanyoAc::setOffTimer(const uint16_t mins) { + const uint8_t hours = ::min((uint8_t)(mins / 60), kSanyoAcHourMax); + _.OffTimer = (hours > 0); + _.OffHour = hours; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRSanyoAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::SANYO_AC; + result.model = -1; // Not supported. + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.sensorTemperature = getSensorTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.sleep = _.Sleep ? 0 : -1; + result.swingv = toCommonSwingV(_.SwingV); + result.beep = _.Beep; + result.iFeel = !getSensor(); + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.econo = false; + result.light = false; + result.filter = false; + result.quiet = false; + result.clean = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRSanyoAc::toString(void) const { + String result = ""; + result.reserve(140); + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kSanyoAcAuto, kSanyoAcCool, + kSanyoAcHeat, kSanyoAcDry, kSanyoAcAuto); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kSanyoAcFanHigh, kSanyoAcFanLow, + kSanyoAcFanAuto, kSanyoAcFanAuto, + kSanyoAcFanMedium); + result += addSwingVToString(_.SwingV, kSanyoAcSwingVAuto, + kSanyoAcSwingVHighest, kSanyoAcSwingVHigh, + kSanyoAcSwingVUpperMiddle, + kSanyoAcSwingVAuto, // Middle is unused + kSanyoAcSwingVLowerMiddle, + kSanyoAcSwingVLow, kSanyoAcSwingVLowest, + // Below are unused. + kSanyoAcSwingVAuto, + kSanyoAcSwingVAuto, + kSanyoAcSwingVAuto, + kSanyoAcSwingVAuto); + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(_.Beep, kBeepStr); + result += addLabeledString(_.Sensor ? kRoomStr : kWallStr, kSensorStr); + result += kCommaSpaceStr; + result += kSensorStr; + result += ' '; + result += addTempToString(getSensorTemp(), true, false); + const uint16_t offtime = getOffTimer(); + result += addLabeledString(offtime ? minsToString(offtime) : kOffStr, + kOffTimerStr); + return result; +} + +#if SEND_SANYO_AC88 +/// Send a SanyoAc88 formatted message. +/// Status: ALPHA / Completely untested. +/// @param[in] data An array of bytes containing the IR command. +/// @warning data's bit order may change. It is not yet confirmed. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// @param[in] repeat Nr. of times the message is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 +void IRsend::sendSanyoAc88(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // (Header + Data + Footer) per repeat + sendGeneric(kSanyoAc88HdrMark, kSanyoAc88HdrSpace, + kSanyoAc88BitMark, kSanyoAc88OneSpace, + kSanyoAc88BitMark, kSanyoAc88ZeroSpace, + kSanyoAc88BitMark, kSanyoAc88Gap, + data, nbytes, kSanyoAc88Freq, false, repeat, kDutyDefault); + space(kDefaultMessageGap); // Make a guess at a post message gap. +} +#endif // SEND_SANYO_AC88 + +#if DECODE_SANYO_AC88 +/// Decode the supplied SanyoAc88 message. +/// Status: ALPHA / Untested. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @warning data's bit order may change. It is not yet confirmed. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 +bool IRrecv::decodeSanyoAc88(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kSanyoAc88Bits) + return false; + + uint16_t used = 0; + // Compliance + const uint16_t expected_repeats = strict ? kSanyoAc88MinRepeat : 0; + + // Handle the expected nr of repeats. + for (uint16_t r = 0; r <= expected_repeats; r++) { + // Header + Data + Footer + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kSanyoAc88HdrMark, kSanyoAc88HdrSpace, + kSanyoAc88BitMark, kSanyoAc88OneSpace, + kSanyoAc88BitMark, kSanyoAc88ZeroSpace, + kSanyoAc88BitMark, + // Expect an inter-message gap, or just the end of msg? + (r < expected_repeats) ? kSanyoAc88Gap + : kDefaultMessageGap, + r == expected_repeats, + _tolerance + kSanyoAc88ExtraTolerance, + kMarkExcess, false); + if (!used) return false; // No match! + offset += used; + } + + // Success + results->decode_type = decode_type_t::SANYO_AC88; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_SANYO_AC88 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRSanyoAc88::IRSanyoAc88(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +/// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?ts=5f0190a5#gid=1050142776&range=A2:B2 +void IRSanyoAc88::stateReset(void) { + static const uint8_t kReset[kSanyoAc88StateLength] = { + 0xAA, 0x55, 0xA0, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10}; + memcpy(_.raw, kReset, kSanyoAc88StateLength); +} + +/// Set up hardware to be able to send a message. +void IRSanyoAc88::begin(void) { _irsend.begin(); } + +#if SEND_SANYO_AC88 +/// Send the current internal state as IR messages. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRSanyoAc88::send(const uint16_t repeat) { + _irsend.sendSanyoAc88(getRaw(), kSanyoAc88StateLength, repeat); +} +#endif // SEND_SANYO_AC88 + +/// Get a PTR to the internal state/code for this protocol with all integrity +/// checks passing. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRSanyoAc88::getRaw(void) { + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +void IRSanyoAc88::setRaw(const uint8_t newState[]) { + memcpy(_.raw, newState, kSanyoAc88StateLength); +} + +/// Set the requested power state of the A/C to on. +void IRSanyoAc88::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRSanyoAc88::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getPower(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRSanyoAc88::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +void IRSanyoAc88::setMode(const uint8_t mode) { + switch (mode) { + case kSanyoAc88Auto: + case kSanyoAc88FeelCool: + case kSanyoAc88Cool: + case kSanyoAc88FeelHeat: + case kSanyoAc88Heat: + case kSanyoAc88Fan: + _.Mode = mode; + break; + default: _.Mode = kSanyoAc88Auto; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSanyoAc88::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kSanyoAc88Cool; + case stdAc::opmode_t::kHeat: return kSanyoAc88Heat; + case stdAc::opmode_t::kFan: return kSanyoAc88Fan; + default: return kSanyoAc88Auto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRSanyoAc88::toCommonMode(const uint8_t mode) { + switch (mode) { + case kSanyoAc88FeelCool: + case kSanyoAc88Cool: + return stdAc::opmode_t::kCool; + case kSanyoAc88FeelHeat: + case kSanyoAc88Heat: + return stdAc::opmode_t::kHeat; + case kSanyoAc88Fan: + return stdAc::opmode_t::kFan; + default: + return stdAc::opmode_t::kAuto; + } +} + +/// Set the desired temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRSanyoAc88::setTemp(const uint8_t degrees) { + uint8_t temp = ::max((uint8_t)kSanyoAc88TempMin, degrees); + _.Temp = ::min((uint8_t)kSanyoAc88TempMax, temp); +} + +/// Get the current desired temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRSanyoAc88::getTemp(void) const { return _.Temp; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRSanyoAc88::setFan(const uint8_t speed) { _.Fan = speed; } + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRSanyoAc88::getFan(void) const { return _.Fan; } + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSanyoAc88::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kSanyoAc88FanLow; + case stdAc::fanspeed_t::kMedium: return kSanyoAc88FanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kSanyoAc88FanHigh; + default: return kSanyoAc88FanAuto; + } +} + +/// Get the current clock time. +/// @return The time as the nr. of minutes past midnight. +uint16_t IRSanyoAc88::getClock(void) const { + return _.ClockHrs * 60 + _.ClockMins; +} + +/// Set the current clock time. +/// @param[in] mins_since_midnight The time as nr. of minutes past midnight. +void IRSanyoAc88::setClock(const uint16_t mins_since_midnight) { + uint16_t mins = ::min(mins_since_midnight, (uint16_t)(23 * 60 + 59)); + _.ClockMins = mins % 60; + _.ClockHrs = mins / 60; + _.ClockSecs = 0; +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRSanyoAc88::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kSanyoAc88FanHigh: return stdAc::fanspeed_t::kHigh; + case kSanyoAc88FanMedium: return stdAc::fanspeed_t::kMedium; + case kSanyoAc88FanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Change the SwingV setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setSwingV(const bool on) { _.SwingV = on; } + +/// Get the value of the current SwingV setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getSwingV(void) const { return _.SwingV; } + +/// Change the Turbo setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setTurbo(const bool on) { _.Turbo = on; } + +/// Get the value of the current Turbo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getTurbo(void) const { return _.Turbo; } + +/// Change the Filter setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setFilter(const bool on) { _.Filter = on; } + +/// Get the value of the current Filter setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getFilter(void) const { return _.Filter; } + +/// Change the Sleep setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setSleep(const bool on) { _.Sleep = on; } + +/// Get the value of the current Sleep setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getSleep(void) const { return _.Sleep; } + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRSanyoAc88::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::SANYO_AC88; + result.model = -1; // Not supported. + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.filter = _.Filter; + result.turbo = _.Turbo; + result.sleep = _.Sleep ? 0 : -1; + result.clock = getClock(); + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.econo = false; + result.light = false; + result.quiet = false; + result.beep = false; + result.clean = false; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRSanyoAc88::toString(void) const { + String result = ""; + result.reserve(115); + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kSanyoAc88Auto, kSanyoAc88Cool, + kSanyoAc88Heat, kSanyoAc88Auto, kSanyoAc88Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kSanyoAc88FanHigh, kSanyoAc88FanLow, + kSanyoAc88FanAuto, kSanyoAc88FanAuto, + kSanyoAc88FanMedium); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(minsToString(getClock()), kClockStr); + return result; +} + +#if SEND_SANYO_AC152 +/// Send a SanyoAc152 formatted message. +/// Status: BETA / Probably works. +/// @param[in] data An array of bytes containing the IR command. +/// @warning data's bit order may change. It is not yet confirmed. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// @param[in] repeat Nr. of times the message is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1826 +void IRsend::sendSanyoAc152(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // (Header + Data + Footer) per repeat + sendGeneric(kSanyoAc152HdrMark, kSanyoAc152HdrSpace, + kSanyoAc152BitMark, kSanyoAc152OneSpace, + kSanyoAc152BitMark, kSanyoAc152ZeroSpace, + kSanyoAc152BitMark, kDefaultMessageGap, + data, nbytes, kSanyoAc152Freq, false, repeat, kDutyDefault); + space(kDefaultMessageGap); // Make a guess at a post message gap. +} +#endif // SEND_SANYO_AC152 + +#if DECODE_SANYO_AC152 +/// Decode the supplied SanyoAc152 message. +/// Status: BETA / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @warning data's bit order may change. It is not yet confirmed. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 +bool IRrecv::decodeSanyoAc152(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kSanyoAc152Bits) + return false; + + // Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kSanyoAc152HdrMark, kSanyoAc152HdrSpace, + kSanyoAc152BitMark, kSanyoAc152OneSpace, + kSanyoAc152BitMark, kSanyoAc152ZeroSpace, + kSanyoAc152BitMark, + kDefaultMessageGap, // Just a guess. + false, _tolerance + kSanyoAc152ExtraTolerance, + kMarkExcess, false)) + return false; // No match! + + // Success + results->decode_type = decode_type_t::SANYO_AC152; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_SANYO_AC152 diff --git a/src/libraries/IRremoteESP8266/src/ir_Sanyo.h b/src/libraries/IRremoteESP8266/src/ir_Sanyo.h new file mode 100644 index 000000000..d81852d1e --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Sanyo.h @@ -0,0 +1,285 @@ +// Copyright 2020-2021 David Conran + +/// @file +/// @brief Support for Sanyo protocols. +/// Sanyo LC7461 support originally by marcosamarinho +/// Sanyo SA 8650B originally added from +/// https://github.com/shirriff/Arduino-IRremote/ +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Sanyo.cpp +/// @see http://pdf.datasheetcatalog.com/datasheet/sanyo/LC7461.pdf +/// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Sanyo.cpp +/// @see http://slydiman.narod.ru/scr/kb/sanyo.htm +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1211 +/// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?usp=sharing +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 +/// @see https://docs.google.com/spreadsheets/d/1weUmGAsEpfX38gg5rlDN69Uchnbr6gQl9FqHffLBIRk/edit#gid=0 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1826 + +// Supports: +// Brand: Sanyo, Model: SA 8650B - disabled +// Brand: Sanyo, Model: LC7461 transmitter IC (SANYO_LC7461) +// Brand: Sanyo, Model: SAP-K121AHA A/C (SANYO_AC) +// Brand: Sanyo, Model: RCS-2HS4E remote (SANYO_AC) +// Brand: Sanyo, Model: SAP-K242AH A/C (SANYO_AC) +// Brand: Sanyo, Model: RCS-2S4E remote (SANYO_AC) +// Brand: Sanyo, Model: RCS-4MHVPIS4EE remote (SANYO_AC152) +// Brand: Sanyo, Model: SAP-KMRV124EHE A/C (SANYO_AC152) + +#ifndef IR_SANYO_H_ +#define IR_SANYO_H_ + +#define __STDC_LIMIT_MACROS +#include +//#ifdef ARDUINO +#include "String.h" +//#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Sanyo A/C message. +union SanyoProtocol{ + uint8_t raw[kSanyoAcStateLength]; ///< The state in IR code form. + // Ref: https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?usp=sharing + struct { + // Byte 0 + uint8_t :8; // 0x6A (Fixed?) + // Byte 1 + uint8_t Temp :5; + uint8_t :3; + // Byte 2 + uint8_t SensorTemp :5; + uint8_t Sensor :1; ///< Sensor location (0 = remote, 1 = A/C) + uint8_t Beep :1; + uint8_t :1; + // Byte 3 + uint8_t OffHour :4; + uint8_t :4; + // Byte 4 + uint8_t Fan :2; + uint8_t OffTimer :1; + uint8_t :1; + uint8_t Mode :3; + uint8_t :1; + // Byte 5 + uint8_t SwingV :3; + uint8_t :3; + uint8_t Power :2; + // Byte 6 + uint8_t :3; + uint8_t Sleep :1; + uint8_t :4; + // Byte 7 + uint8_t :8; + // Byte 8 + uint8_t Sum :8; + }; +}; + +// Constants + +const uint8_t kSanyoAcTempMin = 16; ///< Celsius +const uint8_t kSanyoAcTempMax = 30; ///< Celsius +const uint8_t kSanyoAcTempDelta = 4; ///< Celsius to Native Temp difference. + +const uint8_t kSanyoAcHourMax = 15; ///< 0b1111 + +const uint8_t kSanyoAcHeat = 1; ///< 0b001 +const uint8_t kSanyoAcCool = 2; ///< 0b010 +const uint8_t kSanyoAcDry = 3; ///< 0b011 +const uint8_t kSanyoAcAuto = 4; ///< 0b100 +const uint8_t kSanyoAcFanAuto = 0; ///< 0b00 +const uint8_t kSanyoAcFanHigh = 1; ///< 0b01 +const uint8_t kSanyoAcFanLow = 2; ///< 0b10 +const uint8_t kSanyoAcFanMedium = 3; ///< 0b11 + +// const uint8_t kSanyoAcPowerStandby = 0b00; ///< Standby? +const uint8_t kSanyoAcPowerOff = 0b01; ///< Off +const uint8_t kSanyoAcPowerOn = 0b10; ///< On +const uint8_t kSanyoAcSwingVAuto = 0; ///< 0b000 +const uint8_t kSanyoAcSwingVLowest = 2; ///< 0b010 +const uint8_t kSanyoAcSwingVLow = 3; ///< 0b011 +const uint8_t kSanyoAcSwingVLowerMiddle = 4; ///< 0b100 +const uint8_t kSanyoAcSwingVUpperMiddle = 5; ///< 0b101 +const uint8_t kSanyoAcSwingVHigh = 6; ///< 0b110 +const uint8_t kSanyoAcSwingVHighest = 7; ///< 0b111 + +// Classes +/// Class for handling detailed Sanyo A/C messages. +class IRSanyoAc { + public: + explicit IRSanyoAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_SANYO_AC + void send(const uint16_t repeat = kNoRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_SANYO_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setSensorTemp(const uint8_t degrees); + uint8_t getSensorTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setSensor(const bool location); + bool getSensor(void) const; + void setBeep(const bool on); + bool getBeep(void) const; + void setSwingV(const uint8_t setting); + uint8_t getSwingV(void) const; + void setRaw(const uint8_t newState[]); + uint8_t* getRaw(void); + uint16_t getOffTimer(void) const; + void setOffTimer(const uint16_t mins); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kSanyoAcStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t setting); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + SanyoProtocol _; + void checksum(void); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kSanyoAcStateLength); +}; + +const uint8_t kSanyoAc88Auto = 0; ///< 0b000 +const uint8_t kSanyoAc88FeelCool = 1; ///< 0b001 +const uint8_t kSanyoAc88Cool = 2; ///< 0b010 +const uint8_t kSanyoAc88FeelHeat = 3; ///< 0b011 +const uint8_t kSanyoAc88Heat = 4; ///< 0b100 +const uint8_t kSanyoAc88Fan = 5; ///< 0b101 + +const uint8_t kSanyoAc88TempMin = 10; ///< Celsius +const uint8_t kSanyoAc88TempMax = 30; ///< Celsius + +const uint8_t kSanyoAc88FanAuto = 0; ///< 0b00 +const uint8_t kSanyoAc88FanLow = 1; ///< 0b11 +const uint8_t kSanyoAc88FanMedium = 2; ///< 0b10 +const uint8_t kSanyoAc88FanHigh = 3; ///< 0b11 + +/// Native representation of a Sanyo 88-bit A/C message. +union SanyoAc88Protocol{ + uint8_t raw[kSanyoAc88StateLength]; ///< The state in IR code form. + // Ref: https://docs.google.com/spreadsheets/d/1weUmGAsEpfX38gg5rlDN69Uchnbr6gQl9FqHffLBIRk/edit#gid=0 + struct { + // Byte 0-1 + uint8_t :8; // 0xAA (Fixed?) + uint8_t :8; // 0x55 (Fixed?) + // Byte 2 + uint8_t Fan :2; + uint8_t :2; + uint8_t Mode :3; + uint8_t Power :1; + // Byte 3 + uint8_t Temp :5; + uint8_t Filter :1; + uint8_t SwingV :1; + uint8_t :1; + // Byte 4 + uint8_t ClockSecs :8; // Nr. of Seconds + // Byte 5 + uint8_t ClockMins :8; // Nr. of Minutes + // Byte 6 + uint8_t ClockHrs :8; // Nr. of Hours + // Byte 7-9 (Timer times?) + uint8_t :8; + uint8_t :8; + uint8_t :8; + // Byte 10 + uint8_t :3; + uint8_t Turbo :1; + uint8_t EnableStartTimer :1; + uint8_t EnableStopTimer :1; + uint8_t Sleep :1; + uint8_t :1; + }; +}; + +// Classes +/// Class for handling detailed Sanyo A/C messages. +class IRSanyoAc88 { + public: + explicit IRSanyoAc88(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_SANYO_AC88 + void send(const uint16_t repeat = kSanyoAc88MinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_SANYO_AC88 + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setFilter(const bool on); + bool getFilter(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + uint16_t getClock(void) const; + void setClock(const uint16_t mins_since_midnight); + void setRaw(const uint8_t newState[]); + uint8_t* getRaw(void); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + SanyoAc88Protocol _; + void checksum(void); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kSanyoAcStateLength); +}; +#endif // IR_SANYO_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Sharp.cpp b/src/libraries/IRremoteESP8266/src/ir_Sharp.cpp new file mode 100644 index 000000000..369654088 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Sharp.cpp @@ -0,0 +1,977 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017, 2019 David Conran + +/// @file +/// @brief Support for Sharp protocols. +/// @see http://www.sbprojects.net/knowledge/ir/sharp.htm +/// @see http://lirc.sourceforge.net/remotes/sharp/GA538WJSA +/// @see http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf +/// @see http://www.hifi-remote.com/johnsfine/DecodeIR.html#Sharp +/// @see GlobalCache's IR Control Tower data. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/638 +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/SharpHeatpumpIR.cpp + +#include "ir_Sharp.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +// period time = 1/38000Hz = 26.316 microseconds. +const uint16_t kSharpTick = 26; +const uint16_t kSharpBitMarkTicks = 10; +const uint16_t kSharpBitMark = kSharpBitMarkTicks * kSharpTick; +const uint16_t kSharpOneSpaceTicks = 70; +const uint16_t kSharpOneSpace = kSharpOneSpaceTicks * kSharpTick; +const uint16_t kSharpZeroSpaceTicks = 30; +const uint16_t kSharpZeroSpace = kSharpZeroSpaceTicks * kSharpTick; +const uint16_t kSharpGapTicks = 1677; +const uint16_t kSharpGap = kSharpGapTicks * kSharpTick; +// Address(5) + Command(8) + Expansion(1) + Check(1) +const uint64_t kSharpToggleMask = + ((uint64_t)1 << (kSharpBits - kSharpAddressBits)) - 1; +const uint64_t kSharpAddressMask = ((uint64_t)1 << kSharpAddressBits) - 1; +const uint64_t kSharpCommandMask = ((uint64_t)1 << kSharpCommandBits) - 1; + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::addToggleToString; +using irutils::minsToString; + +// Also used by Denon protocol +#if (SEND_SHARP || SEND_DENON) +/// Send a (raw) Sharp message +/// @note Status: STABLE / Working fine. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note his procedure handles the inversion of bits required per protocol. +/// The protocol spec says to send the LSB first, but legacy code & usage +/// has us sending the MSB first. Grrrr. Normal invocation of encodeSharp() +/// handles this for you, assuming you are using the correct/standard values. +/// e.g. sendSharpRaw(encodeSharp(address, command)); +void IRsend::sendSharpRaw(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + uint64_t tempdata = data; + for (uint16_t i = 0; i <= repeat; i++) { + // Protocol demands that the data be sent twice; once normally, + // then with all but the address bits inverted. + // Note: Previously this used to be performed 3 times (normal, inverted, + // normal), however all data points to that being incorrect. + for (uint8_t n = 0; n < 2; n++) { + sendGeneric(0, 0, // No Header + kSharpBitMark, kSharpOneSpace, kSharpBitMark, kSharpZeroSpace, + kSharpBitMark, kSharpGap, tempdata, nbits, 38, true, + 0, // Repeats are handled already. + 33); + // Invert the data per protocol. This is always called twice, so it's + // returned to original upon exiting the inner loop. + tempdata ^= kSharpToggleMask; + } + } +} + +/// Encode a (raw) Sharp message from it's components. +/// Status: STABLE / Works okay. +/// @param[in] address The value of the address to be sent. +/// @param[in] command The value of the address to be sent. (8 bits) +/// @param[in] expansion The value of the expansion bit to use. +/// (0 or 1, typically 1) +/// @param[in] check The value of the check bit to use. (0 or 1, typically 0) +/// @param[in] MSBfirst Flag indicating MSB first or LSB first order. +/// @return A uint32_t containing the raw Sharp message for `sendSharpRaw()`. +/// @note Assumes the standard Sharp bit sizes. +/// Historically sendSharp() sends address & command in +/// MSB first order. This is actually incorrect. It should be sent in LSB +/// order. The behaviour of sendSharp() hasn't been changed to maintain +/// backward compatibility. +uint32_t IRsend::encodeSharp(const uint16_t address, const uint16_t command, + const uint16_t expansion, const uint16_t check, + const bool MSBfirst) { + // Mask any unexpected bits. + uint16_t tempaddress = GETBITS16(address, 0, kSharpAddressBits); + uint16_t tempcommand = GETBITS16(command, 0, kSharpCommandBits); + uint16_t tempexpansion = GETBITS16(expansion, 0, 1); + uint16_t tempcheck = GETBITS16(check, 0, 1); + + if (!MSBfirst) { // Correct bit order if needed. + tempaddress = reverseBits(tempaddress, kSharpAddressBits); + tempcommand = reverseBits(tempcommand, kSharpCommandBits); + } + // Concatenate all the bits. + return (tempaddress << (kSharpCommandBits + 2)) | (tempcommand << 2) | + (tempexpansion << 1) | tempcheck; +} + +/// Send a Sharp message +/// Status: DEPRECATED / Previously working fine. +/// @deprecated Only use this if you are using legacy from the original +/// Arduino-IRremote library. 99% of the time, you will want to use +/// `sendSharpRaw()` instead +/// @param[in] address Address value to be sent. +/// @param[in] command Command value to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note This procedure has a non-standard invocation style compared to similar +/// sendProtocol() routines. This is due to legacy, compatibility, & historic +/// reasons. Normally the calling syntax version is like sendSharpRaw(). +/// This procedure transmits the address & command in MSB first order, which is +/// incorrect. This behaviour is left as-is to maintain backward +/// compatibility with legacy code. +/// In short, you should use sendSharpRaw(), encodeSharp(), and the correct +/// values of address & command instead of using this, & the wrong values. +void IRsend::sendSharp(const uint16_t address, uint16_t const command, + const uint16_t nbits, const uint16_t repeat) { + sendSharpRaw(encodeSharp(address, command, 1, 0, true), nbits, repeat); +} +#endif // (SEND_SHARP || SEND_DENON) + +// Used by decodeDenon too. +#if (DECODE_SHARP || DECODE_DENON) +/// Decode the supplied Sharp message. +/// Status: STABLE / Working fine. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @param[in] expansion Should we expect the expansion bit to be set. +/// Default is true. +/// @return True if it can decode it, false if it can't. +/// @note This procedure returns a value suitable for use in `sendSharpRaw()`. +/// @todo Need to ensure capture of the inverted message as it can +/// be missed due to the interrupt timeout used to detect an end of message. +/// Several compliance checks are disabled until that is resolved. +bool IRrecv::decodeSharp(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict, + const bool expansion) { + if (results->rawlen <= 2 * nbits + kFooter - 1 + offset) + return false; // Not enough entries to be a Sharp message. + // Compliance + if (strict) { + if (nbits != kSharpBits) return false; // Request is out of spec. + // DISABLED - See TODO +#ifdef UNIT_TEST + // An in spec message has the data sent normally, then inverted. So we + // expect twice as many entries than to just get the results. + if (results->rawlen <= (2 * (2 * nbits + kFooter)) - 1 + offset) + return false; +#endif + } + + uint64_t data = 0; + + // Match Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + 0, 0, // No Header + kSharpBitMark, kSharpOneSpace, + kSharpBitMark, kSharpZeroSpace, + kSharpBitMark, kSharpGap, true, 35); + if (!used) return false; + offset += used; + // Compliance + if (strict) { + // Check the state of the expansion bit is what we expect. + if ((data & 0b10) >> 1 != expansion) return false; + // The check bit should be cleared in a normal message. + if (data & 0b1) return false; + // DISABLED - See TODO +#ifdef UNIT_TEST + // Grab the second copy of the data (i.e. inverted) + uint64_t second_data = 0; + // Match Data + Footer + if (!matchGeneric(results->rawbuf + offset, &second_data, + results->rawlen - offset, nbits, + 0, 0, + kSharpBitMark, kSharpOneSpace, + kSharpBitMark, kSharpZeroSpace, + kSharpBitMark, kSharpGap, true, 35)) return false; + // Check that second_data has been inverted correctly. + if (data != (second_data ^ kSharpToggleMask)) return false; +#endif // UNIT_TEST + } + + // Success + results->decode_type = SHARP; + results->bits = nbits; + results->value = data; + // Address & command are actually transmitted in LSB first order. + results->address = reverseBits(data, nbits) & kSharpAddressMask; + results->command = + reverseBits((data >> 2) & kSharpCommandMask, kSharpCommandBits); + return true; +} +#endif // (DECODE_SHARP || DECODE_DENON) + +#if SEND_SHARP_AC +/// Send a Sharp A/C message. +/// Status: Alpha / Untested. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/638 +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/SharpHeatpumpIR.cpp +void IRsend::sendSharpAc(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kSharpAcStateLength) + return; // Not enough bytes to send a proper message. + + sendGeneric(kSharpAcHdrMark, kSharpAcHdrSpace, + kSharpAcBitMark, kSharpAcOneSpace, + kSharpAcBitMark, kSharpAcZeroSpace, + kSharpAcBitMark, kSharpAcGap, + data, nbytes, 38000, false, repeat, 50); +} +#endif // SEND_SHARP_AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRSharpAc::IRSharpAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRSharpAc::begin(void) { _irsend.begin(); } + +#if SEND_SHARP_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRSharpAc::send(const uint16_t repeat) { + _irsend.sendSharpAc(getRaw(), kSharpAcStateLength, repeat); +} +#endif // SEND_SHARP_AC + +/// Calculate the checksum for a given state. +/// @param[in] state The array to calc the checksum of. +/// @param[in] length The length/size of the array. +/// @return The calculated 4-bit checksum value. +uint8_t IRSharpAc::calcChecksum(uint8_t state[], const uint16_t length) { + uint8_t xorsum = xorBytes(state, length - 1); + xorsum ^= GETBITS8(state[length - 1], kLowNibble, kNibbleSize); + xorsum ^= GETBITS8(xorsum, kHighNibble, kNibbleSize); + return GETBITS8(xorsum, kLowNibble, kNibbleSize); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRSharpAc::validChecksum(uint8_t state[], const uint16_t length) { + return GETBITS8(state[length - 1], kHighNibble, kNibbleSize) == + IRSharpAc::calcChecksum(state, length); +} + +/// Calculate and set the checksum values for the internal state. +void IRSharpAc::checksum(void) { + _.Sum = calcChecksum(_.raw); +} + +/// Reset the state of the remote to a known good state/sequence. +void IRSharpAc::stateReset(void) { + static const uint8_t reset[kSharpAcStateLength] = { + 0xAA, 0x5A, 0xCF, 0x10, 0x00, 0x01, 0x00, 0x00, 0x08, 0x80, 0x00, 0xE0, + 0x01}; + memcpy(_.raw, reset, kSharpAcStateLength); + _temp = getTemp(); + _mode = _.Mode; + _fan = _.Fan; + _model = getModel(true); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRSharpAc::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length/size of the new_code array. +void IRSharpAc::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kSharpAcStateLength)); + _model = getModel(true); +} + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRSharpAc::setModel(const sharp_ac_remote_model_t model) { + switch (model) { + case sharp_ac_remote_model_t::A705: + case sharp_ac_remote_model_t::A903: + _model = model; + _.Model = true; + break; + default: + _model = sharp_ac_remote_model_t::A907; + _.Model = false; + } + _.Model2 = (_model != sharp_ac_remote_model_t::A907); + // Redo the operating mode as some models don't support all modes. + setMode(_.Mode); +} + +/// Get/Detect the model of the A/C. +/// @param[in] raw Try to determine the model from the raw code only. +/// @return The enum of the compatible model. +sharp_ac_remote_model_t IRSharpAc::getModel(const bool raw) const { + if (raw) { + if (_.Model2) { + if (_.Model) + return sharp_ac_remote_model_t::A705; + else + return sharp_ac_remote_model_t::A903; + } else { + return sharp_ac_remote_model_t::A907; + } + } + return _model; +} + +/// Set the value of the Power Special setting without any checks. +/// @param[in] value The value to set Power Special to. +inline void IRSharpAc::setPowerSpecial(const uint8_t value) { + _.PowerSpecial = value; +} + +/// Get the value of the Power Special setting. +/// @return The setting's value. +uint8_t IRSharpAc::getPowerSpecial(void) const { + return _.PowerSpecial; +} + +/// Clear the "special"/non-normal bits in the power section. +/// e.g. for normal/common command modes. +void IRSharpAc::clearPowerSpecial(void) { + setPowerSpecial(_.PowerSpecial & kSharpAcPowerOn); +} + +/// Is one of the special power states in use? +/// @return true, it is. false, it isn't. +bool IRSharpAc::isPowerSpecial(void) const { + switch (_.PowerSpecial) { + case kSharpAcPowerSetSpecialOff: + case kSharpAcPowerSetSpecialOn: + case kSharpAcPowerTimerSetting: return true; + default: return false; + } +} + +/// Set the requested power state of the A/C to on. +void IRSharpAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRSharpAc::off(void) { setPower(false); } + +/// Change the power setting, including the previous power state. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @param[in] prev_on true, the setting is on. false, the setting is off. +void IRSharpAc::setPower(const bool on, const bool prev_on) { + setPowerSpecial(on ? (prev_on ? kSharpAcPowerOn : kSharpAcPowerOnFromOff) + : kSharpAcPowerOff); + // Power operations are incompatible with clean mode. + if (_.Clean) setClean(false); + _.Special = kSharpAcSpecialPower; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSharpAc::getPower(void) const { + switch (_.PowerSpecial) { + case kSharpAcPowerUnknown: + case kSharpAcPowerOff: return false; + default: return true; // Everything else is "probably" on. + } +} + +/// Set the value of the Special (button/command?) setting. +/// @param[in] mode The value to set Special to. +void IRSharpAc::setSpecial(const uint8_t mode) { + switch (mode) { + case kSharpAcSpecialPower: + case kSharpAcSpecialTurbo: + case kSharpAcSpecialTempEcono: + case kSharpAcSpecialFan: + case kSharpAcSpecialSwing: + case kSharpAcSpecialTimer: + case kSharpAcSpecialTimerHalfHour: + _.Special = mode; + break; + default: + _.Special = kSharpAcSpecialPower; + } +} + +/// Get the value of the Special (button/command?) setting. +/// @return The setting's value. +uint8_t IRSharpAc::getSpecial(void) const { return _.Special; } + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +/// @param[in] save Do we save this setting as a user set one? +void IRSharpAc::setTemp(const uint8_t temp, const bool save) { + switch (_.Mode) { + // Auto & Dry don't allow temp changes and have a special temp. + case kSharpAcAuto: + case kSharpAcDry: + _.raw[kSharpAcByteTemp] = 0; + return; + default: + switch (getModel()) { + case sharp_ac_remote_model_t::A705: + _.raw[kSharpAcByteTemp] = 0xD0; + break; + default: + _.raw[kSharpAcByteTemp] = 0xC0; + } + } + uint8_t degrees = ::max(temp, kSharpAcMinTemp); + degrees = ::min(degrees, kSharpAcMaxTemp); + if (save) _temp = degrees; + _.Temp = degrees - kSharpAcMinTemp; + _.Special = kSharpAcSpecialTempEcono; + clearPowerSpecial(); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRSharpAc::getTemp(void) const { + return _.Temp + kSharpAcMinTemp; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRSharpAc::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @param[in] save Do we save this setting as a user set one? +void IRSharpAc::setMode(const uint8_t mode, const bool save) { + uint8_t realMode = mode; + if (mode == kSharpAcHeat) { + switch (getModel()) { + case sharp_ac_remote_model_t::A705: + case sharp_ac_remote_model_t::A903: + // These models have no heat mode, use Fan mode instead. + realMode = kSharpAcFan; + break; + default: + break; + } + } + + switch (realMode) { + case kSharpAcAuto: // Also kSharpAcFan + case kSharpAcDry: + // When Dry or Auto, Fan always 2(Auto) + setFan(kSharpAcFanAuto, false); + // FALLTHRU + case kSharpAcCool: + case kSharpAcHeat: + _.Mode = realMode; + break; + default: + setFan(kSharpAcFanAuto, false); + _.Mode = kSharpAcAuto; + } + // Dry/Auto have no temp setting. This step will enforce it. + setTemp(_temp, false); + // Save the mode in case we need to revert to it. eg. Clean + if (save) _mode = _.Mode; + + _.Special = kSharpAcSpecialPower; + clearPowerSpecial(); +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @param[in] save Do we save this setting as a user set one? +void IRSharpAc::setFan(const uint8_t speed, const bool save) { + switch (speed) { + case kSharpAcFanAuto: + case kSharpAcFanMin: + case kSharpAcFanMed: + case kSharpAcFanHigh: + case kSharpAcFanMax: + _.Fan = speed; + if (save) _fan = speed; + break; + default: + _.Fan = kSharpAcFanAuto; + _fan = kSharpAcFanAuto; + } + _.Special = kSharpAcSpecialFan; + clearPowerSpecial(); +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRSharpAc::getFan(void) const { + return _.Fan; +} + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSharpAc::getTurbo(void) const { + return (_.PowerSpecial == kSharpAcPowerSetSpecialOn) && + (_.Special == kSharpAcSpecialTurbo); +} + +/// Set the Turbo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note If you use this method, you will need to send it before making +/// other changes to the settings, as they may overwrite some of the bits +/// used by this setting. +void IRSharpAc::setTurbo(const bool on) { + if (on) setFan(kSharpAcFanMax); + setPowerSpecial(on ? kSharpAcPowerSetSpecialOn : kSharpAcPowerSetSpecialOff); + _.Special = kSharpAcSpecialTurbo; +} + +/// Get the Vertical Swing setting of the A/C. +/// @return The position of the Vertical Swing setting. +uint8_t IRSharpAc::getSwingV(void) const { return _.Swing; } + +/// Set the Vertical Swing setting of the A/C. +/// @note Some positions may not work on all models. +/// @param[in] position The desired position/setting. +/// @note `setSwingV(kSharpAcSwingVLowest)` will only allow the Lowest setting +/// in Heat mode, it will default to `kSharpAcSwingVLow` otherwise. +/// If you want to set this value in other modes e.g. Cool, you must +/// use `setSwingV`s optional `force` parameter. +/// @param[in] force Do we override the safety checks and just do it? +void IRSharpAc::setSwingV(const uint8_t position, const bool force) { + switch (position) { + case kSharpAcSwingVCoanda: + // Only allowed in Heat mode. + if (!force && getMode() != kSharpAcHeat) { + setSwingV(kSharpAcSwingVLow); // Use the next lowest setting. + return; + } + // FALLTHRU + case kSharpAcSwingVHigh: + case kSharpAcSwingVMid: + case kSharpAcSwingVLow: + case kSharpAcSwingVToggle: + case kSharpAcSwingVOff: + case kSharpAcSwingVLast: // Technically valid, but we don't use it. + // All expected non-positions set the special bits. + _.Special = kSharpAcSpecialSwing; + // FALLTHRU + case kSharpAcSwingVIgnore: + _.Swing = position; + } +} + +/// Convert a standard A/C vertical swing into its native setting. +/// @param[in] position A stdAc::swingv_t position to convert. +/// @return The equivalent native horizontal swing position. +uint8_t IRSharpAc::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: return kSharpAcSwingVHigh; + case stdAc::swingv_t::kMiddle: return kSharpAcSwingVMid; + case stdAc::swingv_t::kLow: return kSharpAcSwingVLow; + case stdAc::swingv_t::kLowest: return kSharpAcSwingVCoanda; + case stdAc::swingv_t::kAuto: return kSharpAcSwingVToggle; + case stdAc::swingv_t::kOff: return kSharpAcSwingVOff; + default: return kSharpAcSwingVIgnore; + } +} + +/// Get the (vertical) Swing Toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSharpAc::getSwingToggle(void) const { + return getSwingV() == kSharpAcSwingVToggle; +} + +/// Set the (vertical) Swing Toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSharpAc::setSwingToggle(const bool on) { + setSwingV(on ? kSharpAcSwingVToggle : kSharpAcSwingVIgnore); + if (on) _.Special = kSharpAcSpecialSwing; +} + +/// Get the Ion (Filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSharpAc::getIon(void) const { return _.Ion; } + +/// Set the Ion (Filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSharpAc::setIon(const bool on) { + _.Ion = on; + clearPowerSpecial(); + if (on) _.Special = kSharpAcSpecialSwing; +} + +/// Get the Economical mode toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +/// @note Shares the same location as the Light setting on A705. +bool IRSharpAc::_getEconoToggle(void) const { + return (_.PowerSpecial == kSharpAcPowerSetSpecialOn) && + (_.Special == kSharpAcSpecialTempEcono); +} + +/// Set the Economical mode toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @warning Probably incompatible with `setTurbo()` +/// @note Shares the same location as the Light setting on A705. +void IRSharpAc::_setEconoToggle(const bool on) { + if (on) _.Special = kSharpAcSpecialTempEcono; + setPowerSpecial(on ? kSharpAcPowerSetSpecialOn : kSharpAcPowerSetSpecialOff); +} + +/// Set the Economical mode toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @warning Probably incompatible with `setTurbo()` +/// @note Available on the A907 models. +void IRSharpAc::setEconoToggle(const bool on) { + if (_model == sharp_ac_remote_model_t::A907) _setEconoToggle(on); +} + +/// Get the Economical mode toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +/// @note Available on the A907 models. +bool IRSharpAc::getEconoToggle(void) const { + return _model == sharp_ac_remote_model_t::A907 && _getEconoToggle(); +} + +/// Set the Light mode toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @warning Probably incompatible with `setTurbo()` +/// @note Not available on the A907 model. +void IRSharpAc::setLightToggle(const bool on) { + if (_model != sharp_ac_remote_model_t::A907) _setEconoToggle(on); +} + +/// Get the Light toggle setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +/// @note Not available on the A907 model. +bool IRSharpAc::getLightToggle(void) const { + return _model != sharp_ac_remote_model_t::A907 && _getEconoToggle(); +} + +/// Get how long the timer is set for, in minutes. +/// @return The time in nr of minutes. +uint16_t IRSharpAc::getTimerTime(void) const { + return _.TimerHours * kSharpAcTimerIncrement * 2 + + ((_.Special == kSharpAcSpecialTimerHalfHour) ? kSharpAcTimerIncrement + : 0); +} + +/// Is the Timer enabled? +/// @return true, the setting is on. false, the setting is off. +bool IRSharpAc::getTimerEnabled(void) const { return _.TimerEnabled; } + +/// Get the current timer type. +/// @return true, It's an "On" timer. false, It's an "Off" timer. +bool IRSharpAc::getTimerType(void) const { return _.TimerType; } + +/// Set or cancel the timer function. +/// @param[in] enable Is the timer to be enabled (true) or canceled(false)? +/// @param[in] timer_type An On (true) or an Off (false). Ignored if canceled. +/// @param[in] mins Nr. of minutes the timer is to be set to. +/// @note Rounds down to 30 min increments. (max: 720 mins (12h), 0 is Off) +void IRSharpAc::setTimer(bool enable, bool timer_type, uint16_t mins) { + uint8_t half_hours = ::min(mins / kSharpAcTimerIncrement, + kSharpAcTimerHoursMax * 2); + if (half_hours == 0) enable = false; + if (!enable) { + half_hours = 0; + timer_type = kSharpAcOffTimerType; + } + _.TimerEnabled = enable; + _.TimerType = timer_type; + _.TimerHours = half_hours / 2; + // Handle non-round hours. + _.Special = (half_hours % 2) ? kSharpAcSpecialTimerHalfHour + : kSharpAcSpecialTimer; + setPowerSpecial(kSharpAcPowerTimerSetting); +} + +/// Get the Clean setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRSharpAc::getClean(void) const { + return _.Clean; +} + +/// Set the Economical mode toggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Officially A/C unit needs to be "Off" before clean mode can be entered +void IRSharpAc::setClean(const bool on) { + // Clean mode appears to be just default dry mode, with an extra bit set. + if (on) { + setMode(kSharpAcDry, false); + setPower(true, false); + } else { + // Restore the previous operation mode & fan speed. + setMode(_mode, false); + setFan(_fan, false); + } + _.Clean = on; + clearPowerSpecial(); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSharpAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kSharpAcCool; + case stdAc::opmode_t::kHeat: return kSharpAcHeat; + case stdAc::opmode_t::kDry: return kSharpAcDry; + // No Fan mode. + default: return kSharpAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @param[in] model The enum of the appropriate model. +/// @return The native equivalent of the enum. +uint8_t IRSharpAc::convertFan(const stdAc::fanspeed_t speed, + const sharp_ac_remote_model_t model) { + switch (model) { + case sharp_ac_remote_model_t::A705: + case sharp_ac_remote_model_t::A903: + switch (speed) { + case stdAc::fanspeed_t::kLow: return kSharpAcFanA705Low; + case stdAc::fanspeed_t::kMedium: return kSharpAcFanA705Med; + default: {}; // Fall thru to the next/default clause if not the above + // special cases. + } + // FALL THRU + default: + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kSharpAcFanMin; + case stdAc::fanspeed_t::kMedium: return kSharpAcFanMed; + case stdAc::fanspeed_t::kHigh: return kSharpAcFanHigh; + case stdAc::fanspeed_t::kMax: return kSharpAcFanMax; + default: return kSharpAcFanAuto; + } + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRSharpAc::toCommonMode(const uint8_t mode) const { + switch (mode) { + case kSharpAcCool: return stdAc::opmode_t::kCool; + case kSharpAcHeat: return stdAc::opmode_t::kHeat; + case kSharpAcDry: return stdAc::opmode_t::kDry; + case kSharpAcAuto: // Also kSharpAcFan + switch (getModel()) { + case sharp_ac_remote_model_t::A705: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } + break; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRSharpAc::toCommonFanSpeed(const uint8_t speed) const { + switch (getModel()) { + case sharp_ac_remote_model_t::A705: + case sharp_ac_remote_model_t::A903: + switch (speed) { + case kSharpAcFanA705Low: return stdAc::fanspeed_t::kLow; + case kSharpAcFanA705Med: return stdAc::fanspeed_t::kMedium; + } + // FALL-THRU + default: + switch (speed) { + case kSharpAcFanMax: return stdAc::fanspeed_t::kMax; + case kSharpAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kSharpAcFanMed: return stdAc::fanspeed_t::kMedium; + case kSharpAcFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @param[in] mode What operating mode are we in? +/// @return The common vertical swing position. +stdAc::swingv_t IRSharpAc::toCommonSwingV(const uint8_t pos, + const stdAc::opmode_t mode) const { + switch (pos) { + case kSharpAcSwingVHigh: return stdAc::swingv_t::kHighest; + case kSharpAcSwingVMid: return stdAc::swingv_t::kMiddle; + case kSharpAcSwingVLow: return stdAc::swingv_t::kLow; + case kSharpAcSwingVCoanda: // Coanda has mode dependent positionss + switch (mode) { + case stdAc::opmode_t::kCool: return stdAc::swingv_t::kHighest; + case stdAc::opmode_t::kHeat: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kOff; + } + case kSharpAcSwingVToggle: return stdAc::swingv_t::kAuto; + default: return stdAc::swingv_t::kOff; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRSharpAc::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) result = *prev; + result.protocol = decode_type_t::SHARP_AC; + result.model = getModel(); + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.turbo = getTurbo(); + if (getSwingV() != kSharpAcSwingVIgnore) + result.swingv = toCommonSwingV(getSwingV(), result.mode); + result.filter = _.Ion; + result.econo = getEconoToggle(); + result.light = getLightToggle(); + result.clean = _.Clean; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRSharpAc::toString(void) const { + String result = ""; + const sharp_ac_remote_model_t model = getModel(); + result.reserve(170); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::SHARP_AC, getModel(), false); + + result += addLabeledString(isPowerSpecial() ? String("-") + : String(getPower() ? kOnStr + : kOffStr), + kPowerStr); + const uint8_t mode = _.Mode; + result += addModeToString( + mode, + // Make the value invalid if the model doesn't support an Auto mode. + (model == sharp_ac_remote_model_t::A907) ? kSharpAcAuto : 255, + kSharpAcCool, kSharpAcHeat, kSharpAcDry, kSharpAcFan); + result += addTempToString(getTemp()); + switch (model) { + case sharp_ac_remote_model_t::A705: + case sharp_ac_remote_model_t::A903: + result += addFanToString(_.Fan, kSharpAcFanMax, kSharpAcFanA705Low, + kSharpAcFanAuto, kSharpAcFanAuto, + kSharpAcFanA705Med); + break; + default: + result += addFanToString(_.Fan, kSharpAcFanMax, kSharpAcFanMin, + kSharpAcFanAuto, kSharpAcFanAuto, + kSharpAcFanMed); + } + if (getSwingV() == kSharpAcSwingVIgnore) { + result += addIntToString(kSharpAcSwingVIgnore, kSwingVStr); + result += kSpaceLBraceStr; + result += kNAStr; + result += ')'; + } else { + result += addSwingVToString( + getSwingV(), 0xFF, + // Coanda means Highest when in Cool mode. + (mode == kSharpAcCool) ? kSharpAcSwingVCoanda : kSharpAcSwingVToggle, + kSharpAcSwingVHigh, + 0xFF, // Upper Middle is unused + kSharpAcSwingVMid, + 0xFF, // Lower Middle is unused + kSharpAcSwingVLow, + kSharpAcSwingVCoanda, + kSharpAcSwingVOff, + // Below are unused. + kSharpAcSwingVToggle, + 0xFF, + 0xFF); + } + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(_.Ion, kIonStr); + switch (model) { + case sharp_ac_remote_model_t::A705: + case sharp_ac_remote_model_t::A903: + result += addToggleToString(getLightToggle(), kLightStr); + break; + default: + result += addToggleToString(getEconoToggle(), kEconoStr); + } + result += addBoolToString(_.Clean, kCleanStr); + if (_.TimerEnabled) + result += addLabeledString(minsToString(getTimerTime()), + _.TimerType ? kOnTimerStr : kOffTimerStr); + return result; +} + +#if DECODE_SHARP_AC +/// Decode the supplied Sharp A/C message. +/// Status: STABLE / Known working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/638 +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/SharpHeatpumpIR.cpp +bool IRrecv::decodeSharpAc(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance + if (strict && nbits != kSharpAcBits) return false; + + // Match Header + Data + Footer + uint16_t used; + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kSharpAcHdrMark, kSharpAcHdrSpace, + kSharpAcBitMark, kSharpAcOneSpace, + kSharpAcBitMark, kSharpAcZeroSpace, + kSharpAcBitMark, kSharpAcGap, true, + _tolerance, kMarkExcess, false); + if (used == 0) return false; + offset += used; + // Compliance + if (strict) { + if (!IRSharpAc::validChecksum(results->state)) return false; + } + + // Success + results->decode_type = SHARP_AC; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_SHARP_AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Sharp.h b/src/libraries/IRremoteESP8266/src/ir_Sharp.h new file mode 100644 index 000000000..b8c748a77 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Sharp.h @@ -0,0 +1,241 @@ +// Copyright 2019 crankyoldgit + +/// @file +/// @brief Support for Sharp protocols. +/// @see http://www.sbprojects.net/knowledge/ir/sharp.htm +/// @see http://lirc.sourceforge.net/remotes/sharp/GA538WJSA +/// @see http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf +/// @see http://www.hifi-remote.com/johnsfine/DecodeIR.html#Sharp +/// @see GlobalCache's IR Control Tower data. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/638 +/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/SharpHeatpumpIR.cpp +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1091 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1387 + +// Supports: +// Brand: Sharp, Model: LC-52D62U TV +// Brand: Sharp, Model: AY-ZP40KR A/C (A907) +// Brand: Sharp, Model: AH-AxSAY A/C (A907) +// Brand: Sharp, Model: CRMC-A907 JBEZ remote (A907) +// Brand: Sharp, Model: CRMC-A950 JBEZ (A907) +// Brand: Sharp, Model: AH-PR13-GL A/C (A903) +// Brand: Sharp, Model: CRMC-A903JBEZ remote (A903) +// Brand: Sharp, Model: AH-XP10NRY A/C (A903) +// Brand: Sharp, Model: CRMC-820 JBEZ remote (A903) +// Brand: Sharp, Model: CRMC-A705 JBEZ remote (A705) +// Brand: Sharp, Model: AH-A12REVP-1 A/C (A903) +// Brand: Sharp, Model: CRMC-A863 JBEZ remote (A903) + +#ifndef IR_SHARP_H_ +#define IR_SHARP_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif +#include "IRutils.h" + +/// Native representation of a Sharp A/C message. +union SharpProtocol{ + uint8_t raw[kSharpAcStateLength]; ///< State of the remote in IR code form + struct { + // Byte 0~3 + uint8_t pad[4]; + // Byte 4 + uint8_t Temp :4; + uint8_t Model :1; + uint8_t :3; + // Byte 5 + uint8_t :4; + uint8_t PowerSpecial :4; + // Byte 6 + uint8_t Mode :2; + uint8_t :1; + uint8_t Clean :1; + uint8_t Fan :3; + uint8_t :1; + // Byte 7 + uint8_t TimerHours :4; + uint8_t :2; + uint8_t TimerType :1; + uint8_t TimerEnabled:1; + // Byte 8 + uint8_t Swing :3; + uint8_t :5; + // Byte 9 + uint8_t :8; + // Byte 10 + uint8_t Special :8; + // Byte 11 + uint8_t :2; + uint8_t Ion :1; + uint8_t :1; + uint8_t Model2 :1; + uint8_t :3; + // Byte 12 + uint8_t :4; + uint8_t Sum :4; + }; +}; + +// Constants +const uint16_t kSharpAcHdrMark = 3800; +const uint16_t kSharpAcHdrSpace = 1900; +const uint16_t kSharpAcBitMark = 470; +const uint16_t kSharpAcZeroSpace = 500; +const uint16_t kSharpAcOneSpace = 1400; +const uint32_t kSharpAcGap = kDefaultMessageGap; + +const uint8_t kSharpAcByteTemp = 4; +const uint8_t kSharpAcMinTemp = 15; // Celsius +const uint8_t kSharpAcMaxTemp = 30; // Celsius + +const uint8_t kSharpAcPowerUnknown = 0; // 0b0000 +const uint8_t kSharpAcPowerOnFromOff = 1; // 0b0001 +const uint8_t kSharpAcPowerOff = 2; // 0b0010 +const uint8_t kSharpAcPowerOn = 3; // 0b0011 (Normal) +const uint8_t kSharpAcPowerSetSpecialOn = 6; // 0b0110 +const uint8_t kSharpAcPowerSetSpecialOff = 7; // 0b0111 +const uint8_t kSharpAcPowerTimerSetting = 8; // 0b1000 + +const uint8_t kSharpAcAuto = 0b00; // A907 only +const uint8_t kSharpAcFan = 0b00; // A705 only +const uint8_t kSharpAcDry = 0b11; +const uint8_t kSharpAcCool = 0b10; +const uint8_t kSharpAcHeat = 0b01; // A907 only +const uint8_t kSharpAcFanAuto = 0b010; // 2 +const uint8_t kSharpAcFanMin = 0b100; // 4 (FAN1) +const uint8_t kSharpAcFanMed = 0b011; // 3 (FAN2) +const uint8_t kSharpAcFanA705Low = 0b011; // 3 (A903 too) +const uint8_t kSharpAcFanHigh = 0b101; // 5 (FAN3) +const uint8_t kSharpAcFanA705Med = 0b101; // 5 (A903 too) +const uint8_t kSharpAcFanMax = 0b111; // 7 (FAN4) + +const uint8_t kSharpAcTimerIncrement = 30; // Mins +const uint8_t kSharpAcTimerHoursOff = 0b0000; +const uint8_t kSharpAcTimerHoursMax = 0b1100; // 12 +const uint8_t kSharpAcOffTimerType = 0b0; +const uint8_t kSharpAcOnTimerType = 0b1; + +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/discussions/1590#discussioncomment-1260213 +const uint8_t kSharpAcSwingVIgnore = 0b000; // Don't change the swing setting. +const uint8_t kSharpAcSwingVHigh = 0b001; // 0° down. Similar to Cool Coanda. +const uint8_t kSharpAcSwingVOff = 0b010; // Stop & Go to last fixed pos. +const uint8_t kSharpAcSwingVMid = 0b011; // 30° down +const uint8_t kSharpAcSwingVLow = 0b100; // 45° down +const uint8_t kSharpAcSwingVLast = 0b101; // Same as kSharpAcSwingVOff. +// Toggles between last fixed pos & either 75° down (Heat) or 0° down (Cool) +// i.e. alternate between last pos <-> 75° down if in Heat mode, AND +// alternate between last pos <-> 0° down if in Cool mode. +// Note: `setSwingV(kSharpAcSwingVLowest)` will only allow the Lowest setting in +// Heat mode, it will default to `kSharpAcSwingVLow` otherwise. +// If you want to set this value in other modes e.g. Cool, you must +// use `setSwingV`s optional `force` parameter. +const uint8_t kSharpAcSwingVLowest = 0b110; +const uint8_t kSharpAcSwingVCoanda = kSharpAcSwingVLowest; +const uint8_t kSharpAcSwingVToggle = 0b111; // Toggle Constant swinging on/off. + +const uint8_t kSharpAcSpecialPower = 0x00; +const uint8_t kSharpAcSpecialTurbo = 0x01; +const uint8_t kSharpAcSpecialTempEcono = 0x04; +const uint8_t kSharpAcSpecialFan = 0x05; +const uint8_t kSharpAcSpecialSwing = 0x06; +const uint8_t kSharpAcSpecialTimer = 0xC0; +const uint8_t kSharpAcSpecialTimerHalfHour = 0xDE; + +// Classes +/// Class for handling detailed Sharp A/C messages. +class IRSharpAc { + public: + explicit IRSharpAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_SHARP_AC + void send(const uint16_t repeat = kSharpAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_SHARP_AC + void begin(void); + void setModel(const sharp_ac_remote_model_t model); + sharp_ac_remote_model_t getModel(const bool raw = false) const; + void on(void); + void off(void); + void setPower(const bool on, const bool prev_on = true); + bool getPower(void) const; + bool isPowerSpecial(void) const; + void setTemp(const uint8_t temp, const bool save = true); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan, const bool save = true); + uint8_t getFan(void) const; + void setMode(const uint8_t mode, const bool save = true); + uint8_t getMode(void) const; + void setSpecial(const uint8_t mode); + uint8_t getSpecial(void) const; + bool getTurbo(void) const; + void setTurbo(const bool on); + bool getSwingToggle(void) const; + void setSwingToggle(const bool on); + uint8_t getSwingV(void) const; + void setSwingV(const uint8_t position, const bool force = false); + bool getIon(void) const; + void setIon(const bool on); + bool getEconoToggle(void) const; + void setEconoToggle(const bool on); + bool getLightToggle(void) const; + void setLightToggle(const bool on); + uint16_t getTimerTime(void) const; + bool getTimerEnabled(void) const; + bool getTimerType(void) const; + void setTimer(bool enable, bool timer_type, uint16_t mins); + bool getClean(void) const; + void setClean(const bool on); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kSharpAcStateLength); + static bool validChecksum(uint8_t state[], + const uint16_t length = kSharpAcStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed, + const sharp_ac_remote_model_t model = + sharp_ac_remote_model_t::A907); + static uint8_t convertSwingV(const stdAc::swingv_t position); + stdAc::opmode_t toCommonMode(const uint8_t mode) const; + stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed) const; + stdAc::swingv_t toCommonSwingV( + const uint8_t pos, + const stdAc::opmode_t mode = stdAc::opmode_t::kHeat) const; + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + SharpProtocol _; + uint8_t _temp; ///< Saved copy of the desired temp. + uint8_t _mode; ///< Saved copy of the desired mode. + uint8_t _fan; ///< Saved copy of the desired fan speed. + sharp_ac_remote_model_t _model; ///< Saved copy of the model. + void stateReset(void); + void checksum(void); + static uint8_t calcChecksum(uint8_t state[], + const uint16_t length = kSharpAcStateLength); + void setPowerSpecial(const uint8_t value); + uint8_t getPowerSpecial(void) const; + void clearPowerSpecial(void); + bool _getEconoToggle(void) const; + void _setEconoToggle(const bool on); +}; + +#endif // IR_SHARP_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Sherwood.cpp b/src/libraries/IRremoteESP8266/src/ir_Sherwood.cpp new file mode 100644 index 000000000..5a18aa8cf --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Sherwood.cpp @@ -0,0 +1,25 @@ +// Copyright 2017 David Conran + +/// @file +/// @brief Support for Sherwood protocols. + +// Supports: +// Brand: Sherwood, Model: RC-138 remote +// Brand: Sherwood, Model: RD6505(B) Receiver + +// #include +#include "IRsend.h" +#include "minmax.h" + +#if SEND_SHERWOOD +/// Send an IR command to a Sherwood device. +/// Status: STABLE / Known working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note Sherwood remote codes appear to be NEC codes with a mandatory repeat +/// code. i.e. repeat should be >= kSherwoodMinRepeat (1). +void IRsend::sendSherwood(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendNEC(data, nbits, ::max((uint16_t)kSherwoodMinRepeat, repeat)); +} +#endif // SEND_SHERWOOD diff --git a/src/libraries/IRremoteESP8266/src/ir_Sony.cpp b/src/libraries/IRremoteESP8266/src/ir_Sony.cpp new file mode 100644 index 000000000..5889c8806 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Sony.cpp @@ -0,0 +1,192 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2016 marcosamarinho +// Copyright 2017,2020 David Conran + +/// @file +/// @brief Support for Sony SIRC(Serial Infra-Red Control) protocols. +/// Sony originally added from https://github.com/shirriff/Arduino-IRremote/ +/// Updates from marcosamarinho +/// @see http://www.sbprojects.net/knowledge/ir/sirc.php +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1018 + +// Supports: +// Brand: Sony, Model: HT-CT380 Soundbar (Uses 38kHz & 3 repeats) +// Brand: Sony, Model: HT-SF150 Soundbar (Uses 38kHz & 3 repeats) + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + + +// Constants +const uint16_t kSonyTick = 200; +const uint16_t kSonyHdrMarkTicks = 12; +const uint16_t kSonyHdrMark = kSonyHdrMarkTicks * kSonyTick; +const uint16_t kSonySpaceTicks = 3; +const uint16_t kSonySpace = kSonySpaceTicks * kSonyTick; +const uint16_t kSonyOneMarkTicks = 6; +const uint16_t kSonyOneMark = kSonyOneMarkTicks * kSonyTick; +const uint16_t kSonyZeroMarkTicks = 3; +const uint16_t kSonyZeroMark = kSonyZeroMarkTicks * kSonyTick; +const uint16_t kSonyRptLengthTicks = 225; +const uint16_t kSonyRptLength = kSonyRptLengthTicks * kSonyTick; +const uint16_t kSonyMinGapTicks = 50; +const uint16_t kSonyMinGap = kSonyMinGapTicks * kSonyTick; +const uint16_t kSonyStdFreq = 40000; // kHz +const uint16_t kSonyAltFreq = 38000; // kHz + +#if SEND_SONY +/// Send a standard Sony/SIRC(Serial Infra-Red Control) message. (40kHz) +/// Status: STABLE / Known working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note sendSony() should typically be called with repeat=2 as Sony devices +/// expect the message to be sent at least 3 times. +void IRsend::sendSony(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + _sendSony(data, nbits, repeat, kSonyStdFreq); +} + +/// Send an alternative 38kHz Sony/SIRC(Serial Infra-Red Control) message. +/// Status: STABLE / Known working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @note `sendSony38()` should typically be called with repeat=3 as these Sony +/// devices expect the message to be sent at least 4 times. +/// @warning Messages send via this method will be detected by this library as +/// just `SONY`, not `SONY_38K` as the library has no way to determine the +/// modulation frequency used. Hence, there is no `decodeSony38()`. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1018 +void IRsend::sendSony38(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + _sendSony(data, nbits, repeat, kSonyAltFreq); +} + +/// Internal procedure to generate a Sony/SIRC(Serial Infra-Red Control) message +/// Status: STABLE / Known working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @param[in] freq Frequency of the modulation to transmit at. (Hz or kHz) +void IRsend::_sendSony(const uint64_t data, const uint16_t nbits, + const uint16_t repeat, const uint16_t freq) { + sendGeneric(kSonyHdrMark, kSonySpace, kSonyOneMark, kSonySpace, kSonyZeroMark, + kSonySpace, + 0, // No Footer mark. + kSonyMinGap, kSonyRptLength, data, nbits, freq, true, repeat, 33); +} + +/// Convert Sony/SIRC command, address, & extended bits into sendSony format. +/// Status: STABLE / Should be working. +/// @param[in] nbits Sony protocol bit size. +/// @param[in] command Sony command bits. +/// @param[in] address Sony address bits. +/// @param[in] extended Sony extended bits. +/// @return A `sendSony()` etc compatible data message. +uint32_t IRsend::encodeSony(const uint16_t nbits, const uint16_t command, + const uint16_t address, const uint16_t extended) { + uint32_t result = 0; + switch (nbits) { + case 12: // 5 address bits. + result = address & 0x1F; + break; + case 15: // 8 address bits. + result = address & 0xFF; + break; + case 20: // 5 address bits, 8 extended bits. + result = address & 0x1F; + result |= (extended & 0xFF) << 5; + break; + default: + return 0; // This is not an expected Sony bit size/protocol. + } + result = (result << 7) | (command & 0x7F); // All sizes have 7 command bits. + return reverseBits(result, nbits); // sendSony uses reverse ordered bits. +} +#endif // SEND_SONY + +#if DECODE_SONY +/// Decode the supplied Sony/SIRC message. +/// Status: STABLE / Should be working. strict mode is ALPHA / Untested. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @note SONY protocol, SIRC (Serial Infra-Red Control) can be 12, 15, or 20 +/// bits long. +bool IRrecv::decodeSony(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader - 1 + offset) + return false; // Message is smaller than we expected. + + // Compliance + if (strict) { + switch (nbits) { // Check we've been called with a correct bit size. + case 12: + case 15: + case 20: + break; + default: + return false; // The request doesn't strictly match the protocol defn. + } + } + + uint64_t data = 0; + uint16_t actualBits; + + // Header + if (!matchMark(results->rawbuf[offset], kSonyHdrMark)) return false; + // Calculate how long the common tick time is based on the header mark. + uint32_t tick = results->rawbuf[offset++] * kRawTick / kSonyHdrMarkTicks; + + // Data + for (actualBits = 0; offset < results->rawlen - 1; actualBits++, offset++) { + // The gap after a Sony packet for a repeat should be kSonyMinGap according + // to the spec. + if (matchAtLeast(results->rawbuf[offset], kSonyMinGapTicks * tick)) + break; // Found a repeat space. + if (!matchSpace(results->rawbuf[offset++], kSonySpaceTicks * tick)) + return false; + if (matchMark(results->rawbuf[offset], kSonyOneMarkTicks * tick)) + data = (data << 1) | 1; + else if (matchMark(results->rawbuf[offset], kSonyZeroMarkTicks * tick)) + data <<= 1; + else + return false; + } + // No Footer for Sony. + + // Compliance + if (strict && actualBits != nbits) + return false; // We got the wrong number of bits. + + // Success + results->bits = actualBits; + results->value = data; + // We can't detect SONY_38K messages so always assume it is just `SONY` 40kHz. + results->decode_type = SONY; + // Message comes in LSB first. Convert ot MSB first. + data = reverseBits(data, actualBits); + // Decode the address & command from raw decode value. + switch (actualBits) { + case 12: // 7 command bits, 5 address bits. + case 15: // 7 command bits, 8 address bits. + results->command = data & 0x7F; // Bits 0-6 + results->address = data >> 7; // Bits 7-14 + break; + case 20: // 7 command bits, 5 address bits, 8 extended (command) bits. + results->command = (data & 0x7F) + ((data >> 12) << 7); // Bits 0-6,12-19 + results->address = (data >> 7) & 0x1F; // Bits 7-11 + break; + default: // Shouldn't happen, but just in case. + results->address = 0; + results->command = 0; + } + return true; +} +#endif // DECODE_SONY diff --git a/src/libraries/IRremoteESP8266/src/ir_Symphony.cpp b/src/libraries/IRremoteESP8266/src/ir_Symphony.cpp new file mode 100644 index 000000000..973873ed8 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Symphony.cpp @@ -0,0 +1,96 @@ +// Copyright 2020 David Conran + +/// @file +/// @brief Support for Symphony protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1057 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1105 +/// @see https://www.alldatasheet.com/datasheet-pdf/pdf/124369/ANALOGICTECH/SM5021B.html + +// Supports: +// Brand: Symphony, Model: Air Cooler 3Di +// Brand: SamHop, Model: SM3015 Fan Remote Control +// Brand: SamHop, Model: SM5021 Encoder chip +// Brand: SamHop, Model: SM5032 Decoder chip +// Brand: Blyss, Model: Owen-SW-5 3 Fan +// Brand: Blyss, Model: WP-YK8 090218 remote +// Brand: Westinghouse, Model: Ceiling fan +// Brand: Westinghouse, Model: 78095 Remote +// Brand: Satellite Electronic, Model: ID6 Remote +// Brand: Satellite Electronic, Model: JY199I Fan driver +// Brand: Satellite Electronic, Model: JY199I-L Fan driver +// Brand: SilverCrest, Model: SSVS 85 A1 Fan + +// Known Codes: +// SilverCrest SSVS 85 A1 Fan: +// 0x581 - On/Off +// 0x582 - Speed +// 0x584 - Mist +// 0x588 - Timer +// 0x590 - OSC + +//// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtimer.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kSymphonyZeroMark = 400; +const uint16_t kSymphonyZeroSpace = 1250; +const uint16_t kSymphonyOneMark = kSymphonyZeroSpace; +const uint16_t kSymphonyOneSpace = kSymphonyZeroMark; +const uint32_t kSymphonyFooterGap = 4 * (kSymphonyZeroMark + + kSymphonyZeroSpace); + +#if SEND_SYMPHONY +/// Send a Symphony packet. +/// Status: STABLE / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendSymphony(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendGeneric(0, 0, + kSymphonyOneMark, kSymphonyOneSpace, + kSymphonyZeroMark, kSymphonyZeroSpace, + 0, kSymphonyFooterGap, + data, nbits, 38000, true, repeat, kDutyDefault); +} +#endif // SEND_SYMPHONY + +#if DECODE_SYMPHONY +/// Decode the supplied Symphony packet/message. +/// Status: STABLE / Should be working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeSymphony(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint64_t data = 0; + + if (results->rawlen < 2 * nbits + offset - 1) + return false; // Not enough entries to ever be SYMPHONY. + // Compliance + if (strict && nbits != kSymphonyBits) return false; + + if (!matchGenericConstBitTime(results->rawbuf + offset, &data, + results->rawlen - offset, + nbits, + 0, 0, // No Header + kSymphonyOneMark, kSymphonyZeroMark, + 0, kSymphonyFooterGap, true, + _tolerance, 0)) + return false; + + // Success + results->value = data; + results->decode_type = decode_type_t::SYMPHONY; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_SYMPHONY diff --git a/src/libraries/IRremoteESP8266/src/ir_Tcl.cpp b/src/libraries/IRremoteESP8266/src/ir_Tcl.cpp new file mode 100644 index 000000000..21626bf26 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Tcl.cpp @@ -0,0 +1,618 @@ +// Copyright 2019, 2021, 2022 David Conran + +/// @file +/// @brief Support for TCL protocols. + +#include "ir_Tcl.h" +// #include +// #include +#ifndef ARDUINO +//#include +#endif +#include "string.h" +#include "IRremoteESP8266.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint8_t kTcl112AcTimerResolution = 20; // Minutes +const uint16_t kTcl112AcTimerMax = 720; // Minutes (12 hrs) + +const uint16_t kTcl96AcHdrMark = 1056; // uSeconds. +const uint16_t kTcl96AcHdrSpace = 550; // uSeconds. +const uint16_t kTcl96AcBitMark = 600; // uSeconds. +const uint32_t kTcl96AcGap = kDefaultMessageGap; // Just a guess. +const uint8_t kTcl96AcSpaceCount = 4; +const uint16_t kTcl96AcBitSpaces[kTcl96AcSpaceCount] = {360, // 0b00 + 838, // 0b01 + 2182, // 0b10 + 1444}; // 0b11 + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addSwingVToString; +using irutils::addTempFloatToString; +using irutils::minsToString; + +#if SEND_TCL112AC +/// Send a TCL 112-bit A/C message. +/// Status: Beta / Probably working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendTcl112Ac(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kTcl112AcHdrMark, kTcl112AcHdrSpace, + kTcl112AcBitMark, kTcl112AcOneSpace, + kTcl112AcBitMark, kTcl112AcZeroSpace, + kTcl112AcBitMark, kTcl112AcGap, + data, nbytes, 38000, false, repeat, 50); +} +#endif // SEND_TCL112AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRTcl112Ac::IRTcl112Ac(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRTcl112Ac::begin(void) { _irsend.begin(); } + +#if SEND_TCL112AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRTcl112Ac::send(const uint16_t repeat) { + uint8_t save[kTcl112AcStateLength]; + // Do we need to send the special "quiet" message? + if (_quiet != _quiet_prev) { + // Backup the current state. + memcpy(save, _.raw, kTcl112AcStateLength); + const uint8_t quiet_off[kTcl112AcStateLength] = { + 0x23, 0xCB, 0x26, 0x02, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65}; + // Use a known good quiet/mute off/type 2 state for the time being. + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1528#issuecomment-876989044 + setRaw(quiet_off); + setQuiet(_quiet); + // Send it. + _irsend.sendTcl112Ac(getRaw(), kTcl112AcStateLength, repeat); + // Now it's been sent, update the quiet previous state. + _quiet_prev = _quiet; + // Restore the old state. + setRaw(save); + // Make sure it looks like a normal TCL mesg if needed. + if (_.MsgType == kTcl112AcNormal) _.isTcl = true; + } + // Send the normal (type 1) state. + _irsend.sendTcl112Ac(getRaw(), kTcl112AcStateLength, repeat); +} +#endif // SEND_TCL112AC + +/// Calculate the checksum for a given state. +/// @param[in] state The array to calc the checksum of. +/// @param[in] length The length/size of the array. +/// @return The calculated checksum value. +uint8_t IRTcl112Ac::calcChecksum(uint8_t state[], const uint16_t length) { + if (length) { + if (length > 4 && state[3] == 0x02) { // Special nessage? + return sumBytes(state, length - 1, 0xF); // Checksum needs an offset. + } else { + return sumBytes(state, length - 1); + } + } else { + return 0; + } +} + +/// Calculate & set the checksum for the current internal state of the remote. +/// @param[in] length The length/size of the internal array to checksum. +void IRTcl112Ac::checksum(const uint16_t length) { + // Stored the checksum value in the last byte. + if (length > 1) + _.Sum = calcChecksum(_.raw, length); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRTcl112Ac::validChecksum(uint8_t state[], const uint16_t length) { + return (length > 1 && state[length - 1] == calcChecksum(state, length)); +} + +/// Check the supplied state looks like a TCL112AC message. +/// @param[in] state The array to verify the checksum of. +/// @note Assumes the state is the correct size. +/// @return true, if the state looks like a TCL112AC message. Otherwise, false. +/// @warning This is just a guess. +bool IRTcl112Ac::isTcl(const uint8_t state[]) { + Tcl112Protocol mesg; + memcpy(mesg.raw, state, kTcl112AcStateLength); + return (mesg.MsgType != kTcl112AcNormal) || mesg.isTcl; +} + +/// Reset the internal state of the emulation. (On, Cool, 24C) +void IRTcl112Ac::stateReset(void) { + // A known good state. (On, Cool, 24C) + static const uint8_t reset[kTcl112AcStateLength] = { + 0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x07, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x03}; + memcpy(_.raw, reset, kTcl112AcStateLength); + _quiet = false; + _quiet_prev = false; + _quiet_explictly_set = false; +} + +/// Get/Detect the model of the A/C. +/// @return The enum of the compatible model. +tcl_ac_remote_model_t IRTcl112Ac::getModel(void) const { + return isTcl(_.raw) ? tcl_ac_remote_model_t::TAC09CHSD + : tcl_ac_remote_model_t::GZ055BE1; +} + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRTcl112Ac::setModel(const tcl_ac_remote_model_t model) { + _.isTcl = (model != tcl_ac_remote_model_t::GZ055BE1); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRTcl112Ac::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length/size of the new_code array. +void IRTcl112Ac::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kTcl112AcStateLength)); +} + +/// Set the requested power state of the A/C to on. +void IRTcl112Ac::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRTcl112Ac::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getPower(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRTcl112Ac::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note Fan/Ventilation mode sets the fan speed to high. +/// Unknown values default to Auto. +void IRTcl112Ac::setMode(const uint8_t mode) { + // If we get an unexpected mode, default to AUTO. + switch (mode) { + case kTcl112AcFan: + setFan(kTcl112AcFanHigh); + // FALLTHRU + case kTcl112AcAuto: + case kTcl112AcCool: + case kTcl112AcHeat: + case kTcl112AcDry: + _.Mode = mode; + break; + default: + _.Mode = kTcl112AcAuto; + } +} + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +/// @note The temperature resolution is 0.5 of a degree. +void IRTcl112Ac::setTemp(const float celsius) { + // Make sure we have desired temp in the correct range. + float safecelsius = ::max(celsius, kTcl112AcTempMin); + safecelsius = ::min(safecelsius, kTcl112AcTempMax); + // Convert to integer nr. of half degrees. + uint8_t nrHalfDegrees = safecelsius * 2; + // Do we have a half degree celsius? + _.HalfDegree = nrHalfDegrees & 1; + _.Temp = static_cast(kTcl112AcTempMax - nrHalfDegrees / 2); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +/// @note The temperature resolution is 0.5 of a degree. +float IRTcl112Ac::getTemp(void) const { + float result = kTcl112AcTempMax - _.Temp; + if (_.HalfDegree) result += 0.5; + return result; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @note Unknown speeds will default to Auto. +void IRTcl112Ac::setFan(const uint8_t speed) { + switch (speed) { + case kTcl112AcFanAuto: + case kTcl112AcFanMin: + case kTcl112AcFanLow: + case kTcl112AcFanMed: + case kTcl112AcFanHigh: + _.Fan = speed; + break; + default: + _.Fan = kTcl112AcFanAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRTcl112Ac::getFan(void) const { return _.Fan; } + +/// Set the economy setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setEcono(const bool on) { _.Econo = on; } + +/// Get the economy setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getEcono(void) const { return _.Econo; } + +/// Set the Health (Filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setHealth(const bool on) { _.Health = on; } + +/// Get the Health (Filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getHealth(void) const { return _.Health; } + +/// Set the Light (LED/Display) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setLight(const bool on) { _.Light = !on; } // Cleared when on. + +/// Get the Light (LED/Display) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getLight(void) const { return !_.Light; } + +/// Set the horizontal swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setSwingHorizontal(const bool on) { _.SwingH = on; } + +/// Get the horizontal swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getSwingHorizontal(void) const { return _.SwingH; } + +/// Set the vertical swing setting of the A/C. +/// @param[in] setting The value of the desired setting. +void IRTcl112Ac::setSwingVertical(const uint8_t setting) { + switch (setting) { + case kTcl112AcSwingVOff: + case kTcl112AcSwingVHighest: + case kTcl112AcSwingVHigh: + case kTcl112AcSwingVMiddle: + case kTcl112AcSwingVLow: + case kTcl112AcSwingVLowest: + case kTcl112AcSwingVOn: + _.SwingV = setting; + } +} + +/// Get the vertical swing setting of the A/C. +/// @return The current setting. +uint8_t IRTcl112Ac::getSwingVertical(void) const { return _.SwingV; } + +/// Set the Turbo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setTurbo(const bool on) { + _.Turbo = on; + if (on) { + _.Fan = kTcl112AcFanHigh; + _.SwingV = kTcl112AcSwingVOn; + } +} + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getTurbo(void) const { return _.Turbo; } + +/// Set the Quiet setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setQuiet(const bool on) { + _quiet_explictly_set = true; + _quiet = on; + if (_.MsgType == kTcl112AcSpecial) _.Quiet = on; +} + +/// Get the Quiet setting of the A/C. +/// @param[in] def The default value to use if we are not sure. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getQuiet(const bool def) const { + if (_.MsgType == kTcl112AcSpecial) + return _.Quiet; + else + return _quiet_explictly_set ? _quiet : def; +} + +/// Get how long the On Timer is set for, in minutes. +/// @return The time in nr of minutes. +uint16_t IRTcl112Ac::getOnTimer(void) const { + return _.OnTimer * kTcl112AcTimerResolution; +} + +/// Set or cancel the On Timer function. +/// @param[in] mins Nr. of minutes the timer is to be set to. +/// @note Rounds down to 20 min increments. (max: 720 mins (12h), 0 is Off) +void IRTcl112Ac::setOnTimer(const uint16_t mins) { + _.OnTimer = ::min(mins, kTcl112AcTimerMax) / kTcl112AcTimerResolution; + _.OnTimerEnabled = _.OnTimer > 0; + _.TimerIndicator = _.OnTimerEnabled || _.OffTimerEnabled; +} + +/// Get how long the Off Timer is set for, in minutes. +/// @return The time in nr of minutes. +uint16_t IRTcl112Ac::getOffTimer(void) const { + return _.OffTimer * kTcl112AcTimerResolution; +} + +/// Set or cancel the Off Timer function. +/// @param[in] mins Nr. of minutes the timer is to be set to. +/// @note Rounds down to 20 min increments. (max: 720 mins (12h), 0 is Off) +void IRTcl112Ac::setOffTimer(const uint16_t mins) { + _.OffTimer = ::min(mins, kTcl112AcTimerMax) / kTcl112AcTimerResolution; + _.OffTimerEnabled = _.OffTimer > 0; + _.TimerIndicator = _.OnTimerEnabled || _.OffTimerEnabled; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTcl112Ac::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kTcl112AcCool; + case stdAc::opmode_t::kHeat: return kTcl112AcHeat; + case stdAc::opmode_t::kDry: return kTcl112AcDry; + case stdAc::opmode_t::kFan: return kTcl112AcFan; + default: return kTcl112AcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTcl112Ac::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kTcl112AcFanMin; + case stdAc::fanspeed_t::kLow: return kTcl112AcFanLow; + case stdAc::fanspeed_t::kMedium: return kTcl112AcFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kTcl112AcFanHigh; + default: return kTcl112AcFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRTcl112Ac::toCommonMode(const uint8_t mode) { + switch (mode) { + case kTcl112AcCool: return stdAc::opmode_t::kCool; + case kTcl112AcHeat: return stdAc::opmode_t::kHeat; + case kTcl112AcDry: return stdAc::opmode_t::kDry; + case kTcl112AcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] position The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTcl112Ac::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kOff: return kTcl112AcSwingVOff; + case stdAc::swingv_t::kHighest: return kTcl112AcSwingVHighest; + case stdAc::swingv_t::kHigh: return kTcl112AcSwingVHigh; + case stdAc::swingv_t::kMiddle: return kTcl112AcSwingVMiddle; + case stdAc::swingv_t::kLow: return kTcl112AcSwingVLow; + case stdAc::swingv_t::kLowest: return kTcl112AcSwingVLowest; + default: return kTcl112AcSwingVOn; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRTcl112Ac::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kTcl112AcFanHigh: return stdAc::fanspeed_t::kMax; + case kTcl112AcFanMed: return stdAc::fanspeed_t::kMedium; + case kTcl112AcFanLow: return stdAc::fanspeed_t::kLow; + case kTcl112AcFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] setting A native position to convert. +/// @return The common vertical swing position. +stdAc::swingv_t IRTcl112Ac::toCommonSwingV(const uint8_t setting) { + switch (setting) { + case kTcl112AcSwingVOff: return stdAc::swingv_t::kOff; + default: return stdAc::swingv_t::kAuto; + } +} +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRTcl112Ac::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) result = *prev; + result.protocol = decode_type_t::TCL112AC; + result.model = getModel(); + result.quiet = getQuiet(result.quiet); + // The rest only get updated if it is a "normal" message. + if (_.MsgType == kTcl112AcNormal) { + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = toCommonSwingV(_.SwingV); + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + result.turbo = _.Turbo; + result.filter = _.Health; + result.econo = _.Econo; + result.light = getLight(); + } + // Not supported. + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRTcl112Ac::toString(void) const { + String result = ""; + result.reserve(220); // Reserve some heap for the string to reduce fragging. + tcl_ac_remote_model_t model = getModel(); + result += addModelToString(decode_type_t::TCL112AC, model, false); + result += addIntToString(_.MsgType, D_STR_TYPE); + switch (_.MsgType) { + case kTcl112AcNormal: + result += addBoolToString(_.Power, kPowerStr); + result += addModeToString(_.Mode, kTcl112AcAuto, kTcl112AcCool, + kTcl112AcHeat, kTcl112AcDry, kTcl112AcFan); + result += addTempFloatToString(getTemp()); + result += addFanToString(_.Fan, kTcl112AcFanHigh, kTcl112AcFanLow, + kTcl112AcFanAuto, kTcl112AcFanMin, + kTcl112AcFanMed); + result += addSwingVToString(_.SwingV, kTcl112AcSwingVOff, + kTcl112AcSwingVHighest, + kTcl112AcSwingVHigh, + 0xFF, // unused + kTcl112AcSwingVMiddle, + 0xFF, // unused + kTcl112AcSwingVLow, + kTcl112AcSwingVLowest, + kTcl112AcSwingVOff, + kTcl112AcSwingVOn, // Swing + 0xFF, 0xFF); // Both Unused + if (model != tcl_ac_remote_model_t::GZ055BE1) { + result += addBoolToString(_.SwingH, kSwingHStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addBoolToString(_.Health, kHealthStr); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(getLight(), kLightStr); + } + result += addLabeledString( + _.OnTimerEnabled ? minsToString(getOnTimer()) : kOffStr, + kOnTimerStr); + result += addLabeledString( + _.OffTimerEnabled ? minsToString(getOffTimer()) : kOffStr, + kOffTimerStr); + break; + case kTcl112AcSpecial: + result += addBoolToString(_.Quiet, kQuietStr); + break; + } + return result; +} + +#if DECODE_TCL112AC +/// @file +/// @note There is no `decodedecodeTcl112Ac()`. +/// It's the same as `decodeMitsubishi112()`. A shared routine is used. +/// You can find it in: ir_Mitsubishi.cpp +#endif // DECODE_TCL112AC + +#if SEND_TCL96AC +/// Send a TCL 96-bit A/C message. +/// Status: BETA / Untested on a real device working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendTcl96Ac(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + enableIROut(38); + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kTcl96AcHdrMark); + space(kTcl96AcHdrSpace); + // Data + for (uint16_t pos = 0; pos < nbytes; pos++) { + uint8_t databyte = data[pos]; + for (uint8_t bits = 0; bits < 8; bits += 2) { + mark(kTcl96AcBitMark); + space(kTcl96AcBitSpaces[GETBITS8(databyte, 8 - 2, 2)]); + databyte <<= 2; + } + } + // Footer + mark(kTcl96AcBitMark); + space(kTcl96AcGap); + } +} +#endif // SEND_TCL96AC + +#if DECODE_TCL96AC +/// Decode the supplied Tcl96Ac message. +/// Status: ALPHA / Experimental. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeTcl96Ac(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < nbits + kHeader + kFooter - 1 + offset) + return false; // Message is smaller than we expected. + if (strict && nbits != kTcl96AcBits) + return false; // Not strictly a TCL96AC message. + uint8_t data = 0; + // Header. + if (!matchMark(results->rawbuf[offset++], kTcl96AcHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kTcl96AcHdrSpace)) return false; + // Data (2 bits at a time) + for (uint16_t bits_so_far = 0; bits_so_far < nbits; bits_so_far += 2) { + if (bits_so_far % 8) + data <<= 2; // Make space for the new data bits. + else + data = 0; + if (!matchMark(results->rawbuf[offset++], kTcl96AcBitMark)) return false; + uint8_t value = 0; + while (value < kTcl96AcSpaceCount) { + if (matchSpace(results->rawbuf[offset], kTcl96AcBitSpaces[value])) { + data += value; + break; + } + value++; + } + if (value >= kTcl96AcSpaceCount) return false; // No matches. + offset++; + *(results->state + bits_so_far / 8) = data; + } + // Footer + if (!matchMark(results->rawbuf[offset++], kTcl96AcBitMark)) return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kTcl96AcGap)) return false; + // Success + results->decode_type = TCL96AC; + results->bits = nbits; + return true; +} +#endif // DECODE_TCL96AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Tcl.h b/src/libraries/IRremoteESP8266/src/ir_Tcl.h new file mode 100644 index 000000000..2a0aaa6d7 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Tcl.h @@ -0,0 +1,201 @@ +// Copyright 2019, 2021 David Conran + +/// @file +/// @brief Support for TCL protocols. + +// Supports: +// Brand: Leberg, Model: LBS-TOR07 A/C (TAC09CHSD) +// Brand: TCL, Model: TAC-09CHSD/XA31I A/C (TAC09CHSD) +// Brand: Teknopoint, Model: Allegro SSA-09H A/C (GZ055BE1) +// Brand: Teknopoint, Model: GZ-055B-E1 remote (GZ055BE1) +// Brand: Daewoo, Model: DSB-F0934ELH-V A/C +// Brand: Daewoo, Model: GYKQ-52E remote +// Brand: TCL, Model: GYKQ-58(XM) remote (TCL96AC) + +#ifndef IR_TCL_H_ +#define IR_TCL_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRrecv.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a TCL 112 A/C message. +union Tcl112Protocol{ + uint8_t raw[kTcl112AcStateLength]; ///< The State in IR code form. + struct { + // Byte 0~2 + uint8_t :8; + uint8_t :8; + uint8_t :8; + // Byte 3 + uint8_t MsgType :2; + uint8_t :6; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t :2; + uint8_t Power :1; + uint8_t OffTimerEnabled :1; + uint8_t OnTimerEnabled :1; + uint8_t Quiet :1; + uint8_t Light :1; + uint8_t Econo :1; + // Byte 6 + uint8_t Mode :4; + uint8_t Health :1; + uint8_t Turbo :1; + uint8_t :2; + // Byte 7 + uint8_t Temp :4; + uint8_t :4; + // Byte 8 + uint8_t Fan :3; + uint8_t SwingV :3; + uint8_t TimerIndicator :1; + uint8_t :1; + // Byte 9 + uint8_t :1; // 0 + uint8_t OffTimer :6; + uint8_t :1; // 0 + // Byte 10 + uint8_t :1; // 0 + uint8_t OnTimer :6; + uint8_t :1; // 0 + // Byte 11 + uint8_t :8; // 00000000 + // Byte 12 + uint8_t :3; + uint8_t SwingH :1; + uint8_t :1; + uint8_t HalfDegree :1; + uint8_t :1; + uint8_t isTcl :1; + // Byte 13 + uint8_t Sum :8; + }; +}; + +// Constants +const uint16_t kTcl112AcHdrMark = 3000; +const uint16_t kTcl112AcHdrSpace = 1650; +const uint16_t kTcl112AcBitMark = 500; +const uint16_t kTcl112AcOneSpace = 1050; +const uint16_t kTcl112AcZeroSpace = 325; +const uint32_t kTcl112AcGap = kDefaultMessageGap; // Just a guess. +// Total tolerance percentage to use for matching the header mark. +const uint8_t kTcl112AcHdrMarkTolerance = 6; +const uint8_t kTcl112AcTolerance = 5; // Extra Percentage for the rest. + +const uint8_t kTcl112AcHeat = 1; +const uint8_t kTcl112AcDry = 2; +const uint8_t kTcl112AcCool = 3; +const uint8_t kTcl112AcFan = 7; +const uint8_t kTcl112AcAuto = 8; + +const uint8_t kTcl112AcFanAuto = 0b000; +const uint8_t kTcl112AcFanMin = 0b001; // Aka. "Night" +const uint8_t kTcl112AcFanLow = 0b010; +const uint8_t kTcl112AcFanMed = 0b011; +const uint8_t kTcl112AcFanHigh = 0b101; +const uint8_t kTcl112AcFanNight = kTcl112AcFanMin; +const uint8_t kTcl112AcFanQuiet = kTcl112AcFanMin; + +const float kTcl112AcTempMax = 31.0; +const float kTcl112AcTempMin = 16.0; + +const uint8_t kTcl112AcSwingVOff = 0b000; +const uint8_t kTcl112AcSwingVHighest = 0b001; +const uint8_t kTcl112AcSwingVHigh = 0b010; +const uint8_t kTcl112AcSwingVMiddle = 0b011; +const uint8_t kTcl112AcSwingVLow = 0b100; +const uint8_t kTcl112AcSwingVLowest = 0b101; +const uint8_t kTcl112AcSwingVOn = 0b111; +// MsgType +const uint8_t kTcl112AcNormal = 0b01; +const uint8_t kTcl112AcSpecial = 0b10; + +// Classes +/// Class for handling detailed TCL A/C messages. +class IRTcl112Ac { + public: + explicit IRTcl112Ac(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_TCL112AC + void send(const uint16_t repeat = kTcl112AcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TCL + void begin(void); + void stateReset(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kTcl112AcStateLength); + tcl_ac_remote_model_t getModel(void) const; + void setModel(const tcl_ac_remote_model_t model); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const float celsius); // Celsius in 0.5 increments + float getTemp(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + static uint8_t calcChecksum(uint8_t state[], + const uint16_t length = kTcl112AcStateLength); + static bool validChecksum(uint8_t state[], + const uint16_t length = kTcl112AcStateLength); + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setHealth(const bool on); + bool getHealth(void) const; + void setLight(const bool on); + bool getLight(void) const; + void setSwingHorizontal(const bool on); + bool getSwingHorizontal(void) const; + void setSwingVertical(const uint8_t setting); + uint8_t getSwingVertical(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setQuiet(const bool on); + bool getQuiet(const bool def = false) const; + uint16_t getOnTimer(void) const; + void setOnTimer(const uint16_t mins); + uint16_t getOffTimer(void) const; + void setOffTimer(const uint16_t mins); + static bool isTcl(const uint8_t state[]); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertSwingV(const stdAc::swingv_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t setting); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Tcl112Protocol _; + bool _quiet_prev; + bool _quiet; + bool _quiet_explictly_set; + void checksum(const uint16_t length = kTcl112AcStateLength); +}; + +#endif // IR_TCL_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Technibel.cpp b/src/libraries/IRremoteESP8266/src/ir_Technibel.cpp new file mode 100644 index 000000000..7f6eac231 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Technibel.cpp @@ -0,0 +1,409 @@ +// Copyright 2020 Quentin Briollant + +/// @file +/// @brief Support for Technibel protocol. + +#include "ir_Technibel.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +// #include +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addLabeledString; +using irutils::addTempToString; +using irutils::minsToString; + +const uint16_t kTechnibelAcHdrMark = 8836; +const uint16_t kTechnibelAcHdrSpace = 4380; +const uint16_t kTechnibelAcBitMark = 523; +const uint16_t kTechnibelAcOneSpace = 1696; +const uint16_t kTechnibelAcZeroSpace = 564; +const uint32_t kTechnibelAcGap = kDefaultMessageGap; +const uint16_t kTechnibelAcFreq = 38000; + + +#if SEND_TECHNIBEL_AC +/// Send an Technibel AC formatted message. +/// Status: STABLE / Reported as working on a real device. +/// @param[in] data containing the IR command. +/// @param[in] nbits Nr. of bits to send. usually kTechnibelAcBits +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendTechnibelAc(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kTechnibelAcHdrMark, kTechnibelAcHdrSpace, + kTechnibelAcBitMark, kTechnibelAcOneSpace, + kTechnibelAcBitMark, kTechnibelAcZeroSpace, + kTechnibelAcBitMark, kTechnibelAcGap, + data, nbits, kTechnibelAcFreq, true, // LSB First. + repeat, kDutyDefault); +} +#endif // SEND_TECHNIBEL_AC + +#if DECODE_TECHNIBEL_AC +/// Status: STABLE / Reported as working on a real device +/// @param[in,out] results Ptr to data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect (kTechnibelAcBits). +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeTechnibelAc(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance + if (strict && nbits != kTechnibelAcBits) { + return false; + } + + uint64_t data = 0; + + // Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kTechnibelAcHdrMark, kTechnibelAcHdrSpace, + kTechnibelAcBitMark, kTechnibelAcOneSpace, + kTechnibelAcBitMark, kTechnibelAcZeroSpace, + kTechnibelAcBitMark, kTechnibelAcGap, true, + _tolerance, kMarkExcess, true)) return false; + + // Compliance + if (strict && !IRTechnibelAc::validChecksum(data)) return false; + + // Success + results->decode_type = decode_type_t::TECHNIBEL_AC; + results->bits = nbits; + results->value = data; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_TECHNIBEL_AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRTechnibelAc::IRTechnibelAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRTechnibelAc::begin(void) { _irsend.begin(); } + +#if SEND_TECHNIBEL_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRTechnibelAc::send(const uint16_t repeat) { + _irsend.sendTechnibelAc(getRaw(), kTechnibelAcBits, repeat); +} +#endif // SEND_TECHNIBEL_AC + +/// Compute the checksum of the supplied state. +/// @param[in] state A valid code for this protocol. +/// @return The calculated checksum of the supplied state. +uint8_t IRTechnibelAc::calcChecksum(const uint64_t state) { + uint8_t sum = 0; + // Add up all the 8 bit data chunks. + for (uint8_t offset = kTechnibelAcTimerHoursOffset; + offset < kTechnibelAcHeaderOffset; offset += 8) + sum += GETBITS64(state, offset, 8); + return ~sum + 1; +} + +/// Confirm the checksum of the supplied state is valid. +/// @param[in] state A valid code for this protocol. +/// @return `true` if the checksum is correct, otherwise `false`. +bool IRTechnibelAc::validChecksum(const uint64_t state) { + TechnibelProtocol p{.raw = state}; + return calcChecksum(state) == p.Sum; +} + +/// Set the checksum of the internal state. +void IRTechnibelAc::checksum(void) { + _.Sum = calcChecksum(_.raw); +} + +/// Reset the internal state of the emulation. +/// @note Mode:Cool, Power:Off, fan:Low, temp:20, swing:Off, sleep:Off +void IRTechnibelAc::stateReset(void) { + _.raw = kTechnibelAcResetState; + _saved_temp = 20; // DegC (Random reasonable default value) + _saved_temp_units = 0; // Celsius +} + +/// Get a copy of the internal state/code for this protocol. +/// @return A code for this protocol based on the current internal state. +uint64_t IRTechnibelAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRTechnibelAc::setRaw(const uint64_t state) { + _.raw = state; +} + +/// Set the requested power state of the A/C to on. +void IRTechnibelAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRTechnibelAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTechnibelAc::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTechnibelAc::getPower(void) const { + return _.Power; +} + +/// Set the temperature unit setting. +/// @param[in] fahrenheit true, the unit is °F. false, the unit is °C. +void IRTechnibelAc::setTempUnit(const bool fahrenheit) { + _saved_temp_units = fahrenheit; + _.UseFah = fahrenheit; +} + +/// Get the temperature unit setting. +/// @return true, the unit is °F. false, the unit is °C. +bool IRTechnibelAc::getTempUnit(void) const { + return _.UseFah; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees. +/// @param[in] fahrenheit The temperature unit: true=°F, false(default)=°C. +void IRTechnibelAc::setTemp(const uint8_t degrees, const bool fahrenheit) { + setTempUnit(fahrenheit); + uint8_t temp_min = fahrenheit ? kTechnibelAcTempMinF : kTechnibelAcTempMinC; + uint8_t temp_max = fahrenheit ? kTechnibelAcTempMaxF : kTechnibelAcTempMaxC; + _saved_temp = ::min(temp_max, ::max(temp_min, degrees)); + _.Temp = _saved_temp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees. +uint8_t IRTechnibelAc::getTemp(void) const { + return _.Temp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRTechnibelAc::setFan(const uint8_t speed) { + // Mode fan speed rules. + if (_.Mode == kTechnibelAcDry && speed != kTechnibelAcFanLow) { + _.Fan = kTechnibelAcFanLow; + return; + } + switch (speed) { + case kTechnibelAcFanHigh: + case kTechnibelAcFanMedium: + case kTechnibelAcFanLow: + _.Fan = speed; + break; + default: + _.Fan = kTechnibelAcFanLow; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRTechnibelAc::getFan(void) const { + return _.Fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTechnibelAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kTechnibelAcFanLow; + case stdAc::fanspeed_t::kMedium: return kTechnibelAcFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kTechnibelAcFanHigh; + default: return kTechnibelAcFanLow; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRTechnibelAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kTechnibelAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kTechnibelAcFanMedium: return stdAc::fanspeed_t::kMedium; + default: return stdAc::fanspeed_t::kLow; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRTechnibelAc::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRTechnibelAc::setMode(const uint8_t mode) { + _.Mode = mode; + switch (mode) { + case kTechnibelAcHeat: + case kTechnibelAcFan: + case kTechnibelAcDry: + case kTechnibelAcCool: + break; + default: + _.Mode = kTechnibelAcCool; + } + setFan(_.Fan); // Re-force any fan speed constraints. + // Restore previous temp settings for cool mode. + setTemp(_saved_temp, _saved_temp_units); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTechnibelAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kHeat: return kTechnibelAcHeat; + case stdAc::opmode_t::kDry: return kTechnibelAcDry; + case stdAc::opmode_t::kFan: return kTechnibelAcFan; + default: return kTechnibelAcCool; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRTechnibelAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kTechnibelAcHeat: return stdAc::opmode_t::kHeat; + case kTechnibelAcDry: return stdAc::opmode_t::kDry; + case kTechnibelAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; + } +} + +/// Set the (vertical) swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTechnibelAc::setSwing(const bool on) { + _.Swing = on; +} + +/// Get the (vertical) swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTechnibelAc::getSwing(void) const { + return _.Swing; +} + +/// Convert a stdAc::swingv_t enum into it's native swing. +/// @param[in] swing The enum to be converted. +/// @return true, the swing is on. false, the swing is off. +bool IRTechnibelAc::convertSwing(const stdAc::swingv_t swing) { + return swing != stdAc::swingv_t::kOff; +} + +/// Convert a native swing into its stdAc equivalent. +/// @param[in] swing true, the swing is on. false, the swing is off. +/// @return The stdAc equivalent of the native setting. +stdAc::swingv_t IRTechnibelAc::toCommonSwing(const bool swing) { + return swing ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTechnibelAc::setSleep(const bool on) { + _.Sleep = on; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTechnibelAc::getSleep(void) const { + return _.Sleep; +} + +/// Set the enable timer setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTechnibelAc::setTimerEnabled(const bool on) { + _.TimerEnable = on; +} + +/// Is the timer function enabled? +/// @return true, the setting is on. false, the setting is off. +bool IRTechnibelAc::getTimerEnabled(void) const { + return _.TimerEnable; +} + +/// Set the timer for when the A/C unit will switch off. +/// @param[in] nr_of_mins Number of minutes before power off. +/// `0` will clear the timer. Max is 24 hrs (1440 mins). +/// @note Time is stored internally in hours. +void IRTechnibelAc::setTimer(const uint16_t nr_of_mins) { + const uint8_t hours = nr_of_mins / 60; + _.TimerHours = ::min(kTechnibelAcTimerMax, hours); + // Enable or not? + setTimerEnabled(hours); +} + +/// Get the timer time for when the A/C unit will switch power state. +/// @return The number of minutes left on the timer. `0` means off. +uint16_t IRTechnibelAc::getTimer(void) const { + return _.TimerEnable ? _.TimerHours * 60 : 0; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRTechnibelAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::TECHNIBEL_AC; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = !_.UseFah; + result.degrees = _.Temp; + result.fanspeed = toCommonFanSpeed(_.Fan); + result.sleep = _.Sleep ? 0 : -1; + result.swingv = toCommonSwing(_.Swing); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRTechnibelAc::toString(void) const { + String result = ""; + result.reserve(100); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, 255, // No Auto, so use impossible value + kTechnibelAcCool, kTechnibelAcHeat, kTechnibelAcDry, + kTechnibelAcFan); + result += addFanToString(_.Fan, kTechnibelAcFanHigh, kTechnibelAcFanLow, + kTechnibelAcFanLow, kTechnibelAcFanLow, + kTechnibelAcFanMedium); + result += addTempToString(_.Temp, !_.UseFah); + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(_.Swing, kSwingVStr); + result += addLabeledString(_.TimerEnable ? minsToString(getTimer()) + : kOffStr, + kTimerStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Technibel.h b/src/libraries/IRremoteESP8266/src/ir_Technibel.h new file mode 100644 index 000000000..7c71f0843 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Technibel.h @@ -0,0 +1,135 @@ +// Copyright 2020 Quentin Briollant + +/// @file +/// @brief Support for Technibel protocol. + +#ifndef IR_TECHNIBEL_H_ +#define IR_TECHNIBEL_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +// Supports: +// Brand: Technibel, Model: IRO PLUS + +/// Native representation of a Technibel A/C message. +union TechnibelProtocol{ + uint64_t raw; // The state of the IR remote. + struct { + uint8_t Sum :8; + uint8_t Footer :8; + uint8_t TimerHours :5; + uint8_t :3; + uint8_t Temp :7; + uint8_t :1; + uint8_t Fan :3; + uint8_t :1; + uint8_t Sleep :1; + uint8_t Swing :1; + uint8_t UseFah :1; + uint8_t TimerEnable :1; + uint8_t Mode :4; + uint8_t FanChange :1; + uint8_t TempChange :1; + uint8_t TimerChange :1; + uint8_t Power :1; + uint8_t Header :8; + }; +}; + +// Constants + +const uint8_t kTechnibelAcTimerHoursOffset = 16; +const uint8_t kTechnibelAcTimerMax = 24; + +const uint8_t kTechnibelAcTempMinC = 16; // Deg C +const uint8_t kTechnibelAcTempMaxC = 31; // Deg C +const uint8_t kTechnibelAcTempMinF = 61; // Deg F +const uint8_t kTechnibelAcTempMaxF = 88; // Deg F + +const uint8_t kTechnibelAcFanSize = 4; +const uint8_t kTechnibelAcFanLow = 0b0001; +const uint8_t kTechnibelAcFanMedium = 0b0010; +const uint8_t kTechnibelAcFanHigh = 0b0100; + +const uint8_t kTechnibelAcCool = 0b0001; +const uint8_t kTechnibelAcDry = 0b0010; +const uint8_t kTechnibelAcFan = 0b0100; +const uint8_t kTechnibelAcHeat = 0b1000; + +const uint8_t kTechnibelAcHeaderOffset = 48; +const uint8_t kTechnibelAcHeader = 0b00011000; + +const uint64_t kTechnibelAcResetState = 0x180101140000EA; ///< +///< Mode:Cool, Power:Off, fan:Low, temp:20, swing:Off, sleep:Off + + +// Classes +/// Class for handling detailed Technibel A/C messages. +class IRTechnibelAc { + public: + explicit IRTechnibelAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_TECHNIBEL_AC + void send(const uint16_t repeat = kTechnibelAcDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TECHNIBEL_AC + void begin(void); + static uint8_t calcChecksum(const uint64_t state); + static bool validChecksum(const uint64_t state); + void setPower(const bool on); + bool getPower(void) const; + void on(void); + void off(void); + void setTempUnit(const bool celsius); + bool getTempUnit(void) const; + void setTemp(const uint8_t temp, const bool fahrenheit = false); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwing(const bool on); + bool getSwing(void) const; + static bool convertSwing(const stdAc::swingv_t swing); + static stdAc::swingv_t toCommonSwing(const bool swing); + void setSleep(const bool on); + bool getSleep(void) const; + void setTimerEnabled(const bool on); + bool getTimerEnabled(void) const; + void setTimer(const uint16_t nr_of_mins); + uint16_t getTimer(void) const; + uint64_t getRaw(void); + void setRaw(const uint64_t state); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; +#else + IRsendTest _irsend; +#endif + TechnibelProtocol _; + uint8_t _saved_temp; // The previously user requested temp value. + uint8_t _saved_temp_units; // The previously user requested temp units. + void checksum(void); +}; +#endif // IR_TECHNIBEL_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Teco.cpp b/src/libraries/IRremoteESP8266/src/ir_Teco.cpp new file mode 100644 index 000000000..44d02ef99 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Teco.cpp @@ -0,0 +1,377 @@ +// Copyright 2019 Fabien Valthier + +/// @file +/// @brief Support for Teco protocols. + +#include "ir_Teco.h" +// #include +#include "IRremoteESP8266.h" +#include "IRtext.h" +#include "IRutils.h" +#ifndef ARDUINO +//#include +#endif +#include "String.h" +#include "minmax.h" + +// Constants +// using SPACE modulation. +const uint16_t kTecoHdrMark = 9000; +const uint16_t kTecoHdrSpace = 4440; +const uint16_t kTecoBitMark = 620; +const uint16_t kTecoOneSpace = 1650; +const uint16_t kTecoZeroSpace = 580; +const uint32_t kTecoGap = kDefaultMessageGap; // Made-up value. Just a guess. + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; + +#if SEND_TECO +/// Send a Teco A/C message. +/// Status: Beta / Probably working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendTeco(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kTecoHdrMark, kTecoHdrSpace, kTecoBitMark, kTecoOneSpace, + kTecoBitMark, kTecoZeroSpace, kTecoBitMark, kTecoGap, + data, nbits, 38000, false, repeat, kDutyDefault); +} +#endif // SEND_TECO + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRTecoAc::IRTecoAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRTecoAc::begin(void) { _irsend.begin(); } + +#if SEND_TECO +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRTecoAc::send(const uint16_t repeat) { + _irsend.sendTeco(_.raw, kTecoBits, repeat); +} +#endif // SEND_TECO + +/// Reset the internal state of the emulation. +/// @note Mode:auto, Power:Off, fan:auto, temp:16, swing:off, sleep:off +void IRTecoAc::stateReset(void) { + _.raw = kTecoReset; +} + +/// Get a copy of the internal state/code for this protocol. +/// @return A code for this protocol based on the current internal state. +uint64_t IRTecoAc::getRaw(void) const { return _.raw; } + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRTecoAc::setRaw(const uint64_t new_code) { _.raw = new_code; } + +/// Set the requested power state of the A/C to on. +void IRTecoAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRTecoAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTecoAc::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTecoAc::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRTecoAc::setTemp(const uint8_t temp) { + uint8_t newtemp = temp; + newtemp = ::min(newtemp, kTecoMaxTemp); + newtemp = ::max(newtemp, kTecoMinTemp); + _.Temp = newtemp - kTecoMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRTecoAc::getTemp(void) const { + return _.Temp + kTecoMinTemp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRTecoAc::setFan(const uint8_t speed) { + uint8_t newspeed = speed; + switch (speed) { + case kTecoFanAuto: + case kTecoFanHigh: + case kTecoFanMed: + case kTecoFanLow: break; + default: newspeed = kTecoFanAuto; + } + _.Fan = newspeed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRTecoAc::getFan(void) const { + return _.Fan; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRTecoAc::setMode(const uint8_t mode) { + uint8_t newmode = mode; + switch (mode) { + case kTecoAuto: + case kTecoCool: + case kTecoDry: + case kTecoFan: + case kTecoHeat: break; + default: newmode = kTecoAuto; + } + _.Mode = newmode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRTecoAc::getMode(void) const { + return _.Mode; +} + +/// Set the (vertical) swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTecoAc::setSwing(const bool on) { + _.Swing = on; +} + +/// Get the (vertical) swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTecoAc::getSwing(void) const { + return _.Swing; +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTecoAc::setSleep(const bool on) { + _.Sleep = on; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTecoAc::getSleep(void) const { + return _.Sleep; +} + +/// Set the Light (LED/Display) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTecoAc::setLight(const bool on) { + _.Light = on; +} + +/// Get the Light (LED/Display) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTecoAc::getLight(void) const { + return _.Light; +} + +/// Set the Humid setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTecoAc::setHumid(const bool on) { + _.Humid = on; +} + +/// Get the Humid setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTecoAc::getHumid(void) const { + return _.Humid; +} + +/// Set the Save setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTecoAc::setSave(const bool on) { + _.Save = on; +} + +/// Get the Save setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTecoAc::getSave(void) const { + return _.Save; +} + +/// Is the timer function enabled? +/// @return true, the setting is on. false, the setting is off. +inline bool IRTecoAc::getTimerEnabled(void) const { + return _.TimerOn; +} + +/// Get the timer time for when the A/C unit will switch power state. +/// @return The number of minutes left on the timer. `0` means off. +uint16_t IRTecoAc::getTimer(void) const { + uint16_t mins = 0; + if (getTimerEnabled()) { + mins = (_.TensHours * 10 + _.UnitHours) * 60; + if (_.HalfHour) mins += 30; + } + return mins; +} + +/// Set the timer for when the A/C unit will switch power state. +/// @param[in] nr_mins Number of minutes before power state change. +/// `0` will clear the timer. Max is 24 hrs. +/// @note Time is stored internally in increments of 30 mins. +void IRTecoAc::setTimer(const uint16_t nr_mins) { + uint16_t mins = ::min(nr_mins, (uint16_t)(24 * 60)); // Limit to 24 hrs. + uint8_t hours = mins / 60; + _.TimerOn = mins > 0; // Set the timer flag. + _.HalfHour = (mins % 60) >= 30; + _.UnitHours = hours % 10; + _.TensHours = hours / 10; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTecoAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kTecoCool; + case stdAc::opmode_t::kHeat: return kTecoHeat; + case stdAc::opmode_t::kDry: return kTecoDry; + case stdAc::opmode_t::kFan: return kTecoFan; + default: return kTecoAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTecoAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kTecoFanLow; + case stdAc::fanspeed_t::kMedium: return kTecoFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kTecoFanHigh; + default: return kTecoFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRTecoAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kTecoCool: return stdAc::opmode_t::kCool; + case kTecoHeat: return stdAc::opmode_t::kHeat; + case kTecoDry: return stdAc::opmode_t::kDry; + case kTecoFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRTecoAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kTecoFanHigh: return stdAc::fanspeed_t::kMax; + case kTecoFanMed: return stdAc::fanspeed_t::kMedium; + case kTecoFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRTecoAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::TECO; + result.model = -1; // Not supported. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.Swing ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.sleep = _.Sleep ? 0 : -1; + result.light = _.Light; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRTecoAc::toString(void) const { + String result = ""; + result.reserve(100); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kTecoAuto, kTecoCool, kTecoHeat, + kTecoDry, kTecoFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kTecoFanHigh, kTecoFanLow, + kTecoFanAuto, kTecoFanAuto, kTecoFanMed); + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(_.Swing, kSwingStr); + result += addBoolToString(_.Light, kLightStr); + result += addBoolToString(_.Humid, kHumidStr); + result += addBoolToString(_.Save, kSaveStr); + if (getTimerEnabled()) + result += addLabeledString(irutils::minsToString(getTimer()), + kTimerStr); + else + result += addBoolToString(false, kTimerStr); + return result; +} + +#if DECODE_TECO +/// Decode the supplied Teco message. +/// Status: STABLE / Tested. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeTeco(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kTecoBits) return false; // Not what is expected + + uint64_t data = 0; + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kTecoHdrMark, kTecoHdrSpace, + kTecoBitMark, kTecoOneSpace, + kTecoBitMark, kTecoZeroSpace, + kTecoBitMark, kTecoGap, true, + _tolerance, kMarkExcess, false)) return false; + + // Success + results->decode_type = TECO; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_TECO diff --git a/src/libraries/IRremoteESP8266/src/ir_Teco.h b/src/libraries/IRremoteESP8266/src/ir_Teco.h new file mode 100644 index 000000000..17ee15c90 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Teco.h @@ -0,0 +1,131 @@ +// Copyright 2019 Fabien Valthier + +/// @file +/// @brief Support for Teco protocols. + +// Supports: +// Brand: Alaska, Model: SAC9010QC A/C +// Brand: Alaska, Model: SAC9010QC remote + +#ifndef IR_TECO_H_ +#define IR_TECO_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Teco A/C message. +union TecoProtocol{ + uint64_t raw; ///< The state of the IR remote in IR code form. + struct { + uint8_t Mode :3; + uint8_t Power :1; + uint8_t Fan :2; + uint8_t Swing :1; + uint8_t Sleep :1; + uint8_t Temp :4; + uint8_t HalfHour :1; + uint8_t TensHours :2; // number of 10hours + uint8_t TimerOn :1; + uint8_t UnitHours :4; // unit, not thenth + uint8_t Humid :1; + uint8_t Light :1; + uint8_t :1; // "Tree with bubbles" / Filter?? (Not Implemented) + uint8_t Save :1; + uint8_t :8; // Cst 0x50 + uint8_t :8; // Cst 0x02 + }; +}; + +// Constants. +const uint8_t kTecoAuto = 0; // temp = 25C +const uint8_t kTecoCool = 1; +const uint8_t kTecoDry = 2; // temp = 25C, but not shown +const uint8_t kTecoFan = 3; +const uint8_t kTecoHeat = 4; +const uint8_t kTecoFanAuto = 0; // 0b00 +const uint8_t kTecoFanLow = 1; // 0b01 +const uint8_t kTecoFanMed = 2; // 0b10 +const uint8_t kTecoFanHigh = 3; // 0b11 +const uint8_t kTecoMinTemp = 16; // 16C +const uint8_t kTecoMaxTemp = 30; // 30C + +const uint64_t kTecoReset = 0b01001010000000000000010000000000000; + +// Classes +/// Class for handling detailed Teco A/C messages. +class IRTecoAc { + public: + explicit IRTecoAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_TECO + void send(const uint16_t repeat = kTecoDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TECO + void begin(void); + void on(void); + void off(void); + + void setPower(const bool on); + bool getPower(void) const; + + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + + void setSwing(const bool on); + bool getSwing(void) const; + + void setSleep(const bool on); + bool getSleep(void) const; + + void setLight(const bool on); + bool getLight(void) const; + + void setHumid(const bool on); + bool getHumid(void) const; + + void setSave(const bool on); + bool getSave(void) const; + + uint16_t getTimer(void) const; + void setTimer(const uint16_t mins); + + uint64_t getRaw(void) const; + void setRaw(const uint64_t new_code); + + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + TecoProtocol _; + bool getTimerEnabled(void) const; +}; + +#endif // IR_TECO_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Teknopoint.cpp b/src/libraries/IRremoteESP8266/src/ir_Teknopoint.cpp new file mode 100644 index 000000000..4a5eef9be --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Teknopoint.cpp @@ -0,0 +1,81 @@ +// Copyright 2021 David Conran (crankyoldgit) +/// @file +/// @brief Support for the Teknopoint protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1486 + +// Supports: +// Brand: Teknopoint, Model: Allegro SSA-09H A/C +// Brand: Teknopoint, Model: GZ-055B-E1 remote +// Brand: Teknopoint, Model: GZ01-BEJ0-000 remote + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Protocol timings +const uint16_t kTeknopointHdrMark = 3600; +const uint16_t kTeknopointBitMark = 477; +const uint16_t kTeknopointHdrSpace = 1600; +const uint16_t kTeknopointOneSpace = 1200; +const uint16_t kTeknopointZeroSpace = 530; +const uint16_t kTeknopointFreq = 38000; // Hz. (Guess Only) +const uint8_t kTeknopointExtraTol = 10; // Extra tolerance percentage. + +#if SEND_TEKNOPOINT +/// Send a Teknopoint formatted message. +/// Status: BETA / Probably works. +/// @param[in] data An array of bytes containing the IR command. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendTeknopoint(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kTeknopointHdrMark, kTeknopointHdrSpace, + kTeknopointBitMark, kTeknopointOneSpace, + kTeknopointBitMark, kTeknopointZeroSpace, + kTeknopointBitMark, kDefaultMessageGap, + data, nbytes, // Bytes + kTeknopointFreq, false, repeat, kDutyDefault); +} +#endif // SEND_TEKNOPOINT + +#if DECODE_TEKNOPOINT +/// Decode the supplied Teknopoint message. +/// Status: Alpha / Probably works. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeTeknopoint(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 - offset) + return false; // Too short a message to match. + if (strict && nbits != kTeknopointBits) + return false; + + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kTeknopointHdrMark, kTeknopointHdrSpace, + kTeknopointBitMark, kTeknopointOneSpace, + kTeknopointBitMark, kTeknopointZeroSpace, + kTeknopointBitMark, kDefaultMessageGap, + true, _tolerance + kTeknopointExtraTol, + kMarkExcess, false)) return false; + // Compliance + if (strict) { + // Is the checksum valid? + if (sumBytes(results->state, kTeknopointStateLength - 1) != + results->state[kTeknopointStateLength - 1]) return false; + } + // Success + results->decode_type = decode_type_t::TEKNOPOINT; + results->bits = nbits; + return true; +} +#endif // DECODE_TEKNOPOINT + +// Looking for the IRTeknopoint/IRTeknopointAc class? +// It doesn't exist, it is instead part of the `IRTcl112Ac` class. +// i.e. use `IRTcl112Ac::setModel(tcl_ac_remote_model_t::GZ055BE1);` for +// Teknopoint A/Cs. diff --git a/src/libraries/IRremoteESP8266/src/ir_Toshiba.cpp b/src/libraries/IRremoteESP8266/src/ir_Toshiba.cpp new file mode 100644 index 000000000..1430cec24 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Toshiba.cpp @@ -0,0 +1,544 @@ +// Copyright 2017-2021 David Conran + +/// @file +/// @brief Support for Toshiba protocols. +/// @see https://github.com/r45635/HVAC-IR-Control +/// @see https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266T.ino#L77 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1205 +/// @see https://www.toshiba-carrier.co.jp/global/about/index.htm +/// @see http://www.toshiba-carrier.co.th/AboutUs/Pages/CompanyProfile.aspx + +#include "ir_Toshiba.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "String.h" +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using arduino::String; +// Constants + +// Toshiba A/C +const uint16_t kToshibaAcHdrMark = 4400; +const uint16_t kToshibaAcHdrSpace = 4300; +const uint16_t kToshibaAcBitMark = 580; +const uint16_t kToshibaAcOneSpace = 1600; +const uint16_t kToshibaAcZeroSpace = 490; +// Some models have a different inter-message gap. +// See: https://github.com/crankyoldgit/IRremoteESP8266/issues/1420 +const uint16_t kToshibaAcMinGap = 4600; // WH-UB03NJ remote +const uint16_t kToshibaAcUsualGap = 7400; // Others + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::checkInvertedBytePairs; +using irutils::invertBytePairs; + +#if SEND_TOSHIBA_AC +/// Send a Toshiba A/C message. +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendToshibaAC(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kToshibaAcHdrMark, kToshibaAcHdrSpace, kToshibaAcBitMark, + kToshibaAcOneSpace, kToshibaAcBitMark, kToshibaAcZeroSpace, + kToshibaAcBitMark, kToshibaAcUsualGap, data, nbytes, 38, true, + repeat, 50); +} +#endif // SEND_TOSHIBA_AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRToshibaAC::IRToshibaAC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +/// @see https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266T.ino#L103 +void IRToshibaAC::stateReset(void) { + static const uint8_t kReset[kToshibaACStateLength] = { + 0xF2, 0x0D, 0x03, 0xFC, 0x01}; + memcpy(_.raw, kReset, kToshibaACStateLength); + setTemp(22); // Remote defaults to 22C after factory reset. So do the same. + setSwing(kToshibaAcSwingOff); + _prev_mode = getMode(); +} + +/// Set up hardware to be able to send a message. +void IRToshibaAC::begin(void) { _irsend.begin(); } + +#if SEND_TOSHIBA_AC +/// Send the current internal state as IR messages. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRToshibaAC::send(const uint16_t repeat) { + _backupState(); + _irsend.sendToshibaAC(getRaw(), getStateLength(), repeat); + if (_send_swing && (getStateLength() != kToshibaACStateLengthShort)) { + setStateLength(kToshibaACStateLengthShort); + // Swing settings expect the min temp to be set. + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1205#issuecomment-653922374 + setTemp(kToshibaAcMinTemp); + setSwing(_swing_mode); + _irsend.sendToshibaAC(getRaw(), getStateLength(), repeat); + _restoreState(); + } + _send_swing = false; +} +#endif // SEND_TOSHIBA_AC + +/// Get the length of the supplied Toshiba state per it's protocol structure. +/// @param[in] state The array to get the built-in length from. +/// @param[in] size The physical size of the state array. +/// @return Nr. of bytes in use for the provided state message. +uint16_t IRToshibaAC::getInternalStateLength(const uint8_t state[], + const uint16_t size) { + if (size < kToshibaAcLengthByte) return 0; + return ::min((uint16_t)(state[kToshibaAcLengthByte] + kToshibaAcMinLength), + kToshibaACStateLengthLong); +} + +/// Get the length of the current internal state per the protocol structure. +/// @return Nr. of bytes in use for the current internal state message. +uint16_t IRToshibaAC::getStateLength(void) const { + return getInternalStateLength(_.raw, kToshibaACStateLengthLong); +} + +/// Set the internal length of the current internal state per the protocol. +/// @param[in] size Nr. of bytes in use for the current internal state message. +void IRToshibaAC::setStateLength(const uint16_t size) { + if (size < kToshibaAcMinLength) return; + _.Length = size - kToshibaAcMinLength; +} + +/// Make a copy of the internal code-form A/C state. +void IRToshibaAC::_backupState(void) { + memcpy(backup, _.raw, kToshibaACStateLengthLong); +} + +/// Recover the internal code-form A/C state from the backup. +void IRToshibaAC::_restoreState(void) { + memcpy(_.raw, backup, kToshibaACStateLengthLong); +} + +/// Get a PTR to the internal state/code for this protocol with all integrity +/// checks passing. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRToshibaAC::getRaw(void) { + checksum(getStateLength()); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +/// @param[in] length The length/size of the array. +void IRToshibaAC::setRaw(const uint8_t newState[], const uint16_t length) { + memcpy(_.raw, newState, length); + _prev_mode = getMode(); + _send_swing = true; +} + +/// Calculate the checksum for a given state. +/// @param[in] state The array to calc the checksum of. +/// @param[in] length The length/size of the array. +/// @return The calculated checksum value. +uint8_t IRToshibaAC::calcChecksum(const uint8_t state[], + const uint16_t length) { + return length ? xorBytes(state, length - 1) : 0; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRToshibaAC::validChecksum(const uint8_t state[], const uint16_t length) { + return length >= kToshibaAcMinLength && + state[length - 1] == IRToshibaAC::calcChecksum(state, length) && + checkInvertedBytePairs(state, kToshibaAcInvertedLength) && + IRToshibaAC::getInternalStateLength(state, length) == length; +} + +/// Calculate & set the checksum for the current internal state of the remote. +/// @param[in] length The length/size of the internal array to checksum. +void IRToshibaAC::checksum(const uint16_t length) { + // Stored the checksum value in the last byte. + if (length >= kToshibaAcMinLength) { + // Set/clear the short msg bit. + _.ShortMsg = (getStateLength() == kToshibaACStateLengthShort); + // Set/clear the long msg bit. + _.LongMsg = (getStateLength() == kToshibaACStateLengthLong); + invertBytePairs(_.raw, kToshibaAcInvertedLength); + // Always do the Xor checksum LAST! + _.raw[length - 1] = calcChecksum(_.raw, length); + } +} + +/// Set the requested power state of the A/C to on. +void IRToshibaAC::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRToshibaAC::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRToshibaAC::setPower(const bool on) { + if (on) { // On + // If not already on, pick the last non-off mode used + if (!getPower()) setMode(_prev_mode); + } else { // Off + setMode(kToshibaAcOff); + } +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRToshibaAC::getPower(void) const { + return getMode(true) != kToshibaAcOff; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRToshibaAC::setTemp(const uint8_t degrees) { + uint8_t temp = ::max(kToshibaAcMinTemp, degrees); + temp = ::min(kToshibaAcMaxTemp, temp); + _.Temp = temp - kToshibaAcMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRToshibaAC::getTemp(void) const { return _.Temp + kToshibaAcMinTemp; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting (0 is Auto, 1-5 is the speed, 5 is Max) +void IRToshibaAC::setFan(const uint8_t speed) { + uint8_t fan = speed; + // Bounds check + if (fan > kToshibaAcFanMax) + fan = kToshibaAcFanMax; // Set the fan to maximum if out of range. + if (fan > kToshibaAcFanAuto) fan++; + _.Fan = fan; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRToshibaAC::getFan(void) const { + uint8_t fan = _.Fan; + if (fan == kToshibaAcFanAuto) return kToshibaAcFanAuto; + return --fan; +} + +/// Get the swing setting of the A/C. +/// @param[in] raw Calculate the answer from just the state data. +/// @return The current swing mode setting. +uint8_t IRToshibaAC::getSwing(const bool raw) const { + return raw ? _.Swing : _swing_mode; +} + +/// Set the swing setting of the A/C. +/// @param[in] setting The value of the desired setting. +void IRToshibaAC::setSwing(const uint8_t setting) { + switch (setting) { + case kToshibaAcSwingStep: + case kToshibaAcSwingOn: + case kToshibaAcSwingOff: + case kToshibaAcSwingToggle: + _send_swing = true; + _swing_mode = setting; + if (getStateLength() == kToshibaACStateLengthShort) + _.Swing = setting; + } +} + +/// Get the operating mode setting of the A/C. +/// @param[in] raw Get the value without any intelligent processing. +/// @return The current operating mode setting. +uint8_t IRToshibaAC::getMode(const bool raw) const { + const uint8_t mode = _.Mode; + if (raw) return mode; + switch (mode) { + case kToshibaAcOff: return _prev_mode; + default: return mode; + } +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1205#issuecomment-654446771 +void IRToshibaAC::setMode(const uint8_t mode) { + if (mode != _prev_mode) + // Changing mode or power turns Econo & Turbo to off on a real remote. + // Setting the internal message length to "normal" will do that. + setStateLength(kToshibaACStateLength); + switch (mode) { + case kToshibaAcAuto: + case kToshibaAcCool: + case kToshibaAcDry: + case kToshibaAcHeat: + case kToshibaAcFan: + _prev_mode = mode; + // FALL-THRU + case kToshibaAcOff: + _.Mode = mode; + break; + default: + _prev_mode = kToshibaAcAuto; + _.Mode = kToshibaAcAuto; + } +} + +/// Get the Turbo (Powerful) setting of the A/C. +/// @return true, if the current setting is on. Otherwise, false. +bool IRToshibaAC::getTurbo(void) const { + if (getStateLength() == kToshibaACStateLengthLong) + return _.EcoTurbo == kToshibaAcTurboOn; + return false; +} + +/// Set the Turbo (Powerful) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// Note: Turbo mode is mutually exclusive with Economy mode. +void IRToshibaAC::setTurbo(const bool on) { + if (on) { + _.EcoTurbo = kToshibaAcTurboOn; + setStateLength(kToshibaACStateLengthLong); + } else { + if (!getEcono()) setStateLength(kToshibaACStateLength); + } +} + +/// Get the Economy mode setting of the A/C. +/// @return true, if the current setting is on. Otherwise, false. +bool IRToshibaAC::getEcono(void) const { + if (getStateLength() == kToshibaACStateLengthLong) + return _.EcoTurbo == kToshibaAcEconoOn; + return false; +} + +/// Set the Economy mode setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// Note: Economy mode is mutually exclusive with Turbo mode. +void IRToshibaAC::setEcono(const bool on) { + if (on) { + _.EcoTurbo = kToshibaAcEconoOn; + setStateLength(kToshibaACStateLengthLong); + } else { + if (!getTurbo()) setStateLength(kToshibaACStateLength); + } +} + +/// Get the filter (Pure/Ion Filter) setting of the A/C. +/// @return true, if the current setting is on. Otherwise, false. +bool IRToshibaAC::getFilter(void) const { + return (getStateLength() >= kToshibaACStateLength) ? _.Filter : false; +} + +/// Set the filter (Pure/Ion Filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRToshibaAC::setFilter(const bool on) { + _.Filter = on; + if (on) setStateLength(::min(kToshibaACStateLength, getStateLength())); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRToshibaAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kToshibaAcCool; + case stdAc::opmode_t::kHeat: return kToshibaAcHeat; + case stdAc::opmode_t::kDry: return kToshibaAcDry; + case stdAc::opmode_t::kFan: return kToshibaAcFan; + case stdAc::opmode_t::kOff: return kToshibaAcOff; + default: return kToshibaAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRToshibaAC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kToshibaAcFanMax - 4; + case stdAc::fanspeed_t::kLow: return kToshibaAcFanMax - 3; + case stdAc::fanspeed_t::kMedium: return kToshibaAcFanMax - 2; + case stdAc::fanspeed_t::kHigh: return kToshibaAcFanMax - 1; + case stdAc::fanspeed_t::kMax: return kToshibaAcFanMax; + default: return kToshibaAcFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRToshibaAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kToshibaAcCool: return stdAc::opmode_t::kCool; + case kToshibaAcHeat: return stdAc::opmode_t::kHeat; + case kToshibaAcDry: return stdAc::opmode_t::kDry; + case kToshibaAcFan: return stdAc::opmode_t::kFan; + case kToshibaAcOff: return stdAc::opmode_t::kOff; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRToshibaAC::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kToshibaAcFanMax: return stdAc::fanspeed_t::kMax; + case kToshibaAcFanMax - 1: return stdAc::fanspeed_t::kHigh; + case kToshibaAcFanMax - 2: return stdAc::fanspeed_t::kMedium; + case kToshibaAcFanMax - 3: return stdAc::fanspeed_t::kLow; + case kToshibaAcFanMax - 4: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRToshibaAC::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.swingv = stdAc::swingv_t::kOff; + } + result.protocol = decode_type_t::TOSHIBA_AC; + result.model = -1; // Not supported. + // Do we have enough current state info to override any previous state? + // i.e. Was the class just setRaw()'ed with a short "swing" message. + // This should enables us to also ignore the Swing msg's special 17C setting. + if (getStateLength() != kToshibaACStateLengthShort) { + result.power = getPower(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.turbo = getTurbo(); + result.econo = getEcono(); + result.filter = getFilter(); + } + switch (getSwing()) { + case kToshibaAcSwingOn: + result.swingv = stdAc::swingv_t::kAuto; + break; + case kToshibaAcSwingToggle: + if (prev->swingv != stdAc::swingv_t::kOff) + result.swingv = stdAc::swingv_t::kOff; + else + result.swingv = stdAc::swingv_t::kAuto; + break; + default: result.swingv = stdAc::swingv_t::kOff; + } + // Not supported. + result.light = false; + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRToshibaAC::toString(void) const { + String result = ""; + result.reserve(95); + result += addTempToString(getTemp(), true, false); + switch (getStateLength()) { + case kToshibaACStateLengthShort: + result += addIntToString(getSwing(true), kSwingVStr); + result += kSpaceLBraceStr; + switch (getSwing(true)) { + case kToshibaAcSwingOff: result += kOffStr; break; + case kToshibaAcSwingOn: result += kOnStr; break; + case kToshibaAcSwingStep: result += kStepStr; break; + case kToshibaAcSwingToggle: result += kToggleStr; break; + default: result += kUnknownStr; + } + result += ')'; + break; + case kToshibaACStateLengthLong: + case kToshibaACStateLength: + default: + result += addBoolToString(getPower(), kPowerStr); + if (getPower()) + result += addModeToString(getMode(), kToshibaAcAuto, kToshibaAcCool, + kToshibaAcHeat, kToshibaAcDry, kToshibaAcFan); + result += addFanToString(getFan(), kToshibaAcFanMax, kToshibaAcFanMin, + kToshibaAcFanAuto, kToshibaAcFanAuto, + kToshibaAcFanMed); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getEcono(), kEconoStr); + result += addBoolToString(getFilter(), kFilterStr); + } + return result; +} + +#if DECODE_TOSHIBA_AC +/// Decode the supplied Toshiba A/C message. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeToshibaAC(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // Compliance + if (strict) { + switch (nbits) { // Must be called with the correct nr. of bits. + case kToshibaACBits: + case kToshibaACBitsShort: + case kToshibaACBitsLong: + break; + default: + return false; + } + } + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kToshibaAcHdrMark, kToshibaAcHdrSpace, + kToshibaAcBitMark, kToshibaAcOneSpace, + kToshibaAcBitMark, kToshibaAcZeroSpace, + kToshibaAcBitMark, kToshibaAcMinGap, true, + _tolerance, kMarkExcess)) return false; + // Compliance + if (strict) { + // Check that the checksum of the message is correct. + if (!IRToshibaAC::validChecksum(results->state, nbits / 8)) return false; + } + + // Success + results->decode_type = TOSHIBA_AC; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_TOSHIBA_AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Toshiba.h b/src/libraries/IRremoteESP8266/src/ir_Toshiba.h new file mode 100644 index 000000000..a3c0033dd --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Toshiba.h @@ -0,0 +1,195 @@ +// Copyright 2017 David Conran + +/// @file +/// @brief Support for Toshiba protocols. +/// @see https://github.com/r45635/HVAC-IR-Control +/// @see https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266T.ino#L77 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1205 +/// @see https://docs.google.com/spreadsheets/d/1yidE2fvaO9kpCHfKafIdH31q4uaskYR1OwwrkyOxbp0/edit?usp=drivesdk +/// @see https://www.toshiba-carrier.co.jp/global/about/index.htm +/// @see http://www.toshiba-carrier.co.th/AboutUs/Pages/CompanyProfile.aspx +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1692 + +// Supports: +// Brand: Toshiba, Model: RAS-B13N3KV2 +// Brand: Toshiba, Model: Akita EVO II +// Brand: Toshiba, Model: RAS-B13N3KVP-E +// Brand: Toshiba, Model: RAS 18SKP-ES +// Brand: Toshiba, Model: WH-TA04NE +// Brand: Toshiba, Model: WC-L03SE +// Brand: Toshiba, Model: WH-UB03NJ remote +// Brand: Toshiba, Model: RAS-2558V A/C +// Brand: Toshiba, Model: WH-TA01JE remote +// Brand: Toshiba, Model: RAS-25SKVP2-ND A/C +// Brand: Carrier, Model: 42NQV060M2 / 38NYV060M2 A/C +// Brand: Carrier, Model: 42NQV050M2 / 38NYV050M2 A/C +// Brand: Carrier, Model: 42NQV035M2 / 38NYV035M2 A/C +// Brand: Carrier, Model: 42NQV025M2 / 38NYV025M2 A/C + +#ifndef IR_TOSHIBA_H_ +#define IR_TOSHIBA_H_ + +#define __STDC_LIMIT_MACROS +#include +//#ifdef ARDUINO +#include "String.h" +//#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Toshiba A/C message. +union ToshibaProtocol{ + uint8_t raw[kToshibaACStateLengthLong]; ///< The state in code form. + struct { + // Byte[0] - 0xF2 + uint8_t :8; + // Byte[1] - 0x0D (inverted previous byte's value) + uint8_t :8; + // Byte[2] - The expected payload length (in bytes) past the Byte[4]. + ///< Known lengths are: + ///< 1 (56 bit message) + ///< 3 (72 bit message) + ///< 4 (80 bit message) + uint8_t Length :8; + // Byte[3] - The bit-inverted value of the "length" byte. + uint8_t :8; + // Byte[4] + uint8_t :3; + uint8_t LongMsg :1; + uint8_t :1; + uint8_t ShortMsg :1; + uint8_t :2; + // Byte[5] + uint8_t Swing :3; + uint8_t :1; + uint8_t Temp :4; + // Byte[6] + uint8_t Mode :3; + uint8_t :2; + uint8_t Fan :3; + // Byte[7] + uint8_t :4; + uint8_t Filter :1; + uint8_t :3; + + // Byte[8] + // (Checksum for 72 bit messages, Eco/Turbo for long 80 bit messages) + uint8_t EcoTurbo :8; + }; +}; + +// Constants + +const uint8_t kToshibaAcLengthByte = 2; ///< Byte pos of the "length" attribute +const uint8_t kToshibaAcMinLength = 6; ///< Min Nr. of bytes in a message. + +const uint16_t kToshibaAcInvertedLength = 4; ///< Nr. of leading bytes in + ///< inverted pairs. + +const uint8_t kToshibaAcSwingStep = 0; ///< 0b000 +const uint8_t kToshibaAcSwingOn = 1; ///< 0b001 +const uint8_t kToshibaAcSwingOff = 2; ///< 0b010 +const uint8_t kToshibaAcSwingToggle = 4; ///< 0b100 + +const uint8_t kToshibaAcMinTemp = 17; ///< 17C +const uint8_t kToshibaAcMaxTemp = 30; ///< 30C + +const uint8_t kToshibaAcAuto = 0; // 0b000 +const uint8_t kToshibaAcCool = 1; // 0b001 +const uint8_t kToshibaAcDry = 2; // 0b010 +const uint8_t kToshibaAcHeat = 3; // 0b011 +const uint8_t kToshibaAcFan = 4; // 0b100 +const uint8_t kToshibaAcOff = 7; // 0b111 +const uint8_t kToshibaAcFanAuto = 0; // 0b000 +const uint8_t kToshibaAcFanMin = 1; // 0b001 +const uint8_t kToshibaAcFanMed = 3; // 0b011 +const uint8_t kToshibaAcFanMax = 5; // 0b101 + +const uint8_t kToshibaAcTurboOn = 1; // 0b01 +const uint8_t kToshibaAcEconoOn = 3; // 0b11 + +// Legacy defines. (Deprecated) +#define TOSHIBA_AC_AUTO kToshibaAcAuto +#define TOSHIBA_AC_COOL kToshibaAcCool +#define TOSHIBA_AC_DRY kToshibaAcDry +#define TOSHIBA_AC_HEAT kToshibaAcHeat +#define TOSHIBA_AC_POWER kToshibaAcPower +#define TOSHIBA_AC_FAN_AUTO kToshibaAcFanAuto +#define TOSHIBA_AC_FAN_MAX kToshibaAcFanMax +#define TOSHIBA_AC_MIN_TEMP kToshibaAcMinTemp +#define TOSHIBA_AC_MAX_TEMP kToshibaAcMaxTemp + +// Classes +/// Class for handling detailed Toshiba A/C messages. +class IRToshibaAC { + public: + explicit IRToshibaAC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_TOSHIBA_AC + void send(const uint16_t repeat = kToshibaACMinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TOSHIBA_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setFilter(const bool on); + bool getFilter(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(const bool raw = false) const; + void setRaw(const uint8_t newState[], + const uint16_t length = kToshibaACStateLength); + uint8_t* getRaw(void); + static uint16_t getInternalStateLength(const uint8_t state[], + const uint16_t size); + uint16_t getStateLength(void) const; + static bool validChecksum(const uint8_t state[], + const uint16_t length = kToshibaACStateLength); + uint8_t getSwing(const bool raw = true) const; + void setSwing(const uint8_t setting); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + arduino::String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + ToshibaProtocol _; + uint8_t backup[kToshibaACStateLengthLong]; ///< A backup copy of the state. + uint8_t _prev_mode; ///< Store of the previously set mode. + bool _send_swing; ///< Flag indicating if we need to send a swing message. + uint8_t _swing_mode; ///< The saved swing state/mode/command. + void checksum(const uint16_t length = kToshibaACStateLength); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kToshibaACStateLength); + void setStateLength(const uint16_t size); + void _backupState(void); + void _restoreState(void); +}; + +#endif // IR_TOSHIBA_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Toto.cpp b/src/libraries/IRremoteESP8266/src/ir_Toto.cpp new file mode 100644 index 000000000..d00a26929 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Toto.cpp @@ -0,0 +1,131 @@ +// Copyright 2022 David Conran (crankyoldgit) +/// @file +/// @brief Support for the Toto Toilet IR protocols. +/// @see https://www.d-resi.jp/dt/nishi-shinjuku/limited/imgs/pdf/book6.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 + +// Supports: +// Brand: Toto, Model: Washlet Toilet NJ + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kTotoHdrMark = 6197; +const uint16_t kTotoHdrSpace = 2754; +const uint16_t kTotoBitMark = 600; +const uint16_t kTotoOneSpace = 1634; +const uint16_t kTotoZeroSpace = 516; +const uint16_t kTotoGap = 38000; +const uint16_t kTotoSpecialGap = 42482; +const uint64_t kTotoPrefix = 0x0802; +const uint16_t kTotoPrefixBits = 15; + +#if SEND_TOTO +/// Send a Toto Toilet formatted message. +/// Status: BETA / Seems to work. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 +void IRsend::sendToto(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + if (nbits <= kTotoShortBits) { // Short code message. + sendGeneric(kTotoHdrMark, kTotoHdrSpace, + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + kTotoBitMark, kTotoGap, + (data << kTotoPrefixBits) | kTotoPrefix, + nbits + kTotoPrefixBits, + 38, false, repeat, kDutyDefault); + } else { // Long code message + // This is really two messages sent at least one extra time each. + sendToto(data >> kTotoShortBits, nbits - kTotoShortBits, repeat + 1); + sendToto(GETBITS64(data, 0, kTotoShortBits), kTotoShortBits, repeat + 1); + } +} +#endif // SEND_TOTO + +#if DECODE_TOTO +/// Decode the supplied Toto Toilet message. +/// Status: ALPHA / Untested. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 +bool IRrecv::decodeToto(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kTotoShortBits && nbits != kTotoLongBits) + return false; // We expect Toto to be a certain sized messages. + + const uint16_t repeats = (nbits > kTotoShortBits) ? kTotoDefaultRepeat + 1 + : kTotoDefaultRepeat; + const uint16_t ksections = (nbits > kTotoShortBits) ? 2 : 1; + const uint16_t ksection_bits = nbits / ksections; + + if (results->rawlen < (2 * (nbits + kTotoPrefixBits) + kHeader + kFooter) * + (repeats + 1) - 1 + offset) + return false; // We don't have enough entries to possibly match. + + uint16_t used = 0; + + // Long messages have two sections, short have only one. + for (uint16_t section = 1; section <= ksections; section++) { + results->value <<= ksection_bits; + uint64_t data = 0; + uint64_t repeat_data = 0; + for (uint16_t r = 0; r <= repeats; r++) { // We expect a repeat. + uint64_t prefix = 0; + // Header + Prefix + used = matchGeneric(results->rawbuf + offset, &prefix, + results->rawlen - offset, kTotoPrefixBits, + kTotoHdrMark, kTotoHdrSpace, + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + 0, 0, false, // No Footer + kUseDefTol, kMarkExcess, false); + if (!used) return false; // Didn't match, so fail. + offset += used; + if (strict && (prefix != kTotoPrefix)) return false; + // Data + Footer + Gap + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, ksection_bits, + 0, 0, // No Header + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + kTotoBitMark, kTotoGap, true, + kUseDefTol, kMarkExcess, false); + if (!used) return false; // Didn't match, so fail. + offset += used; + if (strict) { + if (r && data != repeat_data) return false; // Repeat didn't match. + // Integrity check. + uint8_t result = 0; + uint64_t check = data; + uint16_t bits = 0; + // Loop over the data 8 bits at a time. + while (bits < ksection_bits) { + result ^= (check & 0b111111111); // Xor with the last 8 bits. + bits += 8; + check >>= 8; + } + if (result) return false; // Intergrity check failed. + } + repeat_data = data; + } + results->value |= data; + } + // Success + results->bits = nbits; + results->decode_type = decode_type_t::TOTO; + results->command = GETBITS64(results->value, 0, ksection_bits - 8); + results->address = GETBITS64(results->value, ksection_bits, + ksection_bits - 8); + return true; +} +#endif // DECODE_TOTO diff --git a/src/libraries/IRremoteESP8266/src/ir_Transcold.cpp b/src/libraries/IRremoteESP8266/src/ir_Transcold.cpp new file mode 100644 index 000000000..e60ee4b78 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Transcold.cpp @@ -0,0 +1,501 @@ +// Copyright 2020 Chandrashekar Shetty (iamDshetty) +// Copyright 2020 crankyoldgit +// Copyright 2021 siriuslzx + +/// @file +/// @brief Support for Transcold A/C protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1256 + +#include "ir_Transcold.h" +// #include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants + +const uint16_t kTranscoldHdrMark = 5944; ///< uSeconds. +const uint16_t kTranscoldBitMark = 555; ///< uSeconds. +const uint16_t kTranscoldHdrSpace = 7563; ///< uSeconds. +const uint16_t kTranscoldOneSpace = 3556; ///< uSeconds. +const uint16_t kTranscoldZeroSpace = 1526; ///< uSeconds. + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::addToggleToString; + +#if SEND_TRANSCOLD +/// Send a Transcold message +/// Status: STABLE / Confirmed Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendTranscold(uint64_t data, uint16_t nbits, uint16_t repeat) { + if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8. + + // Set IR carrier frequency + enableIROut(38); + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kTranscoldHdrMark); + space(kTranscoldHdrSpace); + // Data + // Break data into byte segments, starting at the Most Significant + // Byte. Each byte then being sent normal, then followed inverted. + for (uint16_t i = 8; i <= nbits; i += 8) { + // Grab a bytes worth of data. + // uint8_t segment = (data >> (nbits - i)) & 0xFF; + uint8_t segment = GETBITS64(data, nbits - i, 8); + // Normal + Inverted + uint16_t both = (segment << 8) | (~segment & 0xFF); + sendData(kTranscoldBitMark, kTranscoldOneSpace, kTranscoldBitMark, + kTranscoldZeroSpace, both, 16, true); + } + // Footer + mark(kTranscoldBitMark); + space(kTranscoldHdrSpace); + mark(kTranscoldBitMark); + space(kDefaultMessageGap); + } +} +#endif // SEND_TRANSCOLD + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRTranscoldAc::IRTranscoldAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IRTranscoldAc::stateReset(void) { + setRaw(kTranscoldKnownGoodState); + special_state = kTranscoldOff; + swingFlag = false; + swingHFlag = false; + swingVFlag = false; +} + +/// Set up hardware to be able to send a message. +void IRTranscoldAc::begin(void) { _irsend.begin(); } + +#if SEND_TRANSCOLD +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRTranscoldAc::send(uint16_t repeat) { + _irsend.sendTranscold(getRaw(), kTranscoldBits, repeat); + if (isSpecialState()) { + // make sure to remove special state from special_state + // after command has being transmitted. + special_state = kTranscoldKnownGoodState; + } +} +#endif // SEND_TRANSCOLD + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +uint32_t IRTranscoldAc::getRaw(void) const { + if (isSpecialState()) { + return special_state; + } + return _.raw; + } + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRTranscoldAc::setRaw(const uint32_t new_code) { + if (handleSpecialState(new_code)) { + special_state = new_code; + _.raw = kTranscoldKnownGoodState; + } else { + // must be a command changing Temp|Mode|Fan + // it is safe to just copy to remote var + _.raw = new_code; + special_state = kTranscoldKnownGoodState; + // it isn`t special so might affect Temp|mode|Fan + if (new_code == kTranscoldCmdFan) { + setMode(kTranscoldFan); + } + } +} + +/// Is the current state is a special state? +/// @return true, if it is. false if it isn't. +bool IRTranscoldAc::isSpecialState(void) const { + switch (special_state) { + case kTranscoldOff: + case kTranscoldSwing: return true; + default: return false; + } +} + +/// Adjust any internal settings based on the type of special state we are +/// supplied. Does nothing if it isn't a special state. +/// @param[in] data The state we need to act upon. +/// @note Special state means commands that are not affecting +/// Temperature/Mode/Fan +/// @return true, if it is a special state. false if it isn't. +bool IRTranscoldAc::handleSpecialState(const uint32_t data) { + switch (data) { + case kTranscoldOff: + break; + case kTranscoldSwing: + swingFlag = !swingFlag; + break; + default: + return false; + } + return true; +} + +/// Set the temperature. +/// @param[in] desired The temperature in degrees celsius. +void IRTranscoldAc::setTemp(const uint8_t desired) { + // Range check. + uint8_t temp = ::min(desired, kTranscoldTempMax); + temp = ::max(temp, kTranscoldTempMin) - kTranscoldTempMin + 1; + _.Temp = reverseBits(invertBits(temp, kTranscoldTempSize), + kTranscoldTempSize); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRTranscoldAc::getTemp(void) const { + return reverseBits(invertBits(_.Temp, kTranscoldTempSize), + kTranscoldTempSize) + kTranscoldTempMin - 1; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTranscoldAc::getPower(void) const { + // There is only an off state. Everything else is "on". + return special_state != kTranscoldOff; +} + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTranscoldAc::setPower(const bool on) { + if (!on) { + special_state = kTranscoldOff; + } else { + special_state = kTranscoldKnownGoodState; + } +} + +/// Change the power setting to On. +void IRTranscoldAc::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRTranscoldAc::off(void) { setPower(false); } + +/// Get the Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTranscoldAc::getSwing(void) const { return swingFlag; } + +/// Toggle the Swing mode of the A/C. +void IRTranscoldAc::setSwing(void) { + // Assumes that repeated sending "swing" toggles the action on the device. + // if not, the variable "swingFlag" can be removed. + special_state = kTranscoldSwing; + swingFlag = !swingFlag; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRTranscoldAc::setMode(const uint8_t mode) { + uint32_t actualmode = mode; + switch (actualmode) { + case kTranscoldAuto: + case kTranscoldDry: + _.Fan = kTranscoldFanAuto0; + break; + case kTranscoldCool: + case kTranscoldHeat: + case kTranscoldFan: + _.Fan = kTranscoldFanAuto; + break; + default: // Anything else, go with Auto mode. + actualmode = kTranscoldAuto; + _.Fan = kTranscoldFanAuto0; + } + setTemp(getTemp()); + // Fan mode is a special case of Dry. + if (actualmode == kTranscoldFan) { + actualmode = kTranscoldDry; + _.Temp = kTranscoldFanTempCode; + } + _.Mode = actualmode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRTranscoldAc::getMode(void) const { + uint8_t mode = _.Mode; + if (mode == kTranscoldDry) + if (_.Temp == kTranscoldFanTempCode) return kTranscoldFan; + return mode; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRTranscoldAc::getFan(void) const { + return _.Fan; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @param[in] modecheck Do we enforce any mode limitations before setting? +void IRTranscoldAc::setFan(const uint8_t speed, const bool modecheck) { + uint8_t newspeed = speed; + if (modecheck) { + switch (getMode()) { + case kTranscoldAuto: + case kTranscoldDry: // Dry & Auto mode can't have speed Auto. + if (speed == kTranscoldFanAuto) + newspeed = kTranscoldFanAuto0; + break; + default: // Only Dry & Auto mode can have speed Auto0. + if (speed == kTranscoldFanAuto0) + newspeed = kTranscoldFanAuto; + } + } + switch (speed) { + case kTranscoldFanAuto: + case kTranscoldFanAuto0: + case kTranscoldFanMin: + case kTranscoldFanMed: + case kTranscoldFanMax: + case kTranscoldFanZoneFollow: + case kTranscoldFanFixed: + break; + default: // Unknown speed requested. + newspeed = kTranscoldFanAuto; + break; + } + _.Fan = newspeed; +} + +/// Convert a standard A/C mode into its native mode. +/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent. +/// @return The corresponding native mode. +uint8_t IRTranscoldAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kTranscoldCool; + case stdAc::opmode_t::kHeat: return kTranscoldHeat; + case stdAc::opmode_t::kDry: return kTranscoldDry; + case stdAc::opmode_t::kFan: return kTranscoldFan; + default: return kTranscoldAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTranscoldAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kTranscoldFanMin; + case stdAc::fanspeed_t::kMedium: return kTranscoldFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kTranscoldFanMax; + default: return kTranscoldFanAuto; + } +} + +/// Convert a native mode to it's common stdAc::opmode_t equivalent. +/// @param[in] mode A native operation mode to be converted. +/// @return The corresponding common stdAc::opmode_t mode. +stdAc::opmode_t IRTranscoldAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kTranscoldCool: return stdAc::opmode_t::kCool; + case kTranscoldHeat: return stdAc::opmode_t::kHeat; + case kTranscoldDry: return stdAc::opmode_t::kDry; + case kTranscoldFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRTranscoldAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kTranscoldFanMax: return stdAc::fanspeed_t::kMax; + case kTranscoldFanMed: return stdAc::fanspeed_t::kMedium; + case kTranscoldFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the A/C state to it's common stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return A stdAc::state_t state. +stdAc::state_t IRTranscoldAc::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.swingv = stdAc::swingv_t::kOff; + } + // Not supported. + result.model = -1; // No models used. + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.clean = false; + result.light = false; + result.quiet = false; + result.econo = false; + result.filter = false; + result.beep = false; + result.clock = -1; + result.sleep = -1; + + // Supported. + result.protocol = decode_type_t::TRANSCOLD; + result.celsius = true; + result.power = getPower(); + // Power off state no other state info. Use the previous state if we have it. + if (!result.power) return result; + // Handle the special single command (Swing/Turbo/Light/Clean/Sleep) toggle + // messages. These have no other state info so use the rest of the previous + // state if we have it for them. + if (getSwing()) { + result.swingv = result.swingv != stdAc::swingv_t::kOff ? + stdAc::swingv_t::kOff : stdAc::swingv_t::kAuto; // Invert swing. + return result; + } + // Back to "normal" stateful messages. + result.mode = toCommonMode(getMode()); + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + return result; +} + +/// Convert the internal state into a human readable string. +/// @return The current internal state expressed as a human readable String. +String IRTranscoldAc::toString(void) const { + String result = ""; + result.reserve(100); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + if (!getPower()) return result; // If it's off, there is no other info. + // Special modes. + if (getSwing()) return result + addToggleToString(true, kSwingStr); + result += addModeToString(getMode(), kTranscoldAuto, kTranscoldCool, + kTranscoldHeat, kTranscoldDry, kTranscoldFan); + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + switch (_.Fan) { + case kTranscoldFanAuto: + result += kAutoStr; + break; + case kTranscoldFanAuto0: + result += kAutoStr; + result += '0'; + break; + case kTranscoldFanMax: + result += kMaxStr; + break; + case kTranscoldFanMin: + result += kMinStr; + break; + case kTranscoldFanMed: + result += kMedStr; + break; + case kTranscoldFanZoneFollow: + result += kZoneFollowStr; + break; + case kTranscoldFanFixed: + result += kFixedStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + // Fan mode doesn't have a temperature. + if (getMode() != kTranscoldFan) result += addTempToString(getTemp()); + return result; +} + +#if DECODE_TRANSCOLD +/// Decode the supplied Transcold A/C message. +/// Status: STABLE / Known Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeTranscold(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + // The protocol sends the data normal + inverted, alternating on + // each byte. Hence twice the number of expected data bits. + if (results->rawlen <= 2 * 2 * nbits + kHeader + kFooter - 1 + offset) + return false; + if (strict && nbits != kTranscoldBits) return false; + if (nbits % 8 != 0) return false; + + uint64_t data = 0; + uint64_t inverted = 0; + + if (nbits > sizeof(data) * 8) + return false; // We can't possibly capture a Transcold packet that big. + + // Header + if (!matchMark(results->rawbuf[offset++], kTranscoldHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kTranscoldHdrSpace)) return false; + + // Data + // Twice as many bits as there are normal plus inverted bits. + for (uint16_t i = 0; i < nbits * 2; i++, offset++) { + bool flip = (i / 8) % 2; + if (!matchMark(results->rawbuf[offset++], kTranscoldBitMark)) + return false; + if (matchSpace(results->rawbuf[offset], kTranscoldOneSpace)) { + if (flip) + inverted = (inverted << 1) | 1; + else + data = (data << 1) | 1; + } else if (matchSpace(results->rawbuf[offset], kTranscoldZeroSpace)) { + if (flip) + inverted <<= 1; + else + data <<= 1; + } else { + return false; + } + } + + // Footer + if (!matchMark(results->rawbuf[offset++], kTranscoldBitMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kTranscoldHdrSpace)) return false; + if (!matchMark(results->rawbuf[offset++], kTranscoldBitMark)) return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kDefaultMessageGap)) + return false; + + // Compliance + if (strict && inverted != invertBits(data, nbits)) return false; + + // Success + results->decode_type = decode_type_t::TRANSCOLD; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_TRANSCOLD diff --git a/src/libraries/IRremoteESP8266/src/ir_Transcold.h b/src/libraries/IRremoteESP8266/src/ir_Transcold.h new file mode 100644 index 000000000..5f897a5e6 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Transcold.h @@ -0,0 +1,174 @@ +// Copyright 2020 Chandrashekar Shetty (iamDshetty) +// Copyright 2020 crankyoldgit +// Copyright 2021 siriuslzx + +/// @file +/// @brief Support for Transcold A/C protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1256 +/// @see https://docs.google.com/spreadsheets/d/1qdoyB0FyJm85HPP9oXcfui0n4ztXBFlik6kiNlkO2IM/edit?usp=sharing + +// Supports: +// Brand: Transcold, Model: M1-F-NO-6 A/C + +/*************************************************************************************************************** + + Raw Data Calculation: (UR 12) +//ON button +ON 24 Auto cool close (right) 111011110001000001100001100111100101010010101011 + +//OFF button +OFF 24 Auto cool close (right) 111011110001000001110001100011100101010010101011 + +// MODE +Hot mode 24 auto hot close (right) 111010010001011010100001010111100101010010101011 +Fan mode 0 (prev24) low fan close (right) "11101001 0001011000100001110111100101010010101011" +Dry mode 24 low dry close (right) "11101001 0001011011000001 00111110 0101010010101011" +Auto Mode 0(prev24) low auto close (right) "11101001 0001011011100001 00011110 0101010010101011" +Cool Mode 24 low cool close (right) "11101001 0001011001100001 10011110 0101010010101011" + +//FAN SPEED +fan Speed low 24 low cool close (right) "11101001 0001011001100001 10011110 0101010010101011" +fan Speed medium 24 medium cool close (right) "11101101 000100100110000110011110 0101010010101011" +fan Speed high 24 high cool close (right) "11101011 000101000110000110011110 0101010010101011" +fan Speed auto 24 auto cool close (right) "11101111 000100000110000110011110 0101010010101011" + +//SWING +Swing open 24 auto cool open (left) "11110111 000010000110000110011110 0101010010101011" +Swing close 24 auto cool close (right) "11101111 000100000110000110011110 0101010010101011" + +//TEMPERATURE +temp 30degC Auto cool close (right) 111011110001000001100100100100010101010010101011 +temp 29 Auto cool close (right) 111011110001000001101100100100010101010010101011 +temp 28 Auto cool close (right) 111011110001000001100010100100010101010010101011 +temp 27 Auto cool close (right) 111011110001000001101010100100010101010010101011 +temp 26 Auto cool close (right) 111011110001000001100110100100010101010010101011 +temp 25 Auto cool close (right) 111011110001000001101110100100010101010010101011 +temp 24 Auto cool close (right) 111011110001000001100001100111100101010010101011 +temp 23 Auto cool close (right) 111011110001000001101001100101100101010010101011 +temp 22 Auto cool close (right) 111011110001000001100101100101100101010010101011 +temp 21 Auto cool close (right) 111011110001000001101101100101100101010010101011 +temp 20 Auto cool close (right) 111011110001000001100011100101100101010010101011 +temp 19 Auto cool close (right) 111011110001000001101011100101100101010010101011 +temp 18 Auto cool close (right) 111011110001000001100111100110000101010010101011 +temp 17 Auto cool close (right) 111011110001000001100111100110000101010010101011 +temp 16 Auto cool close (right) 111011110001000001100111100110000101010010101011 + + **************************************************************************************************************/ + +#ifndef IR_TRANSCOLD_H_ +#define IR_TRANSCOLD_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Transcold A/C message. +union TranscoldProtocol{ + uint32_t raw; ///< The state of the IR remote in IR code form. + struct { + uint8_t :8; + uint8_t Temp :4; + uint8_t Mode :4; + uint8_t Fan :4; + uint8_t :4; + uint8_t :8; + }; +}; + +// Constants +// Modes +const uint8_t kTranscoldCool = 0b0110; +const uint8_t kTranscoldDry = 0b1100; +const uint8_t kTranscoldAuto = 0b1110; +const uint8_t kTranscoldHeat = 0b1010; +const uint8_t kTranscoldFan = 0b0010; + +// Fan Control +const uint8_t kTranscoldFanMin = 0b1001; +const uint8_t kTranscoldFanMed = 0b1101; +const uint8_t kTranscoldFanMax = 0b1011; +const uint8_t kTranscoldFanAuto = 0b1111; +const uint8_t kTranscoldFanAuto0 = 0b0110; +const uint8_t kTranscoldFanZoneFollow = 0b0000; +const uint8_t kTranscoldFanFixed = 0b1100; + +// Temperature +const uint8_t kTranscoldTempMin = 18; // Celsius +const uint8_t kTranscoldTempMax = 30; // Celsius +const uint8_t kTranscoldFanTempCode = 0b1111; // Part of Fan Mode. +const uint8_t kTranscoldTempSize = 4; + +const uint8_t kTranscoldPrefix = 0b0000; +const uint8_t kTranscoldUnknown = 0xFF; +const uint32_t kTranscoldOff = 0b111011110111100101010100; +const uint32_t kTranscoldSwing = 0b111001110110000101010100; +const uint32_t kTranscoldSwingH = 0b111101110110000101010100; // NA +const uint32_t kTranscoldSwingV = 0b111001110110000101010100; // NA +const uint32_t kTranscoldCmdFan = 0b111011110110000101010100; // NA + +const uint32_t kTranscoldKnownGoodState = 0xE96554; + +// Classes +/// Class for handling detailed Transcold A/C messages. +class IRTranscoldAc { + public: + explicit IRTranscoldAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_TRANSCOLD + void send(const uint16_t repeat = kTranscoldDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TRANSCOLD + void begin(void); + void on(void); + void off(void); + void setPower(const bool state); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed, const bool modecheck = true); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwing(void); + bool getSwing(void) const; + uint32_t getRaw(void) const; + void setRaw(const uint32_t new_code); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + // internal state + bool swingFlag; + bool swingHFlag; + bool swingVFlag; + + TranscoldProtocol _; + uint32_t special_state; ///< special mode. + bool isSpecialState(void) const; + bool handleSpecialState(const uint32_t data); +}; + +#endif // IR_TRANSCOLD_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Trotec.cpp b/src/libraries/IRremoteESP8266/src/ir_Trotec.cpp new file mode 100644 index 000000000..95c249674 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Trotec.cpp @@ -0,0 +1,643 @@ +// Copyright 2017 stufisher +// Copyright 2019 crankyoldgit + +/// @file +/// @brief Support for Trotec protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/279 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1176 + +#include "ir_Trotec.h" +// #include +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kTrotecHdrMark = 5952; +const uint16_t kTrotecHdrSpace = 7364; +const uint16_t kTrotecBitMark = 592; +const uint16_t kTrotecOneSpace = 1560; +const uint16_t kTrotecZeroSpace = 592; +const uint16_t kTrotecGap = 6184; +const uint16_t kTrotecGapEnd = 1500; // made up value + +const uint16_t kTrotec3550HdrMark = 12000; +const uint16_t kTrotec3550HdrSpace = 5130; +const uint16_t kTrotec3550BitMark = 550; +const uint16_t kTrotec3550OneSpace = 1950; +const uint16_t kTrotec3550ZeroSpace = 500; + +const uint16_t kTrotec3550TimerMax = 8 * 60; ///< 8 hours in Minutes. + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::minsToString; + +#if SEND_TROTEC +/// Send a Trotec message. +/// Status: Beta / Probably Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendTrotec(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kTrotecStateLength) return; + + enableIROut(36); + for (uint16_t r = 0; r <= repeat; r++) { + sendGeneric(kTrotecHdrMark, kTrotecHdrSpace, kTrotecBitMark, + kTrotecOneSpace, kTrotecBitMark, kTrotecZeroSpace, + kTrotecBitMark, kTrotecGap, data, nbytes, 36, false, + 0, // Repeats handled elsewhere + 50); + // More footer + mark(kTrotecBitMark); + space(kTrotecGapEnd); + } +} +#endif // SEND_TROTEC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRTrotecESP::IRTrotecESP(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRTrotecESP::begin(void) { _irsend.begin(); } + +#if SEND_TROTEC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRTrotecESP::send(const uint16_t repeat) { + _irsend.sendTrotec(getRaw(), kTrotecStateLength, repeat); +} +#endif // SEND_TROTEC + +/// Calculate the checksum for a given state. +/// @param[in] state The array to calc the checksum of. +/// @param[in] length The length/size of the array. +/// @return The calculated checksum value. +uint8_t IRTrotecESP::calcChecksum(const uint8_t state[], + const uint16_t length) { + return sumBytes(state + 2, length - 3); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRTrotecESP::validChecksum(const uint8_t state[], const uint16_t length) { + return state[length - 1] == calcChecksum(state, length); +} + +/// Calculate & set the checksum for the current internal state of the remote. +void IRTrotecESP::checksum(void) { + _.Sum = sumBytes(_.raw + 2, kTrotecStateLength - 3); +} + +/// Reset the state of the remote to a known good state/sequence. +void IRTrotecESP::stateReset(void) { + for (uint8_t i = 2; i < kTrotecStateLength; i++) _.raw[i] = 0x0; + + _.Intro1 = kTrotecIntro1; + _.Intro2 = kTrotecIntro2; + + _.Power = false; + setTemp(kTrotecDefTemp); + _.Fan = kTrotecFanMed; + _.Mode = kTrotecAuto; +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRTrotecESP::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRTrotecESP::setRaw(const uint8_t state[]) { + memcpy(_.raw, state, kTrotecStateLength); +} + +/// Set the requested power state of the A/C to on. +void IRTrotecESP::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRTrotecESP::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTrotecESP::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTrotecESP::getPower(void) const { + return _.Power; +} + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +void IRTrotecESP::setSpeed(const uint8_t fan) { + uint8_t speed = ::min(fan, kTrotecFanHigh); + _.Fan = speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRTrotecESP::getSpeed(void) const { + return _.Fan; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRTrotecESP::setMode(const uint8_t mode) { + _.Mode = (mode > kTrotecFan) ? kTrotecAuto : mode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRTrotecESP::getMode(void) const { + return _.Mode; +} + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IRTrotecESP::setTemp(const uint8_t celsius) { + uint8_t temp = ::max(celsius, kTrotecMinTemp); + temp = ::min(temp, kTrotecMaxTemp); + _.Temp = temp - kTrotecMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRTrotecESP::getTemp(void) const { + return _.Temp + kTrotecMinTemp; +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTrotecESP::setSleep(const bool on) { + _.Sleep = on; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRTrotecESP::getSleep(void) const { + return _.Sleep; +} + +/// Set the timer time in nr. of Hours. +/// @param[in] timer Nr. of Hours. Max is `kTrotecMaxTimer` +void IRTrotecESP::setTimer(const uint8_t timer) { + _.Timer = timer; + _.Hours = (timer > kTrotecMaxTimer) ? kTrotecMaxTimer : timer; +} + +/// Get the timer time in nr. of Hours. +/// @return Nr. of Hours. +uint8_t IRTrotecESP::getTimer(void) const { return _.Hours; } + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTrotecESP::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kTrotecCool; + case stdAc::opmode_t::kDry: return kTrotecDry; + case stdAc::opmode_t::kFan: return kTrotecFan; + // Note: No Heat mode. + default: return kTrotecAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTrotecESP::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kTrotecFanLow; + case stdAc::fanspeed_t::kMedium: return kTrotecFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kTrotecFanHigh; + default: return kTrotecFanMed; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRTrotecESP::toCommonMode(const uint8_t mode) { + switch (mode) { + case kTrotecCool: return stdAc::opmode_t::kCool; + case kTrotecDry: return stdAc::opmode_t::kDry; + case kTrotecFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRTrotecESP::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kTrotecFanHigh: return stdAc::fanspeed_t::kMax; + case kTrotecFanMed: return stdAc::fanspeed_t::kMedium; + case kTrotecFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRTrotecESP::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::TROTEC; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.model = -1; // Not supported. + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRTrotecESP::toString(void) const { + String result = ""; + result.reserve(100); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kTrotecAuto, kTrotecCool, kTrotecAuto, + kTrotecDry, kTrotecFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kTrotecFanHigh, kTrotecFanLow, + kTrotecFanHigh, kTrotecFanHigh, kTrotecFanMed); + result += addBoolToString(_.Sleep, kSleepStr); + return result; +} + +#if DECODE_TROTEC +/// Decode the supplied Trotec message. +/// Status: STABLE / Works. Untested on real devices. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeTrotec(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= 2 * nbits + kHeader + 2 * kFooter - 1 + offset) + return false; // Can't possibly be a valid Trotec A/C message. + if (strict && nbits != kTrotecBits) return false; + + uint16_t used; + // Header + Data + Footer #1 + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kTrotecHdrMark, kTrotecHdrSpace, + kTrotecBitMark, kTrotecOneSpace, + kTrotecBitMark, kTrotecZeroSpace, + kTrotecBitMark, kTrotecGap, true, + _tolerance, 0, false); + if (used == 0) return false; + offset += used; + + // Footer #2 + if (!matchMark(results->rawbuf[offset++], kTrotecBitMark)) return false; + if (offset <= results->rawlen && + !matchAtLeast(results->rawbuf[offset++], kTrotecGapEnd)) return false; + // Compliance + // Verify we got a valid checksum. + if (strict && !IRTrotecESP::validChecksum(results->state)) return false; + // Success + results->decode_type = TROTEC; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_TROTEC + +#if SEND_TROTEC_3550 +/// Send a Trotec 3550 message. +/// Status: STABLE / Known to be working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendTrotec3550(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kTrotec3550HdrMark, kTrotec3550HdrSpace, + kTrotec3550BitMark, kTrotec3550OneSpace, + kTrotec3550BitMark, kTrotec3550ZeroSpace, + kTrotec3550BitMark, kDefaultMessageGap, + data, nbytes, 38, true, repeat, kDutyDefault); +} +#endif // SEND_TROTEC_3550 + +#if DECODE_TROTEC_3550 +/// Decode the supplied Trotec 3550 message. +/// Status: STABLE / Known to be working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeTrotec3550(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kTrotecBits) return false; + + // Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kTrotec3550HdrMark, kTrotec3550HdrSpace, + kTrotec3550BitMark, kTrotec3550OneSpace, + kTrotec3550BitMark, kTrotec3550ZeroSpace, + kTrotec3550BitMark, kDefaultMessageGap)) return false; + // Compliance + if (strict && !IRTrotec3550::validChecksum(results->state, nbits / 8)) + return false; + // Success + results->decode_type = TROTEC_3550; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_TROTEC_3550 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRTrotec3550::IRTrotec3550(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRTrotec3550::begin(void) { _irsend.begin(); } + +#if SEND_TROTEC_3550 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRTrotec3550::send(const uint16_t repeat) { + _irsend.sendTrotec3550(getRaw(), kTrotecStateLength, repeat); +} +#endif // SEND_TROTEC_3550 + +/// Calculate the checksum for a given state. +/// @param[in] state The array to calc the checksum of. +/// @param[in] length The length/size of the array. +/// @return The calculated checksum value. +uint8_t IRTrotec3550::calcChecksum(const uint8_t state[], + const uint16_t length) { + return length ? sumBytes(state, length - 1) : 0; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRTrotec3550::validChecksum(const uint8_t state[], const uint16_t length) { + return state[length - 1] == calcChecksum(state, length); +} + +/// Calculate & set the checksum for the current internal state of the remote. +void IRTrotec3550::checksum(void) { _.Sum = calcChecksum(_.raw); } + +/// Reset the state of the remote to a known good state/sequence. +void IRTrotec3550::stateReset(void) { + static const uint8_t kReset[kTrotecStateLength] = { + 0x55, 0x60, 0x00, 0x0D, 0x00, 0x00, 0x10, 0x88, 0x5A}; + memcpy(_.raw, kReset, kTrotecStateLength); +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRTrotec3550::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRTrotec3550::setRaw(const uint8_t state[]) { + memcpy(_.raw, state, kTrotecStateLength); +} + +/// Set the requested power state of the A/C to on. +void IRTrotec3550::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRTrotec3550::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTrotec3550::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTrotec3550::getPower(void) const { return _.Power; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +void IRTrotec3550::setFan(const uint8_t fan) { + uint8_t speed = ::min(fan, kTrotecFanHigh); + _.Fan = speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRTrotec3550::getFan(void) const { return _.Fan; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRTrotec3550::setMode(const uint8_t mode) { + _.Mode = (mode > kTrotecFan) ? kTrotecAuto : mode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRTrotec3550::getMode(void) const { return _.Mode; } + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees. +/// @param[in] celsius Use celsius units. True, Celsius; False Fahrenheit. +void IRTrotec3550::setTemp(const uint8_t degrees, const bool celsius) { + setTempUnit(celsius); + uint8_t minTemp = kTrotec3550MinTempC; + uint8_t maxTemp = kTrotec3550MaxTempC; + if (!celsius) { // Fahrenheit? + minTemp = kTrotec3550MinTempF; + maxTemp = kTrotec3550MaxTempF; + } + uint8_t temp = ::max(degrees, minTemp); + temp = ::min(temp, maxTemp); + if (celsius) { + _.TempC = temp - minTemp; + _.TempF = celsiusToFahrenheit(temp) - kTrotec3550MinTempF; + } else { + _.TempF = temp - minTemp; + _.TempC = fahrenheitToCelsius(temp) - kTrotec3550MinTempC; + } +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees. +uint8_t IRTrotec3550::getTemp(void) const { + return getTempUnit() ? _.TempC + kTrotec3550MinTempC + : _.TempF + kTrotec3550MinTempF; +} + +/// Set the temperature unit that the A/C will use.. +/// @param[in] celsius Use celsius units. True, Celsius; False Fahrenheit. +void IRTrotec3550::setTempUnit(const bool celsius) { _.Celsius = celsius; } + +/// Get the current temperature unit setting. +/// @return True, Celsius; False Fahrenheit. +bool IRTrotec3550::getTempUnit(void) const { return _.Celsius; } + +/// Change the Vertical Swing setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTrotec3550::setSwingV(const bool on) { _.SwingV = on; } + +/// Get the value of the current Vertical Swing setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTrotec3550::getSwingV(void) const { return _.SwingV; } + +/// Get the number of minutes of the Timer setting. +/// @return Nr of minutes. +uint16_t IRTrotec3550::getTimer(void) const { return _.TimerHrs * 60; } + +/// Set the number of minutes of the Timer setting. +/// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer. +void IRTrotec3550::setTimer(const uint16_t mins) { + _.TimerSet = mins > 0; + _.TimerHrs = (::min(mins, kTrotec3550TimerMax) / 60); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTrotec3550::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kTrotecCool; + case stdAc::opmode_t::kDry: return kTrotecDry; + case stdAc::opmode_t::kFan: return kTrotecFan; + // Note: No Heat mode. + default: return kTrotecAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTrotec3550::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kTrotecFanLow; + case stdAc::fanspeed_t::kMedium: return kTrotecFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kTrotecFanHigh; + default: return kTrotecFanMed; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRTrotec3550::toCommonMode(const uint8_t mode) { + switch (mode) { + case kTrotecCool: return stdAc::opmode_t::kCool; + case kTrotecDry: return stdAc::opmode_t::kDry; + case kTrotecFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRTrotec3550::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kTrotecFanHigh: return stdAc::fanspeed_t::kMax; + case kTrotecFanMed: return stdAc::fanspeed_t::kMedium; + case kTrotecFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRTrotec3550::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::TROTEC_3550; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = getTempUnit(); + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + // Not supported. + result.model = -1; + result.swingh = stdAc::swingh_t::kOff; + result.turbo = false; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRTrotec3550::toString(void) const { + String result = ""; + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kTrotecAuto, kTrotecCool, kTrotecAuto, + kTrotecDry, kTrotecFan); + result += addTempToString(getTemp(), _.Celsius); + result += addFanToString(_.Fan, kTrotecFanHigh, kTrotecFanLow, + kTrotecFanHigh, kTrotecFanHigh, kTrotecFanMed); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addLabeledString(_.TimerSet ? minsToString(getTimer()) : kOffStr, + kTimerStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Trotec.h b/src/libraries/IRremoteESP8266/src/ir_Trotec.h new file mode 100644 index 000000000..a3243ccf3 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Trotec.h @@ -0,0 +1,252 @@ +// Copyright 2017 stufisher +// Copyright 2019 crankyoldgit + +/// @file +/// @brief Support for Trotec protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/279 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1176 + +// Supports: +// Brand: Trotec, Model: PAC 3200 A/C (TROTEC) +// Brand: Trotec, Model: PAC 3550 Pro A/C (TROTEC_3550) +// Brand: Duux, Model: Blizzard Smart 10K / DXMA04 A/C (TROTEC) +// For Trotec Model PAC 3900 X, use the Midea protocol instead. + +#ifndef IR_TROTEC_H_ +#define IR_TROTEC_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Trotec A/C message. +union TrotecProtocol{ + uint8_t raw[kTrotecStateLength]; ///< Remote state in IR code form. + struct { + // Byte 0 + uint8_t Intro1:8; // fixed value + // Byte 1 + uint8_t Intro2:8; // fixed value + // Byte 2 + uint8_t Mode :2; + uint8_t :1; + uint8_t Power :1; + uint8_t Fan :2; + uint8_t :2; + // Byte 3 + uint8_t Temp :4; + uint8_t :3; + uint8_t Sleep :1; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t :6; + uint8_t Timer :1; + uint8_t :1; + // Byte 6 + uint8_t Hours :8; + // Byte 7 + uint8_t :8; + // Byte 8 + uint8_t Sum :8; + }; +}; + +// Constants +const uint8_t kTrotecIntro1 = 0x12; +const uint8_t kTrotecIntro2 = 0x34; + +const uint8_t kTrotecAuto = 0; +const uint8_t kTrotecCool = 1; +const uint8_t kTrotecDry = 2; +const uint8_t kTrotecFan = 3; + +const uint8_t kTrotecFanLow = 1; +const uint8_t kTrotecFanMed = 2; +const uint8_t kTrotecFanHigh = 3; + +const uint8_t kTrotecMinTemp = 18; +const uint8_t kTrotecDefTemp = 25; +const uint8_t kTrotecMaxTemp = 32; + +const uint8_t kTrotecMaxTimer = 23; + +/// Native representation of a Trotec 3550 A/C message. +union Trotec3550Protocol{ + uint8_t raw[kTrotecStateLength]; ///< Remote state in IR code form. + struct { + // Byte 0 + uint8_t Intro: 8; // fixed value (0x55) + // Byte 1 + uint8_t SwingV :1; + uint8_t Power :1; + uint8_t :1; // Unknown + uint8_t TimerSet :1; + uint8_t TempC :4; // Temp + kTrotec3550MinTempC for degC) + // Byte 2 + uint8_t TimerHrs :4; + uint8_t :4; // Unknown + // Byte 3 + uint8_t TempF :5; // Temp + kTrotec3550MinTempF for degF) + uint8_t :3; // Unknown + // Byte 4 + uint8_t :8; // Unknown + // Byte 5 + uint8_t :8; // Unknown + // Byte 6 + uint8_t Mode :2; + uint8_t :2; // Unknown + uint8_t Fan :2; + uint8_t :2; // Unknown + // Byte 7 + uint8_t :7; // Unknown + uint8_t Celsius :1; // DegC or DegF + // Byte 8 + uint8_t Sum :8; + }; +}; + +const uint8_t kTrotec3550MinTempC = 16; +const uint8_t kTrotec3550MaxTempC = 30; +const uint8_t kTrotec3550MinTempF = 59; +const uint8_t kTrotec3550MaxTempF = 86; + +// Legacy defines. (Deprecated) +#define TROTEC_AUTO kTrotecAuto +#define TROTEC_COOL kTrotecCool +#define TROTEC_DRY kTrotecDry +#define TROTEC_FAN kTrotecFan +#define TROTEC_FAN_LOW kTrotecFanLow +#define TROTEC_FAN_MED kTrotecFanMed +#define TROTEC_FAN_HIGH kTrotecFanHigh +#define TROTEC_MIN_TEMP kTrotecMinTemp +#define TROTEC_MAX_TEMP kTrotecMaxTemp +#define TROTEC_MAX_TIMER kTrotecMaxTimer + +// Class +/// Class for handling detailed Trotec A/C messages. +class IRTrotecESP { + public: + explicit IRTrotecESP(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_TROTEC + void send(const uint16_t repeat = kTrotecDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TROTEC + void begin(void); + void stateReset(void); + + void on(void); + void off(void); + void setPower(const bool state); + bool getPower(void) const; + + void setTemp(const uint8_t celsius); + uint8_t getTemp(void) const; + + void setSpeed(const uint8_t fan); + uint8_t getSpeed(void) const; + + void setFan(const uint8_t fan) { setSpeed(fan); } + uint8_t getFan(void) const { return getSpeed(); } + + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + + bool getSleep(void) const; + void setSleep(const bool on); + + uint8_t getTimer(void) const; + void setTimer(const uint8_t timer); + + uint8_t* getRaw(void); + void setRaw(const uint8_t state[]); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kTrotecStateLength); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kTrotecStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + TrotecProtocol _; + void checksum(void); +}; + +// Class +/// Class for handling detailed Trotec 3550 A/C messages. +class IRTrotec3550 { + public: + explicit IRTrotec3550(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_TROTEC_3550 + void send(const uint16_t repeat = kTrotecDefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TROTEC_3550 + void begin(void); + void stateReset(void); + void on(void); + void off(void); + void setPower(const bool state); + bool getPower(void) const; + void setTemp(const uint8_t degrees, const bool celsius = true); + uint8_t getTemp(void) const; + void setTempUnit(const bool celsius); + bool getTempUnit(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + bool getSwingV(void) const; + void setSwingV(const bool on); + uint16_t getTimer(void) const; + void setTimer(const uint16_t mins); + uint8_t* getRaw(void); + void setRaw(const uint8_t state[]); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kTrotecStateLength); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kTrotecStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + Trotec3550Protocol _; + void checksum(void); +}; +#endif // IR_TROTEC_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Truma.cpp b/src/libraries/IRremoteESP8266/src/ir_Truma.cpp new file mode 100644 index 000000000..8b877c2a6 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Truma.cpp @@ -0,0 +1,341 @@ +// Copyright 2021 David Conran (crankyoldgit) + +/// @file +/// @brief Support for Truma protocol. +/// This protocol uses mark length bit encoding. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1440 +/// @see https://docs.google.com/spreadsheets/d/1k-RHu0vSIB6IweiTZSa3Rxy3Z_qPUtqwcqot8uXVO6I/edit?usp=sharing + + +#include "ir_Truma.h" +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addModeToString; +using irutils::addTempToString; + +// Constants + +const uint16_t kTrumaLdrMark = 20200; +const uint16_t kTrumaLdrSpace = 1000; +const uint16_t kTrumaHdrMark = 1800; +const uint16_t kTrumaSpace = 630; +const uint16_t kTrumaOneMark = 600; +const uint16_t kTrumaZeroMark = 1200; +const uint16_t kTrumaFooterMark = kTrumaOneMark; +const uint32_t kTrumaGap = kDefaultMessageGap; // Just a guess. + + +#if SEND_TRUMA +/// Send a Truma formatted message. +/// Status: STABLE / Confirmed working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The bit size of the message being sent. +/// @param[in] repeat The number of times the message is to be repeated. +void IRsend::sendTruma(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + for (uint16_t r = 0; r <= repeat; r++) { + enableIROut(38000); + mark(kTrumaLdrMark); + space(kTrumaLdrSpace); + sendGeneric(kTrumaHdrMark, kTrumaSpace, // Header + kTrumaOneMark, kTrumaSpace, // Data + kTrumaZeroMark, kTrumaSpace, + kTrumaFooterMark, kTrumaGap, // Footer + data, nbits, 38, false, 0, kDutyDefault); + } +} +#endif // SEND_TRUMA + +#if DECODE_TRUMA +/// Decode the supplied Truma message. +/// Status: STABLE / Confirmed working with real device. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. Typically kTrumaBits. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeTruma(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader - 1 + offset) + return false; // Can't possibly be a valid message. + if (strict && nbits != kTrumaBits) + return false; // Not strictly a message. + + // Leader. + if (!matchMark(results->rawbuf[offset++], kTrumaLdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kTrumaLdrSpace)) return false; + + uint64_t data = 0; + uint16_t used; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kTrumaHdrMark, kTrumaSpace, + kTrumaOneMark, kTrumaSpace, + kTrumaZeroMark, kTrumaSpace, + kTrumaFooterMark, kTrumaGap, + true, kUseDefTol, kMarkExcess, false); + if (!used) return false; + + // Compliance + if (strict && !IRTrumaAc::validChecksum(data)) return false; // Checksum. + + // Success + results->value = data; + results->decode_type = decode_type_t::TRUMA; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_TRUMA + + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRTrumaAc::IRTrumaAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRTrumaAc::begin(void) { _irsend.begin(); } + +#if SEND_TRUMA +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRTrumaAc::send(const uint16_t repeat) { + _irsend.sendTruma(getRaw(), kTrumaBits, repeat); +} +#endif // SEND_TRUMA + +/// Calculate the checksum for a given state. +/// @param[in] state The value to calc the checksum of. +/// @return The calculated checksum value. +uint8_t IRTrumaAc::calcChecksum(const uint64_t state) { + uint8_t sum = kTrumaChecksumInit; + uint64_t to_checksum = state; + for (uint16_t i = 8; i < kTrumaBits; i += 8) { + sum += (to_checksum & 0xFF); + to_checksum >>= 8; + } + return sum; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The value to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRTrumaAc::validChecksum(const uint64_t state) { + TrumaProtocol state_copy; + state_copy.raw = state; + return state_copy.Sum == calcChecksum(state); +} + +/// Calculate & set the checksum for the current internal state of the remote. +void IRTrumaAc::checksum(void) { _.Sum = calcChecksum(_.raw); } + +/// Reset the state of the remote to a known good state/sequence. +void IRTrumaAc::stateReset(void) { setRaw(kTrumaDefaultState); } + +/// Get a copy of the internal state/code for this protocol. +/// @return The code for this protocol based on the current internal state. +uint64_t IRTrumaAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRTrumaAc::setRaw(const uint64_t state) { + _.raw = state; + _lastfan = _.Fan; + _lastmode = _.Mode; +} + +/// Set the requested power state of the A/C to on. +void IRTrumaAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRTrumaAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTrumaAc::setPower(const bool on) { + _.PowerOff = !on; + _.Mode = on ? _lastmode : kTrumaFan; // Off temporarily sets mode to Fan. +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTrumaAc::getPower(void) const { return !_.PowerOff; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRTrumaAc::setFan(const uint8_t speed) { + switch (speed) { + case kTrumaFanHigh: + case kTrumaFanMed: + case kTrumaFanLow: + _lastfan = speed; // Never allow _lastfan to be Quiet. + _.Fan = speed; + break; + case kTrumaFanQuiet: + if (_.Mode == kTrumaCool) _.Fan = kTrumaFanQuiet; // Only in Cool mode. + break; + default: + setFan(kTrumaFanHigh); + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRTrumaAc::getFan(void) const { return _.Fan; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRTrumaAc::setMode(const uint8_t mode) { + switch (mode) { + case kTrumaAuto: + case kTrumaFan: + if (getQuiet()) setFan(kTrumaFanHigh); // Can only have quiet in Cool. + // FALL THRU + case kTrumaCool: + _.Mode = _.PowerOff ? kTrumaFan : mode; // When Off, only set Fan mode. + _lastmode = mode; + break; + default: + setMode(kTrumaAuto); + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRTrumaAc::getMode(void) const { return _.Mode; } + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IRTrumaAc::setTemp(const uint8_t celsius) { + uint8_t temp = ::max(celsius, kTrumaMinTemp); + temp = ::min(temp, kTrumaMaxTemp); + _.Temp = temp - kTrumaTempOffset; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRTrumaAc::getTemp(void) const { return _.Temp + kTrumaTempOffset; } + +/// Change the Quiet setting. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Quiet is only available in Cool mode. +void IRTrumaAc::setQuiet(const bool on) { + if (on && _.Mode == kTrumaCool) + setFan(kTrumaFanQuiet); + else + setFan(_lastfan); +} + +/// Get the value of the current quiet setting. +/// @return true, the setting is on. false, the setting is off. +bool IRTrumaAc::getQuiet(void) const { return _.Fan == kTrumaFanQuiet; } + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTrumaAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kTrumaCool; + case stdAc::opmode_t::kFan: return kTrumaFan; + default: return kTrumaAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRTrumaAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kTrumaFanQuiet; + case stdAc::fanspeed_t::kLow: return kTrumaFanLow; + case stdAc::fanspeed_t::kMedium: return kTrumaFanMed; + default: return kTrumaFanHigh; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRTrumaAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kTrumaCool: return stdAc::opmode_t::kCool; + case kTrumaFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRTrumaAc::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kTrumaFanMed: return stdAc::fanspeed_t::kMedium; + case kTrumaFanLow: return stdAc::fanspeed_t::kLow; + case kTrumaFanQuiet: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kHigh; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRTrumaAc::toCommon(void) const { + stdAc::state_t result{}; + + result.protocol = decode_type_t::TRUMA; + result.model = -1; // Not supported. + // Do we have enough current state info to override any previous state? + // i.e. Was the class just setRaw()'ed with a short "swing" message. + // This should enables us to also ignore the Swing msg's special 17C setting. + result.power = getPower(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.quiet = getQuiet(); + // Not supported. + result.turbo = false; + result.econo = false; + result.light = false; + result.filter = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRTrumaAc::toString(void) const { + String result = ""; + result.reserve(80); + result += addBoolToString(getPower(), kPowerStr, false); + if (getPower()) // Only show the Operating Mode if the unit is on. + result += addModeToString(_.Mode, kTrumaAuto, kTrumaCool, + kTrumaAuto, kTrumaAuto, kTrumaFan); + + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kTrumaFanHigh, kTrumaFanLow, kTrumaFanHigh, + kTrumaFanQuiet, kTrumaFanMed); + result += addBoolToString(getQuiet(), kQuietStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Truma.h b/src/libraries/IRremoteESP8266/src/ir_Truma.h new file mode 100644 index 000000000..9a168be4e --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Truma.h @@ -0,0 +1,126 @@ +// Copyright 2021 David Conran (crankyoldgit) + +/// @file +/// @brief Support for Truma protocol. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1440 +/// @see https://docs.google.com/spreadsheets/d/1k-RHu0vSIB6IweiTZSa3Rxy3Z_qPUtqwcqot8uXVO6I/edit?usp=sharing + +// Supports: +// Brand: Truma, Model: Aventa A/C +// Brand: Truma, Model: 40091-86700 remote + +#ifndef IR_TRUMA_H_ +#define IR_TRUMA_H_ + +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Truma A/C message. +union TrumaProtocol{ + uint64_t raw; ///< Remote state in IR code form. + struct { + // Byte 0 (least significant byte) + uint8_t :8; // fixed value (0x81) + // Byte 1 + uint8_t Mode :2; + uint8_t PowerOff :1; + uint8_t Fan :3; + uint8_t :2; // fixed value (0b11) + // Byte 2 + uint8_t Temp:5; ///< Temp in DegC minus 10(DEC). + uint8_t :3; + // Byte 3 + uint8_t :8; // fixed value (0xFF) + // Byte 4 + uint8_t :8; // fixed value (0xFF) + // Byte 5 + uint8_t :8; // fixed value (0xFF) + // Byte 6 + uint8_t Sum:8; ///< Checksum value + }; +}; + +// Constants +const uint64_t kTrumaDefaultState = 0x50FFFFFFE6E781; ///< Off, Auto, 16C, High +const uint8_t kTrumaChecksumInit = 5; + +const uint8_t kTrumaAuto = 0; // 0b00 +const uint8_t kTrumaCool = 2; // 0b10 +const uint8_t kTrumaFan = 3; // 0b11 + +const uint8_t kTrumaFanQuiet = 3; // 0b011 +const uint8_t kTrumaFanHigh = 4; // 0b100 +const uint8_t kTrumaFanMed = 5; // 0b101 +const uint8_t kTrumaFanLow = 6; // 0b110 + +const uint8_t kTrumaTempOffset = 10; +const uint8_t kTrumaMinTemp = 16; +const uint8_t kTrumaMaxTemp = 31; + + +// Class +/// Class for handling detailed Truma A/C messages. +class IRTrumaAc { + public: + explicit IRTrumaAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_TRUMA + void send(const uint16_t repeat = kNoRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_TRUMA + void begin(void); + void stateReset(void); + + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + + void setTemp(const uint8_t celsius); + uint8_t getTemp(void) const; + + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + + uint8_t getMode(void) const; + void setMode(const uint8_t mode); + + void setQuiet(const bool on); + bool getQuiet(void) const; + + uint64_t getRaw(void); + void setRaw(const uint64_t state); + static bool validChecksum(const uint64_t state); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + TrumaProtocol _; + uint8_t _lastfan; // Last user chosen/valid fan speed. + uint8_t _lastmode; // Last user chosen operation mode. + static uint8_t calcChecksum(const uint64_t state); + void checksum(void); +}; + +#endif // IR_TRUMA_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Vestel.cpp b/src/libraries/IRremoteESP8266/src/ir_Vestel.cpp new file mode 100644 index 000000000..f66cbd351 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Vestel.cpp @@ -0,0 +1,572 @@ +// Copyright 2018 Erdem U. Altinyurt +// Copyright 2019 David Conran + +/// @file +/// @brief Support for Vestel protocols. +/// Vestel added by Erdem U. Altinyurt + +#include "ir_Vestel.h" +//// #include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "ir_Haier.h" +#include "minmax.h" +// Ref: +// None. Totally reverse engineered. + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addTempToString; +using irutils::minsToString; + +#if SEND_VESTEL_AC +/// Send a Vestel message +/// Status: STABLE / Working. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendVestelAc(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8. + + sendGeneric(kVestelAcHdrMark, kVestelAcHdrSpace, // Header + kVestelAcBitMark, kVestelAcOneSpace, // Data + kVestelAcBitMark, kVestelAcZeroSpace, // Data + kVestelAcBitMark, 100000, // Footer + repeat gap + data, nbits, 38, false, repeat, 50); +} +#endif // SEND_VESTEL_AC + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRVestelAc::IRVestelAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +/// @note Power On, Mode Auto, Fan Auto, Temp = 25C/77F +void IRVestelAc::stateReset(void) { + _.cmdState = kVestelAcStateDefault; + _.timeState = kVestelAcTimeStateDefault; +} + +/// Set up hardware to be able to send a message. +void IRVestelAc::begin(void) { _irsend.begin(); } + +#if SEND_VESTEL_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRVestelAc::send(const uint16_t repeat) { + _irsend.sendVestelAc(getRaw(), kVestelAcBits, repeat); +} +#endif // SEND_VESTEL_AC + +/// Get a copy of the internal state/code for this protocol. +/// @return A code for this protocol based on the current internal state. +uint64_t IRVestelAc::getRaw(void) { + checksum(); + if (!_.UseCmd) return _.timeState; + return _.cmdState; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +void IRVestelAc::setRaw(const uint8_t* newState) { + uint64_t upState = 0; + for (int i = 0; i < 7; i++) + upState |= static_cast(newState[i]) << (i * 8); + setRaw(upState); +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +void IRVestelAc::setRaw(const uint64_t newState) { + _.cmdState = newState; + _.timeState = newState; + if (isTimeCommand()) { + _.cmdState = kVestelAcStateDefault; + _.UseCmd = false; + } else { + _.timeState = kVestelAcTimeStateDefault; + } +} + +/// Set the requested power state of the A/C to on. +void IRVestelAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRVestelAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setPower(const bool on) { + _.Power = (on ? 0b11 : 0b00); + _.UseCmd = true; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::getPower(void) const { + return _.Power; +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRVestelAc::setTemp(const uint8_t temp) { + uint8_t new_temp = ::max(kVestelAcMinTempC, temp); + new_temp = ::min(kVestelAcMaxTemp, new_temp); + _.Temp = new_temp - kVestelAcMinTempH; + _.UseCmd = true; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRVestelAc::getTemp(void) const { + return _.Temp + kVestelAcMinTempH; +} + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +void IRVestelAc::setFan(const uint8_t fan) { + switch (fan) { + case kVestelAcFanLow: + case kVestelAcFanMed: + case kVestelAcFanHigh: + case kVestelAcFanAutoCool: + case kVestelAcFanAutoHot: + case kVestelAcFanAuto: + _.Fan = fan; + break; + default: + _.Fan = kVestelAcFanAuto; + } + _.UseCmd = true; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRVestelAc::getFan(void) const { + return _.Fan; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRVestelAc::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +void IRVestelAc::setMode(const uint8_t mode) { + switch (mode) { + case kVestelAcAuto: + case kVestelAcCool: + case kVestelAcHeat: + case kVestelAcDry: + case kVestelAcFan: + _.Mode = mode; + break; + default: + _.Mode = kVestelAcAuto; + } + _.UseCmd = true; +} + +/// Set Auto mode/level of the A/C. +/// @param[in] autoLevel The auto mode/level setting. +void IRVestelAc::setAuto(const int8_t autoLevel) { + if (autoLevel < -2 || autoLevel > 2) return; + _.Mode = kVestelAcAuto; + _.Fan = (autoLevel < 0 ? kVestelAcFanAutoCool : kVestelAcFanAutoHot); + if (autoLevel == 2) + setTemp(30); + else if (autoLevel == 1) + setTemp(31); + else if (autoLevel == 0) + setTemp(25); + else if (autoLevel == -1) + setTemp(16); + else if (autoLevel == -2) + setTemp(17); +} + +/// Set the timer to be active on the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setTimerActive(const bool on) { + _.Timer = on; + _.UseCmd = false; +} + +/// Get if the Timer is active on the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::isTimerActive(void) const { + return _.Timer; +} + +/// Set Timer option of A/C. +/// @param[in] minutes Nr of minutes the timer is to be set for. +/// @note Valid arguments are 0, 0.5, 1, 2, 3 and 5 hours (in minutes). +/// 0 disables the timer. +void IRVestelAc::setTimer(const uint16_t minutes) { + // Clear both On & Off timers. + _.OnHours = 0; + _.OnTenMins = 0; + // Set the "Off" time with the nr of minutes before we turn off. + _.OffHours = minutes / 60; + _.OffTenMins = (minutes % 60) / 10; + setOffTimerActive(false); + // Yes. On Timer instead of Off timer active. + setOnTimerActive(minutes != 0); + setTimerActive(minutes != 0); +} + +/// Get the Timer time of A/C. +/// @return The number of minutes of time on the timer. +uint16_t IRVestelAc::getTimer(void) const { return getOffTimer(); } + +/// Set the A/C's internal clock. +/// @param[in] minutes The time expressed in nr. of minutes past midnight. +void IRVestelAc::setTime(const uint16_t minutes) { + _.Hours = minutes / 60; + _.Minutes = minutes % 60; + _.UseCmd = false; +} + +/// Get the A/C's internal clock's time. +/// @return The time expressed in nr. of minutes past midnight. +uint16_t IRVestelAc::getTime(void) const { + return _.Hours * 60 + _.Minutes; +} + +/// Set the On timer to be active on the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setOnTimerActive(const bool on) { + _.OnTimer = on; + _.UseCmd = false; +} + +/// Get if the On Timer is active on the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::isOnTimerActive(void) const { + return _.OnTimer; +} + +/// Set the On timer time on the A/C. +/// @param[in] minutes Time in nr. of minutes. +void IRVestelAc::setOnTimer(const uint16_t minutes) { + setOnTimerActive(minutes); + _.OnHours = minutes / 60; + _.OnTenMins = (minutes % 60) / 10; + setTimerActive(false); +} + +/// Get the A/C's On Timer time. +/// @return The time expressed in nr. of minutes. +uint16_t IRVestelAc::getOnTimer(void) const { + return _.OnHours * 60 + _.OnTenMins * 10; +} + +/// Set the Off timer to be active on the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setOffTimerActive(const bool on) { + _.OffTimer = on; + _.UseCmd = false; +} + +/// Get if the Off Timer is active on the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::isOffTimerActive(void) const { + return _.OffTimer; +} + +/// Set the Off timer time on the A/C. +/// @param[in] minutes Time in nr. of minutes. +void IRVestelAc::setOffTimer(const uint16_t minutes) { + setOffTimerActive(minutes); + _.OffHours = minutes / 60; + _.OffTenMins = (minutes % 60) / 10; + setTimerActive(false); +} + +/// Get the A/C's Off Timer time. +/// @return The time expressed in nr. of minutes. +uint16_t IRVestelAc::getOffTimer(void) const { + return _.OffHours * 60 + _.OffTenMins * 10; +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setSleep(const bool on) { + _.TurboSleep = (on ? kVestelAcSleep : kVestelAcNormal); + _.UseCmd = true; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::getSleep(void) const { + return _.TurboSleep == kVestelAcSleep; +} + +/// Set the Turbo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setTurbo(const bool on) { + _.TurboSleep = (on ? kVestelAcTurbo : kVestelAcNormal); + _.UseCmd = true; +} + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::getTurbo(void) const { + return _.TurboSleep == kVestelAcTurbo; +} + +/// Set the Ion (Filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setIon(const bool on) { + _.Ion = on; + _.UseCmd = true; +} + +/// Get the Ion (Filter) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::getIon(void) const { + return _.Ion; +} + +/// Set the Swing Roaming setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVestelAc::setSwing(const bool on) { + _.Swing = (on ? kVestelAcSwing : 0xF); + _.UseCmd = true; +} + +/// Get the Swing Roaming setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVestelAc::getSwing(void) const { + return _.Swing == kVestelAcSwing; +} + +/// Calculate the checksum for a given state. +/// @param[in] state The state to calc the checksum of. +/// @return The calculated checksum value. +uint8_t IRVestelAc::calcChecksum(const uint64_t state) { + // Just counts the set bits +1 on stream and take inverse after mask + return 0xFF - countBits(GETBITS64(state, 20, 44), 44, true, 2); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The state to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRVestelAc::validChecksum(const uint64_t state) { + VestelProtocol vp; + vp.cmdState = state; + return vp.CmdSum == IRVestelAc::calcChecksum(state); +} + +/// Calculate & set the checksum for the current internal state of the remote. +void IRVestelAc::checksum(void) { + // Stored the checksum value in the last byte. + _.CmdSum = calcChecksum(_.cmdState); + _.TimeSum = calcChecksum(_.timeState); +} + +/// Is the current state a time command? +/// @return true, if the state is a time message. Otherwise, false. +bool IRVestelAc::isTimeCommand(void) const { + return !_.UseCmd; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRVestelAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kVestelAcCool; + case stdAc::opmode_t::kHeat: return kVestelAcHeat; + case stdAc::opmode_t::kDry: return kVestelAcDry; + case stdAc::opmode_t::kFan: return kVestelAcFan; + default: return kVestelAcAuto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRVestelAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kVestelAcFanLow; + case stdAc::fanspeed_t::kMedium: return kVestelAcFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kVestelAcFanHigh; + default: return kVestelAcFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRVestelAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kVestelAcCool: return stdAc::opmode_t::kCool; + case kVestelAcHeat: return stdAc::opmode_t::kHeat; + case kVestelAcDry: return stdAc::opmode_t::kDry; + case kVestelAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRVestelAc::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kVestelAcFanHigh: return stdAc::fanspeed_t::kMax; + case kVestelAcFanMed: return stdAc::fanspeed_t::kMedium; + case kVestelAcFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRVestelAc::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::VESTEL_AC; + result.model = -1; // Not supported. + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = (getSwing() ? stdAc::swingv_t::kAuto + : stdAc::swingv_t::kOff); + result.turbo = getTurbo(); + result.filter = _.Ion; + result.sleep = (getSleep() ? 0 : -1); + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRVestelAc::toString(void) const { + String result = ""; + result.reserve(100); // Reserve some heap for the string to reduce fragging. + if (isTimeCommand()) { + result += addLabeledString(minsToString(getTime()), kClockStr, false); + result += addLabeledString( + (_.Timer ? minsToString(getTimer()) : kOffStr), + kTimerStr); + result += addLabeledString( + (_.OnTimer && !_.Timer) ? minsToString(getOnTimer()) : kOffStr, + kOnTimerStr); + result += addLabeledString( + (_.OffTimer ? minsToString(getOffTimer()) : kOffStr), + kOffTimerStr); + return result; + } + // Not a time command, it's a normal command. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, kVestelAcAuto, kVestelAcCool, + kVestelAcHeat, kVestelAcDry, kVestelAcFan); + result += addTempToString(getTemp()); + result += addIntToString(_.Fan, kFanStr); + result += kSpaceLBraceStr; + switch (_.Fan) { + case kVestelAcFanAuto: + result += kAutoStr; + break; + case kVestelAcFanLow: + result += kLowStr; + break; + case kVestelAcFanMed: + result += kMedStr; + break; + case kVestelAcFanHigh: + result += kHighStr; + break; + case kVestelAcFanAutoCool: + result += kAutoStr; + result += ' '; + result += kCoolStr; + break; + case kVestelAcFanAutoHot: + result += kAutoStr; + result += ' '; + result += kHeatStr; + break; + default: + result += kUnknownStr; + } + result += ')'; + result += addBoolToString(getSleep(), kSleepStr); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(_.Ion, kIonStr); + result += addBoolToString(getSwing(), kSwingStr); + return result; +} + +#if DECODE_VESTEL_AC +/// Decode the supplied Vestel message. +/// Status: Alpha / Needs testing against a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeVestelAc(decode_results* results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte. + return false; + + if (strict) + if (nbits != kVestelAcBits) + return false; // Not strictly a Vestel AC message. + + uint64_t data = 0; + + if (nbits > sizeof(data) * 8) + return false; // We can't possibly capture a Vestel packet that big. + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kVestelAcHdrMark, kVestelAcHdrSpace, + kVestelAcBitMark, kVestelAcOneSpace, + kVestelAcBitMark, kVestelAcZeroSpace, + kVestelAcBitMark, 0, false, + kVestelAcTolerance, kMarkExcess, false)) return false; + // Compliance + if (strict) + if (!IRVestelAc::validChecksum(data)) return false; + + // Success + results->decode_type = VESTEL_AC; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + + return true; +} +#endif // DECODE_VESTEL_AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Vestel.h b/src/libraries/IRremoteESP8266/src/ir_Vestel.h new file mode 100644 index 000000000..263260e8a --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Vestel.h @@ -0,0 +1,172 @@ +// Copyright 2018 Erdem U. Altinyurt +// Copyright 2019 David Conran + +/// @file +/// @brief Support for Vestel protocols. +/// Vestel added by Erdem U. Altinyurt + +// Supports: +// Brand: Vestel, Model: BIOX CXP-9 A/C (9K BTU) + +#ifndef IR_VESTEL_H_ +#define IR_VESTEL_H_ + +#define __STDC_LIMIT_MACROS +#include +//#ifdef ARDUINO +#include "String.h" +//#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Vestel A/C message. +union VestelProtocol{ + struct { + uint64_t cmdState; + uint64_t timeState; + }; + struct { + // Command + uint64_t Signature :12; // 0x201 + uint64_t CmdSum :8; + uint64_t Swing :4; // auto 0xA, stop 0xF + uint64_t TurboSleep :4; // normal 0x1, sleep 0x3, turbo 0x7 + uint64_t :8; + uint64_t Temp :4; + uint64_t Fan :4; + uint64_t Mode :3; + uint64_t :3; + uint64_t Ion :1; + uint64_t :1; + uint64_t Power :2; + uint64_t UseCmd :1; + uint64_t :0; + // Time + uint64_t :12; + uint64_t TimeSum :8; + uint64_t OffTenMins :3; + uint64_t OffHours :5; + uint64_t OnTenMins :3; + uint64_t OnHours :5; + uint64_t Hours :5; + uint64_t OnTimer :1; + uint64_t OffTimer :1; + uint64_t Timer :1; + uint64_t Minutes :8; + uint64_t :0; + }; +}; + +// Constants +const uint16_t kVestelAcHdrMark = 3110; +const uint16_t kVestelAcHdrSpace = 9066; +const uint16_t kVestelAcBitMark = 520; +const uint16_t kVestelAcOneSpace = 1535; +const uint16_t kVestelAcZeroSpace = 480; +const uint16_t kVestelAcTolerance = 30; + +const uint8_t kVestelAcMinTempH = 16; +const uint8_t kVestelAcMinTempC = 18; +const uint8_t kVestelAcMaxTemp = 30; + +const uint8_t kVestelAcAuto = 0; +const uint8_t kVestelAcCool = 1; +const uint8_t kVestelAcDry = 2; +const uint8_t kVestelAcFan = 3; +const uint8_t kVestelAcHeat = 4; + +const uint8_t kVestelAcFanAuto = 1; +const uint8_t kVestelAcFanLow = 5; +const uint8_t kVestelAcFanMed = 9; +const uint8_t kVestelAcFanHigh = 0xB; +const uint8_t kVestelAcFanAutoCool = 0xC; +const uint8_t kVestelAcFanAutoHot = 0xD; + +const uint8_t kVestelAcNormal = 1; +const uint8_t kVestelAcSleep = 3; +const uint8_t kVestelAcTurbo = 7; +const uint8_t kVestelAcIon = 4; +const uint8_t kVestelAcSwing = 0xA; + +// Default states +const uint64_t kVestelAcStateDefault = 0x0F00D9001FEF201ULL; +const uint64_t kVestelAcTimeStateDefault = 0x201ULL; + +// Classes +/// Class for handling detailed Vestel A/C messages. +class IRVestelAc { + public: + explicit IRVestelAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_VESTEL_AC + void send(const uint16_t repeat = kNoRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_VESTEL_AC + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setAuto(const int8_t autoLevel); + void setTimer(const uint16_t minutes); + uint16_t getTimer(void) const; + void setTime(const uint16_t minutes); + uint16_t getTime(void) const; + void setOnTimer(const uint16_t minutes); + uint16_t getOnTimer(void) const; + void setOffTimer(const uint16_t minutes); + uint16_t getOffTimer(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t fan); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setRaw(const uint8_t* newState); + void setRaw(const uint64_t newState); + uint64_t getRaw(void); + static bool validChecksum(const uint64_t state); + void setSwing(const bool on); + bool getSwing(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setIon(const bool on); + bool getIon(void) const; + bool isTimeCommand(void) const; + bool isOnTimerActive(void) const; + void setOnTimerActive(const bool on); + bool isOffTimerActive(void) const; + void setOffTimerActive(const bool on); + bool isTimerActive(void) const; + void setTimerActive(const bool on); + static uint8_t calcChecksum(const uint64_t state); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + VestelProtocol _; + void checksum(void); +}; + +#endif // IR_VESTEL_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Voltas.cpp b/src/libraries/IRremoteESP8266/src/ir_Voltas.cpp new file mode 100644 index 000000000..9c8477c43 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Voltas.cpp @@ -0,0 +1,517 @@ +// Copyright 2020 David Conran (crankyoldgit) +// Copyright 2020 manj9501 +/// @file +/// @brief Support for Voltas A/C protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238 + +#include "ir_Voltas.h" +// #include +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +using irutils::addBoolToString; +using irutils::addModelToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addLabeledString; +using irutils::addTempToString; +using irutils::minsToString; + +// Constants +const uint16_t kVoltasBitMark = 1026; ///< uSeconds. +const uint16_t kVoltasOneSpace = 2553; ///< uSeconds. +const uint16_t kVoltasZeroSpace = 554; ///< uSeconds. +const uint16_t kVoltasFreq = 38000; ///< Hz. + +#if SEND_VOLTAS +/// Send a Voltas formatted message. +/// Status: STABLE / Working on real device. +/// @param[in] data An array of bytes containing the IR command. +/// It is assumed to be in MSB order for this code. +/// e.g. +/// @code +/// uint8_t data[kVoltasStateLength] = {0x33, 0x28, 0x88, 0x1A, 0x3B, 0x3B, +/// 0x3B, 0x11, 0x00, 0x40}; +/// @endcode +/// @param[in] nbytes Nr. of bytes of data in the array. (>=kVoltasStateLength) +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendVoltas(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(0, 0, + kVoltasBitMark, kVoltasOneSpace, + kVoltasBitMark, kVoltasZeroSpace, + kVoltasBitMark, kDefaultMessageGap, + data, nbytes, + kVoltasFreq, true, repeat, kDutyDefault); +} +#endif // SEND_VOLTAS + +#if DECODE_VOLTAS +/// Decode the supplied Voltas message. +/// Status: STABLE / Working on real device. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeVoltas(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kVoltasBits) return false; + + // Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + 0, 0, // No header + kVoltasBitMark, kVoltasOneSpace, + kVoltasBitMark, kVoltasZeroSpace, + kVoltasBitMark, kDefaultMessageGap, true)) return false; + + // Compliance + if (strict && !IRVoltas::validChecksum(results->state, nbits / 8)) + return false; + // Success + results->decode_type = decode_type_t::VOLTAS; + results->bits = nbits; + return true; +} +#endif // DECODE_VOLTAS + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRVoltas::IRVoltas(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { + stateReset(); +} + +// Reset the internal state to a fixed known good state. +void IRVoltas::stateReset() { + // This resets to a known-good state. + // ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-674699746 + const uint8_t kReset[kVoltasStateLength] = { + 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xCB}; + setRaw(kReset); +} + +/// Set up hardware to be able to send a message. +void IRVoltas::begin() { _irsend.begin(); } + +#if SEND_VOLTAS +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRVoltas::send(const uint16_t repeat) { + _irsend.sendVoltas(getRaw(), kVoltasStateLength, repeat); +} +#endif // SEND_VOLTAS + +/// Get the model information currently known. +/// @param[in] raw Work out the model info from the current raw state. +/// @return The known model number. +voltas_ac_remote_model_t IRVoltas::getModel(const bool raw) const { + if (raw) { + switch (_.SwingHChange) { + case kVoltasSwingHNoChange: + return voltas_ac_remote_model_t::kVoltas122LZF; + default: + return voltas_ac_remote_model_t::kVoltasUnknown; + } + } else { + return _model; + } +} + +/// Set the current model for the remote. +/// @param[in] model The model number. +void IRVoltas::setModel(const voltas_ac_remote_model_t model) { + switch (model) { + case voltas_ac_remote_model_t::kVoltas122LZF: + _model = model; + setSwingHChange(false); + break; + default: _model = voltas_ac_remote_model_t::kVoltasUnknown; + } +} + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRVoltas::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRVoltas::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kVoltasStateLength); + setModel(getModel(true)); +} + +/// Calculate and set the checksum values for the internal state. +void IRVoltas::checksum(void) { + _.Checksum = calcChecksum(_.raw); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRVoltas::validChecksum(const uint8_t state[], const uint16_t length) { + if (length) return state[length - 1] == calcChecksum(state, length); + return true; +} + +/// Calculate the checksum is valid for a given state. +/// @param[in] state The array to calculate the checksum of. +/// @param[in] length The length of the state array. +/// @return The valid checksum value for the state. +uint8_t IRVoltas::calcChecksum(const uint8_t state[], const uint16_t length) { + uint8_t result = 0; + if (length) + result = sumBytes(state, length - 1); + return ~result; +} + +/// Change the power setting to On. +void IRVoltas::on() { setPower(true); } + +/// Change the power setting to Off. +void IRVoltas::off() { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getPower(void) const { return _.Power; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +void IRVoltas::setMode(const uint8_t mode) { + _.Mode = mode; + switch (mode) { + case kVoltasFan: + setFan(getFan()); // Force the fan speed to a correct one fo the mode. + break; + case kVoltasDry: + setFan(kVoltasFanLow); + setTemp(kVoltasDryTemp); + break; + case kVoltasHeat: + case kVoltasCool: + break; + default: + setMode(kVoltasCool); + return; + } + // Reset some settings if needed. + setEcono(getEcono()); + setTurbo(getTurbo()); + setSleep(getSleep()); +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRVoltas::getMode(void) { return _.Mode; } + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRVoltas::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kHeat: return kVoltasHeat; + case stdAc::opmode_t::kDry: return kVoltasDry; + case stdAc::opmode_t::kFan: return kVoltasFan; + default: return kVoltasCool; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRVoltas::toCommonMode(const uint8_t mode) { + switch (mode) { + case kVoltasHeat: return stdAc::opmode_t::kHeat; + case kVoltasDry: return stdAc::opmode_t::kDry; + case kVoltasFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRVoltas::setTemp(const uint8_t temp) { + uint8_t new_temp = ::max(kVoltasMinTemp, temp); + new_temp = ::min(kVoltasMaxTemp, new_temp); + _.Temp = new_temp - kVoltasMinTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRVoltas::getTemp(void) { return _.Temp + kVoltasMinTemp; } + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +void IRVoltas::setFan(const uint8_t fan) { + switch (fan) { + case kVoltasFanAuto: + if (_.Mode == kVoltasFan) { // Auto speed is not available in fan mode. + setFan(kVoltasFanHigh); + return; + } + // FALL-THRU + case kVoltasFanLow: + case kVoltasFanMed: + case kVoltasFanHigh: + _.FanSpeed = fan; + break; + default: + setFan(kVoltasFanAuto); + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRVoltas::getFan(void) { return _.FanSpeed; } + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRVoltas::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kVoltasFanLow; + case stdAc::fanspeed_t::kMedium: return kVoltasFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kVoltasFanHigh; + default: return kVoltasFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRVoltas::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kVoltasFanHigh: return stdAc::fanspeed_t::kMax; + case kVoltasFanMed: return stdAc::fanspeed_t::kMedium; + case kVoltasFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Vertical Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setSwingV(const bool on) { _.SwingV = on ? 0b111 : 0b000; } + +/// Get the Vertical Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getSwingV(void) const { return _.SwingV == 0b111; } + +/// Set the Horizontal Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setSwingH(const bool on) { + switch (_model) { + case voltas_ac_remote_model_t::kVoltas122LZF: + break; // unsupported on these models. + default: + _.SwingH = on; + setSwingHChange(true); + } +} + +/// Get the Horizontal Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getSwingH(void) const { + switch (_model) { + case voltas_ac_remote_model_t::kVoltas122LZF: + return false; // unsupported on these models. + default: + return _.SwingH; + } +} + +/// Set the bits for changing the Horizontal Swing setting of the A/C. +/// @param[in] on true, the change bits are set. +/// false, the "no change" bits are set. +void IRVoltas::setSwingHChange(const bool on) { + _.SwingHChange = on ? kVoltasSwingHChange : kVoltasSwingHNoChange; + if (!on) _.SwingH = true; // "No Change" also sets SwingH to 1. +} + +/// Are the Horizontal Swing change bits set in the message? +/// @return true, the correct bits are set. false, the correct bits are not set. +bool IRVoltas::getSwingHChange(void) const { + return _.SwingHChange == kVoltasSwingHChange; +} + +/// Change the Wifi setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setWifi(const bool on) { _.Wifi = on; } + +/// Get the value of the current Wifi setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getWifi(void) const { return _.Wifi; } + +/// Change the Turbo setting. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note The Turbo setting is only available in Cool mode. +void IRVoltas::setTurbo(const bool on) { + if (on && _.Mode == kVoltasCool) + _.Turbo = true; + else + _.Turbo = false; +} + +/// Get the value of the current Turbo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getTurbo(void) const { return _.Turbo; } + +/// Change the Economy setting. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note The Economy setting is only available in Cool mode. +void IRVoltas::setEcono(const bool on) { + if (on && _.Mode == kVoltasCool) + _.Econo = true; + else + _.Econo = false; +} + +/// Get the value of the current Econo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getEcono(void) const { return _.Econo; } + +/// Change the Light setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setLight(const bool on) { _.Light = on; } + +/// Get the value of the current Light setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getLight(void) const { return _.Light; } + +/// Change the Sleep setting. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note The Sleep setting is only available in Cool mode. +void IRVoltas::setSleep(const bool on) { + if (on && _.Mode == kVoltasCool) + _.Sleep = true; + else + _.Sleep = false; +} + +/// Get the value of the current Sleep setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getSleep(void) const { return _.Sleep; } + +/// Get the value of the On Timer time. +/// @return Number of minutes before the timer activates. +uint16_t IRVoltas::getOnTime(void) const { + return ::min((unsigned)(12 * _.OnTimer12Hr + _.OnTimerHrs - 1), 23U) * 60 + + _.OnTimerMins; +} + +/// Set the value of the On Timer time. +/// @param[in] nr_of_mins Number of minutes before the timer activates. +/// 0 disables the timer. Max is 23 hrs & 59 mins (1439 mins) +void IRVoltas::setOnTime(const uint16_t nr_of_mins) { + // Cap the total number of mins. + uint16_t mins = ::min(nr_of_mins, (uint16_t)(23 * 60 + 59)); + uint16_t hrs = (mins / 60) + 1; + _.OnTimerMins = mins % 60; + _.OnTimer12Hr = hrs / 12; + _.OnTimerHrs = hrs % 12; + _.OnTimerEnable = (mins > 0); // Is the timer is to be enabled? +} + +/// Get the value of the On Timer time. +/// @return Number of minutes before the timer activates. +uint16_t IRVoltas::getOffTime(void) const { + return ::min((unsigned)(12 * _.OffTimer12Hr + _.OffTimerHrs - 1), 23U) * + 60 + _.OffTimerMins; +} + +/// Set the value of the Off Timer time. +/// @param[in] nr_of_mins Number of minutes before the timer activates. +/// 0 disables the timer. Max is 23 hrs & 59 mins (1439 mins) +void IRVoltas::setOffTime(const uint16_t nr_of_mins) { + // Cap the total number of mins. + uint16_t mins = ::min(nr_of_mins, (uint16_t)(23 * 60 + 59)); + uint16_t hrs = (mins / 60) + 1; + _.OffTimerMins = mins % 60; + _.OffTimer12Hr = hrs / 12; + _.OffTimerHrs = hrs % 12; + _.OffTimerEnable = (mins > 0); // Is the timer is to be enabled? +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if available. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + result.swingh = stdAc::swingh_t::kOff; + } + result.model = getModel(); + result.protocol = decode_type_t::VOLTAS; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.FanSpeed); + result.swingv = getSwingV() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + if (getSwingHChange()) + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + result.turbo = _.Turbo; + result.econo = _.Econo; + result.light = _.Light; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.quiet = false; + result.filter = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRVoltas::toString() { + String result = ""; + result.reserve(200); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::VOLTAS, getModel(), false); + result += addBoolToString(_.Power, kPowerStr); + result += addModeToString(_.Mode, 255, kVoltasCool, kVoltasHeat, + kVoltasDry, kVoltasFan); + result += addTempToString(getTemp()); + result += addFanToString(_.FanSpeed, kVoltasFanHigh, kVoltasFanLow, + kVoltasFanAuto, kVoltasFanAuto, kVoltasFanMed); + result += addBoolToString(getSwingV(), kSwingVStr); + if (getSwingHChange()) + result += addBoolToString(_.SwingH, kSwingHStr); + else + result += addLabeledString(kNAStr, kSwingHStr); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Econo, kEconoStr); + result += addBoolToString(_.Wifi, kWifiStr); + result += addBoolToString(_.Light, kLightStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(_.OnTimerEnable ? minsToString(getOnTime()) + : kOffStr, kOnTimerStr); + result += addLabeledString(_.OffTimerEnable ? minsToString(getOffTime()) + : kOffStr, kOffTimerStr); + return result; +} diff --git a/src/libraries/IRremoteESP8266/src/ir_Voltas.h b/src/libraries/IRremoteESP8266/src/ir_Voltas.h new file mode 100644 index 000000000..63a7ae208 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Voltas.h @@ -0,0 +1,162 @@ +// Copyright 2020 David Conran (crankyoldgit) +// Copyright 2020 manj9501 +/// @file +/// @brief Support for Voltas A/C protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238 + +// Supports: +// Brand: Voltas, Model: 122LZF 4011252 Window A/C +// +// Ref: https://docs.google.com/spreadsheets/d/1zzDEUQ52y7MZ7_xCU3pdjdqbRXOwZLsbTGvKWcicqCI/ +// Ref: https://www.corona.co.jp/box/download.php?id=145060636229 +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/files/8646964/Voltas.Window.AC.122LZF.Remote.Instructions.pdf + +#ifndef IR_VOLTAS_H_ +#define IR_VOLTAS_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Voltas A/C message. +union VoltasProtocol { + uint8_t raw[kVoltasStateLength]; ///< The state in native IR code form + struct { + // Byte 0 + uint8_t SwingH :1; + uint8_t SwingHChange :7; + // Byte 1 + uint8_t Mode :4; + uint8_t :1; // Unknown/Unused + uint8_t FanSpeed :3; + // Byte 2 + uint8_t SwingV :3; + uint8_t Wifi :1; + uint8_t :1; // Unknown/Unused + uint8_t Turbo :1; + uint8_t Sleep :1; + uint8_t Power :1; + // Byte 3 + uint8_t Temp :4; + uint8_t :2; // Typically 0b01 + uint8_t Econo :1; + uint8_t TempSet :1; + // Byte 4 + uint8_t OnTimerMins :6; // 0-59 + uint8_t :1; // Unknown/Unused + uint8_t OnTimer12Hr :1; // (Nr of Hours + 1) % 12. + // Byte 5 + uint8_t OffTimerMins :6; // 0-59 + uint8_t :1; // Unknown/Unused + uint8_t OffTimer12Hr :1; // (Nr of Hours + 1) % 12. + // Byte 6 + uint8_t :8; // Typically 0b00111011(0x3B) + // Byte 7 + uint8_t OnTimerHrs :4; // (Nr of Hours + 1) % 12. + uint8_t OffTimerHrs :4; // (Nr of Hours + 1) % 12. + // Byte 8 + uint8_t :5; // Typically 0b00000 + uint8_t Light :1; + uint8_t OffTimerEnable :1; + uint8_t OnTimerEnable :1; + // Byte 9 + uint8_t Checksum :8; + }; +}; + +// Constants +const uint8_t kVoltasFan = 0b0001; ///< 1 +const uint8_t kVoltasHeat = 0b0010; ///< 2 +const uint8_t kVoltasDry = 0b0100; ///< 4 +const uint8_t kVoltasCool = 0b1000; ///< 8 +const uint8_t kVoltasMinTemp = 16; ///< Celsius +const uint8_t kVoltasDryTemp = 24; ///< Celsius +const uint8_t kVoltasMaxTemp = 30; ///< Celsius +const uint8_t kVoltasFanHigh = 0b001; ///< 1 +const uint8_t kVoltasFanMed = 0b010; ///< 2 +const uint8_t kVoltasFanLow = 0b100; ///< 4 +const uint8_t kVoltasFanAuto = 0b111; ///< 7 +const uint8_t kVoltasSwingHChange = 0b1111100; ///< 0x7D +const uint8_t kVoltasSwingHNoChange = 0b0011001; ///< 0x19 + +// Classes +/// Class for handling detailed Voltas A/C messages. +class IRVoltas { + public: + explicit IRVoltas(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(); +#if SEND_VOLTAS + void send(const uint16_t repeat = kNoRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_VOLTAS + void begin(); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kVoltasStateLength); + void setModel(const voltas_ac_remote_model_t model); + voltas_ac_remote_model_t getModel(const bool raw = false) const; + void setPower(const bool on); + bool getPower(void) const; + void on(void); + void off(void); + void setWifi(const bool on); + bool getWifi(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void); + void setFan(const uint8_t speed); + uint8_t getFan(void); + void setMode(const uint8_t mode); + uint8_t getMode(void); + void setSwingH(const bool on); + bool getSwingH(void) const; + void setSwingHChange(const bool on); + bool getSwingHChange(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setLight(const bool on); + bool getLight(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + uint16_t getOnTime(void) const; + void setOnTime(const uint16_t nr_of_mins); + uint16_t getOffTime(void) const; + void setOffTime(const uint16_t nr_of_mins); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + uint8_t convertMode(const stdAc::opmode_t mode); + uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL); + String toString(void); +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + VoltasProtocol _; ///< The state of the IR remote. + voltas_ac_remote_model_t _model; ///< Model type. + void checksum(void); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kVoltasStateLength); +}; +#endif // IR_VOLTAS_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Whirlpool.cpp b/src/libraries/IRremoteESP8266/src/ir_Whirlpool.cpp new file mode 100644 index 000000000..602e1b0e9 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Whirlpool.cpp @@ -0,0 +1,658 @@ +// Copyright 2018 David Conran + +/// @file +/// @brief Support for Whirlpool protocols. +/// Decoding help from: \@redmusicxd, \@josh929800, \@raducostea +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/509 +/// @note Smart, iFeel, AroundU, PowerSave, & Silent modes are unsupported. +/// Advanced 6thSense, Dehumidify, & Sleep modes are not supported. +/// @note Dim == !Light, Jet == Super == Turbo + +#include "ir_Whirlpool.h" +// #include +#include +#ifndef ARDUINO +//#include +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kWhirlpoolAcHdrMark = 8950; +const uint16_t kWhirlpoolAcHdrSpace = 4484; +const uint16_t kWhirlpoolAcBitMark = 597; +const uint16_t kWhirlpoolAcOneSpace = 1649; +const uint16_t kWhirlpoolAcZeroSpace = 533; +const uint16_t kWhirlpoolAcGap = 7920; +const uint32_t kWhirlpoolAcMinGap = kDefaultMessageGap; // Just a guess. +const uint8_t kWhirlpoolAcSections = 3; + +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addModelToString; +using irutils::addTempToString; +using irutils::minsToString; + +#define GETTIME(x) (_.x##Hours * 60 + _.x##Mins) +#define SETTIME(x, n) do { \ + uint16_t mins = n;\ + _.x##Hours = (mins / 60) % 24;\ + _.x##Mins = mins % 60;\ +} while (0) + +#if SEND_WHIRLPOOL_AC +/// Send a Whirlpool A/C message. +/// Status: BETA / Probably works. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendWhirlpoolAC(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kWhirlpoolAcStateLength) + return; // Not enough bytes to send a proper message. + for (uint16_t r = 0; r <= repeat; r++) { + // Section 1 + sendGeneric(kWhirlpoolAcHdrMark, kWhirlpoolAcHdrSpace, kWhirlpoolAcBitMark, + kWhirlpoolAcOneSpace, kWhirlpoolAcBitMark, + kWhirlpoolAcZeroSpace, kWhirlpoolAcBitMark, kWhirlpoolAcGap, + data, 6, // 6 bytes == 48 bits + 38000, // Complete guess of the modulation frequency. + false, 0, 50); + // Section 2 + sendGeneric(0, 0, kWhirlpoolAcBitMark, kWhirlpoolAcOneSpace, + kWhirlpoolAcBitMark, kWhirlpoolAcZeroSpace, kWhirlpoolAcBitMark, + kWhirlpoolAcGap, data + 6, 8, // 8 bytes == 64 bits + 38000, // Complete guess of the modulation frequency. + false, 0, 50); + // Section 3 + sendGeneric(0, 0, kWhirlpoolAcBitMark, kWhirlpoolAcOneSpace, + kWhirlpoolAcBitMark, kWhirlpoolAcZeroSpace, kWhirlpoolAcBitMark, + kWhirlpoolAcMinGap, data + 14, 7, // 7 bytes == 56 bits + 38000, // Complete guess of the modulation frequency. + false, 0, 50); + } +} +#endif // SEND_WHIRLPOOL_AC + +// Class for emulating a Whirlpool A/C remote. + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRWhirlpoolAc::IRWhirlpoolAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRWhirlpoolAc::stateReset(void) { + for (uint8_t i = 2; i < kWhirlpoolAcStateLength; i++) _.raw[i] = 0x0; + _.raw[0] = 0x83; + _.raw[1] = 0x06; + _.raw[6] = 0x80; + _setTemp(kWhirlpoolAcAutoTemp); // Default to a sane value. +} + +/// Set up hardware to be able to send a message. +void IRWhirlpoolAc::begin(void) { _irsend.begin(); } + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length/size of the array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRWhirlpoolAc::validChecksum(const uint8_t state[], + const uint16_t length) { + if (length > kWhirlpoolAcChecksumByte1 && + state[kWhirlpoolAcChecksumByte1] != + xorBytes(state + 2, kWhirlpoolAcChecksumByte1 - 1 - 2)) { + DPRINTLN("DEBUG: First Whirlpool AC checksum failed."); + return false; + } + if (length > kWhirlpoolAcChecksumByte2 && + state[kWhirlpoolAcChecksumByte2] != + xorBytes(state + kWhirlpoolAcChecksumByte1 + 1, + kWhirlpoolAcChecksumByte2 - kWhirlpoolAcChecksumByte1 - 1)) { + DPRINTLN("DEBUG: Second Whirlpool AC checksum failed."); + return false; + } + // State is too short to have a checksum or everything checked out. + return true; +} + +/// Calculate & set the checksum for the current internal state of the remote. +/// @param[in] length The length/size of the internal state array. +void IRWhirlpoolAc::checksum(uint16_t length) { + if (length >= kWhirlpoolAcChecksumByte1) + _.Sum1 = xorBytes(_.raw + 2, kWhirlpoolAcChecksumByte1 - 1 - 2); + if (length >= kWhirlpoolAcChecksumByte2) + _.Sum2 = xorBytes(_.raw + kWhirlpoolAcChecksumByte1 + 1, + kWhirlpoolAcChecksumByte2 - kWhirlpoolAcChecksumByte1 - 1); +} + +#if SEND_WHIRLPOOL_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +/// @param[in] calcchecksum Do we need to calculate the checksum?. +void IRWhirlpoolAc::send(const uint16_t repeat, const bool calcchecksum) { + _irsend.sendWhirlpoolAC(getRaw(calcchecksum), kWhirlpoolAcStateLength, + repeat); +} +#endif // SEND_WHIRLPOOL_AC + +/// Get a copy of the internal state/code for this protocol. +/// @param[in] calcchecksum Do we need to calculate the checksum?. +/// @return A code for this protocol based on the current internal state. +uint8_t *IRWhirlpoolAc::getRaw(const bool calcchecksum) { + if (calcchecksum) checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length The length/size of the new_code array. +void IRWhirlpoolAc::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, ::min(length, kWhirlpoolAcStateLength)); +} + +/// Get/Detect the model of the A/C. +/// @return The enum of the compatible model. +whirlpool_ac_remote_model_t IRWhirlpoolAc::getModel(void) const { + if (_.J191) + return DG11J191; + else + return DG11J13A; +} + +/// Set the model of the A/C to emulate. +/// @param[in] model The enum of the appropriate model. +void IRWhirlpoolAc::setModel(const whirlpool_ac_remote_model_t model) { + switch (model) { + case DG11J191: + _.J191 = true; + break; + case DG11J13A: + // FALL THRU + default: + _.J191 = false; + } + _setTemp(_desiredtemp); // Different models have different temp values. +} + +/// Calculate the temp. offset in deg C for the current model. +/// @return The temperature offset. +int8_t IRWhirlpoolAc::getTempOffset(void) const { + switch (getModel()) { + case whirlpool_ac_remote_model_t::DG11J191: return -2; + default: return 0; + } +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +/// @param[in] remember Do we save this temperature? +/// @note Internal use only. +void IRWhirlpoolAc::_setTemp(const uint8_t temp, const bool remember) { + if (remember) _desiredtemp = temp; + int8_t offset = getTempOffset(); // Cache the min temp for the model. + uint8_t newtemp = ::max((uint8_t)(kWhirlpoolAcMinTemp + offset), temp); + newtemp = ::min((uint8_t)(kWhirlpoolAcMaxTemp + offset), newtemp); + _.Temp = newtemp - (kWhirlpoolAcMinTemp + offset); +} + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRWhirlpoolAc::setTemp(const uint8_t temp) { + _setTemp(temp); + setSuper(false); // Changing temp cancels Super/Jet mode. + _.Cmd = kWhirlpoolAcCommandTemp; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRWhirlpoolAc::getTemp(void) const { + return _.Temp + kWhirlpoolAcMinTemp + getTempOffset(); +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note Internal use only. +void IRWhirlpoolAc::_setMode(const uint8_t mode) { + switch (mode) { + case kWhirlpoolAcAuto: + setFan(kWhirlpoolAcFanAuto); + _setTemp(kWhirlpoolAcAutoTemp, false); + setSleep(false); // Cancel sleep mode when in auto/6thsense mode. + // FALL THRU + case kWhirlpoolAcHeat: + case kWhirlpoolAcCool: + case kWhirlpoolAcDry: + case kWhirlpoolAcFan: + _.Mode = mode; + _.Cmd = kWhirlpoolAcCommandMode; + break; + default: + return; + } + if (mode == kWhirlpoolAcAuto) _.Cmd = kWhirlpoolAcCommand6thSense; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRWhirlpoolAc::setMode(const uint8_t mode) { + setSuper(false); // Changing mode cancels Super/Jet mode. + _setMode(mode); +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRWhirlpoolAc::getMode(void) const { + return _.Mode; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRWhirlpoolAc::setFan(const uint8_t speed) { + switch (speed) { + case kWhirlpoolAcFanAuto: + case kWhirlpoolAcFanLow: + case kWhirlpoolAcFanMedium: + case kWhirlpoolAcFanHigh: + _.Fan = speed; + setSuper(false); // Changing fan speed cancels Super/Jet mode. + _.Cmd = kWhirlpoolAcCommandFanSpeed; + break; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRWhirlpoolAc::getFan(void) const { + return _.Fan; +} + +/// Set the (vertical) swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRWhirlpoolAc::setSwing(const bool on) { + _.Swing1 = on; + _.Swing2 = on; + _.Cmd = kWhirlpoolAcCommandSwing; +} + +/// Get the (vertical) swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRWhirlpoolAc::getSwing(void) const { + return _.Swing1 && _.Swing2; +} + +/// Set the Light (Display/LED) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRWhirlpoolAc::setLight(const bool on) { + // Cleared when on. + _.LightOff = !on; +} + +/// Get the Light (Display/LED) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRWhirlpoolAc::getLight(void) const { + return !_.LightOff; +} + +/// Set the clock time in nr. of minutes past midnight. +/// @param[in] minspastmidnight The time expressed as minutes past midnight. +void IRWhirlpoolAc::setClock(const uint16_t minspastmidnight) { + SETTIME(Clock, minspastmidnight); +} + +/// Get the clock time in nr. of minutes past midnight. +/// @return The time expressed as the Nr. of minutes past midnight. +uint16_t IRWhirlpoolAc::getClock(void) const { + return GETTIME(Clock); +} + +/// Set the Off Timer time. +/// @param[in] minspastmidnight The time expressed as minutes past midnight. +void IRWhirlpoolAc::setOffTimer(const uint16_t minspastmidnight) { + SETTIME(Off, minspastmidnight); +} + +/// Get the Off Timer time.. +/// @return The time expressed as the Nr. of minutes past midnight. +uint16_t IRWhirlpoolAc::getOffTimer(void) const { + return GETTIME(Off); +} + +/// Is the Off timer enabled? +/// @return true, the Timer is enabled. false, the Timer is disabled. +bool IRWhirlpoolAc::isOffTimerEnabled(void) const { + return _.OffTimerEnabled; +} + +/// Enable the Off Timer. +/// @param[in] on true, the timer is enabled. false, the timer is disabled. +void IRWhirlpoolAc::enableOffTimer(const bool on) { + _.OffTimerEnabled = on; + _.Cmd = kWhirlpoolAcCommandOffTimer; +} + +/// Set the On Timer time. +/// @param[in] minspastmidnight The time expressed as minutes past midnight. +void IRWhirlpoolAc::setOnTimer(const uint16_t minspastmidnight) { + SETTIME(On, minspastmidnight); +} + +/// Get the On Timer time.. +/// @return The time expressed as the Nr. of minutes past midnight. +uint16_t IRWhirlpoolAc::getOnTimer(void) const { + return GETTIME(On); +} + +/// Is the On timer enabled? +/// @return true, the Timer is enabled. false, the Timer is disabled. +bool IRWhirlpoolAc::isOnTimerEnabled(void) const { + return _.OnTimerEnabled; +} + +/// Enable the On Timer. +/// @param[in] on true, the timer is enabled. false, the timer is disabled. +void IRWhirlpoolAc::enableOnTimer(const bool on) { + _.OnTimerEnabled = on; + _.Cmd = kWhirlpoolAcCommandOnTimer; +} + +/// Change the power toggle setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRWhirlpoolAc::setPowerToggle(const bool on) { + _.Power = on; + setSuper(false); // Changing power cancels Super/Jet mode. + _.Cmd = kWhirlpoolAcCommandPower; +} + +/// Get the value of the current power toggle setting. +/// @return true, the setting is on. false, the setting is off. +bool IRWhirlpoolAc::getPowerToggle(void) const { + return _.Power; +} + +/// Get the Command (Button) setting of the A/C. +/// @return The current Command (Button) of the A/C. +uint8_t IRWhirlpoolAc::getCommand(void) const { + return _.Cmd; +} + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRWhirlpoolAc::setSleep(const bool on) { + _.Sleep = on; + if (on) setFan(kWhirlpoolAcFanLow); + _.Cmd = kWhirlpoolAcCommandSleep; +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRWhirlpoolAc::getSleep(void) const { + return _.Sleep; +} + +/// Set the Super (Turbo/Jet) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRWhirlpoolAc::setSuper(const bool on) { + if (on) { + setFan(kWhirlpoolAcFanHigh); + switch (_.Mode) { + case kWhirlpoolAcHeat: + setTemp(kWhirlpoolAcMaxTemp + getTempOffset()); + break; + case kWhirlpoolAcCool: + default: + setTemp(kWhirlpoolAcMinTemp + getTempOffset()); + setMode(kWhirlpoolAcCool); + break; + } + _.Super1 = 1; + _.Super2 = 1; + } else { + _.Super1 = 0; + _.Super2 = 0; + } + _.Cmd = kWhirlpoolAcCommandSuper; +} + +/// Get the Super (Turbo/Jet) setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRWhirlpoolAc:: getSuper(void) const { + return _.Super1 && _.Super2; +} + +/// Set the Command (Button) setting of the A/C. +/// @param[in] code The current Command (Button) of the A/C. +void IRWhirlpoolAc::setCommand(const uint8_t code) { + _.Cmd = code; +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRWhirlpoolAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kAuto: return kWhirlpoolAcAuto; + case stdAc::opmode_t::kHeat: return kWhirlpoolAcHeat; + case stdAc::opmode_t::kDry: return kWhirlpoolAcDry; + case stdAc::opmode_t::kFan: return kWhirlpoolAcFan; + // Default to Cool as some Whirlpool models don't have an Auto mode. + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1283 + default: return kWhirlpoolAcCool; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRWhirlpoolAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kWhirlpoolAcFanLow; + case stdAc::fanspeed_t::kMedium: return kWhirlpoolAcFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kWhirlpoolAcFanHigh; + default: return kWhirlpoolAcFanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRWhirlpoolAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kWhirlpoolAcCool: return stdAc::opmode_t::kCool; + case kWhirlpoolAcHeat: return stdAc::opmode_t::kHeat; + case kWhirlpoolAcDry: return stdAc::opmode_t::kDry; + case kWhirlpoolAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRWhirlpoolAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kWhirlpoolAcFanHigh: return stdAc::fanspeed_t::kMax; + case kWhirlpoolAcFanMedium: return stdAc::fanspeed_t::kMedium; + case kWhirlpoolAcFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRWhirlpoolAc::toCommon(const stdAc::state_t *prev) const { + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.power = false; + } + result.protocol = decode_type_t::WHIRLPOOL_AC; + result.model = getModel(); + if (_.Power) result.power = !result.power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = getSwing() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.turbo = getSuper(); + result.light = getLight(); + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.filter = false; + result.econo = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRWhirlpoolAc::toString(void) const { + String result = ""; + result.reserve(200); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::WHIRLPOOL_AC, getModel(), false); + result += addBoolToString(_.Power, kPowerToggleStr); + result += addModeToString(_.Mode, kWhirlpoolAcAuto, kWhirlpoolAcCool, + kWhirlpoolAcHeat, kWhirlpoolAcDry, kWhirlpoolAcFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kWhirlpoolAcFanHigh, kWhirlpoolAcFanLow, + kWhirlpoolAcFanAuto, kWhirlpoolAcFanAuto, + kWhirlpoolAcFanMedium); + result += addBoolToString(getSwing(), kSwingStr); + result += addBoolToString(getLight(), kLightStr); + result += addLabeledString(minsToString(getClock()), kClockStr); + result += addLabeledString( + _.OnTimerEnabled ? minsToString(getOnTimer()) : kOffStr, kOnTimerStr); + result += addLabeledString( + _.OffTimerEnabled ? minsToString(getOffTimer()) : kOffStr, kOffTimerStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addBoolToString(getSuper(), kSuperStr); + result += addIntToString(_.Cmd, kCommandStr); + result += kSpaceLBraceStr; + switch (_.Cmd) { + case kWhirlpoolAcCommandLight: + result += kLightStr; + break; + case kWhirlpoolAcCommandPower: + result += kPowerStr; + break; + case kWhirlpoolAcCommandTemp: + result += kTempStr; + break; + case kWhirlpoolAcCommandSleep: + result += kSleepStr; + break; + case kWhirlpoolAcCommandSuper: + result += kSuperStr; + break; + case kWhirlpoolAcCommandOnTimer: + result += kOnTimerStr; + break; + case kWhirlpoolAcCommandMode: + result += kModeStr; + break; + case kWhirlpoolAcCommandSwing: + result += kSwingStr; + break; + case kWhirlpoolAcCommandIFeel: + result += kIFeelStr; + break; + case kWhirlpoolAcCommandFanSpeed: + result += kFanStr; + break; + case kWhirlpoolAcCommand6thSense: + result += k6thSenseStr; + break; + case kWhirlpoolAcCommandOffTimer: + result += kOffTimerStr; + break; + default: + result += kUnknownStr; + break; + } + result += ')'; + return result; +} + +#if DECODE_WHIRLPOOL_AC + +/// Decode the supplied Whirlpool A/C message. +/// Status: STABLE / Working as intended. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeWhirlpoolAC(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + 4 + kHeader + kFooter - 1 + offset) + return false; // Can't possibly be a valid Whirlpool A/C message. + if (strict) { + if (nbits != kWhirlpoolAcBits) return false; + } + + const uint8_t sectionSize[kWhirlpoolAcSections] = {6, 8, 7}; + + // Header + if (!matchMark(results->rawbuf[offset++], kWhirlpoolAcHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kWhirlpoolAcHdrSpace)) + return false; + + // Data Sections + uint16_t pos = 0; + for (uint8_t section = 0; section < kWhirlpoolAcSections; + section++) { + uint16_t used; + // Section Data + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, sectionSize[section] * 8, + 0, 0, + kWhirlpoolAcBitMark, kWhirlpoolAcOneSpace, + kWhirlpoolAcBitMark, kWhirlpoolAcZeroSpace, + kWhirlpoolAcBitMark, kWhirlpoolAcGap, + section >= kWhirlpoolAcSections - 1, + _tolerance, kMarkExcess, false); + if (used == 0) return false; + offset += used; + pos += sectionSize[section]; + } + + // Compliance + if (strict) { + // Re-check we got the correct size/length due to the way we read the data. + if (pos * 8 != nbits) return false; + if (!IRWhirlpoolAc::validChecksum(results->state, nbits / 8)) + return false; + } + + // Success + results->decode_type = WHIRLPOOL_AC; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // WHIRLPOOL_AC diff --git a/src/libraries/IRremoteESP8266/src/ir_Whirlpool.h b/src/libraries/IRremoteESP8266/src/ir_Whirlpool.h new file mode 100644 index 000000000..0f7bff0dc --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Whirlpool.h @@ -0,0 +1,205 @@ +// Copyright 2018 David Conran + +/// @file +/// @brief Support for Whirlpool protocols. +/// Decoding help from: \@redmusicxd, \@josh929800, \@raducostea +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/509 +/// @note Smart, iFeel, AroundU, PowerSave, & Silent modes are unsupported. +/// Advanced 6thSense, Dehumidify, & Sleep modes are not supported. +/// @note Dim == !Light, Jet == Super == Turbo + +// Supports: +// Brand: Whirlpool, Model: DG11J1-3A remote +// Brand: Whirlpool, Model: DG11J1-04 remote +// Brand: Whirlpool, Model: DG11J1-91 remote +// Brand: Whirlpool, Model: SPIS409L A/C +// Brand: Whirlpool, Model: SPIS412L A/C +// Brand: Whirlpool, Model: SPIW409L A/C +// Brand: Whirlpool, Model: SPIW412L A/C +// Brand: Whirlpool, Model: SPIW418L A/C + +#ifndef IR_WHIRLPOOL_H_ +#define IR_WHIRLPOOL_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include "String.h" +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +/// Native representation of a Whirlpool A/C message. +union WhirlpoolProtocol{ + uint8_t raw[kWhirlpoolAcStateLength]; ///< The state in IR code form + struct { + // Byte 0~1 + uint8_t pad0[2]; + // Byte 2 + uint8_t Fan :2; + uint8_t Power :1; + uint8_t Sleep :1; + uint8_t :3; + uint8_t Swing1 :1; + // Byte 3 + uint8_t Mode :3; + uint8_t :1; + uint8_t Temp :4; + // Byte 4 + uint8_t :8; + // Byte 5 + uint8_t :4; + uint8_t Super1 :1; + uint8_t :2; + uint8_t Super2 :1; + // Byte 6 + uint8_t ClockHours :5; + uint8_t LightOff :1; + uint8_t :2; + // Byte 7 + uint8_t ClockMins :6; + uint8_t :1; + uint8_t OffTimerEnabled :1; + // Byte 8 + uint8_t OffHours :5; + uint8_t :1; + uint8_t Swing2 :1; + uint8_t :1; + // Byte 9 + uint8_t OffMins :6; + uint8_t :1; + uint8_t OnTimerEnabled :1; + // Byte 10 + uint8_t OnHours :5; + uint8_t :3; + // Byte 11 + uint8_t OnMins :6; + uint8_t :2; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t Sum1 :8; + // Byte 14 + uint8_t :8; + // Byte 15 + uint8_t Cmd :8; + // Byte 16~17 + uint8_t pad1[2]; + // Byte 18 + uint8_t :3; + uint8_t J191 :1; + uint8_t :4; + // Byte 19 + uint8_t :8; + // Byte 20 + uint8_t Sum2 :8; + }; +}; + +// Constants +const uint8_t kWhirlpoolAcChecksumByte1 = 13; +const uint8_t kWhirlpoolAcChecksumByte2 = kWhirlpoolAcStateLength - 1; +const uint8_t kWhirlpoolAcHeat = 0; +const uint8_t kWhirlpoolAcAuto = 1; +const uint8_t kWhirlpoolAcCool = 2; +const uint8_t kWhirlpoolAcDry = 3; +const uint8_t kWhirlpoolAcFan = 4; +const uint8_t kWhirlpoolAcFanAuto = 0; +const uint8_t kWhirlpoolAcFanHigh = 1; +const uint8_t kWhirlpoolAcFanMedium = 2; +const uint8_t kWhirlpoolAcFanLow = 3; +const uint8_t kWhirlpoolAcMinTemp = 18; // 18C (DG11J1-3A), 16C (DG11J1-91) +const uint8_t kWhirlpoolAcMaxTemp = 32; // 32C (DG11J1-3A), 30C (DG11J1-91) +const uint8_t kWhirlpoolAcAutoTemp = 23; // 23C +const uint8_t kWhirlpoolAcCommandLight = 0x00; +const uint8_t kWhirlpoolAcCommandPower = 0x01; +const uint8_t kWhirlpoolAcCommandTemp = 0x02; +const uint8_t kWhirlpoolAcCommandSleep = 0x03; +const uint8_t kWhirlpoolAcCommandSuper = 0x04; +const uint8_t kWhirlpoolAcCommandOnTimer = 0x05; +const uint8_t kWhirlpoolAcCommandMode = 0x06; +const uint8_t kWhirlpoolAcCommandSwing = 0x07; +const uint8_t kWhirlpoolAcCommandIFeel = 0x0D; +const uint8_t kWhirlpoolAcCommandFanSpeed = 0x11; +const uint8_t kWhirlpoolAcCommand6thSense = 0x17; +const uint8_t kWhirlpoolAcCommandOffTimer = 0x1D; + +// Classes +/// Class for handling detailed Whirlpool A/C messages. +class IRWhirlpoolAc { + public: + explicit IRWhirlpoolAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_WHIRLPOOL_AC + void send(const uint16_t repeat = kWhirlpoolAcDefaultRepeat, + const bool calcchecksum = true); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_WHIRLPOOL_AC + void begin(void); + void setPowerToggle(const bool on); + bool getPowerToggle(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setSuper(const bool on); + bool getSuper(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSwing(const bool on); + bool getSwing(void) const; + void setLight(const bool on); + bool getLight(void) const; + uint16_t getClock(void) const; + void setClock(const uint16_t minspastmidnight); + uint16_t getOnTimer(void) const; + void setOnTimer(const uint16_t minspastmidnight); + void enableOnTimer(const bool on); + bool isOnTimerEnabled(void) const; + uint16_t getOffTimer(void) const; + void setOffTimer(const uint16_t minspastmidnight); + void enableOffTimer(const bool on); + bool isOffTimerEnabled(void) const; + void setCommand(const uint8_t code); + uint8_t getCommand(void) const; + whirlpool_ac_remote_model_t getModel(void) const; + void setModel(const whirlpool_ac_remote_model_t model); + uint8_t* getRaw(const bool calcchecksum = true); + void setRaw(const uint8_t new_code[], + const uint16_t length = kWhirlpoolAcStateLength); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kWhirlpoolAcStateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + WhirlpoolProtocol _; + uint8_t _desiredtemp; ///< The last user explicitly set temperature. + void checksum(const uint16_t length = kWhirlpoolAcStateLength); + void _setTemp(const uint8_t temp, const bool remember = true); + void _setMode(const uint8_t mode); + int8_t getTempOffset(void) const; +}; + +#endif // IR_WHIRLPOOL_H_ diff --git a/src/libraries/IRremoteESP8266/src/ir_Whynter.cpp b/src/libraries/IRremoteESP8266/src/ir_Whynter.cpp new file mode 100644 index 000000000..5286597cf --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Whynter.cpp @@ -0,0 +1,103 @@ +// Copyright 2009 Ken Shirriff +// Copyright 2017 David Conran + +/// @file +/// @brief Support for Whynter protocols. +/// Whynter A/C ARC-110WD added by Francesco Meschia +/// Whynter originally added from https://github.com/shirriff/Arduino-IRremote/ + +// Supports: +// Brand: Whynter, Model: ARC-110WD A/C + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kWhynterTick = 50; +const uint16_t kWhynterHdrMarkTicks = 57; +const uint16_t kWhynterHdrMark = kWhynterHdrMarkTicks * kWhynterTick; +const uint16_t kWhynterHdrSpaceTicks = 57; +const uint16_t kWhynterHdrSpace = kWhynterHdrSpaceTicks * kWhynterTick; +const uint16_t kWhynterBitMarkTicks = 15; +const uint16_t kWhynterBitMark = kWhynterBitMarkTicks * kWhynterTick; +const uint16_t kWhynterOneSpaceTicks = 43; +const uint16_t kWhynterOneSpace = kWhynterOneSpaceTicks * kWhynterTick; +const uint16_t kWhynterZeroSpaceTicks = 15; +const uint16_t kWhynterZeroSpace = kWhynterZeroSpaceTicks * kWhynterTick; +const uint16_t kWhynterMinCommandLengthTicks = 2160; // Totally made up value. +const uint32_t kWhynterMinCommandLength = + kWhynterMinCommandLengthTicks * kWhynterTick; +const uint16_t kWhynterMinGapTicks = + kWhynterMinCommandLengthTicks - + (2 * (kWhynterBitMarkTicks + kWhynterZeroSpaceTicks) + + kWhynterBits * (kWhynterBitMarkTicks + kWhynterOneSpaceTicks)); +const uint16_t kWhynterMinGap = kWhynterMinGapTicks * kWhynterTick; + +#if SEND_WHYNTER +/// Send a Whynter message. +/// Status: STABLE +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Whynter.cpp +void IRsend::sendWhynter(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + // Set IR carrier frequency + enableIROut(38); + + for (uint16_t i = 0; i <= repeat; i++) { + // (Pre-)Header + mark(kWhynterBitMark); + space(kWhynterZeroSpace); + sendGeneric( + kWhynterHdrMark, kWhynterHdrSpace, kWhynterBitMark, kWhynterOneSpace, + kWhynterBitMark, kWhynterZeroSpace, kWhynterBitMark, kWhynterMinGap, + kWhynterMinCommandLength - (kWhynterBitMark + kWhynterZeroSpace), data, + nbits, 38, true, 0, // Repeats are already handled. + 50); + } +} +#endif // SEND_WHYNTER + +#if DECODE_WHYNTER +/// Decode the supplied Whynter message. +/// Status: STABLE / Working. Strict mode is ALPHA. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Whynter.cpp +bool IRrecv::decodeWhynter(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen <= 2 * nbits + 2 * kHeader + kFooter - 1 + offset) + return false; // We don't have enough entries to possibly match. + + // Compliance + if (strict && nbits != kWhynterBits) + return false; // Incorrect nr. of bits per spec. + + uint64_t data = 0; + // Pre-Header + // Sequence begins with a bit mark and a zero space. + if (!matchMark(results->rawbuf[offset++], kWhynterBitMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kWhynterZeroSpace)) return false; + // Match Main Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kWhynterHdrMark, kWhynterHdrSpace, + kWhynterBitMark, kWhynterOneSpace, + kWhynterBitMark, kWhynterZeroSpace, + kWhynterBitMark, kWhynterMinGap, true)) return false; + // Success + results->decode_type = WHYNTER; + results->bits = nbits; + results->value = data; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_WHYNTER diff --git a/src/libraries/IRremoteESP8266/src/ir_Wowwee.cpp b/src/libraries/IRremoteESP8266/src/ir_Wowwee.cpp new file mode 100644 index 000000000..8c99e1037 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Wowwee.cpp @@ -0,0 +1,91 @@ +// Copyright 2022 David Conran + +/// @file +/// @brief Support for WowWee RoboRapter protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues1938 + +// Supports: +// Brand: WowWee, Model: RoboRapter-X + +// WowWee RoboRapter-X messages +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1938#issuecomment-1367968228 +// +// Button Code +// ====== ===== +// Left 0x180 +// Forward 0x186 +// Backward 0x187 +// Right 0x188 +// Stop 0x18E +// Head Counterclockwise 0x191 +// Tail Left 0x192 +// Tail Right 0x193 +// Head Clockwise 0x194 +// Guard Mode 0x1B0 +// Roam 0x1B1 +// Cautious Mood 0x1B2 +// Playful Mood 0x1B3 +// Hunting Mood 0x1B4 +// Demo 0x1D0 +// Bite 0x1D1 + +// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kWowweeHdrMark = 6684; +const uint16_t kWowweeHdrSpace = 723; +const uint16_t kWowweeBitMark = 912; +const uint16_t kWowweeOneSpace = 3259; +const uint16_t kWowweeZeroSpace = kWowweeHdrSpace; +const uint16_t kWowweeFreq = 38000; // Hz. (Just a guess) + + +#if SEND_WOWWEE +/// Send a WowWee formatted message. +/// Status: STABLE / Confirmed working with real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendWowwee(uint64_t data, uint16_t nbits, uint16_t repeat) { + sendGeneric(kWowweeHdrMark, kWowweeHdrSpace, + kWowweeBitMark, kWowweeOneSpace, + kWowweeBitMark, kWowweeZeroSpace, + kWowweeBitMark, kDefaultMessageGap, data, + nbits, kWowweeFreq, true, repeat, 33); +} +#endif // SEND_WOWWEE + +#if DECODE_WOWWEE +/// Decode the supplied WowWee message. +/// Status: STABLE / Confirmed working with real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +bool IRrecv::decodeWowwee(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kWowweeBits) + return false; // We expect Wowwee to be a certain sized message. + + uint64_t data = 0; + + // Match Header + Data + Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kWowweeHdrMark, kWowweeHdrSpace, + kWowweeBitMark, kWowweeOneSpace, + kWowweeBitMark, kWowweeZeroSpace, + kWowweeBitMark, kDefaultMessageGap, true)) return false; + // Success + results->bits = nbits; + results->value = data; + results->decode_type = WOWWEE; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_WOWWEE diff --git a/src/libraries/IRremoteESP8266/src/ir_Xmp.cpp b/src/libraries/IRremoteESP8266/src/ir_Xmp.cpp new file mode 100644 index 000000000..e8931fb10 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Xmp.cpp @@ -0,0 +1,227 @@ +// Copyright 2021 David Conran + +/// @file +/// @brief Support for XMP protocols. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1414 +/// @see http://www.hifi-remote.com/wiki/index.php/XMP + +// Supports: +// Brand: Xfinity, Model: XR2 remote +// Brand: Xfinity, Model: XR11 remote + + +//// #include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" +#include "minmax.h" + +// Constants +const uint16_t kXmpMark = 210; ///< uSeconds. +const uint16_t kXmpBaseSpace = 760; ///< uSeconds +const uint16_t kXmpSpaceStep = 135; ///< uSeconds +const uint16_t kXmpFooterSpace = 13000; ///< uSeconds. +const uint32_t kXmpMessageGap = 80400; ///< uSeconds. +const uint8_t kXmpWordSize = kNibbleSize; ///< nr. of Bits in a word. +const uint8_t kXmpMaxWordValue = (1 << kXmpWordSize) - 1; // Max word value. +const uint8_t kXmpSections = 2; ///< Nr. of Data sections +const uint8_t kXmpRepeatCode = 0b1000; +const uint8_t kXmpRepeatCodeAlt = 0b1001; + +using irutils::setBits; + +namespace IRXmpUtils { + /// Get the current checksum value from an XMP data section. + /// @param[in] data The value of the data section. + /// @param[in] nbits The number of data bits in the section. + /// @return The value of the stored checksum. + /// @warning Returns 0 if we can't obtain a valid checksum. + uint8_t getSectionChecksum(const uint32_t data, const uint16_t nbits) { + // The checksum is the 2nd most significant nibble of a section. + return (nbits < 2 * kNibbleSize) ? 0 : GETBITS32(data, + nbits - (2 * kNibbleSize), + kNibbleSize); + } + + /// Calculate the correct checksum value for an XMP data section. + /// @param[in] data The value of the data section. + /// @param[in] nbits The number of data bits in the section. + /// @return The value of the correct checksum. + uint8_t calcSectionChecksum(const uint32_t data, const uint16_t nbits) { + return (0xF & ~(irutils::sumNibbles(data, nbits / kNibbleSize, 0xF, false) - + getSectionChecksum(data, nbits))); + } + + /// Recalculate a XMP message code ensuring it has the checksums valid. + /// @param[in] data The value of the XMP message code. + /// @param[in] nbits The number of data bits in the entire message code. + /// @return The corrected XMP message with valid checksum sections. + uint64_t updateChecksums(const uint64_t data, const uint16_t nbits) { + const uint16_t sectionbits = nbits / kXmpSections; + uint64_t result = data; + for (uint16_t sectionOffset = 0; sectionOffset < nbits; + sectionOffset += sectionbits) { + const uint16_t checksumOffset = sectionOffset + sectionbits - + (2 * kNibbleSize); + setBits(&result, checksumOffset, kNibbleSize, + calcSectionChecksum(GETBITS64(data, sectionOffset, sectionbits), + sectionbits)); + } + return result; + } + + /// Calculate the bit offset the repeat nibble in an XMP code. + /// @param[in] nbits The number of data bits in the entire message code. + /// @return The offset to the start of the XMP repeat nibble. + uint16_t calcRepeatOffset(const uint16_t nbits) { + return (nbits < 3 * kNibbleSize) ? 0 + : (nbits / kXmpSections) - + (3 * kNibbleSize); + } + + /// Test if an XMP message code is a repeat or not. + /// @param[in] data The value of the XMP message code. + /// @param[in] nbits The number of data bits in the entire message code. + /// @return true, if it looks like a repeat, false if not. + bool isRepeat(const uint64_t data, const uint16_t nbits) { + switch (GETBITS64(data, calcRepeatOffset(nbits), kNibbleSize)) { + case kXmpRepeatCode: + case kXmpRepeatCodeAlt: + return true; + default: + return false; + } + } + + /// Adjust an XMP message code to make it a valid repeat or non-repeat code. + /// @param[in] data The value of the XMP message code. + /// @param[in] nbits The number of data bits in the entire message code. + /// @param[in] repeat_code The value of the XMP repeat nibble to use. + /// A value of `8` is the normal value for a repeat. `9` has also been seen. + /// A value of `0` will convert the code to a non-repeat code. + /// @return The valud of the modified XMP code. + uint64_t adjustRepeat(const uint64_t data, const uint16_t nbits, + const uint8_t repeat_code) { + uint64_t result = data; + setBits(&result, calcRepeatOffset(nbits), kNibbleSize, repeat_code); + return updateChecksums(result, nbits); + } +} // namespace IRXmpUtils + +using IRXmpUtils::calcSectionChecksum; +using IRXmpUtils::getSectionChecksum; +using IRXmpUtils::isRepeat; +using IRXmpUtils::adjustRepeat; + + +#if SEND_XMP +/// Send a XMP packet. +/// Status: STABLE / Confirmed working against a real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The number of bits of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendXmp(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + enableIROut(38000); + if (nbits < 2 * kXmpWordSize) return; // Too small to send, abort! + uint64_t send_data = data; + for (uint16_t r = 0; r <= repeat; r++) { + uint16_t bits_so_far = kXmpWordSize; + for (uint64_t mask = ((uint64_t)kXmpMaxWordValue) << (nbits - kXmpWordSize); + mask; + mask >>= kXmpWordSize) { + uint8_t word = (send_data & mask) >> (nbits - bits_so_far); + mark(kXmpMark); + space(kXmpBaseSpace + word * kXmpSpaceStep); + bits_so_far += kXmpWordSize; + // Are we at a data section boundary? + if ((bits_so_far - kXmpWordSize) % (nbits / kXmpSections) == 0) { // Yes. + mark(kXmpMark); + space(kXmpFooterSpace); + } + } + space(kXmpMessageGap - kXmpFooterSpace); + + // Modify the value if needed, to make it into a valid repeat code. + if (!isRepeat(send_data, nbits)) + send_data = adjustRepeat(send_data, nbits, kXmpRepeatCode); + } +} +#endif // SEND_XMP + +#if DECODE_XMP +/// Decode the supplied XMP packet/message. +/// Status: STABLE / Confirmed working against a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +bool IRrecv::decodeXmp(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint64_t data = 0; + + if (results->rawlen < 2 * (nbits / kXmpWordSize) + (kXmpSections * kFooter) + + offset - 1) + return false; // Not enough entries to ever be XMP. + + // Compliance + if (strict && nbits != kXmpBits) return false; + + // Data + // Sections + for (uint8_t section = 1; section <= kXmpSections; section++) { + for (uint16_t bits_so_far = 0; bits_so_far < nbits / kXmpSections; + bits_so_far += kXmpWordSize) { + if (!matchMarkRange(results->rawbuf[offset++], kXmpMark)) return 0; + uint8_t value = 0; + bool found = false; + for (; value <= kXmpMaxWordValue; value++) { + if (matchSpaceRange(results->rawbuf[offset], + kXmpBaseSpace + value * kXmpSpaceStep, + kXmpSpaceStep / 2, 0)) { + found = true; + break; + } + } + if (!found) return 0; // Failure. + data <<= kXmpWordSize; + data += value; + offset++; + } + // Section Footer + if (!matchMarkRange(results->rawbuf[offset++], kXmpMark)) return 0; + if (section < kXmpSections) { + if (!matchSpace(results->rawbuf[offset++], kXmpFooterSpace)) return 0; + } else { // Last section + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset++], kXmpFooterSpace)) return 0; + } + } + + // Compliance + if (strict) { + // Validate checksums. + uint64_t checksum_data = data; + const uint16_t section_size = nbits / kXmpSections; + // Each section has a checksum. + for (uint16_t section = 0; section < kXmpSections; section++) { + if (getSectionChecksum(checksum_data, section_size) != + calcSectionChecksum(checksum_data, section_size)) + return 0; + checksum_data >>= section_size; + } + } + + // Success + results->value = data; + results->decode_type = decode_type_t::XMP; + results->bits = nbits; + results->address = 0; + results->command = 0; + // See if it is a repeat message. + results->repeat = isRepeat(data, nbits); + return true; +} +#endif // DECODE_XMP diff --git a/src/libraries/IRremoteESP8266/src/ir_Zepeal.cpp b/src/libraries/IRremoteESP8266/src/ir_Zepeal.cpp new file mode 100644 index 000000000..96017226c --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/ir_Zepeal.cpp @@ -0,0 +1,94 @@ +// Copyright 2020 Christian Nilsson (nikize) + +/// @file +/// @brief Support for Zepeal protocol. +/// This protocol uses fixed length bit encoding. +/// Most official information about Zepeal seems to be from Denkyosha +/// @see https://www.denkyosha.co.jp/ + +// Supports: +// Brand: Zepeal, Model: DRT-A3311(BG) floor fan +// Brand: Zepeal, Model: DRT-A3311(BG) 5 button remote + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants + +const uint16_t kZepealHdrMark = 2330; +const uint16_t kZepealHdrSpace = 3380; +const uint16_t kZepealOneMark = 1300; +const uint16_t kZepealZeroMark = 420; +const uint16_t kZepealOneSpace = kZepealZeroMark; +const uint16_t kZepealZeroSpace = kZepealOneMark; +const uint16_t kZepealFooterMark = 420; +const uint16_t kZepealGap = 6750; + +const uint8_t kZepealTolerance = 40; + +// Signature limits possible false possitvies, +// but might need change (removal) if more devices are detected +const uint8_t kZepealSignature = 0x6C; + +// Known Zepeal DRT-A3311(BG) Buttons - documentation rather than actual usage +const uint16_t kZepealCommandSpeed = 0x6C82; +const uint16_t kZepealCommandOffOn = 0x6C81; +const uint16_t kZepealCommandRhythm = 0x6C84; +const uint16_t kZepealCommandOffTimer = 0x6C88; +const uint16_t kZepealCommandOnTimer = 0x6CC3; + +#if SEND_ZEPEAL +/// Send a Zepeal formatted message. +/// Status: STABLE / Works on real device. +/// @param[in] data The message to be sent. +/// @param[in] nbits The bit size of the message being sent. +/// @param[in] repeat The number of times the message is to be repeated. +void IRsend::sendZepeal(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kZepealHdrMark, kZepealHdrSpace, + kZepealOneMark, kZepealOneSpace, + kZepealZeroMark, kZepealZeroSpace, + kZepealFooterMark, kZepealGap, + data, nbits, 38, true, repeat, kDutyDefault); +} +#endif // SEND_ZEPEAL + +#if DECODE_ZEPEAL +/// Decode the supplied Zepeal message. +/// Status: STABLE / Works on real device. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. Typically kZepealBits. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeZepeal(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset) + return false; // Can't possibly be a valid message. + if (strict && nbits != kZepealBits) + return false; // Not strictly a message. + + uint64_t data = 0; + uint16_t used; + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kZepealHdrMark, kZepealHdrSpace, + kZepealOneMark, kZepealOneSpace, + kZepealZeroMark, kZepealZeroSpace, + kZepealFooterMark, kZepealGap, true, + kZepealTolerance); + if (!used) return false; + if (strict && (data >> 8) != kZepealSignature) return false; + + // Success + results->value = data; + results->decode_type = decode_type_t::ZEPEAL; + results->bits = nbits; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_ZEPEAL diff --git a/src/libraries/IRremoteESP8266/src/itoa.cpp b/src/libraries/IRremoteESP8266/src/itoa.cpp new file mode 100644 index 000000000..f35446d6b --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/itoa.cpp @@ -0,0 +1,79 @@ +#include +#include "itoa.h" + +//WARNING! +//from https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa + + + /* reverse: reverse string s in place */ + char* reverse(char* s) + { + int i, j; + char c; + + for (i = 0, j = strlen(s)-1; i 0); /* delete it */ + if (sign < 0) + s[i++] = '-'; + s[i] = '\0'; + return reverse(s); +} + +char* ltoa(long n, char *s, int radix) +{ + long i, sign; + + if ((sign = n) < 0) /* record sign */ + n = -n; /* make n positive */ + do { /* generate digits in reverse order */ + i = 0; + s[i++] = n % radix + '0'; /* get next digit */ + } while ((n /= radix) > 0); /* delete it */ + if (sign < 0) + s[i++] = '-'; + s[i] = '\0'; + return reverse(s); +} + +char* utoa(unsigned n, char *s, int radix) +{ + int i; + + do { /* generate digits in reverse order */ + i = 0; + s[i++] = n % radix + '0'; /* get next digit */ + } while ((n /= radix) > 0); /* delete it */ + s[i] = '\0'; + return reverse(s); +} + + +char* ultoa(unsigned long n, char *s, int radix) +{ + int i; + + do { /* generate digits in reverse order */ + i = 0; + s[i++] = n % radix + '0'; /* get next digit */ + } while ((n /= radix) > 0); /* delete it */ + s[i] = '\0'; + return reverse(s); +} + diff --git a/src/libraries/IRremoteESP8266/src/itoa.h b/src/libraries/IRremoteESP8266/src/itoa.h new file mode 100644 index 000000000..a3cde1037 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/itoa.h @@ -0,0 +1,26 @@ +/* + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + + +char* itoa(int value, char *string, int radix); +char* ltoa(long value, char *string, int radix); +char* utoa(unsigned value, char *string, int radix); +char* ultoa(unsigned long value, char *string, int radix); + diff --git a/src/libraries/IRremoteESP8266/src/locale/README.md b/src/libraries/IRremoteESP8266/src/locale/README.md new file mode 100644 index 000000000..b8689f34a --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/README.md @@ -0,0 +1,97 @@ +# Internationalisation (I18N) & Locale Files + +This directory contains the files used by the library to store the text it uses. If you want to add support for a language, this is the +correct place. If you are adding text strings to a routine, you should use the ones here. + +## Changing the language/locale used by the library. +There are several ways to change which locale file is used by the library. Use which ever one suits your needs best. +To keep the space used by the library to a minimum, all methods require the change to happen at compile time. +There is _no_ runtime option to change locales. + +### Change `_IR_LOCALE_` in the `src/IRremoteESP8266.h` file. +In the [IRremoteESP8266.h](../IRremoteESP8266.h#L57-L59) file, find and locate the lines that look like: +```c++ +#ifndef _IR_LOCALE_ +#define _IR_LOCALE_ en-AU +#endif // _IR_LOCALE_ +``` + +Change `en-AU` to the language & country that best suits your needs. e.g. `de-DE` for Germany/German. + +### Use a compile-time build flag. +Use the compiler flag: `-D_IR_LOCALE_=en-AU` when compiling the library. Especially when compiling the `IRtext.cpp` file. +Change `en-AU` to a value which matches one of the file names in this directory. e.g. `de-DE` for Germany/German, which will use +the `de_DE.h` file. + +### Use the appropriate pre-prepared build environment. _(PlatformIO only)_ +If you examine the `platformio.ini` file located in the same directory as the example code you may find pre-setup compile environments +for the different supported locales. +Choose the appropriate one for you language by asking PlatformIO to build or upload using that environment. +e.g. See `IRrecvDumpV2`'s [platformio.ini](../../examples/IRrecvDumpV2/platformio.ini) + +### Use a custom `build_flags`. _(PlatformIO only)_ +Edit the `platformio.ini` file in the directory containing your example/source code. +Either in the default PlatformIO environment (`[env]`), or in which ever PlatformIO environment you using, +change or add the following line: +``` +build_flags = -D_IR_LOCALE_=en-AU ; Or use which ever locale variable you want. +``` + +Every time you change that line, you should do a `pio clean` or choose the `clean` option from the build menu, to ensure a fresh copy +of `IRtext.o` is created. + +## Adding support for a new locale/language. + +Only [ASCII](https://en.wikipedia.org/wiki/ASCII#8-bit_codes)/[UTF-8](https://en.wikipedia.org/wiki/UTF-8) 8-bit characters are supported. +[Unicode](https://en.wikipedia.org/wiki/Unicode) is **not** supported. Unicode may work. It may not. It's just not supported. +i.e. If Arduino's `Serial.print()` can handle it, it will probably work. + +### Copy/create a new locale file in this directory. +Copy [en-AU.h](en-AU.h) or which every is a closer fit for your language to `xx-YY.h` where `xx` is the [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes) for the language. +e.g. `en` is English. `de` is German etc. and `YY` is the ISO country code. e.g. `AU` is Australia. +Modify the comments and all `LOCALE_EN_AU_H_`s in the file to `LOCALE_XX_YY_H_` for your locale. + + +### Override any `#‍define` values that reside in `defaults.h` +Go through the [defaults.h](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/locale/defaults.h) file, and find any `#‍define` lines that define a macro starting with `D_` that has text +that needs to change for your locale. +Copy or create a corresponding `#‍define D_STR_HELLOWORLD "Hello World"` in your `xx-YY.h` file, and translate the text appropriately +e.g. `#‍define D_STR_HELLOWORLD "Bonjour le monde"` (French) + +Any values you `#‍define` in `xx-YY.h` will override the corresponding value in the [defaults.h](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/locale/defaults.h) file. + +### Supporting a dialect/regional variant of another _existing_ language/locale. +Similar to the previous step, if you only need to modify a small subset of the strings used in another locale file, then include the +other locale file and then make sure to `#‍undef` any strings that need to be (re-)changed. +See the [Swiss-German](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/locale/de-CH.h) for an example of how to do this. i.e. It `#‍include "locale/de-DE.h"`s the German locale, and +redefines any strings that are not standard [German](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/locale/de-DE.h). + +## Adding new text strings to the library. +If you need to add an entirely new string to the library to support some feature etc. e.g. _"Widget"_. +You should first understand how the library tries to do this such that it is easy to support different languages for it. + +1. Use a constant named `kWidgetStr` in the appropriate statement in the `.cpp` file. +2. Edit [IRtext.cpp](IRtext.cpp), and add the appropriate line for your new constant. e.g. +```c++ +String kWidgetStr = D_STR_WIDGET; +``` +The `kWidgetStr` variable will house the sole copy of the string for the entire library. This limits any duplication. +The `D_STR_WIDGET` macro will be what is targeted by the different language / locales files. + +3. Edit [locale/defaults.h](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/locale/defaults.h), and add the appropriate stanza for your new string. e.g. +```c++ +#ifndef D_STR_WIDGET +#define D_STR_WIDGET "Turbo" +#endif // D_STR_WIDGET +``` + + +4. _(Manual)_ Update [IRtext.h](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/IRtext.h), and add the appropriate line for your new constant. e.g. +```c++ +extern const String kWidgetStr; +``` +For any file that `#‍include `s this file, it will tell it that the string is stored elsewhere, +and to look for it elsewhere at the object linking stage of the build. This is what makes the string be referenced from a central location. + +4. _(Automatic)_ Run `tools/generate_irtext_h.sh` to update [IRtext.h](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/IRtext.h). +In the [src/locale](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/locale/) directory. Run the `../../tools/generate_irtext_h.sh` command. It will update the file for you automatically. diff --git a/src/libraries/IRremoteESP8266/src/locale/de-CH.h b/src/libraries/IRremoteESP8266/src/locale/de-CH.h new file mode 100644 index 000000000..875ffd394 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/de-CH.h @@ -0,0 +1,158 @@ +// Copyright 2019 - Martin (@finfinack) +// Locale/language file for German / Switzerland. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_DE_CH_H_ +#define LOCALE_DE_CH_H_ + +// Import German / Germany as default overrides. +#include "locale/de-DE.h" + +// As we have loaded another language, we need to #undef anything we need +// to update/change. + +#undef D_STR_ON +#define D_STR_ON "Ii" +#undef D_STR_OFF +#define D_STR_OFF "Us" +#undef D_STR_TOGGLE +#define D_STR_TOGGLE "Umschalte" +#undef D_STR_SLEEP +#define D_STR_SLEEP "Schlafe" +#undef D_STR_LIGHT +#define D_STR_LIGHT "Liecht" +#undef D_STR_POWERFUL +#define D_STR_POWERFUL "Starch" +#undef D_STR_QUIET +#define D_STR_QUIET "Liislig" +#undef D_STR_CLEAN +#define D_STR_CLEAN "Reinige" +#undef D_STR_PURIFY +#define D_STR_PURIFY "Frische" +#undef D_STR_HEALTH +#define D_STR_HEALTH "Gsundheit" +#undef D_STR_HUMID +#define D_STR_HUMID "Füecht" +#undef D_STR_SAVE +#define D_STR_SAVE "Speichere" +#undef D_STR_EYE +#define D_STR_EYE "Aug" +#undef D_STR_FOLLOW +#define D_STR_FOLLOW "Folge" +#undef D_STR_HOLD +#define D_STR_HOLD "Halte" +#undef D_STR_BUTTON +#define D_STR_BUTTON "Chnopf" +#undef D_STR_UP +#define D_STR_UP "Ufe" +#undef D_STR_TEMPUP +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP +#undef D_STR_DOWN +#define D_STR_DOWN "Abe" +#undef D_STR_TEMPDOWN +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN +#undef D_STR_CHANGE +#define D_STR_CHANGE "Wechsele" +#undef D_STR_MOVE +#define D_STR_MOVE "Verschiebe" +#undef D_STR_SET +#define D_STR_SET "Setze" +#undef D_STR_CANCEL +#define D_STR_CANCEL "Abbreche" +#undef D_STR_WEEKLY +#define D_STR_WEEKLY "Wüchentlich" +#undef D_STR_WEEKLYTIMER +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " " D_STR_TIMER +#undef D_STR_OUTSIDE +#define D_STR_OUTSIDE "Dusse" +#undef D_STR_LOUD +#define D_STR_LOUD "Luut" +#undef D_STR_UPPER +#define D_STR_UPPER "Obe" +#undef D_STR_LOWER +#define D_STR_LOWER "Une" +#undef D_STR_CIRCULATE +#define D_STR_CIRCULATE "Zirkuliere" +#undef D_STR_CEILING +#define D_STR_CEILING "Decki" +#undef D_STR_6THSENSE +#define D_STR_6THSENSE "6te Sinn" + +#undef D_STR_COOL +#define D_STR_COOL "Chüehle" +#undef D_STR_HEAT +#define D_STR_HEAT "Heize" +#undef D_STR_DRY +#define D_STR_DRY "Tröchne" + +#undef D_STR_MED +#define D_STR_MED "Mit" +#undef D_STR_MEDIUM +#define D_STR_MEDIUM "Mittel" + +#undef D_STR_HIGHEST +#define D_STR_HIGHEST "Höchscht" +#undef D_STR_HIGH +#define D_STR_HIGH "Höch" +#undef D_STR_HI +#define D_STR_HI "H" +#undef D_STR_MID +#define D_STR_MID "M" +#undef D_STR_MIDDLE +#define D_STR_MIDDLE "Mittel" +#undef D_STR_LOW +#define D_STR_LOW "Tüüf" +#undef D_STR_LO +#define D_STR_LO "T" +#undef D_STR_LOWEST +#define D_STR_LOWEST "Tüfschte" +#undef D_STR_MAXRIGHT +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT +#undef D_STR_RIGHTMAX_NOSPACE +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX +#undef D_STR_MAXLEFT +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT +#undef D_STR_LEFTMAX_NOSPACE +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX +#undef D_STR_CENTRE +#define D_STR_CENTRE "Mitti" +#undef D_STR_TOP +#define D_STR_TOP "Obe" +#undef D_STR_BOTTOM +#define D_STR_BOTTOM "Une" + +#undef D_STR_DAY +#define D_STR_DAY "Tag" +#undef D_STR_DAYS +#define D_STR_DAYS "Täg" +#undef D_STR_HOUR +#define D_STR_HOUR "Stund" +#undef D_STR_HOURS +#define D_STR_HOURS D_STR_HOUR "e" +#undef D_STR_MINUTE +#define D_STR_MINUTE "Minute" +#undef D_STR_MINUTES +#define D_STR_MINUTES D_STR_MINUTE +#undef D_STR_SECONDS +#define D_STR_SECONDS D_STR_SECOND +#undef D_STR_NOW +#define D_STR_NOW "Jetz" + +#undef D_STR_NO +#define D_STR_NO "Nei" + +#undef D_STR_REPEAT +#define D_STR_REPEAT "Wiederhole" + +// IRrecvDumpV2+ +#undef D_STR_TIMESTAMP +#define D_STR_TIMESTAMP "Ziitstämpfel" +#undef D_STR_IRRECVDUMP_STARTUP +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump lauft und wartet uf IR Iigab ufem Pin %d" +#undef D_WARN_BUFFERFULL +#define D_WARN_BUFFERFULL \ + "WARNUNG: IR Code isch zgross für de Buffer (>= %d). " \ + "Dem Resultat sött mer nöd vertraue bevor das behobe isch. " \ + "Bearbeite & vergrössere `kCaptureBufferSize`." + +#endif // LOCALE_DE_CH_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/de-DE.h b/src/libraries/IRremoteESP8266/src/locale/de-DE.h new file mode 100644 index 000000000..884f36350 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/de-DE.h @@ -0,0 +1,135 @@ +// Copyright 2019 - Martin (@finfinack) +// Locale/language file for German / Germany. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_DE_DE_H_ +#define LOCALE_DE_DE_H_ + +#define D_STR_UNKNOWN "UNBEKANNT" +#define D_STR_PROTOCOL "Protokoll" +#define D_STR_ON "Ein" +#define D_STR_OFF "Aus" +#define D_STR_MODE "Modus" +#define D_STR_TOGGLE "Umschalten" +#define D_STR_SLEEP "Schlafen" +#define D_STR_LIGHT "Licht" +#define D_STR_POWERFUL "Stark" +#define D_STR_QUIET "Ruhig" +#define D_STR_ECONO "Eco" +#define D_STR_BEEP "Piep" +#define D_STR_MOULD "Schimmel" +#define D_STR_CLEAN "Reinigen" +#define D_STR_PURIFY "Frischen" +#define D_STR_TIMER "Timer" +#define D_STR_ONTIMER D_STR_ON " " D_STR_TIMER +#define D_STR_OFFTIMER D_STR_OFF " " D_STR_TIMER +#define D_STR_CLOCK "Uhr" +#define D_STR_COMMAND "Befehl" +#define D_STR_HEALTH "Gesundheit" +#define D_STR_TEMP "Temp" +#define D_STR_HUMID "Feucht" +#define D_STR_SAVE "Speichern" +#define D_STR_EYE "Auge" +#define D_STR_FOLLOW "Folgen" +#define D_STR_FRESH "Frisch" +#define D_STR_HOLD "Halten" +#define D_STR_BUTTON "Knopf" +#define D_STR_NIGHT "Nacht" +#define D_STR_SILENT "Ruhig" +#define D_STR_UP "Hinauf" +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP +#define D_STR_DOWN "Hinunter" +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN +#define D_STR_CHANGE "Wechseln" +#define D_STR_MOVE "Verschieben" +#define D_STR_SET "Setzen" +#define D_STR_CANCEL "Abbrechen" +#define D_STR_COMFORT "Komfort" +#define D_STR_WEEKLY "Wöchentlich" +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " " D_STR_TIMER +#define D_STR_FAST "Schnell" +#define D_STR_SLOW "Langsam" +#define D_STR_AIRFLOW "Luftzug" +#define D_STR_STEP "Schritt" +#define D_STR_NA "N/A" +#define D_STR_OUTSIDE "Draussen" +#define D_STR_LOUD "Laut" +#define D_STR_UPPER "Oben" +#define D_STR_LOWER "Unten" +#define D_STR_BREEZE "Brise" +#define D_STR_CIRCULATE "Zirkulieren" +#define D_STR_CEILING "Decke" +#define D_STR_WALL "Wand" +#define D_STR_ROOM "Raum" +#define D_STR_6THSENSE "6ter Sinn" +#define D_STR_FIXED "Fixiert" + +#define D_STR_AUTOMATIC "Automatisch" +#define D_STR_MANUAL "Manuell" +#define D_STR_COOL "Kühlen" +#define D_STR_HEAT "Heizen" +#define D_STR_FAN "Lüfter" +#define D_STR_FANONLY "nur_lüfter" +#define D_STR_DRY "Trocken" + +#define D_STR_MED "Mit" +#define D_STR_MEDIUM "Mittel" + +#define D_STR_HIGHEST "Höchste" +#define D_STR_HIGH "Hoch" +#define D_STR_HI "H" +#define D_STR_MID "M" +#define D_STR_MIDDLE "Mittel" +#define D_STR_LOW "Tief" +#define D_STR_LO "T" +#define D_STR_LOWEST "Tiefste" +#define D_STR_RIGHT "Rechts" +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX +#define D_STR_LEFT "Links" +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX +#define D_STR_WIDE "Breit" +#define D_STR_CENTRE "Mitte" +#define D_STR_TOP "Oben" +#define D_STR_BOTTOM "Unten" + +#define D_STR_DAY "Tag" +#define D_STR_DAYS D_STR_DAY "e" +#define D_STR_HOUR "Stunde" +#define D_STR_HOURS D_STR_HOUR "n" +#define D_STR_MINUTES D_STR_MINUTE "n" +#define D_STR_SECOND "Sekunde" +#define D_STR_SECONDS D_STR_SECOND "n" +#define D_STR_NOW "Jetzt" +// These don't translate well to German as typically only 2 letter +// abbreviations are used. Hence, this is an approximation. +#define D_STR_THREELETTERDAYS "SonMonDieMitDonFreSam" + +#define D_STR_YES "Ja" +#define D_STR_NO "Nein" +#define D_STR_TRUE "Wahr" +#define D_STR_FALSE "Falsch" + +#define D_STR_REPEAT "Wiederholen" +#define D_STR_PREVIOUS "Vorher" +#define D_STR_FAHRENHEIT "Fahrenheit" +#define D_STR_CELSIUS_FAHRENHEIT D_STR_CELSIUS "/" D_STR_FAHRENHEIT +#define D_STR_DISPLAY "Anzeige" +#define D_STR_INSIDE "Innen" +#define D_STR_POWERBUTTON "Netzschalter" +#define D_STR_PREVIOUSPOWER "Vorheriger Einschaltzustand" +#define D_STR_DISPLAYTEMP "Anzeigetemperatur" + +// IRrecvDumpV2+ +#define D_STR_TIMESTAMP "Zeitstempel" +#define D_STR_LIBRARY "Bibliothek" +#define D_STR_TOLERANCE "Toleranz" +#define D_STR_MESGDESC "Nachr. Beschr." +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump läuft und wartet auf IR Eingabe auf Pin %d" +#define D_WARN_BUFFERFULL \ + "WARNUNG: IR Code ist zu gross für Buffer (>= %d). " \ + "Dem Resultat sollte nicht vertraut werden bevor das behoben ist. " \ + "Bearbeite & vergrössere `kCaptureBufferSize`." + +#endif // LOCALE_DE_DE_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/defaults.h b/src/libraries/IRremoteESP8266/src/locale/defaults.h new file mode 100644 index 000000000..0a8984270 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/defaults.h @@ -0,0 +1,1145 @@ +// Copyright 2019 - David Conran (@crankyoldgit) +// The default text to use throughout the library. +// The library will use this text if no locale (_IR_LOCALE_) is set or if +// the locale doesn't define particular values. +// If they are defined, this file should NOT override them. +// +// This file should contain a #define for every translateable/locale dependant +// string used by the library. Language specific files don't have to include +// everything. +// +// NOTE: ASCII/UTF-8 characters only. Unicode is NOT supported. +// +// The defaults are English (AU) / en-AU. Australia (AU) is pretty much the same +// as English (UK) for this libraries use case. +#ifndef LOCALE_DEFAULTS_H_ +#define LOCALE_DEFAULTS_H_ + +#ifndef D_STR_UNKNOWN +#define D_STR_UNKNOWN "UNKNOWN" +#endif // D_STR_UNKNOWN +#ifndef D_STR_PROTOCOL +#define D_STR_PROTOCOL "Protocol" +#endif // D_STR_PROTOCOL +#ifndef D_STR_POWER +#define D_STR_POWER "Power" +#endif // D_STR_POWER +#ifndef D_STR_PREVIOUS +#define D_STR_PREVIOUS "Previous" +#endif // D_STR_PREVIOUS +#ifndef D_STR_ON +#define D_STR_ON "On" +#endif // D_STR_ON +#ifndef D_STR_1 +#define D_STR_1 "1" +#endif // D_STR_1 +#ifndef D_STR_OFF +#define D_STR_OFF "Off" +#endif // D_STR_OFF +#ifndef D_STR_0 +#define D_STR_0 "0" +#endif // D_STR_0 +#ifndef D_STR_MODE +#define D_STR_MODE "Mode" +#endif // D_STR_MODE +#ifndef D_STR_TOGGLE +#define D_STR_TOGGLE "Toggle" +#endif // D_STR_TOGGLE +#ifndef D_STR_TURBO +#define D_STR_TURBO "Turbo" +#endif // D_STR_TURBO +#ifndef D_STR_SUPER +#define D_STR_SUPER "Super" +#endif // D_STR_SUPER +#ifndef D_STR_SLEEP +#define D_STR_SLEEP "Sleep" +#endif // D_STR_SLEEP +#ifndef D_STR_LIGHT +#define D_STR_LIGHT "Light" +#endif // D_STR_LIGHT +#ifndef D_STR_POWERFUL +#define D_STR_POWERFUL "Powerful" +#endif // D_STR_POWERFUL +#ifndef D_STR_QUIET +#define D_STR_QUIET "Quiet" +#endif // D_STR_QUIET +#ifndef D_STR_ECONO +#define D_STR_ECONO "Econo" +#endif // D_STR_ECONO +#ifndef D_STR_SWING +#define D_STR_SWING "Swing" +#endif // D_STR_SWING +#ifndef D_STR_SWINGH +#define D_STR_SWINGH D_STR_SWING"(H)" // Set `D_STR_SWING` first! +#endif // D_STR_SWINGH +#ifndef D_STR_SWINGV +#define D_STR_SWINGV D_STR_SWING"(V)" // Set `D_STR_SWING` first! +#endif // D_STR_SWINGV +#ifndef D_STR_BEEP +#define D_STR_BEEP "Beep" +#endif // D_STR_BEEP +#ifndef D_STR_MOULD +#define D_STR_MOULD "Mould" +#endif // D_STR_MOULD +#ifndef D_STR_CLEAN +#define D_STR_CLEAN "Clean" +#endif // D_STR_CLEAN +#ifndef D_STR_PURIFY +#define D_STR_PURIFY "Purify" +#endif // D_STR_PURIFY +#ifndef D_STR_TIMER +#define D_STR_TIMER "Timer" +#endif // D_STR_TIMER +#ifndef D_STR_ONTIMER +#define D_STR_ONTIMER D_STR_ON " " D_STR_TIMER // Set `D_STR_ON` first! +#endif // D_STR_ONTIMER +#ifndef D_STR_OFFTIMER +#define D_STR_OFFTIMER D_STR_OFF " " D_STR_TIMER // Set `D_STR_OFF` first! +#endif // D_STR_OFFTIMER +#ifndef D_STR_TIMERMODE +#define D_STR_TIMERMODE D_STR_TIMER " " D_STR_MODE // Set `D_STR_MODE` first! +#endif // D_STR_TIMERMODE +#ifndef D_STR_CLOCK +#define D_STR_CLOCK "Clock" +#endif // D_STR_CLOCK +#ifndef D_STR_COMMAND +#define D_STR_COMMAND "Command" +#endif // D_STR_COMMAND +#ifndef D_STR_XFAN +#define D_STR_XFAN "XFan" +#endif // D_STR_XFAN +#ifndef D_STR_HEALTH +#define D_STR_HEALTH "Health" +#endif // D_STR_HEALTH +#ifndef D_STR_MODEL +#define D_STR_MODEL "Model" +#endif // D_STR_MODEL +#ifndef D_STR_TEMP +#define D_STR_TEMP "Temp" +#endif // D_STR_TEMP +#ifndef D_STR_IFEEL +#define D_STR_IFEEL "IFeel" +#endif // D_STR_IFEEL +#ifndef D_STR_ISEE +#define D_STR_ISEE "ISee" +#endif // D_STR_ISEE +#ifndef D_STR_HUMID +#define D_STR_HUMID "Humid" +#endif // D_STR_HUMID +#ifndef D_STR_SAVE +#define D_STR_SAVE "Save" +#endif // D_STR_SAVE +#ifndef D_STR_EYE +#define D_STR_EYE "Eye" +#endif // D_STR_EYE +#ifndef D_STR_FOLLOW +#define D_STR_FOLLOW "Follow" +#endif // D_STR_FOLLOW +#ifndef D_STR_ION +#define D_STR_ION "Ion" +#endif // D_STR_ION +#ifndef D_STR_FRESH +#define D_STR_FRESH "Fresh" +#endif // D_STR_FRESH +#ifndef D_STR_HOLD +#define D_STR_HOLD "Hold" +#endif // D_STR_HOLD +#ifndef D_STR_8C_HEAT +#define D_STR_8C_HEAT "8C " D_STR_HEAT // Set `D_STR_HEAT` first! +#endif // D_STR_8C_HEAT +#ifndef D_STR_10C_HEAT +#define D_STR_10C_HEAT "10C " D_STR_HEAT // Set `D_STR_HEAT` first! +#endif // D_STR_10C_HEAT +#ifndef D_STR_BUTTON +#define D_STR_BUTTON "Button" +#endif // D_STR_BUTTON +#ifndef D_STR_NIGHT +#define D_STR_NIGHT "Night" +#endif // D_STR_NIGHT +#ifndef D_STR_SILENT +#define D_STR_SILENT "Silent" +#endif // D_STR_SILENT +#ifndef D_STR_FILTER +#define D_STR_FILTER "Filter" +#endif // D_STR_FILTER +#ifndef D_STR_3D +#define D_STR_3D "3D" +#endif // D_STR_3D +#ifndef D_STR_CELSIUS +#define D_STR_CELSIUS "Celsius" +#endif // D_STR_CELSIUS +#ifndef D_STR_FAHRENHEIT +#define D_STR_FAHRENHEIT "Fahrenheit" +#endif // D_STR_FAHRENHEIT +#ifndef D_STR_CELSIUS_FAHRENHEIT +#define D_STR_CELSIUS_FAHRENHEIT D_STR_CELSIUS "/" D_STR_FAHRENHEIT +#endif // D_STR_CELSIUS_FAHRENHEIT +#ifndef D_STR_UP +#define D_STR_UP "Up" +#endif // D_STR_UP +#ifndef D_STR_TEMPUP +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP // Set `D_STR_TEMP` first! +#endif // D_STR_TEMPUP +#ifndef D_STR_DOWN +#define D_STR_DOWN "Down" +#endif // D_STR_DOWN +#ifndef D_STR_TEMPDOWN +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN // Set `D_STR_TEMP` first! +#endif // D_STR_TEMPDOWN +#ifndef D_STR_CHANGE +#define D_STR_CHANGE "Change" +#endif // D_STR_CHANGE +#ifndef D_STR_START +#define D_STR_START "Start" +#endif // D_STR_START +#ifndef D_STR_STOP +#define D_STR_STOP "Stop" +#endif // D_STR_STOP +#ifndef D_STR_MOVE +#define D_STR_MOVE "Move" +#endif // D_STR_MOVE +#ifndef D_STR_SET +#define D_STR_SET "Set" +#endif // D_STR_SET +#ifndef D_STR_CANCEL +#define D_STR_CANCEL "Cancel" +#endif // D_STR_CANCEL +#ifndef D_STR_COMFORT +#define D_STR_COMFORT "Comfort" +#endif // D_STR_COMFORT +#ifndef D_STR_SENSOR +#define D_STR_SENSOR "Sensor" +#endif // D_STR_SENSOR +#ifndef D_STR_ABSENSEDETECT +#define D_STR_ABSENSEDETECT "Absense detect" +#endif // D_STR_ABSENSEDETECT +#ifndef D_STR_DIRECT +#define D_STR_DIRECT "Direct" +#endif // D_STR_DIRECT +#ifndef D_STR_INDIRECT +#define D_STR_INDIRECT "Indirect" +#endif // D_STR_INDIRECT +#ifndef D_STR_DIRECTINDIRECTMODE +#define D_STR_DIRECTINDIRECTMODE D_STR_DIRECT " / " \ +D_STR_INDIRECT " " D_STR_MODE +#endif // D_STR_DIRECTINDIRECTMODE +#ifndef D_STR_DISPLAY +#define D_STR_DISPLAY "Display" +#endif // D_STR_DISPLAY +#ifndef D_STR_WEEKLY +#define D_STR_WEEKLY "Weekly" +#endif // D_STR_WEEKLY +#ifndef D_STR_WEEKLYTIMER +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " " D_STR_TIMER // Needs `D_STR_WEEKLY`! +#endif // D_STR_WEEKLYTIMER +#ifndef D_STR_WIFI +#define D_STR_WIFI "WiFi" +#endif // D_STR_WIFI +#ifndef D_STR_LAST +#define D_STR_LAST "Last" +#endif // D_STR_LAST +#ifndef D_STR_FAST +#define D_STR_FAST "Fast" +#endif // D_STR_FAST +#ifndef D_STR_SLOW +#define D_STR_SLOW "Slow" +#endif // D_STR_SLOW +#ifndef D_STR_AIRFLOW +#define D_STR_AIRFLOW "Air Flow" +#endif // D_STR_AIRFLOW +#ifndef D_STR_STEP +#define D_STR_STEP "Step" +#endif // D_STR_STEP +#ifndef D_STR_NA +#define D_STR_NA "N/A" +#endif // D_STR_NA +#ifndef D_STR_INSIDE +#define D_STR_INSIDE "Inside" +#endif // D_STR_INSIDE +#ifndef D_STR_OUTSIDE +#define D_STR_OUTSIDE "Outside" +#endif // D_STR_OUTSIDE +#ifndef D_STR_LOUD +#define D_STR_LOUD "Loud" +#endif // D_STR_LOUD +#ifndef D_STR_UPPER +#define D_STR_UPPER "Upper" +#endif // D_STR_UPPER +#ifndef D_STR_LOWER +#define D_STR_LOWER "Lower" +#endif // D_STR_LOWER +#ifndef D_STR_BREEZE +#define D_STR_BREEZE "Breeze" +#endif // D_STR_BREEZE +#ifndef D_STR_CIRCULATE +#define D_STR_CIRCULATE "Circulate" +#endif // D_STR_CIRCULATE +#ifndef D_STR_CEILING +#define D_STR_CEILING "Ceiling" +#endif // D_STR_CEILING +#ifndef D_STR_WALL +#define D_STR_WALL "Wall" +#endif // D_STR_WALL +#ifndef D_STR_ROOM +#define D_STR_ROOM "Room" +#endif // D_STR_ROOM +#ifndef D_STR_6THSENSE +#define D_STR_6THSENSE "6th Sense" +#endif // D_STR_6THSENSE +#ifndef D_STR_ZONEFOLLOW +#define D_STR_ZONEFOLLOW "Zone Follow" +#endif // D_STR_ZONEFOLLOW +#ifndef D_STR_FIXED +#define D_STR_FIXED "Fixed" +#endif // D_STR_FIXED +#ifndef D_STR_TYPE +#define D_STR_TYPE "Type" +#endif // D_STR_TYPE +#ifndef D_STR_SPECIAL +#define D_STR_SPECIAL "Special" +#endif // D_STR_SPECIAL +#ifndef D_STR_RECYCLE +#define D_STR_RECYCLE "Recycle" +#endif // D_STR_RECYCLE +#ifndef D_STR_ID +#define D_STR_ID "Id" +#endif // D_STR_ID +#ifndef D_STR_VANE +#define D_STR_VANE "Vane" +#endif // D_STR_VANE +#ifndef D_STR_LOCK +#define D_STR_LOCK "Lock" +#endif // D_STR_LOCK +#ifndef D_STR_REPORT +#define D_STR_REPORT "Report" +#endif // D_STR_REPORT + +#ifndef D_STR_AUTO +#define D_STR_AUTO "Auto" +#endif // D_STR_AUTO +#ifndef D_STR_AUTOMATIC +#define D_STR_AUTOMATIC "Automatic" +#endif // D_STR_AUTOMATIC +#ifndef D_STR_MANUAL +#define D_STR_MANUAL "Manual" +#endif // D_STR_MANUAL +#ifndef D_STR_COOL +#define D_STR_COOL "Cool" +#endif // D_STR_COOL +#ifndef D_STR_COOLING +#define D_STR_COOLING "Cooling" +#endif // D_STR_COOLING +#ifndef D_STR_HEAT +#define D_STR_HEAT "Heat" +#endif // D_STR_HEAT +#ifndef D_STR_HEATING +#define D_STR_HEATING "Heating" +#endif // D_STR_HEATING +#ifndef D_STR_FAN +#define D_STR_FAN "Fan" +#endif // D_STR_FAN +#ifndef D_STR_FANONLY +#define D_STR_FANONLY "fan-only" +#endif // D_STR_FANONLY +#ifndef D_STR_FAN_ONLY +#define D_STR_FAN_ONLY "fan_only" +#endif // D_STR_FAN_ONLY +#ifndef D_STR_ONLY +#define D_STR_ONLY "Only" +#endif // D_STR_ONLY +#ifndef D_STR_FANSPACEONLY +#define D_STR_FANSPACEONLY D_STR_FAN " " D_STR_ONLY +#endif // D_STR_FANSPACEONLY +#ifndef D_STR_FANONLYNOSPACE +#define D_STR_FANONLYNOSPACE D_STR_FAN D_STR_ONLY +#endif // D_STR_FANONLYNOSPACE +#ifndef D_STR_DRY +#define D_STR_DRY "Dry" +#endif // D_STR_DRY +#ifndef D_STR_DRYING +#define D_STR_DRYING "Drying" +#endif // D_STR_DRYING +#ifndef D_STR_DEHUMIDIFY +#define D_STR_DEHUMIDIFY "Dehumidify" +#endif // D_STR_DEHUMIDIFY + +#ifndef D_STR_MAX +#define D_STR_MAX "Max" +#endif // D_STR_MAX +#ifndef D_STR_MAXIMUM +#define D_STR_MAXIMUM "Maximum" +#endif // D_STR_MAXIMUM +#ifndef D_STR_MIN +#define D_STR_MIN "Min" +#endif // D_STR_MIN +#ifndef D_STR_MINIMUM +#define D_STR_MINIMUM "Minimum" +#endif // D_STR_MINIMUM +#ifndef D_STR_MED +#define D_STR_MED "Med" +#endif // D_STR_MED +#ifndef D_STR_MEDIUM +#define D_STR_MEDIUM "Medium" +#endif // D_STR_MEDIUM +#ifndef D_STR_MED_HIGH +#define D_STR_MED_HIGH D_STR_MED "-" D_STR_HIGH +#endif // D_STR_MED_HIGH + +#ifndef D_STR_HIGHEST +#define D_STR_HIGHEST "Highest" +#endif // D_STR_HIGHEST +#ifndef D_STR_HIGH +#define D_STR_HIGH "High" +#endif // D_STR_HIGH +#ifndef D_STR_HI +#define D_STR_HI "Hi" +#endif // D_STR_HI +#ifndef D_STR_MID +#define D_STR_MID "Mid" +#endif // D_STR_MID +#ifndef D_STR_MIDDLE +#define D_STR_MIDDLE "Middle" +#endif // D_STR_MIDDLE +#ifndef D_STR_LOW +#define D_STR_LOW "Low" +#endif // D_STR_LOW +#ifndef D_STR_LO +#define D_STR_LO "Lo" +#endif // D_STR_LO +#ifndef D_STR_LOWEST +#define D_STR_LOWEST "Lowest" +#endif // D_STR_LOWEST +#ifndef D_STR_RIGHT +#define D_STR_RIGHT "Right" +#endif // D_STR_RIGHT +#ifndef D_STR_MAXRIGHT +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT // Set `D_STR_MAX` first! +#endif // D_STR_MAXRIGHT +#ifndef D_STR_MAXRIGHT_NOSPACE +#define D_STR_MAXRIGHT_NOSPACE D_STR_MAX D_STR_RIGHT // Set `D_STR_MAX` first! +#endif // D_STR_MAXRIGHT_NOSPACE +#ifndef D_STR_RIGHTMAX +#define D_STR_RIGHTMAX D_STR_RIGHT " " D_STR_MAX // Set `D_STR_MAX` first! +#endif // D_STR_RIGHTMAX +#ifndef D_STR_RIGHTMAX_NOSPACE +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX // Set `D_STR_MAX` first! +#endif // D_STR_RIGHTMAX_NOSPACE +#ifndef D_STR_LEFT +#define D_STR_LEFT "Left" +#endif // D_STR_LEFT +#ifndef D_STR_MAXLEFT +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT // Set `D_STR_MAX` first! +#endif // D_STR_MAXLEFT +#ifndef D_STR_MAXLEFT_NOSPACE +#define D_STR_MAXLEFT_NOSPACE D_STR_MAX D_STR_LEFT // Set `D_STR_MAX` first! +#endif // D_STR_MAXLEFT_NOSPACE +#ifndef D_STR_LEFTMAX +#define D_STR_LEFTMAX D_STR_LEFT " " D_STR_MAX // Set `D_STR_MAX` first! +#endif // D_STR_LEFTMAX +#ifndef D_STR_LEFTMAX_NOSPACE +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX // Set `D_STR_MAX` first! +#endif // D_STR_LEFTMAX_NOSPACE +#ifndef D_STR_WIDE +#define D_STR_WIDE "Wide" +#endif // D_STR_WIDE +#ifndef D_STR_CENTRE +#define D_STR_CENTRE "Centre" +#endif // D_STR_CENTRE +#ifndef D_STR_TOP +#define D_STR_TOP "Top" +#endif // D_STR_TOP +#ifndef D_STR_BOTTOM +#define D_STR_BOTTOM "Bottom" +#endif // D_STR_BOTTOM +#ifndef D_STR_UPPER_MIDDLE +#define D_STR_UPPER_MIDDLE D_STR_UPPER "-" D_STR_MIDDLE +#endif // D_STR_UPPER_MIDDLE +#ifndef D_STR_CONFIG +#define D_STR_CONFIG "Config" +#endif // D_STR_CONFIG +#ifndef D_STR_CONTROL +#define D_STR_CONTROL "Control" +#endif // D_STR_CONTROL +#ifndef D_STR_SET_TIMER +#define D_STR_SET_TIMER D_STR_SET " " D_STR_TIMER +#endif // D_STR_AC_TIMER +#ifndef D_STR_SCHEDULE +#define D_STR_SCHEDULE "Schedule" +#endif // D_STR_SCHEDULE +#ifndef D_STR_CH +#define D_STR_CH "CH#" +#endif // D_STR_CH +#ifndef D_STR_TIMER_ACTIVE_DAYS +#define D_STR_TIMER_ACTIVE_DAYS "TimerActiveDays" +#endif // D_STR_TIMER_ACTIVE_DAYS +#ifndef D_STR_KEY +#define D_STR_KEY "Key" +#endif // D_STR_KEY +#ifndef D_STR_VALUE +#define D_STR_VALUE "Value" +#endif // D_STR_VALUE + +// Compound words/phrases/descriptions from pre-defined words. +// Note: Obviously these need to be defined *after* their component words. +#ifndef D_STR_ECONOTOGGLE +#define D_STR_ECONOTOGGLE D_STR_ECONO " " D_STR_TOGGLE +#endif // D_STR_ECONOTOGGLE +#ifndef D_STR_EYEAUTO +#define D_STR_EYEAUTO D_STR_EYE " " D_STR_AUTO +#endif // D_STR_EYEAUTO +#ifndef D_STR_LIGHTTOGGLE +#define D_STR_LIGHTTOGGLE D_STR_LIGHT " " D_STR_TOGGLE +#endif // D_STR_LIGHTTOGGLE +#ifndef D_STR_OUTSIDEQUIET +#define D_STR_OUTSIDEQUIET D_STR_OUTSIDE " " D_STR_QUIET +#endif // D_STR_OUTSIDEQUIET +#ifndef D_STR_POWERTOGGLE +#define D_STR_POWERTOGGLE D_STR_POWER " " D_STR_TOGGLE +#endif // D_STR_POWERTOGGLE +#ifndef D_STR_POWERBUTTON +#define D_STR_POWERBUTTON D_STR_POWER " " D_STR_BUTTON +#endif // D_STR_POWERBUTTON +#ifndef D_STR_PREVIOUSPOWER +#define D_STR_PREVIOUSPOWER D_STR_PREVIOUS " " D_STR_POWER +#endif // D_STR_PREVIOUSPOWER +#ifndef D_STR_DISPLAYTEMP +#define D_STR_DISPLAYTEMP D_STR_DISPLAY " " D_STR_TEMP +#endif // D_STR_DISPLAYTEMP +#ifndef D_STR_IFEELREPORT +#define D_STR_IFEELREPORT D_STR_IFEEL " " D_STR_REPORT +#endif // D_STR_IFEELREPORT +#ifndef D_STR_SENSORTEMP +#define D_STR_SENSORTEMP D_STR_SENSOR " " D_STR_TEMP +#endif // D_STR_SENSORTEMP +#ifndef D_STR_SLEEP_TIMER +#define D_STR_SLEEP_TIMER D_STR_SLEEP " " D_STR_TIMER +#endif // D_STR_SLEEP_TIMER +#ifndef D_STR_SWINGVMODE +#define D_STR_SWINGVMODE D_STR_SWINGV " " D_STR_MODE +#endif // D_STR_SWINGVMODE +#ifndef D_STR_SWINGVTOGGLE +#define D_STR_SWINGVTOGGLE D_STR_SWINGV " " D_STR_TOGGLE +#endif // D_STR_SWINGVTOGGLE +#ifndef D_STR_TURBOTOGGLE +#define D_STR_TURBOTOGGLE D_STR_TURBO " " D_STR_TOGGLE +#endif // D_STR_TURBOTOGGLE + +// Separators +#ifndef D_CHR_TIME_SEP +#define D_CHR_TIME_SEP ':' +#endif // D_CHR_TIME_SEP +#ifndef D_STR_SPACELBRACE +#define D_STR_SPACELBRACE " (" +#endif // D_STR_SPACELBRACE +#ifndef D_STR_COMMASPACE +#define D_STR_COMMASPACE ", " +#endif // D_STR_COMMASPACE +#ifndef D_STR_COLONSPACE +#define D_STR_COLONSPACE ": " +#endif // D_STR_COLONSPACE +#ifndef D_STR_DASH +#define D_STR_DASH "-" +#endif // D_STR_DASH + +#ifndef D_STR_DAY +#define D_STR_DAY "Day" +#endif // D_STR_DAY +#ifndef D_STR_DAYS +#define D_STR_DAYS D_STR_DAY "s" +#endif // D_STR_DAYS +#ifndef D_STR_HOUR +#define D_STR_HOUR "Hour" +#endif // D_STR_HOUR +#ifndef D_STR_HOURS +#define D_STR_HOURS D_STR_HOUR "s" +#endif // D_STR_HOURS +#ifndef D_STR_MINUTE +#define D_STR_MINUTE "Minute" +#endif // D_STR_MINUTE +#ifndef D_STR_MINUTES +#define D_STR_MINUTES D_STR_MINUTE "s" +#endif // D_STR_MINUTES +#ifndef D_STR_SECOND +#define D_STR_SECOND "Second" +#endif // D_STR_SECOND +#ifndef D_STR_SECONDS +#define D_STR_SECONDS D_STR_SECOND "s" +#endif // D_STR_SECONDS +#ifndef D_STR_NOW +#define D_STR_NOW "Now" +#endif // D_STR_NOW +#ifndef D_STR_THREELETTERDAYS +#define D_STR_THREELETTERDAYS "SunMonTueWedThuFriSat" +#endif // D_STR_THREELETTERDAYS + +#ifndef D_STR_YES +#define D_STR_YES "Yes" +#endif // D_STR_YES +#ifndef D_STR_NO +#define D_STR_NO "No" +#endif // D_STR_NO +#ifndef D_STR_TRUE +#define D_STR_TRUE "True" +#endif // D_STR_TRUE +#ifndef D_STR_FALSE +#define D_STR_FALSE "False" +#endif // D_STR_FALSE + +#ifndef D_STR_REPEAT +#define D_STR_REPEAT "Repeat" +#endif // D_STR_REPEAT +#ifndef D_STR_CODE +#define D_STR_CODE "Code" +#endif // D_STR_CODE +#ifndef D_STR_BITS +#define D_STR_BITS "Bits" +#endif // D_STR_BITS + +// Model Names +#ifndef D_STR_YAW1F +#define D_STR_YAW1F "YAW1F" +#endif // D_STR_YAW1F +#ifndef D_STR_YBOFB +#define D_STR_YBOFB "YBOFB" +#endif // D_STR_YBOFB +#ifndef D_STR_YX1FSF +#define D_STR_YX1FSF "YX1FSF" +#endif // D_STR_YX1FSF +#ifndef D_STR_V9014557_A +#define D_STR_V9014557_A "V9014557-A" +#endif // D_STR_V9014557_A +#ifndef D_STR_V9014557_B +#define D_STR_V9014557_B "V9014557-B" +#endif // D_STR_V9014557_B +#ifndef D_STR_RLT0541HTA_A +#define D_STR_RLT0541HTA_A "R-LT0541-HTA-A" +#endif // D_STR_RLT0541HTA_A +#ifndef D_STR_RLT0541HTA_B +#define D_STR_RLT0541HTA_B "R-LT0541-HTA-B" +#endif // D_STR_RLT0541HTA_B +#ifndef D_STR_ARRAH2E +#define D_STR_ARRAH2E "ARRAH2E" +#endif // D_STR_ARRAH2E +#ifndef D_STR_ARDB1 +#define D_STR_ARDB1 "ARDB1" +#endif // D_STR_ARDB1 +#ifndef D_STR_ARREB1E +#define D_STR_ARREB1E "ARREB1E" +#endif // D_STR_ARREB1E +#ifndef D_STR_ARJW2 +#define D_STR_ARJW2 "ARJW2" +#endif // D_STR_ARJW2 +#ifndef D_STR_ARRY4 +#define D_STR_ARRY4 "ARRY4" +#endif // D_STR_ARRY4 +#ifndef D_STR_ARREW4E +#define D_STR_ARREW4E "ARREW4E" +#endif // D_STR_ARREW4E +#ifndef D_STR_GE6711AR2853M +#define D_STR_GE6711AR2853M "GE6711AR2853M" +#endif // D_STR_GE6711AR2853M +#ifndef D_STR_AKB75215403 +#define D_STR_AKB75215403 "AKB75215403" +#endif // D_STR_AKB75215403 +#ifndef D_STR_AKB74955603 +#define D_STR_AKB74955603 "AKB74955603" +#endif // D_STR_AKB74955603 +#ifndef D_STR_AKB73757604 +#define D_STR_AKB73757604 "AKB73757604" +#endif // D_STR_AKB73757604 +#ifndef D_STR_LG6711A20083V +#define D_STR_LG6711A20083V "LG6711A20083V" +#endif // D_STR_LG6711A20083V +#ifndef D_STR_KKG9AC1 +#define D_STR_KKG9AC1 "KKG9AC1" +#endif // D_STR_KKG9AC1 +#ifndef D_STR_KKG29AC1 +#define D_STR_KKG29AC1 "KKG29AC1" +#endif // D_STR_KKG9AC1 +#ifndef D_STR_LKE +#define D_STR_LKE "LKE" +#endif // D_STR_LKE +#ifndef D_STR_NKE +#define D_STR_NKE "NKE" +#endif // D_STR_NKE +#ifndef D_STR_DKE +#define D_STR_DKE "DKE" +#endif // D_STR_DKE +#ifndef D_STR_PKR +#define D_STR_PKR "PKR" +#endif // D_STR_PKR +#ifndef D_STR_JKE +#define D_STR_JKE "JKE" +#endif // D_STR_JKE +#ifndef D_STR_CKP +#define D_STR_CKP "CKP" +#endif // D_STR_CKP +#ifndef D_STR_RKR +#define D_STR_RKR "RKR" +#endif // D_STR_RKR +#ifndef D_STR_PANASONICLKE +#define D_STR_PANASONICLKE "PANASONICLKE" +#endif // D_STR_PANASONICLKE +#ifndef D_STR_PANASONICNKE +#define D_STR_PANASONICNKE "PANASONICNKE" +#endif // D_STR_PANASONICNKE +#ifndef D_STR_PANASONICDKE +#define D_STR_PANASONICDKE "PANASONICDKE" +#endif // D_STR_PANASONICDKE +#ifndef D_STR_PANASONICPKR +#define D_STR_PANASONICPKR "PANASONICPKR" +#endif // D_STR_PANASONICPKR +#ifndef D_STR_PANASONICJKE +#define D_STR_PANASONICJKE "PANASONICJKE" +#endif // D_STR_PANASONICJKE +#ifndef D_STR_PANASONICCKP +#define D_STR_PANASONICCKP "PANASONICCKP" +#endif // D_STR_PANASONICCKP +#ifndef D_STR_PANASONICRKR +#define D_STR_PANASONICRKR "PANASONICRKR" +#endif // D_STR_PANASONICRKR +#ifndef D_STR_A907 +#define D_STR_A907 "A907" +#endif // D_STR_A907 +#ifndef D_STR_A705 +#define D_STR_A705 "A705" +#endif // D_STR_A705 +#ifndef D_STR_A903 +#define D_STR_A903 "A903" +#endif // D_STR_A903 +#ifndef D_STR_TAC09CHSD +#define D_STR_TAC09CHSD "TAC09CHSD" +#endif // D_STR_TAC09CHSD +#ifndef D_STR_GZ055BE1 +#define D_STR_GZ055BE1 "GZ055BE1" +#endif // D_STR_GZ055BE1 +#ifndef D_STR_122LZF +#define D_STR_122LZF "122LZF" +#endif // D_STR_122LZF +#ifndef D_STR_DG11J13A +#define D_STR_DG11J13A "DG11J13A" +#endif // D_STR_DG11J13A +#ifndef D_STR_DG11J104 +#define D_STR_DG11J104 "DG11J104" +#endif // D_STR_DG11J104 +#ifndef D_STR_DG11J191 +#define D_STR_DG11J191 "DG11J191" +#endif // D_STR_DG11J191 +#ifndef D_STR_ARGO_WREM2 +#define D_STR_ARGO_WREM2 "WREM2" +#endif // D_STR_ARGO_WREM2 +#ifndef D_STR_ARGO_WREM3 +#define D_STR_ARGO_WREM3 "WREM3" +#endif // D_STR_ARGO_WREM3 + +// Protocols Names +#ifndef D_STR_AIRTON +#define D_STR_AIRTON "AIRTON" +#endif // D_STR_AIRTON +#ifndef D_STR_AIRWELL +#define D_STR_AIRWELL "AIRWELL" +#endif // D_STR_AIRWELL +#ifndef D_STR_AIWA_RC_T501 +#define D_STR_AIWA_RC_T501 "AIWA_RC_T501" +#endif // D_STR_AIWA_RC_T501 +#ifndef D_STR_AMCOR +#define D_STR_AMCOR "AMCOR" +#endif // D_STR_AMCOR +#ifndef D_STR_ARGO +#define D_STR_ARGO "ARGO" +#endif // D_STR_ARGO +#ifndef D_STR_ARRIS +#define D_STR_ARRIS "ARRIS" +#endif // D_STR_ARRIS +#ifndef D_STR_BOSCH +#define D_STR_BOSCH "BOSCH" +#endif // D_STR_BOSCH +#ifndef D_STR_BOSCH144 +#define D_STR_BOSCH144 D_STR_BOSCH "144" +#endif // D_STR_BOSCH144 +#ifndef D_STR_BOSE +#define D_STR_BOSE "BOSE" +#endif // D_STR_BOSE +#ifndef D_STR_CARRIER_AC +#define D_STR_CARRIER_AC "CARRIER_AC" +#endif // D_STR_CARRIER_AC +#ifndef D_STR_CARRIER_AC40 +#define D_STR_CARRIER_AC40 D_STR_CARRIER_AC "40" +#endif // D_STR_CARRIER_AC40 +#ifndef D_STR_CARRIER_AC64 +#define D_STR_CARRIER_AC64 D_STR_CARRIER_AC "64" +#endif // D_STR_CARRIER_AC64 +#ifndef D_STR_CARRIER_AC84 +#define D_STR_CARRIER_AC84 D_STR_CARRIER_AC "84" +#endif // D_STR_CARRIER_AC84 +#ifndef D_STR_CARRIER_AC128 +#define D_STR_CARRIER_AC128 D_STR_CARRIER_AC "128" +#endif // D_STR_CARRIER_AC128 +#ifndef D_STR_CLIMABUTLER +#define D_STR_CLIMABUTLER "CLIMABUTLER" +#endif // D_STR_CLIMABUTLER +#ifndef D_STR_COOLIX +#define D_STR_COOLIX "COOLIX" +#endif // D_STR_COOLIX +#ifndef D_STR_COOLIX48 +#define D_STR_COOLIX48 D_STR_COOLIX "48" +#endif // D_STR_COOLIX48 +#ifndef D_STR_CORONA_AC +#define D_STR_CORONA_AC "CORONA_AC" +#endif // D_STR_CORONA_AC +#ifndef D_STR_DAIKIN +#define D_STR_DAIKIN "DAIKIN" +#endif // D_STR_DAIKIN +#ifndef D_STR_DAIKIN128 +#define D_STR_DAIKIN128 D_STR_DAIKIN "128" +#endif // D_STR_DAIKIN128 +#ifndef D_STR_DAIKIN152 +#define D_STR_DAIKIN152 D_STR_DAIKIN "152" +#endif // D_STR_DAIKIN152 +#ifndef D_STR_DAIKIN160 +#define D_STR_DAIKIN160 D_STR_DAIKIN "160" +#endif // D_STR_DAIKIN160 +#ifndef D_STR_DAIKIN176 +#define D_STR_DAIKIN176 D_STR_DAIKIN "176" +#endif // D_STR_DAIKIN176 +#ifndef D_STR_DAIKIN2 +#define D_STR_DAIKIN2 D_STR_DAIKIN "2" +#endif // D_STR_DAIKIN2 +#ifndef D_STR_DAIKIN200 +#define D_STR_DAIKIN200 D_STR_DAIKIN "200" +#endif // D_STR_DAIKIN200 +#ifndef D_STR_DAIKIN216 +#define D_STR_DAIKIN216 D_STR_DAIKIN "216" +#endif // D_STR_DAIKIN216 +#ifndef D_STR_DAIKIN312 +#define D_STR_DAIKIN312 D_STR_DAIKIN "312" +#endif // D_STR_DAIKIN312 +#ifndef D_STR_DAIKIN64 +#define D_STR_DAIKIN64 D_STR_DAIKIN "64" +#endif // D_STR_DAIKIN64 +#ifndef D_STR_DELONGHI_AC +#define D_STR_DELONGHI_AC "DELONGHI_AC" +#endif // D_STR_DELONGHI_AC +#ifndef D_STR_DENON +#define D_STR_DENON "DENON" +#endif // D_STR_DENON +#ifndef D_STR_DISH +#define D_STR_DISH "DISH" +#endif // D_STR_DISH +#ifndef D_STR_DOSHISHA +#define D_STR_DOSHISHA "DOSHISHA" +#endif // D_STR_DOSHISHA +#ifndef D_STR_ECOCLIM +#define D_STR_ECOCLIM "ECOCLIM" +#endif // D_STR_ECOCLIM +#ifndef D_STR_ELECTRA_AC +#define D_STR_ELECTRA_AC "ELECTRA_AC" +#endif // D_STR_ELECTRA_AC +#ifndef D_STR_ELITESCREENS +#define D_STR_ELITESCREENS "ELITESCREENS" +#endif // D_STR_ELITESCREENS +#ifndef D_STR_EPSON +#define D_STR_EPSON "EPSON" +#endif // D_STR_EPSON +#ifndef D_STR_FUJITSU_AC +#define D_STR_FUJITSU_AC "FUJITSU_AC" +#endif // D_STR_FUJITSU_AC +#ifndef D_STR_GICABLE +#define D_STR_GICABLE "GICABLE" +#endif // D_STR_GICABLE +#ifndef D_STR_GLOBALCACHE +#define D_STR_GLOBALCACHE "GLOBALCACHE" +#endif // D_STR_GLOBALCACHE +#ifndef D_STR_GOODWEATHER +#define D_STR_GOODWEATHER "GOODWEATHER" +#endif // D_STR_GOODWEATHER +#ifndef D_STR_GORENJE +#define D_STR_GORENJE "GORENJE" +#endif // D_STR_GORENJE +#ifndef D_STR_GREE +#define D_STR_GREE "GREE" +#endif // D_STR_GREE +#ifndef D_STR_HAIER_AC +#define D_STR_HAIER_AC "HAIER_AC" +#endif // D_STR_HAIER_AC +#ifndef D_STR_HAIER_AC_YRW02 +#define D_STR_HAIER_AC_YRW02 D_STR_HAIER_AC "_YRW02" +#endif // D_STR_HAIER_AC_YRW02 +#ifndef D_STR_HAIER_AC160 +#define D_STR_HAIER_AC160 D_STR_HAIER_AC "160" +#endif // D_STR_HAIER_AC160 +#ifndef D_STR_HAIER_AC176 +#define D_STR_HAIER_AC176 D_STR_HAIER_AC "176" +#endif // D_STR_HAIER_AC176 +#ifndef D_STR_HITACHI_AC +#define D_STR_HITACHI_AC "HITACHI_AC" +#endif // D_STR_HITACHI_AC +#ifndef D_STR_HITACHI_AC1 +#define D_STR_HITACHI_AC1 D_STR_HITACHI_AC "1" +#endif // D_STR_HITACHI_AC1 +#ifndef D_STR_HITACHI_AC2 +#define D_STR_HITACHI_AC2 D_STR_HITACHI_AC "2" +#endif // D_STR_HITACHI_AC2 +#ifndef D_STR_HITACHI_AC3 +#define D_STR_HITACHI_AC3 D_STR_HITACHI_AC "3" +#endif // D_STR_HITACHI_AC3 +#ifndef D_STR_HITACHI_AC264 +#define D_STR_HITACHI_AC264 D_STR_HITACHI_AC "264" +#endif // D_STR_HITACHI_AC264 +#ifndef D_STR_HITACHI_AC296 +#define D_STR_HITACHI_AC296 D_STR_HITACHI_AC "296" +#endif // D_STR_HITACHI_AC296 +#ifndef D_STR_HITACHI_AC344 +#define D_STR_HITACHI_AC344 D_STR_HITACHI_AC "344" +#endif // D_STR_HITACHI_AC344 +#ifndef D_STR_HITACHI_AC424 +#define D_STR_HITACHI_AC424 D_STR_HITACHI_AC "424" +#endif // D_STR_HITACHI_AC424 +#ifndef D_STR_INAX +#define D_STR_INAX "INAX" +#endif // D_STR_INAX +#ifndef D_STR_JVC +#define D_STR_JVC "JVC" +#endif // D_STR_JVC +#ifndef D_STR_KELON +#define D_STR_KELON "KELON" +#endif // D_STR_KELON +#ifndef D_STR_KELON168 +#define D_STR_KELON168 D_STR_KELON "168" +#endif // D_STR_KELON168 +#ifndef D_STR_KELVINATOR +#define D_STR_KELVINATOR "KELVINATOR" +#endif // D_STR_KELVINATOR +#ifndef D_STR_LASERTAG +#define D_STR_LASERTAG "LASERTAG" +#endif // D_STR_LASERTAG +#ifndef D_STR_LEGOPF +#define D_STR_LEGOPF "LEGOPF" +#endif // D_STR_LEGOPF +#ifndef D_STR_LG +#define D_STR_LG "LG" +#endif // D_STR_LG +#ifndef D_STR_LG2 +#define D_STR_LG2 "LG2" +#endif // D_STR_LG2 +#ifndef D_STR_LUTRON +#define D_STR_LUTRON "LUTRON" +#endif // D_STR_LUTRON +#ifndef D_STR_MAGIQUEST +#define D_STR_MAGIQUEST "MAGIQUEST" +#endif // D_STR_MAGIQUEST +#ifndef D_STR_METZ +#define D_STR_METZ "METZ" +#endif // D_STR_METZ +#ifndef D_STR_MIDEA +#define D_STR_MIDEA "MIDEA" +#endif // D_STR_MIDEA +#ifndef D_STR_MIDEA24 +#define D_STR_MIDEA24 "MIDEA24" +#endif // D_STR_MIDEA24 +#ifndef D_STR_MILESTAG2 +#define D_STR_MILESTAG2 "MILESTAG2" +#endif // D_STR_MILESTAG2 +#ifndef D_STR_MIRAGE +#define D_STR_MIRAGE "MIRAGE" +#endif // D_STR_MIRAGE +#ifndef D_STR_MITSUBISHI +#define D_STR_MITSUBISHI "MITSUBISHI" +#endif // D_STR_MITSUBISHI +#ifndef D_STR_MITSUBISHI112 +#define D_STR_MITSUBISHI112 "MITSUBISHI112" +#endif // D_STR_MITSUBISHI112 +#ifndef D_STR_MITSUBISHI136 +#define D_STR_MITSUBISHI136 "MITSUBISHI136" +#endif // D_STR_MITSUBISHI136 +#ifndef D_STR_MITSUBISHI2 +#define D_STR_MITSUBISHI2 "MITSUBISHI2" +#endif // D_STR_MITSUBISHI2 +#ifndef D_STR_MITSUBISHI_AC +#define D_STR_MITSUBISHI_AC "MITSUBISHI_AC" +#endif // D_STR_MITSUBISHI_AC +#ifndef D_STR_MITSUBISHI_HEAVY_152 +#define D_STR_MITSUBISHI_HEAVY_152 "MITSUBISHI_HEAVY_152" +#endif // D_STR_MITSUBISHI_HEAVY_152 +#ifndef D_STR_MITSUBISHI_HEAVY_88 +#define D_STR_MITSUBISHI_HEAVY_88 "MITSUBISHI_HEAVY_88" +#endif // D_STR_MITSUBISHI_HEAVY_88 +#ifndef D_STR_MULTIBRACKETS +#define D_STR_MULTIBRACKETS "MULTIBRACKETS" +#endif // D_STR_MULTIBRACKETS +#ifndef D_STR_MWM +#define D_STR_MWM "MWM" +#endif // D_STR_MWM +#ifndef D_STR_NEC +#define D_STR_NEC "NEC" +#endif // D_STR_NEC +#ifndef D_STR_NEC_LIKE +#define D_STR_NEC_LIKE D_STR_NEC "_LIKE" +#endif // D_STR_NEC_LIKE +#ifndef D_STR_NEC_NON_STRICT +#define D_STR_NEC_NON_STRICT D_STR_NEC " (NON-STRICT)" +#endif // D_STR_NEC_NON_STRICT +#ifndef D_STR_NEOCLIMA +#define D_STR_NEOCLIMA "NEOCLIMA" +#endif // D_STR_NEOCLIMA +#ifndef D_STR_NIKAI +#define D_STR_NIKAI "NIKAI" +#endif // D_STR_NIKAI +#ifndef D_STR_PANASONIC +#define D_STR_PANASONIC "PANASONIC" +#endif // D_STR_PANASONIC +#ifndef D_STR_PANASONIC_AC +#define D_STR_PANASONIC_AC "PANASONIC_AC" +#endif // D_STR_PANASONIC_AC +#ifndef D_STR_PANASONIC_AC32 +#define D_STR_PANASONIC_AC32 D_STR_PANASONIC_AC"32" +#endif // D_STR_PANASONIC_AC32 +#ifndef D_STR_PIONEER +#define D_STR_PIONEER "PIONEER" +#endif // D_STR_PIONEER +#ifndef D_STR_PRONTO +#define D_STR_PRONTO "PRONTO" +#endif // D_STR_PRONTO +#ifndef D_STR_RAW +#define D_STR_RAW "RAW" +#endif // D_STR_RAW +#ifndef D_STR_RC5 +#define D_STR_RC5 "RC5" +#endif // D_STR_RC5 +#ifndef D_STR_RC5X +#define D_STR_RC5X "RC5X" +#endif // D_STR_RC5X +#ifndef D_STR_RC6 +#define D_STR_RC6 "RC6" +#endif // D_STR_RC6 +#ifndef D_STR_RCMM +#define D_STR_RCMM "RCMM" +#endif // D_STR_RCMM +#ifndef D_STR_RHOSS +#define D_STR_RHOSS "RHOSS" +#endif // D_STR_RHOSS +#ifndef D_STR_SAMSUNG +#define D_STR_SAMSUNG "SAMSUNG" +#endif // D_STR_SAMSUNG +#ifndef D_STR_SAMSUNG36 +#define D_STR_SAMSUNG36 "SAMSUNG36" +#endif // D_STR_SAMSUNG36 +#ifndef D_STR_SAMSUNG_AC +#define D_STR_SAMSUNG_AC "SAMSUNG_AC" +#endif // D_STR_SAMSUNG_AC +#ifndef D_STR_SANYO +#define D_STR_SANYO "SANYO" +#endif // D_STR_SANYO +#ifndef D_STR_SANYO_AC +#define D_STR_SANYO_AC D_STR_SANYO "_AC" +#endif // D_STR_SANYO_AC +#ifndef D_STR_SANYO_AC88 +#define D_STR_SANYO_AC88 D_STR_SANYO_AC "88" +#endif // D_STR_SANYO_AC88 +#ifndef D_STR_SANYO_AC152 +#define D_STR_SANYO_AC152 D_STR_SANYO_AC "152" +#endif // D_STR_SANYO_AC152 +#ifndef D_STR_SANYO_LC7461 +#define D_STR_SANYO_LC7461 D_STR_SANYO "_LC7461" +#endif // D_STR_SANYO_LC7461 +#ifndef D_STR_SHARP +#define D_STR_SHARP "SHARP" +#endif // D_STR_SHARP +#ifndef D_STR_SHARP_AC +#define D_STR_SHARP_AC "SHARP_AC" +#endif // D_STR_SHARP_AC +#ifndef D_STR_SHERWOOD +#define D_STR_SHERWOOD "SHERWOOD" +#endif // D_STR_SHERWOOD +#ifndef D_STR_SONY +#define D_STR_SONY "SONY" +#endif // D_STR_SONY +#ifndef D_STR_SONY_38K +#define D_STR_SONY_38K "SONY_38K" +#endif // D_STR_SONY_38K +#ifndef D_STR_SYMPHONY +#define D_STR_SYMPHONY "SYMPHONY" +#endif // D_STR_SYMPHONY +#ifndef D_STR_TCL96AC +#define D_STR_TCL96AC "TCL96AC" +#endif // D_STR_TCL96AC +#ifndef D_STR_TCL112AC +#define D_STR_TCL112AC "TCL112AC" +#endif // D_STR_TCL112AC +#ifndef D_STR_TECHNIBEL_AC +#define D_STR_TECHNIBEL_AC "TECHNIBEL_AC" +#endif // D_STR_TECHNIBEL_AC +#ifndef D_STR_TECO +#define D_STR_TECO "TECO" +#endif // D_STR_TECO +#ifndef D_STR_TEKNOPOINT +#define D_STR_TEKNOPOINT "TEKNOPOINT" +#endif // D_STR_TEKNOPOINT +#ifndef D_STR_TOSHIBA_AC +#define D_STR_TOSHIBA_AC "TOSHIBA_AC" +#endif // D_STR_TOSHIBA_AC +#ifndef D_STR_TOTO +#define D_STR_TOTO "TOTO" +#endif // D_STR_TOTO +#ifndef D_STR_TRANSCOLD +#define D_STR_TRANSCOLD "TRANSCOLD" +#endif // D_STR_TRANSCOLD +#ifndef D_STR_TROTEC +#define D_STR_TROTEC "TROTEC" +#endif // D_STR_TROTEC +#ifndef D_STR_TROTEC_3550 +#define D_STR_TROTEC_3550 D_STR_TROTEC "_3550" +#endif // D_STR_TROTEC_3550 +#ifndef D_STR_TRUMA +#define D_STR_TRUMA "TRUMA" +#endif // D_STR_TRUMA +#ifndef D_STR_UNUSED +#define D_STR_UNUSED "UNUSED" +#endif // D_STR_UNUSED +#ifndef D_STR_VESTEL_AC +#define D_STR_VESTEL_AC "VESTEL_AC" +#endif // D_STR_VESTEL_AC +#ifndef D_STR_VOLTAS +#define D_STR_VOLTAS "VOLTAS" +#endif // D_STR_VOLTAS +#ifndef D_STR_WHIRLPOOL_AC +#define D_STR_WHIRLPOOL_AC "WHIRLPOOL_AC" +#endif // D_STR_WHIRLPOOL_AC +#ifndef D_STR_WHYNTER +#define D_STR_WHYNTER "WHYNTER" +#endif // D_STR_WHYNTER +#ifndef D_STR_WOWWEE +#define D_STR_WOWWEE "WOWWEE" +#endif // D_STR_WOWWEE +#ifndef D_STR_XMP +#define D_STR_XMP "XMP" +#endif // D_STR_XMP +#ifndef D_STR_ZEPEAL +#define D_STR_ZEPEAL "ZEPEAL" +#endif // D_STR_ZEPEAL + +// IRrecvDumpV2+ +#ifndef D_STR_TIMESTAMP +#define D_STR_TIMESTAMP "Timestamp" +#endif // D_STR_TIMESTAMP +#ifndef D_STR_LIBRARY +#define D_STR_LIBRARY "Library" +#endif // D_STR_LIBRARY +#ifndef D_STR_MESGDESC +#define D_STR_MESGDESC "Mesg Desc." +#endif // D_STR_MESGDESC +#ifndef D_STR_TOLERANCE +#define D_STR_TOLERANCE "Tolerance" +#endif // D_STR_TOLERANCE +#ifndef D_STR_IRRECVDUMP_STARTUP +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump is now running and waiting for IR input on Pin %d" +#endif // D_STR_IRRECVDUMP_STARTUP +#ifndef D_WARN_BUFFERFULL +#define D_WARN_BUFFERFULL \ + "WARNING: IR code is too big for buffer (>= %d). " \ + "This result shouldn't be trusted until this is resolved. " \ + "Edit & increase `kCaptureBufferSize`." +#endif // D_WARN_BUFFERFULL + +#endif // LOCALE_DEFAULTS_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/en-AU.h b/src/libraries/IRremoteESP8266/src/locale/en-AU.h new file mode 100644 index 000000000..63ecf2282 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/en-AU.h @@ -0,0 +1,8 @@ +// Copyright 2019 - David Conran (@crankyoldgit) +// Locale/language file for English / Australia. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_EN_AU_H_ +#define LOCALE_EN_AU_H_ +// Nothing should really need to be set here, as en-AU is the default +// locale/language. +#endif // LOCALE_EN_AU_H__ diff --git a/src/libraries/IRremoteESP8266/src/locale/en-IE.h b/src/libraries/IRremoteESP8266/src/locale/en-IE.h new file mode 100644 index 000000000..6a0304bca --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/en-IE.h @@ -0,0 +1,8 @@ +// Copyright 2019 - David Conran (@crankyoldgit) +// Locale/language file for English / Ireland. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_EN_IE_H_ +#define LOCALE_EN_IE_H_ +// Nothing should really need to be set here, as en-IE is the same as en-AU, +// which is the default locale/language. +#endif // LOCALE_EN_IE_H__ diff --git a/src/libraries/IRremoteESP8266/src/locale/en-UK.h b/src/libraries/IRremoteESP8266/src/locale/en-UK.h new file mode 100644 index 000000000..2aa57f5ce --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/en-UK.h @@ -0,0 +1,8 @@ +// Copyright 2019 - David Conran (@crankyoldgit) +// Locale/language file for English / United Kingdom. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_EN_UK_H_ +#define LOCALE_EN_UK_H_ +// Nothing should really need to be set here, as en-UK is the same as en-AU, +// which is the default locale/language. +#endif // LOCALE_EN_UK_H__ diff --git a/src/libraries/IRremoteESP8266/src/locale/en-US.h b/src/libraries/IRremoteESP8266/src/locale/en-US.h new file mode 100644 index 000000000..7dda393bf --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/en-US.h @@ -0,0 +1,13 @@ +// Copyright 2019 - David Conran (@crankyoldgit) +// Locale/language file for English / United States of America. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_EN_US_H_ +#define LOCALE_EN_US_H_ +// Not much should really need to be set here, as English is the default +// locale/language. + +// Overrides to the default. +#define D_STR_CENTRE "Center" +#define D_STR_MOULD "Mold" + +#endif // LOCALE_EN_US_H__ diff --git a/src/libraries/IRremoteESP8266/src/locale/es-ES.h b/src/libraries/IRremoteESP8266/src/locale/es-ES.h new file mode 100644 index 000000000..e8e54e5b2 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/es-ES.h @@ -0,0 +1,136 @@ +// Copyright 2019 - Carlos (@charlieyv) +// Locale/language file for Spanish / Spain. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_ES_ES_H_ +#define LOCALE_ES_ES_H_ + +#define D_STR_UNKNOWN "DESCONOCIDO" +#define D_STR_PROTOCOL "Protocolo" +#define D_STR_POWER "Poder" +#define D_STR_PREVIOUS "Anterior" +#define D_STR_PREVIOUSPOWER D_STR_POWER " " D_STR_PREVIOUS +#define D_STR_ON "Encendido" +#define D_STR_OFF "Apagado" +#define D_STR_MODE "Modo" +#define D_STR_TOGGLE "Palanca" +#define D_STR_SLEEP "Dormir" +#define D_STR_LIGHT "Luz" +#define D_STR_POWERFUL "Poderoso" +#define D_STR_QUIET "Silencio" +#define D_STR_ECONO "Econo" +#define D_STR_SWING "Oscilar" +#define D_STR_SWINGH D_STR_SWING"(H)" +#define D_STR_SWINGV D_STR_SWING"(V)" +#define D_STR_BEEP "Bip" +#define D_STR_MOULD "Molde" +#define D_STR_CLEAN "Limpiar" +#define D_STR_PURIFY "Purificar" +#define D_STR_TIMER "Temporizador" +#define D_STR_ONTIMER D_STR_ON " " D_STR_TIMER +#define D_STR_OFFTIMER D_STR_OFF " " D_STR_TIMER +#define D_STR_CLOCK "Reloj" +#define D_STR_COMMAND "Comando" +#define D_STR_HEALTH "Salud" +#define D_STR_MODEL "Modelo" +#define D_STR_TEMP "Temperatura" +#define D_STR_HUMID "Humedo" +#define D_STR_SAVE "Guardar" +#define D_STR_EYE "Ojo" +#define D_STR_FOLLOW "Seguir" +#define D_STR_FRESH "Fresco" +#define D_STR_HOLD "Mantener" +#define D_STR_8C_HEAT "8C " D_STR_HEAT +#define D_STR_BUTTON "Boton" +#define D_STR_NIGHT "Noche" +#define D_STR_SILENT "Silencio" +#define D_STR_FILTER "Filtro" +#define D_STR_UP "Arriba" +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP +#define D_STR_DOWN "Abajo" +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN +#define D_STR_CHANGE "Cambiar" +#define D_STR_START "Comenzar" +#define D_STR_STOP "Parar" +#define D_STR_MOVE "Mover" +#define D_STR_SET "Fijar" +#define D_STR_CANCEL "Cancelar" +#define D_STR_COMFORT "Comodo" +#define D_STR_WEEKLY "Semanal" +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " " D_STR_TIMER +#define D_STR_LAST "Ultimo" +#define D_STR_FAST "Rapido" +#define D_STR_SLOW "Lento" +#define D_STR_AIRFLOW "Flujo de Aire" +#define D_STR_STEP "Paso" +#define D_STR_OUTSIDE "Afuera" +#define D_STR_LOUD "Ruidoso" +#define D_STR_UPPER "Superior" +#define D_STR_LOWER "Inferior" +#define D_STR_BREEZE "Brisa" +#define D_STR_CIRCULATE "Circular" +#define D_STR_CEILING "Techo" +#define D_STR_WALL "Pared" +#define D_STR_ROOM "Cuarto" +#define D_STR_6THSENSE "6to. Sentido" +#define D_STR_ZONEFOLLOW "Zona Seguir" +#define D_STR_FIXED "Fijo" +#define D_STR_AUTOMATIC "Automatico" +#define D_STR_COOL "Frio" +#define D_STR_HEAT "Calor" +#define D_STR_FAN "Ventilador" +#define D_STR_FANONLY "ventilador_solamente" +#define D_STR_DRY "Seco" +#define D_STR_MAX "Max" +#define D_STR_MAXIMUM "Maximo" +#define D_STR_MIN "Min" +#define D_STR_MINIMUM "Minimo" +#define D_STR_MED "Med" +#define D_STR_MEDIUM "Medio" +#define D_STR_HIGHEST "Mas Alto" +#define D_STR_HIGH "Alto" +#define D_STR_HI D_STR_HIGH +#define D_STR_MIDDLE "Medio" +#define D_STR_MID D_STR_MIDDLE +#define D_STR_LOW "Bajo" +#define D_STR_LO D_STR_LOW +#define D_STR_LOWEST "Mas Bajo" +#define D_STR_RIGHT "Derecha" +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX +#define D_STR_LEFT "Izquierda" +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX +#define D_STR_WIDE "Ancho" +#define D_STR_CENTRE "Centro" +#define D_STR_TOP "Tope" +#define D_STR_BOTTOM "Fondo" +#define D_STR_DAY "Dia" +#define D_STR_DAYS D_STR_DAY "s" +#define D_STR_HOUR "Hora" +#define D_STR_HOURS D_STR_HOUR "s" +#define D_STR_MINUTE "Minuto" +#define D_STR_MINUTES D_STR_MINUTE "s" +#define D_STR_SECOND "Segundo" +#define D_STR_SECONDS D_STR_SECOND "s" +#define D_STR_NOW "Ahora" +#define D_STR_THREELETTERDAYS "DomLunMarMieJueVieSab" +#define D_STR_YES "Si" +#define D_STR_NO "No" +#define D_STR_TRUE "Cierto" +#define D_STR_FALSE "Falso" +#define D_STR_REPEAT "Repetir" +#define D_STR_CODE "Codigo" + +// IRrecvDumpV2+ +#define D_STR_TIMESTAMP "marca de tiempo" +#define D_STR_LIBRARY "Libreria" +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump esta ahora corriendo y esperando por comando IR en Pin %d" +#ifndef D_WARN_BUFFERFULL +#define D_WARN_BUFFERFULL \ + "WARNING: Codigo IR es muy grande para el buffer (>= %d). "\ + "Este resultando no debe ser reconocido hasta que esto sea resuelto." \ + "Edite & incremente `kCaptureBufferSize`." +#endif // D_WARN_BUFFERFULL + +#endif // LOCALE_ES_ES_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/fr-FR.h b/src/libraries/IRremoteESP8266/src/locale/fr-FR.h new file mode 100644 index 000000000..f8c2f8235 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/fr-FR.h @@ -0,0 +1,117 @@ +// Copyright 2019 - Mathieu D(@Knackie) +// Locale/language file for French / Quebec. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_FR_FR_H_ +#define LOCALE_FR_FR_H_ + +#define D_STR_UNKNOWN "INCONNU" +#define D_STR_PROTOCOL "Protocole" +#define D_STR_TOGGLE "Bascule" +#define D_STR_SLEEP "Pause" +#define D_STR_LIGHT "Lumière" +#define D_STR_POWERFUL "Puissance" +#define D_STR_PREVIOUS "Precedente" +#define D_STR_PREVIOUSPOWER D_STR_POWER " " D_STR_PREVIOUS +#define D_STR_QUIET "Silence" +#define D_STR_ECONO "Economie" +#define D_STR_BEEP "Bip" +#define D_STR_MOULD "Moule" +#define D_STR_CLEAN "Nettoyer" +#define D_STR_PURIFY "Purifier" +#define D_STR_ON "On" +#define D_STR_OFF "Off" +#define D_STR_ONTIMER D_STR_ON " " D_STR_TIMER +#define D_STR_OFFTIMER D_STR_OFF " " D_STR_TIMER +#define D_STR_CLOCK "Heure" +#define D_STR_COMMAND "Commandement" +#define D_STR_HEALTH "Santé" +#define D_STR_TEMP "Temporaire" +#define D_STR_HUMID "Humidité" +#define D_STR_SAVE "Sauvegarder" +#define D_STR_EYE "Oeil" +#define D_STR_FOLLOW "Suivre" +#define D_STR_FRESH "Frais" +#define D_STR_HOLD "Maintenir" +#define D_STR_BUTTON "Bouton" +#define D_STR_NIGHT "Nuit" +#define D_STR_SILENT "Silence" +#define D_STR_UP "En haut" +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP +#define D_STR_DOWN "En bas" +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN +#define D_STR_CHANGE "Changement" +#define D_STR_SET "Mettre" +#define D_STR_CANCEL "Annuler" +#define D_STR_COMFORT "Confort" +#define D_STR_WEEKLY "Chaque semaine" +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " " D_STR_TIMER +#define D_STR_FAST "Rapide" +#define D_STR_SLOW "Lent" +#define D_STR_AIRFLOW "Ebauche" +#define D_STR_STEP "Etape" +#define D_STR_OUTSIDE "Plein air" +#define D_STR_LOUD "Fort" +#define D_STR_UPPER "Au dessus" +#define D_STR_LOWER "En dessous" +#define D_STR_BREEZE "Brise" +#define D_STR_CIRCULATE "Faire circuler" +#define D_STR_CEILING "Plafond" +#define D_STR_WALL "Mur" +#define D_STR_ROOM "Pièce" +#define D_STR_6THSENSE "6ter Sens" +#define D_STR_FIXED "Fixer" + +#define D_STR_AUTOMATIC "Automatique" +#define D_STR_MANUAL "Manuel" +#define D_STR_COOL "Frais" +#define D_STR_HEAT "Chaleur" +#define D_STR_FAN "Ventillateur" +#define D_STR_FANONLY "Seul_fan" +#define D_STR_DRY "Sec" + +#define D_STR_MEDIUM "Moyen" + +#define D_STR_HIGHEST "Le plus haut" +#define D_STR_HIGH "Haut" +#define D_STR_HI "H" +#define D_STR_MID "M" +#define D_STR_MIDDLE "Moitié" +#define D_STR_LOW "Bas" +#define D_STR_LO "B" +#define D_STR_LOWEST "Le plus bas" +#define D_STR_RIGHT "Droite" +#define D_STR_MAX "Max" +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX +#define D_STR_LEFT "Gauche" +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX +#define D_STR_WIDE "Large" +#define D_STR_TOP "Au-dessus" +#define D_STR_BOTTOM "En-dessous" + +#define D_STR_DAY "Jour" +#define D_STR_HOUR "Heure" +#define D_STR_SECOND "Seconde" +#define D_STR_NOW "Maintenant" +#define D_STR_THREELETTERDAYS "LunMarMerJeuVenSamDim" + +#define D_STR_YES "Oui" +#define D_STR_NO "Non" +#define D_STR_TRUE "Vrai" +#define D_STR_FALSE "Faux" + +#define D_STR_REPEAT "Répetition" + +// IRrecvDumpV2+ +#define D_STR_TIMESTAMP "Horodatage" +#define D_STR_LIBRARY "Bibliothèque" +#define D_STR_MESGDESC "Rèférence" +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump fonctionne et attend l’entrée IR sur la broche %d" +#define D_WARN_BUFFERFULL \ + "ATTENTION: IR Code est trop gros pour le buffer (>= %d). " \ + "Le résultat ne doit pas être approuvé avant que cela soit résolu. " \ + "Modifier et agrandir `kCaptureBufferSize`." + +#endif // LOCALE_FR_FR_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/it-IT.h b/src/libraries/IRremoteESP8266/src/locale/it-IT.h new file mode 100644 index 000000000..9457b6e40 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/it-IT.h @@ -0,0 +1,159 @@ +// Copyright 2020 - Enrico Gueli (@egueli) +// Locale/language file for Italian. +// This file will override the default values located in `defaults.h`. + +#ifndef LOCALE_IT_IT_H_ +#define LOCALE_IT_IT_H_ + +#define D_STR_UNKNOWN "SCONOSCIUTO" +#define D_STR_PROTOCOL "Protocollo" +#define D_STR_POWER "Accensione" +#define D_STR_PREVIOUS "Precedente" +#define D_STR_PREVIOUSPOWER D_STR_POWER " " D_STR_PREVIOUS +#define D_STR_ON "Acceso" +#define D_STR_OFF "Spento" +#define D_STR_MODE "Modalità" +#define D_STR_TOGGLE "Alterna" +#define D_STR_SLEEP "Sonno" +#define D_STR_LIGHT "Leggero" +#define D_STR_POWERFUL "Forte" +#define D_STR_QUIET "Silenzioso" +#define D_STR_ECONO "Eco" +#define D_STR_SWING "Swing" +#define D_STR_SWINGH D_STR_SWING"(O)" // Set `D_STR_SWING` first! +#define D_STR_SWINGV D_STR_SWING"(V)" // Set `D_STR_SWING` first! +#define D_STR_MOULD "Muffa" +#define D_STR_CLEAN "Pulizia" +#define D_STR_PURIFY "Purifica" +#define D_STR_TIMER "Timer" +#define D_STR_ONTIMER D_STR_ON " " D_STR_TIMER // Set `D_STR_ON` first! +#define D_STR_OFFTIMER D_STR_OFF " " D_STR_TIMER // Set `D_STR_OFF` first! +#define D_STR_CLOCK "Orologio" +#define D_STR_COMMAND "Comando" +#define D_STR_MODEL "Modello" +#define D_STR_TEMP "Temp" +#define D_STR_HUMID "Umido" +#define D_STR_SAVE "Salva" +#define D_STR_EYE "Occhio" +#define D_STR_FOLLOW "Segui" +#define D_STR_ION "Ioni" +#define D_STR_FRESH "Fresco" +#define D_STR_HOLD "Mantieni" +#define D_STR_8C_HEAT "8C " D_STR_HEAT // Set `D_STR_HEAT` first! +#define D_STR_BUTTON "Pulsante" +#define D_STR_NIGHT "Notte" +#define D_STR_SILENT "Silenzioso" +#define D_STR_FILTER "Filtro" +#define D_STR_UP "Su" +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP // Set `D_STR_TEMP` first! +#define D_STR_DOWN "Giù" +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN // Set `D_STR_TEMP` first! +#define D_STR_CHANGE "Cambia" +#define D_STR_START "Avvia" +#define D_STR_STOP "Ferma" +#define D_STR_MOVE "Muovi" +#define D_STR_SET "Imposta" +#define D_STR_CANCEL "Annulla" +#define D_STR_SENSOR "Sensore" +#define D_STR_WEEKLY "Settimanale" +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " " D_STR_TIMER // Needs `D_STR_WEEKLY`! +#define D_STR_LAST "Ultimo" +#define D_STR_FAST "Veloce" +#define D_STR_SLOW "Lento" +#define D_STR_AIRFLOW "Flusso d'aria" +#define D_STR_STEP "Passo" +#define D_STR_NA "N/D" +#define D_STR_OUTSIDE "Esterno" +#define D_STR_LOUD "Rumoroso" +#define D_STR_UPPER "Superiore" +#define D_STR_LOWER "Inferiore" +#define D_STR_CIRCULATE "Circolare" +#define D_STR_CEILING "Soffitto" +#define D_STR_WALL "Muro" +#define D_STR_ROOM "Camera" +#define D_STR_FIXED "Fisso" + +#define D_STR_AUTO "Auto" +#define D_STR_AUTOMATIC "Automatico" +#define D_STR_MANUAL "Manuale" +#define D_STR_COOL "Fresco" +#define D_STR_HEAT "Caldo" +#define D_STR_FAN "Ventola" +#define D_STR_FANONLY "solo_ventola" +#define D_STR_DRY "Secco" + +#define D_STR_MAX "Max" +#define D_STR_MAXIMUM "Massimo" +#define D_STR_MINIMUM "Minimo" +#define D_STR_MEDIUM "Medio" + +#define D_STR_HIGHEST "Molto alto" +#define D_STR_HIGH "Alto" +#define D_STR_MID "Med" +#define D_STR_MIDDLE "Medio" +#define D_STR_LOW "Basso" +#define D_STR_LOWEST "Bassissimo" +#define D_STR_RIGHT "Destra" +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT // Set `D_STR_MAX` first! +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX // Set `D_STR_MAX` first! +#define D_STR_LEFT "Sinistra" +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT // Set `D_STR_MAX` first! +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX // Set `D_STR_MAX` first! +#define D_STR_WIDE "Largo" +#define D_STR_CENTRE "Centro" +#define D_STR_TOP "Superiore" +#define D_STR_BOTTOM "Inferiore" +// Compound words/phrases/descriptions from pre-defined words. +// Note: Obviously these need to be defined *after* their component words. + +#define D_STR_EYEAUTO D_STR_EYE " " D_STR_AUTO +#define D_STR_LIGHTTOGGLE D_STR_LIGHT " " D_STR_TOGGLE +#define D_STR_OUTSIDEQUIET D_STR_OUTSIDE " " D_STR_QUIET +#define D_STR_POWERTOGGLE D_STR_POWER " " D_STR_TOGGLE +#define D_STR_SENSORTEMP D_STR_SENSOR " " D_STR_TEMP +#define D_STR_SLEEP_TIMER D_STR_SLEEP " " D_STR_TIMER +#define D_STR_SWINGVMODE D_STR_SWINGV " " D_STR_MODE +#define D_STR_SWINGVTOGGLE D_STR_SWINGV " " D_STR_TOGGLE +// Separators +#ifndef D_CHR_TIME_SEP +#define D_CHR_TIME_SEP '.' +#endif // D_CHR_TIME_SEP + +#define D_STR_SPACELBRACE " (" +#define D_STR_COMMASPACE ", " +#define D_STR_COLONSPACE ": " + +#define D_STR_DAY "Giorno" +#define D_STR_DAYS D_STR_DAY "s" +#define D_STR_HOUR "Ore" +#define D_STR_HOURS D_STR_HOUR "s" +#define D_STR_MINUTE "Minuti" +#define D_STR_MINUTES D_STR_MINUTE "s" +#define D_STR_SECOND "Secondi" +#define D_STR_SECONDS D_STR_SECOND "s" +#define D_STR_NOW "Adesso" +#define D_STR_THREELETTERDAYS "DomLunMarMerGioVenSab" + +#define D_STR_YES "Sì" +#define D_STR_TRUE "Vero" +#define D_STR_FALSE "Falso" + +#define D_STR_REPEAT "Ripeti" +#define D_STR_CODE "Codice" +#define D_STR_BITS "Bit" + +// IRrecvDumpV2+ +#define D_STR_LIBRARY "Libreria" +#define D_STR_MESGDESC "Desc. Mess." +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump è ora attivo e in attesa di segnali IR dal pin %d" + +#ifndef D_WARN_BUFFERFULL +#define D_WARN_BUFFERFULL \ + "ATTENZIONE: il codice IR è troppo grande per il buffer (>= %d). " \ + "Non fare affidamento a questi risultati finché questo problema " \ + "non è risolto." \ + "Modifica e aumenta `kCaptureBufferSize`." +#endif // D_WARN_BUFFERFULL + +#endif // LOCALE_IT_IT_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/nl-NL.h b/src/libraries/IRremoteESP8266/src/locale/nl-NL.h new file mode 100644 index 000000000..3cac00b5e --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/nl-NL.h @@ -0,0 +1,136 @@ +// Copyright 2022 - Stijn (@stijnb1234) +// Locale/language file for Dutch / The Netherlands. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_NL_NL_H_ +#define LOCALE_NL_NL_H_ + +#define D_STR_UNKNOWN "ONBEKEND" +#define D_STR_POWER "Stroom" +#define D_STR_PREVIOUS "Vorige" +#define D_STR_ON "Aan" +#define D_STR_OFF "Uit" +#define D_STR_MODE "Modus" +#define D_STR_TOGGLE "Omschakelen" +#define D_STR_SLEEP "Slaap" +#define D_STR_LIGHT "Licht" +#define D_STR_POWERFUL "Sterk" +#define D_STR_QUIET "Rustig" +#define D_STR_ECONO "Eco" +#define D_STR_SWING "Zwaai" +#define D_STR_BEEP "Piep" +#define D_STR_MOULD "Schimmel" +#define D_STR_CLEAN "Reinigen" +#define D_STR_PURIFY "Zuiver" +#define D_STR_TIMER "Timer" +#define D_STR_ONTIMER D_STR_TIMER " " D_STR_ON +#define D_STR_OFFTIMER D_STR_TIMER " " D_STR_OFF +#define D_STR_CLOCK "Klok" +#define D_STR_COMMAND "Commando" +#define D_STR_XFAN "XVentilator" +#define D_STR_HEALTH "Gezondheid" +#define D_STR_IFEEL "IkVoel" +#define D_STR_ISEE "IkZie" +#define D_STR_HUMID "Vochtigheid" +#define D_STR_SAVE "Opslaan" +#define D_STR_EYE "Ogen" +#define D_STR_FOLLOW "Volgen" +#define D_STR_FRESH "Fris" +#define D_STR_HOLD "Houd" +#define D_STR_BUTTON "Knop" +#define D_STR_NIGHT "Nacht" +#define D_STR_SILENT "Stil" +#define D_STR_UP "Omhoog" +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP +#define D_STR_DOWN "Omlaag" +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN +#define D_STR_CHANGE "Wisselen" +#define D_STR_MOVE "Verplaatsen" +#define D_STR_SET "Instellen" +#define D_STR_CANCEL "Annuleren" +#define D_STR_COMFORT "Comfortabel" +#define D_STR_WEEKLY "Weekelijks" +#define D_STR_WEEKLYTIMER D_STR_TIMER " " D_STR_WEEKLY +#define D_STR_FAST "Snel" +#define D_STR_SLOW "Langzaam" +#define D_STR_AIRFLOW "Luchtstroom" +#define D_STR_STEP "Stap" +#define D_STR_NA "N/A" +#define D_STR_OUTSIDE "Buiten" +#define D_STR_LOUD "Luid" +#define D_STR_UPPER "Boven" +#define D_STR_LOWER "Beneden" +#define D_STR_BREEZE "Wind" +#define D_STR_CIRCULATE "Circulatie" +#define D_STR_CEILING "Plafond" +#define D_STR_WALL "Muur" +#define D_STR_ROOM "Kamer" +#define D_STR_6THSENSE "6e Zintuig" +#define D_STR_FIXED "Vast" + +#define D_STR_AUTOMATIC "Automatisch" +#define D_STR_MANUAL "Handmatig" +#define D_STR_COOL "Koelen" +#define D_STR_HEAT "Verwarmen" +#define D_STR_FAN "Venilator" +#define D_STR_FANONLY "alleen_fan" +#define D_STR_DRY "Drogen" + +#define D_STR_MED "Mid" +#define D_STR_MEDIUM "Medium" + +#define D_STR_HIGHEST "Hoogste" +#define D_STR_HIGH "Hoog" +#define D_STR_HI "H" +#define D_STR_MID "M" +#define D_STR_MIDDLE "Medium" +#define D_STR_LOW "Laag" +#define D_STR_LO "L" +#define D_STR_LOWEST "Laagste" +#define D_STR_RIGHT "Rechts" +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX +#define D_STR_LEFT "Links" +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX +#define D_STR_WIDE "Breed" +#define D_STR_CENTRE "Midden" +#define D_STR_TOP "Boven" +#define D_STR_BOTTOM "Onder" + +#define D_STR_DAY "Dag" +#define D_STR_DAYS D_STR_DAY "en" +#define D_STR_HOUR "Uur" +#define D_STR_HOURS D_STR_HOUR +#define D_STR_MINUTE "Minuut" +#define D_STR_MINUTES "Minuten" +#define D_STR_SECOND "Seconde" +#define D_STR_SECONDS D_STR_SECOND "n" +#define D_STR_NOW "Nu" +#define D_STR_THREELETTERDAYS "ZonMaaDinWoeDonVriZat" + +#define D_STR_YES "Ja" +#define D_STR_NO "Nee" +#define D_STR_TRUE "Waar" +#define D_STR_FALSE "Niet Waar" + +#define D_STR_REPEAT "Herhalen" +#define D_STR_PREVIOUS "Vorige" +#define D_STR_DISPLAY "Display" +#define D_STR_INSIDE "Binnen" +#define D_STR_POWERBUTTON "Hoofdschakelaar" +#define D_STR_PREVIOUSPOWER "Vorige inschakelstatus" +#define D_STR_DISPLAYTEMP "Temperatuurweergave" + +// IRrecvDumpV2+ +#define D_STR_TIMESTAMP "Tijdsaanduiding" +#define D_STR_LIBRARY "Bibliotheek" +#define D_STR_TOLERANCE "Tolerantie" +#define D_STR_MESGDESC "Beschrijving" +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump draait en wacht op IR-signaal op pin %d" +#define D_WARN_BUFFERFULL \ + "WAARSCHUWING: IR-code is te groot voor buffer (>= %d). " \ + "Het resultaat kan niet worden vertrouwd totdat het is verholpen. " \ + "Wijzig & vergroot `kCaptureBufferSize`." + +#endif // LOCALE_NL_NL_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/pt-BR.h b/src/libraries/IRremoteESP8266/src/locale/pt-BR.h new file mode 100644 index 000000000..4b2c929e0 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/pt-BR.h @@ -0,0 +1,177 @@ +// Copyright 2020 - Guilherme (@guieiras) +// Locale/language file for Portuguese / Brazil. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_PT_BR_H_ +#define LOCALE_PT_BR_H_ + +#define D_STR_UNKNOWN "DESCONHECIDO" +#define D_STR_PROTOCOL "Protocolo" +#define D_STR_POWER "Energia" +#define D_STR_PREVIOUS "Anterior" +#define D_STR_ON "Ligado" +#define D_STR_OFF "Desligado" +#define D_STR_MODE "Modo" +#define D_STR_TOGGLE "Alterar" +#define D_STR_TURBO "Turbo" +#define D_STR_SUPER "Super" +#define D_STR_SLEEP "Dormir" +#define D_STR_LIGHT "Luz" +#define D_STR_POWERFUL "Potente" +#define D_STR_QUIET "Silencioso" +#define D_STR_ECONO "Econômico" +#define D_STR_SWING "Girar" +#define D_STR_SWINGH D_STR_SWING"(H)" +#define D_STR_SWINGV D_STR_SWING"(V)" +#define D_STR_BEEP "Tocar beep" +#define D_STR_MOULD "Moldar" +#define D_STR_CLEAN "Limpar" +#define D_STR_PURIFY "Purificar" +#define D_STR_TIMER "Timer" +#define D_STR_ONTIMER D_STR_TIMER " " D_STR_ON +#define D_STR_OFFTIMER D_STR_TIMER " " D_STR_OFF +#define D_STR_TIMERMODE D_STR_MODE " " D_STR_TIMER +#define D_STR_CLOCK "Relógio" +#define D_STR_COMMAND "Comando" +#define D_STR_HEALTH "Saúde" +#define D_STR_MODEL "Modelo" +#define D_STR_TEMP "Temperatura" +#define D_STR_HUMID "Umidificar" +#define D_STR_SAVE "Salvar" +#define D_STR_EYE "Ver" +#define D_STR_FOLLOW "Acompanhar" +#define D_STR_ION "Ionizar" +#define D_STR_FRESH "Refrescar" +#define D_STR_HOLD "Manter" +#define D_STR_BUTTON "Botão" +#define D_STR_NIGHT "Noite" +#define D_STR_SILENT "Silencioso" +#define D_STR_FILTER "Filtrar" +#define D_STR_3D "3D" +#define D_STR_CELSIUS "Celsius" +#define D_STR_FAHRENHEIT "Fahrenheit" +#define D_STR_CELSIUS_FAHRENHEIT D_STR_CELSIUS "/" D_STR_FAHRENHEIT +#define D_STR_UP "Aumentar" +#define D_STR_TEMPUP D_STR_UP " " D_STR_TEMP +#define D_STR_DOWN "Diminuir" +#define D_STR_TEMPDOWN D_STR_DOWN " " D_STR_TEMP +#define D_STR_CHANGE "Alterar" +#define D_STR_START "Iniciar" +#define D_STR_STOP "Parar" +#define D_STR_MOVE "Mover" +#define D_STR_SET "Definir" +#define D_STR_CANCEL "Cancelar" +#define D_STR_COMFORT "Conforto" +#define D_STR_SENSOR "Sensor" +#define D_STR_DISPLAY "Mostrar" +#define D_STR_WEEKLY "Semanal" +#define D_STR_WEEKLYTIMER D_STR_TIMER " " D_STR_WEEKLY +#define D_STR_WIFI "WiFi" +#define D_STR_LAST "Último" +#define D_STR_FAST "Rápido" +#define D_STR_SLOW "Devagar" +#define D_STR_AIRFLOW "Fluxo de Ar" +#define D_STR_STEP "Etapa" +#define D_STR_NA "N/A" +#define D_STR_INSIDE "Interno" +#define D_STR_OUTSIDE "Externo" +#define D_STR_LOUD "Alto" +#define D_STR_UPPER "Mais alto" +#define D_STR_LOWER "Mais baixo" +#define D_STR_BREEZE "Brisa" +#define D_STR_CIRCULATE "Circular" +#define D_STR_CEILING "Teto" +#define D_STR_WALL "Parede" +#define D_STR_ROOM "Sala" +#define D_STR_6THSENSE "Sexto sentido" +#define D_STR_ZONEFOLLOW "Acompanhar ambiente" +#define D_STR_FIXED "Fixo" +#define D_STR_TYPE "Tipo" +#define D_STR_SPECIAL "Especial" +#define D_STR_RECYCLE "Reciclar" +#define D_STR_ID "Id" +#define D_STR_VANE "Vane" + +#define D_STR_AUTO "Auto" +#define D_STR_AUTOMATIC "Automático" +#define D_STR_MANUAL "Manual" +#define D_STR_COOL "Esfriar" +#define D_STR_HEAT "Aquecer" +#define D_STR_FAN "Ventilar" +#define D_STR_FANONLY "Apenas-ventilar" +#define D_STR_FAN_ONLY "Apenas_ventilar" +#define D_STR_ONLY "Apenas" +#define D_STR_FANSPACEONLY D_STR_ONLY " " D_STR_FAN +#define D_STR_FANONLYNOSPACE D_STR_ONLY D_STR_FAN +#define D_STR_DRY "Secar" +#define D_STR_8C_HEAT D_STR_HEAT " 8C" + +#define D_STR_MAX "Max" +#define D_STR_MAXIMUM "Máximo" +#define D_STR_MIN "Min" +#define D_STR_MINIMUM "Mínimo" +#define D_STR_MED "Med" +#define D_STR_MEDIUM "Médio" + +#define D_STR_HIGHEST "Mais alto" +#define D_STR_HIGH "Alto" +#define D_STR_HI "Médio alto" +#define D_STR_MID "Médio" +#define D_STR_MIDDLE "Médio baixo" +#define D_STR_LOW "Inferior" +#define D_STR_LO "Baixo" +#define D_STR_LOWEST "Mais baixo" +#define D_STR_RIGHT "Direita" +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT " (" D_STR_MAX ")" +#define D_STR_LEFT "Esquerda" +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT " (" D_STR_MAX ")" +#define D_STR_WIDE "Amplo" +#define D_STR_CENTRE "Centro" +#define D_STR_TOP "Topo" +#define D_STR_BOTTOM "Baixo" + +// Separators +#define D_CHR_TIME_SEP ':' +#define D_STR_SPACELBRACE " (" +#define D_STR_COMMASPACE ", " +#define D_STR_COLONSPACE ": " + +#define D_STR_DAY "Dia" +#define D_STR_DAYS D_STR_DAY "s" +#define D_STR_HOUR "Hora" +#define D_STR_HOURS D_STR_HOUR "s" +#define D_STR_MINUTE "Minuto" +#define D_STR_MINUTES D_STR_MINUTE "s" +#define D_STR_SECOND "Segundo" +#define D_STR_SECONDS D_STR_SECOND "s" +#define D_STR_NOW "Agora" +#define D_STR_THREELETTERDAYS "DomSegTerQuaQuiSexSab" + +#define D_STR_YES "Sim" +#define D_STR_NO "Não" +#define D_STR_TRUE "Verdadeiro" +#define D_STR_FALSE "Falso" + +#define D_STR_REPEAT "Repetir" +#define D_STR_CODE "Código" +#define D_STR_BITS "Bits" + +// IRrecvDumpV2+ +#define D_STR_TIMESTAMP "Timestamp" +#define D_STR_LIBRARY "Biblioteca" +#define D_STR_MESGDESC "Descrição da mensagem" +#define D_STR_TOLERANCE "Tolerância" +#ifndef D_STR_IRRECVDUMP_STARTUP +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump está rodando e aguardando por entradas IR no pino %d" +#endif // D_STR_IRRECVDUMP_STARTUP + +#ifndef D_WARN_BUFFERFULL +#define D_WARN_BUFFERFULL \ + "AVISO: O código IR é muito grande para o buffer (>= %d). " \ + "Esse resultado não é confiavel e precisa ser resolvido. " \ + "Edite e aumente o valor de `kCaptureBufferSize`." +#endif // D_WARN_BUFFERFULL + +#endif // LOCALE_PT_BR_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/ru-RU.h b/src/libraries/IRremoteESP8266/src/locale/ru-RU.h new file mode 100644 index 000000000..02a892403 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/ru-RU.h @@ -0,0 +1,152 @@ +// Copyright 2021 - PtilopsisLeucotis (@PtilopsisLeucotis) +// Locale/language file for Russian / Russia. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_RU_RU_H_ +#define LOCALE_RU_RU_H_ + +#define D_STR_UNKNOWN "НЕИЗВЕСТНО" +#define D_STR_PROTOCOL "Протокол" +#define D_STR_POWER "Питание" +#define D_STR_PREVIOUS "Предыдущий" +#define D_STR_ON "Вкл" +#define D_STR_OFF "Выкл" +#define D_STR_MODE "Режим" +#define D_STR_TOGGLE "Переключить" +#define D_STR_TURBO "Турбо" +#define D_STR_SUPER "Супер" +#define D_STR_SLEEP "Сон" +#define D_STR_LIGHT "Свет" +#define D_STR_POWERFUL "Мощный" +#define D_STR_QUIET "Тихий" +#define D_STR_ECONO "Экономичный" +#define D_STR_SWING "Качание" +#define D_STR_SWINGH D_STR_SWING"(Г)" +#define D_STR_SWINGV D_STR_SWING"(В)" +#define D_STR_BEEP "Звук" +#define D_STR_MOULD "Плесень" +#define D_STR_CLEAN "Чистый" +#define D_STR_PURIFY "Очистка" +#define D_STR_TIMER "Таймер" +#define D_STR_ONTIMER "Таймер Включения" +#define D_STR_OFFTIMER "Таймер Выключения" +#define D_STR_TIMERMODE "Режим Таймера" +#define D_STR_CLOCK "Часы" +#define D_STR_COMMAND "Команда" +#define D_STR_HEALTH "Здоровье" +#define D_STR_MODEL "Модель" +#define D_STR_TEMP "Температура" +#define D_STR_HUMID "Влажность" +#define D_STR_SAVE "Сохранить" +#define D_STR_EYE "Глаз" +#define D_STR_FOLLOW "Следовать" +#define D_STR_ION "Ион" +#define D_STR_FRESH "Свежесть" +#define D_STR_HOLD "Удержать" +#define D_STR_BUTTON "Кнопка" +#define D_STR_NIGHT "Ночь" +#define D_STR_SILENT "Тихий" +#define D_STR_FILTER "Фильтр" +#define D_STR_CELSIUS "Цельсий" +#define D_STR_FAHRENHEIT "Фаренгейт" +#define D_STR_UP "Выше" +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP +#define D_STR_DOWN "Ниже" +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN +#define D_STR_CHANGE "Изменение" +#define D_STR_START "Запуск" +#define D_STR_STOP "Остановка" +#define D_STR_MOVE "Перемещение" +#define D_STR_SET "Установка" +#define D_STR_CANCEL "Отмена" +#define D_STR_COMFORT "Комфорт" +#define D_STR_SENSOR "Сенсор" +#define D_STR_DISPLAY "Дисплей" +#define D_STR_WEEKLY "Недельный" +#define D_STR_LAST "Последний" +#define D_STR_FAST "Быстро" +#define D_STR_SLOW "Медленно" +#define D_STR_AIRFLOW "Воздушный Поток" +#define D_STR_STEP "Шаг" +#define D_STR_NA "Н/Д" +#define D_STR_INSIDE "Внутри" +#define D_STR_OUTSIDE "Снаружи" +#define D_STR_LOUD "Громко" +#define D_STR_UPPER "Верхнее" +#define D_STR_LOWER "Нижнее" +#define D_STR_BREEZE "Бриз" +#define D_STR_CIRCULATE "Циркуляция" +#define D_STR_CEILING "Потолок" +#define D_STR_WALL "Стена" +#define D_STR_ROOM "Комната" +#define D_STR_6THSENSE "6-ое чувство" +#define D_STR_FIXED "Фиксированный" +#define D_STR_TYPE "Тип" +#define D_STR_SPECIAL "Специальный" +#define D_STR_RECYCLE "Рециркуляция" +#define D_STR_VANE "Жалюзи" +#define D_STR_LOCK "Блокировка" +#define D_STR_AUTO "Авто" +#define D_STR_AUTOMATIC "Автоматический" +#define D_STR_MANUAL "Ручной" +#define D_STR_COOL "Охл" +#define D_STR_COOLING "Охлаждение" +#define D_STR_HEAT "Нагр" +#define D_STR_HEATING "Обогрев" +#define D_STR_FAN "Вентиляция" +#define D_STR_ONLY "Только" +#define D_STR_FANSPACEONLY D_STR_ONLY " " D_STR_FAN +#define D_STR_FANONLYNOSPACE D_STR_ONLY D_STR_FAN +#define D_STR_DRY "Сухо" +#define D_STR_DRYING "Сушка" +#define D_STR_DEHUMIDIFY "Осушение" +#define D_STR_MAX "Макс" +#define D_STR_MAXIMUM "Максимум" +#define D_STR_MIN "Мин" +#define D_STR_MINIMUM "Минимум" +#define D_STR_MED "Сред" +#define D_STR_MEDIUM "Среднее" +#define D_STR_HIGHEST "Верхнее" +#define D_STR_HIGH "Верх" +#define D_STR_HI "Верх" +#define D_STR_MID "Сред" +#define D_STR_MIDDLE "Середина" +#define D_STR_LOW "Низ" +#define D_STR_LO "Низ" +#define D_STR_LOWEST "Нижнее" +#define D_STR_RIGHT "Право" +#define D_STR_LEFT "Лево" +#define D_STR_WIDE "Широкий" +#define D_STR_CENTRE "Центр" +#define D_STR_TOP "Наивысший" +#define D_STR_BOTTOM "Наинизший" +#define D_STR_DAY "День" +#define D_STR_DAYS "Дней" +#define D_STR_HOUR "Час" +#define D_STR_HOURS "Часов" +#define D_STR_MINUTE "Минута" +#define D_STR_MINUTES "Минут" +#define D_STR_SECOND "Секунда" +#define D_STR_SECONDS "Секунд" +#define D_STR_NOW "Сейчас" +#define D_STR_THREELETTERDAYS "ВскПндВтрСреЧтвПтнСуб" +#define D_STR_YES "Да" +#define D_STR_NO "Нет" +#define D_STR_TRUE "Истина" +#define D_STR_FALSE "Ложь" +#define D_STR_REPEAT "Повтор" +#define D_STR_CODE "Код" +#define D_STR_BITS "Бит" + +// IRrecvDumpV2+ +#define D_STR_TIMESTAMP "Метка Времени" +#define D_STR_LIBRARY "Библиотека" +#define D_STR_MESGDESC "Описание Сообщения." +#define D_STR_TOLERANCE "Допуск" +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump запущен и ождает ИК команды на Входе %d" +#define D_WARN_BUFFERFULL \ + "ПРЕДУПРЕЖДЕНИЕ: ИК код слишком велик для буфера (>= %d). " \ + "Этому результату не следует доверять, пока это не будет исправлено. " \ + "Исправьте и увеличьте `kCaptureBufferSize`." + +#endif // LOCALE_RU_RU_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/sv-SE.h b/src/libraries/IRremoteESP8266/src/locale/sv-SE.h new file mode 100644 index 000000000..b82ec42bc --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/sv-SE.h @@ -0,0 +1,189 @@ +// Copyright 2021 - Tom Rosenback (@tomrosenback) +// Locale/language file for swedish / Sweden. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_SV_SE_H_ +#define LOCALE_SV_SE_H_ + +#define D_STR_UNKNOWN "OKÄND" +#define D_STR_PROTOCOL "Protokoll" +#define D_STR_POWER "Strömläge" +#define D_STR_PREVIOUS "Föregående" +#define D_STR_ON "På" +#define D_STR_OFF "Av" +#define D_STR_MODE "Läge" +#define D_STR_TOGGLE "Växla" +#define D_STR_TURBO "Turbo" +#define D_STR_SUPER "Super" +#define D_STR_SLEEP "Sova" +#define D_STR_LIGHT "Svag" +#define D_STR_POWERFUL "Kraftig" +#define D_STR_QUIET "Tyst" +#define D_STR_ECONO "Eko" +#define D_STR_SWING "Sving" +#define D_STR_SWINGH D_STR_SWING"(H)" +#define D_STR_SWINGV D_STR_SWING"(V)" +#define D_STR_BEEP "Pip" +#define D_STR_MOULD "Forma" +#define D_STR_CLEAN "Rengör" +#define D_STR_PURIFY "Rena" +#define D_STR_TIMER "Timer" +#define D_STR_ONTIMER "På timer" +#define D_STR_OFFTIMER "Av timer" +#define D_STR_TIMERMODE "Timerläge" +#define D_STR_CLOCK "Klocka" +#define D_STR_COMMAND "Kommando" +#define D_STR_XFAN "XFan" +#define D_STR_HEALTH "Hälsa" +#define D_STR_MODEL "Modell" +#define D_STR_TEMP "Temp" +#define D_STR_IFEEL "Känns som" +#define D_STR_HUMID "Humid" +#define D_STR_SAVE "Save" +#define D_STR_EYE "Öga" +#define D_STR_FOLLOW "Följ" +#define D_STR_ION "Ion" +#define D_STR_FRESH "Frisk" +#define D_STR_HOLD "Håll" +#define D_STR_8C_HEAT "8C " D_STR_HEAT +#define D_STR_10C_HEAT "10C " D_STR_HEAT +#define D_STR_BUTTON "Knapp" +#define D_STR_NIGHT "Natt" +#define D_STR_SILENT "Tyst" +#define D_STR_FILTER "Filter" +#define D_STR_3D "3D" +#define D_STR_CELSIUS "Celsius" +#define D_STR_FAHRENHEIT "Fahrenheit" +#define D_STR_CELSIUS_FAHRENHEIT D_STR_CELSIUS "/" D_STR_FAHRENHEIT +#define D_STR_UP "Upp" +#define D_STR_TEMPUP D_STR_TEMP " upp" +#define D_STR_DOWN "Ner" +#define D_STR_TEMPDOWN D_STR_TEMP " ner" +#define D_STR_CHANGE "Ändra" +#define D_STR_START "Starta" +#define D_STR_STOP "Stoppa" +#define D_STR_MOVE "Flytta" +#define D_STR_SET "Ställ in" +#define D_STR_CANCEL "Avbryt" +#define D_STR_COMFORT "Komfort" +#define D_STR_SENSOR "Sensor" +#define D_STR_DISPLAY "Display" +#define D_STR_WEEKLY "Veckovis" +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " timer" +#define D_STR_WIFI "WiFi" +#define D_STR_LAST "Senast" +#define D_STR_FAST "Snabb" +#define D_STR_SLOW "Sakta" +#define D_STR_AIRFLOW "Luftflöde" +#define D_STR_STEP "Steppa" +#define D_STR_NA "N/A" +#define D_STR_INSIDE "Inne" +#define D_STR_OUTSIDE "Ute" +#define D_STR_LOUD "Hög" +#define D_STR_UPPER "Övre" +#define D_STR_LOWER "Nedre" +#define D_STR_BREEZE "Bris" +#define D_STR_CIRCULATE "Cirkulera" +#define D_STR_CEILING "Tak" +#define D_STR_WALL "Vägg" +#define D_STR_ROOM "Rum" +#define D_STR_6THSENSE "6e sinne" +#define D_STR_ZONEFOLLOW "Följ zon" +#define D_STR_FIXED "Fast" +#define D_STR_TYPE "Typ" +#define D_STR_SPECIAL "Speciell" +#define D_STR_RECYCLE "Återvinn" +#define D_STR_ID "Id" +#define D_STR_VANE "Vindflöjel" + +#define D_STR_AUTO "Auto" +#define D_STR_AUTOMATIC "Automatisk" +#define D_STR_MANUAL "Manuell" +#define D_STR_COOL "Kyla" +#define D_STR_HEAT "Värme" +#define D_STR_FAN "Fläkt" +#define D_STR_FANONLY "fläkt-enbart" +#define D_STR_FAN_ONLY "fläkt_enbart" +#define D_STR_ONLY "Enbart" +#define D_STR_FANSPACEONLY D_STR_FAN " " D_STR_ONLY +#define D_STR_FANONLYNOSPACE D_STR_FAN D_STR_ONLY +#define D_STR_DRY "Torka" + +#define D_STR_MAX "Max" +#define D_STR_MAXIMUM "Maximum" +#define D_STR_MIN "Min" +#define D_STR_MINIMUM "Minimum" +#define D_STR_MED "Med" +#define D_STR_MEDIUM "Medium" + +#define D_STR_HIGHEST "Högsta" +#define D_STR_HIGH "Hög" +#define D_STR_HI "Hög" +#define D_STR_MID "Mellan" +#define D_STR_MIDDLE "Mellan" +#define D_STR_LOW "Låg" +#define D_STR_LO "Låg" +#define D_STR_LOWEST "Lägsta" +#define D_STR_RIGHT "Höger" +#define D_STR_MAXRIGHT D_STR_MAX " höger" +#define D_STR_RIGHTMAX_NOSPACE D_STR_MAX D_STR_RIGHT +#define D_STR_LEFT "Vänster" +#define D_STR_MAXLEFT D_STR_MAX " vänster" +#define D_STR_LEFTMAX_NOSPACE D_STR_MAX D_STR_LEFT +#define D_STR_WIDE "Vid" +#define D_STR_CENTRE "Mitten" +#define D_STR_TOP "Topp" +#define D_STR_BOTTOM "Botten" + +#define D_STR_ECONOTOGGLE D_STR_TOGGLE " eko" +#define D_STR_EYEAUTO D_STR_AUTO " öga" +#define D_STR_LIGHTTOGGLE D_STR_TOGGLE " svag" +#define D_STR_OUTSIDEQUIET D_STR_QUIET " ute" +#define D_STR_POWERTOGGLE D_STR_TOGGLE " strömläge" +#define D_STR_POWERBUTTON "Strömknapp" +#define D_STR_PREVIOUSPOWER "Föregående strömläge" +#define D_STR_DISPLAYTEMP "Displaytemp" +#define D_STR_SENSORTEMP "Sensortemp" +#define D_STR_SLEEP_TIMER "Sovtimer" +#define D_STR_SWINGVMODE D_STR_SWINGV " läge" +#define D_STR_SWINGVTOGGLE D_STR_TOGGLE " sving(v)" +#define D_STR_TURBOTOGGLE D_STR_TOGGLE " turbo" + +// Separatorer +#define D_CHR_TIME_SEP ':' +#define D_STR_SPACELBRACE " (" +#define D_STR_COMMASPACE ", " +#define D_STR_COLONSPACE ": " + +#define D_STR_DAY "Dag" +#define D_STR_DAYS D_STR_DAY "ar" +#define D_STR_HOUR "Timme" +#define D_STR_HOURS "Timmar" +#define D_STR_MINUTE "Minut" +#define D_STR_MINUTES D_STR_MINUTE "er" +#define D_STR_SECOND "Sekund" +#define D_STR_SECONDS D_STR_MINUTE "er" +#define D_STR_NOW "Nu" +#define D_STR_THREELETTERDAYS "SönMånTisOnsTorFreLör" + +#define D_STR_YES "Ja" +#define D_STR_NO "Nej" +#define D_STR_TRUE "Sant" +#define D_STR_FALSE "Falskt" + +#define D_STR_REPEAT "Upprepa" +#define D_STR_CODE "Kod" +#define D_STR_BITS "Bitar" + +// IRrecvDumpV2+ +#define D_STR_TIMESTAMP "Tidskod" +#define D_STR_LIBRARY "Bibliotek" +#define D_STR_MESGDESC "Meddelande beskr." +#define D_STR_TOLERANCE "Tolerans" +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump har nu startats och väntar på IR signaler på PIN %d" +#define D_WARN_BUFFERFULL \ + "VARNING: IR koden är för stor för att rymmas i bufferminnet (>= %d). " \ + "Detta resultat är inte pålitligt innan problemet är åtgärdat. " \ + "Redigera och öka `kCaptureBufferSize`." + +#endif // LOCALE_SV_SE_H_ diff --git a/src/libraries/IRremoteESP8266/src/locale/zh-CN.h b/src/libraries/IRremoteESP8266/src/locale/zh-CN.h new file mode 100644 index 000000000..5330c93e3 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/locale/zh-CN.h @@ -0,0 +1,465 @@ +// Copyright 2020 - MiaoYi (@Caffreyfans) +// Locale/language file for China / Simplified. +// This file will override the default values located in `defaults.h`. +#ifndef LOCALE_ZH_CN_H_ +#define LOCALE_ZH_CN_H_ + +#ifndef D_STR_UNKNOWN +#define D_STR_UNKNOWN "未知" +#endif // D_STR_UNKNOWN +#ifndef D_STR_PROTOCOL +#define D_STR_PROTOCOL "协议" +#endif // D_STR_PROTOCOL +#ifndef D_STR_POWER +#define D_STR_POWER "电源" +#endif // D_STR_POWER +#ifndef D_STR_PREVIOUS +#define D_STR_PREVIOUS "以前" +#endif // D_STR_PREVIOUS +#ifndef D_STR_ON +#define D_STR_ON "开" +#endif // D_STR_ON +#ifndef D_STR_OFF +#define D_STR_OFF "关" +#endif // D_STR_OFF +#ifndef D_STR_MODE +#define D_STR_MODE "模式" +#endif // D_STR_MODE +#ifndef D_STR_TOGGLE +#define D_STR_TOGGLE "切换" +#endif // D_STR_TOGGLE +#ifndef D_STR_TURBO +#define D_STR_TURBO "强力" +#endif // D_STR_TURBO +#ifndef D_STR_SUPER +#define D_STR_SUPER "超级" +#endif // D_STR_SUPER +#ifndef D_STR_SLEEP +#define D_STR_SLEEP "睡眠" +#endif // D_STR_SLEEP +#ifndef D_STR_LIGHT +#define D_STR_LIGHT "灯光" +#endif // D_STR_LIGHT +#ifndef D_STR_POWERFUL +#define D_STR_POWERFUL "强劲模式" +#endif // D_STR_POWERFUL +#ifndef D_STR_QUIET +#define D_STR_QUIET "安静" +#endif // D_STR_QUIET +#ifndef D_STR_ECONO +#define D_STR_ECONO "经济" +#endif // D_STR_ECONO +#ifndef D_STR_SWING +#define D_STR_SWING "扫风" +#endif // D_STR_SWING +#ifndef D_STR_SWINGH +#define D_STR_SWINGH D_STR_SWING"(H)" // Set `D_STR_SWING` first! +#endif // D_STR_SWINGH +#ifndef D_STR_SWINGV +#define D_STR_SWINGV D_STR_SWING"(V)" // Set `D_STR_SWING` first! +#endif // D_STR_SWINGV +#ifndef D_STR_BEEP +#define D_STR_BEEP "蜂鸣" +#endif // D_STR_BEEP +#ifndef D_STR_MOULD +#define D_STR_MOULD "模子" +#endif // D_STR_MOULD +#ifndef D_STR_CLEAN +#define D_STR_CLEAN "清洁" +#endif // D_STR_CLEAN +#ifndef D_STR_PURIFY +#define D_STR_PURIFY "净化" +#endif // D_STR_PURIFY +#ifndef D_STR_TIMER +#define D_STR_TIMER "计时器" +#endif // D_STR_TIMER +#ifndef D_STR_ONTIMER +#define D_STR_ONTIMER D_STR_ON " " D_STR_TIMER // Set `D_STR_ON` first! +#endif // D_STR_ONTIMER +#ifndef D_STR_OFFTIMER +#define D_STR_OFFTIMER D_STR_OFF " " D_STR_TIMER // Set `D_STR_OFF` first! +#endif // D_STR_OFFTIMER +#ifndef D_STR_CLOCK +#define D_STR_CLOCK "时钟" +#endif // D_STR_CLOCK +#ifndef D_STR_COMMAND +#define D_STR_COMMAND "命令" +#endif // D_STR_COMMAND +#ifndef D_STR_XFAN +#define D_STR_XFAN "XFan" +#endif // D_STR_XFAN +#ifndef D_STR_HEALTH +#define D_STR_HEALTH "健康" +#endif // D_STR_HEALTH +#ifndef D_STR_MODEL +#define D_STR_MODEL "模式" +#endif // D_STR_MODEL +#ifndef D_STR_TEMP +#define D_STR_TEMP "温度" +#endif // D_STR_TEMP +#ifndef D_STR_IFEEL +#define D_STR_IFEEL "IFeel" +#endif // D_STR_IFEEL +#ifndef D_STR_HUMID +#define D_STR_HUMID "湿度" +#endif // D_STR_HUMID +#ifndef D_STR_SAVE +#define D_STR_SAVE "保存" +#endif // D_STR_SAVE +#ifndef D_STR_EYE +#define D_STR_EYE "眼" +#endif // D_STR_EYE +#ifndef D_STR_FOLLOW +#define D_STR_FOLLOW "跟随" +#endif // D_STR_FOLLOW +#ifndef D_STR_ION +#define D_STR_ION "Ion" +#endif // D_STR_ION +#ifndef D_STR_FRESH +#define D_STR_FRESH "刷新" +#endif // D_STR_FRESH +#ifndef D_STR_HOLD +#define D_STR_HOLD "保持" +#endif // D_STR_HOLD +#ifndef D_STR_8C_HEAT +#define D_STR_8C_HEAT "8C " D_STR_HEAT // Set `D_STR_HEAT` first! +#endif // D_STR_8C_HEAT +#ifndef D_STR_BUTTON +#define D_STR_BUTTON "按钮" +#endif // D_STR_BUTTON +#ifndef D_STR_NIGHT +#define D_STR_NIGHT "夜间" +#endif // D_STR_NIGHT +#ifndef D_STR_SILENT +#define D_STR_SILENT "安静" +#endif // D_STR_SILENT +#ifndef D_STR_FILTER +#define D_STR_FILTER "过滤" +#endif // D_STR_FILTER +#ifndef D_STR_3D +#define D_STR_3D "3D" +#endif // D_STR_3D +#ifndef D_STR_CELSIUS +#define D_STR_CELSIUS "摄氏度" +#endif // D_STR_CELSIUS +#ifndef D_STR_UP +#define D_STR_UP "上" +#endif // D_STR_UP +#ifndef D_STR_TEMPUP +#define D_STR_TEMPUP D_STR_TEMP " " D_STR_UP // Set `D_STR_TEMP` first! +#endif // D_STR_TEMPUP +#ifndef D_STR_DOWN +#define D_STR_DOWN "下" +#endif // D_STR_DOWN +#ifndef D_STR_TEMPDOWN +#define D_STR_TEMPDOWN D_STR_TEMP " " D_STR_DOWN // Set `D_STR_TEMP` first! +#endif // D_STR_TEMPDOWN +#ifndef D_STR_CHANGE +#define D_STR_CHANGE "改变" +#endif // D_STR_CHANGE +#ifndef D_STR_START +#define D_STR_START "开始" +#endif // D_STR_START +#ifndef D_STR_STOP +#define D_STR_STOP "结束" +#endif // D_STR_STOP +#ifndef D_STR_MOVE +#define D_STR_MOVE "移动" +#endif // D_STR_MOVE +#ifndef D_STR_SET +#define D_STR_SET "设置" +#endif // D_STR_SET +#ifndef D_STR_CANCEL +#define D_STR_CANCEL "取消" +#endif // D_STR_CANCEL +#ifndef D_STR_COMFORT +#define D_STR_COMFORT "舒适" +#endif // D_STR_COMFORT +#ifndef D_STR_SENSOR +#define D_STR_SENSOR "传感器" +#endif // D_STR_SENSOR +#ifndef D_STR_WEEKLY +#define D_STR_WEEKLY "每周" +#endif // D_STR_WEEKLY +#ifndef D_STR_WEEKLYTIMER +#define D_STR_WEEKLYTIMER D_STR_WEEKLY " " D_STR_TIMER // Needs `D_STR_WEEKLY`! +#endif // D_STR_WEEKLYTIMER +#ifndef D_STR_WIFI +#define D_STR_WIFI "WiFi" +#endif // D_STR_WIFI +#ifndef D_STR_LAST +#define D_STR_LAST "最近" +#endif // D_STR_LAST +#ifndef D_STR_FAST +#define D_STR_FAST "快" +#endif // D_STR_FAST +#ifndef D_STR_SLOW +#define D_STR_SLOW "慢" +#endif // D_STR_SLOW +#ifndef D_STR_AIRFLOW +#define D_STR_AIRFLOW "空气流动" +#endif // D_STR_AIRFLOW +#ifndef D_STR_STEP +#define D_STR_STEP "步" +#endif // D_STR_STEP +#ifndef D_STR_NA +#define D_STR_NA "不适用" +#endif // D_STR_NA +#ifndef D_STR_OUTSIDE +#define D_STR_OUTSIDE "室外" +#endif // D_STR_OUTSIDE +#ifndef D_STR_LOUD +#define D_STR_LOUD "大声" +#endif // D_STR_LOUD +#ifndef D_STR_UPPER +#define D_STR_UPPER "更高" +#endif // D_STR_UPPER +#ifndef D_STR_LOWER +#define D_STR_LOWER "更低" +#endif // D_STR_LOWER +#ifndef D_STR_BREEZE +#define D_STR_BREEZE "微风" +#endif // D_STR_BREEZE +#ifndef D_STR_CIRCULATE +#define D_STR_CIRCULATE "流通" +#endif // D_STR_CIRCULATE +#ifndef D_STR_CEILING +#define D_STR_CEILING "天花板" +#endif // D_STR_CEILING +#ifndef D_STR_WALL +#define D_STR_WALL "墙" +#endif // D_STR_WALL +#ifndef D_STR_ROOM +#define D_STR_ROOM "房间" +#endif // D_STR_ROOM +#ifndef D_STR_6THSENSE +#define D_STR_6THSENSE "第六感" +#endif // D_STR_6THSENSE +#ifndef D_STR_ZONEFOLLOW +#define D_STR_ZONEFOLLOW "区域跟随" +#endif // D_STR_ZONEFOLLOW +#ifndef D_STR_FIXED +#define D_STR_FIXED "固定" +#endif // D_STR_FIXED + +#ifndef D_STR_AUTO +#define D_STR_AUTO "自动" +#endif // D_STR_AUTO +#ifndef D_STR_AUTOMATIC +#define D_STR_AUTOMATIC "自动的" +#endif // D_STR_AUTOMATIC +#ifndef D_STR_MANUAL +#define D_STR_MANUAL "手动" +#endif // D_STR_MANUAL +#ifndef D_STR_COOL +#define D_STR_COOL "制冷" +#endif // D_STR_COOL +#ifndef D_STR_HEAT +#define D_STR_HEAT "加热" +#endif // D_STR_HEAT +#ifndef D_STR_FAN +#define D_STR_FAN "风扇" +#endif // D_STR_FAN +#ifndef D_STR_FANONLY +#define D_STR_FANONLY "仅风扇" +#endif // D_STR_FANONLY +#ifndef D_STR_DRY +#define D_STR_DRY "干燥" +#endif // D_STR_DRY + +#ifndef D_STR_MAX +#define D_STR_MAX "最大" +#endif // D_STR_MAX +#ifndef D_STR_MAXIMUM +#define D_STR_MAXIMUM "最小" +#endif // D_STR_MAXIMUM +#ifndef D_STR_MIN +#define D_STR_MIN "最低" +#endif // D_STR_MIN +#ifndef D_STR_MINIMUM +#define D_STR_MINIMUM "最低" +#endif // D_STR_MINIMUM +#ifndef D_STR_MED +#define D_STR_MED "中" +#endif // D_STR_MED +#ifndef D_STR_MEDIUM +#define D_STR_MEDIUM "中" +#endif // D_STR_MEDIUM + +#ifndef D_STR_HIGHEST +#define D_STR_HIGHEST "最高" +#endif // D_STR_HIGHEST +#ifndef D_STR_HIGH +#define D_STR_HIGH "高" +#endif // D_STR_HIGH +#ifndef D_STR_HI +#define D_STR_HI "嗨" +#endif // D_STR_HI +#ifndef D_STR_MID +#define D_STR_MID "中" +#endif // D_STR_MID +#ifndef D_STR_MIDDLE +#define D_STR_MIDDLE "居中" +#endif // D_STR_MIDDLE +#ifndef D_STR_LOW +#define D_STR_LOW "低" +#endif // D_STR_LOW +#ifndef D_STR_LO +#define D_STR_LO "低" +#endif // D_STR_LO +#ifndef D_STR_LOWEST +#define D_STR_LOWEST "最低" +#endif // D_STR_LOWEST +#ifndef D_STR_RIGHT +#define D_STR_RIGHT "右" +#endif // D_STR_RIGHT +#ifndef D_STR_MAXRIGHT +#define D_STR_MAXRIGHT D_STR_MAX " " D_STR_RIGHT // Set `D_STR_MAX` first! +#endif // D_STR_MAXRIGHT +#ifndef D_STR_RIGHTMAX_NOSPACE +#define D_STR_RIGHTMAX_NOSPACE D_STR_RIGHT D_STR_MAX // Set `D_STR_MAX` first! +#endif // D_STR_RIGHTMAX_NOSPACE +#ifndef D_STR_LEFT +#define D_STR_LEFT "左" +#endif // D_STR_LEFT +#ifndef D_STR_MAXLEFT +#define D_STR_MAXLEFT D_STR_MAX " " D_STR_LEFT // Set `D_STR_MAX` first! +#endif // D_STR_MAXLEFT +#ifndef D_STR_LEFTMAX_NOSPACE +#define D_STR_LEFTMAX_NOSPACE D_STR_LEFT D_STR_MAX // Set `D_STR_MAX` first! +#endif // D_STR_LEFTMAX_NOSPACE +#ifndef D_STR_WIDE +#define D_STR_WIDE "扫风" +#endif // D_STR_WIDE +#ifndef D_STR_CENTRE +#define D_STR_CENTRE "中间" +#endif // D_STR_CENTRE +#ifndef D_STR_TOP +#define D_STR_TOP "上部" +#endif // D_STR_TOP +#ifndef D_STR_BOTTOM +#define D_STR_BOTTOM "底部" +#endif // D_STR_BOTTOM + +// Compound words/phrases/descriptions from pre-defined words. +// Note: Obviously these need to be defined *after* their component words. +#ifndef D_STR_EYEAUTO +#define D_STR_EYEAUTO D_STR_EYE " " D_STR_AUTO +#endif // D_STR_EYEAUTO +#ifndef D_STR_LIGHTTOGGLE +#define D_STR_LIGHTTOGGLE D_STR_LIGHT " " D_STR_TOGGLE +#endif // D_STR_LIGHTTOGGLE +#ifndef D_STR_OUTSIDEQUIET +#define D_STR_OUTSIDEQUIET D_STR_OUTSIDE " " D_STR_QUIET +#endif // D_STR_OUTSIDEQUIET +#ifndef D_STR_POWERTOGGLE +#define D_STR_POWERTOGGLE D_STR_POWER " " D_STR_TOGGLE +#endif // D_STR_POWERTOGGLE +#ifndef D_STR_PREVIOUSPOWER +#define D_STR_PREVIOUSPOWER D_STR_PREVIOUS " " D_STR_POWER +#endif // D_STR_PREVIOUSPOWER +#ifndef D_STR_SENSORTEMP +#define D_STR_SENSORTEMP D_STR_SENSOR " " D_STR_TEMP +#endif // D_STR_SENSORTEMP +#ifndef D_STR_SLEEP_TIMER +#define D_STR_SLEEP_TIMER D_STR_SLEEP " " D_STR_TIMER +#endif // D_STR_SLEEP_TIMER +#ifndef D_STR_SWINGVMODE +#define D_STR_SWINGVMODE D_STR_SWINGV " " D_STR_MODE +#endif // D_STR_SWINGVMODE +#ifndef D_STR_SWINGVTOGGLE +#define D_STR_SWINGVTOGGLE D_STR_SWINGV " " D_STR_TOGGLE +#endif // D_STR_SWINGVTOGGLE + +// Separators +#ifndef D_CHR_TIME_SEP +#define D_CHR_TIME_SEP ':' +#endif // D_CHR_TIME_SEP +#ifndef D_STR_SPACELBRACE +#define D_STR_SPACELBRACE " (" +#endif // D_STR_SPACELBRACE +#ifndef D_STR_COMMASPACE +#define D_STR_COMMASPACE ", " +#endif // D_STR_COMMASPACE +#ifndef D_STR_COLONSPACE +#define D_STR_COLONSPACE ": " +#endif // D_STR_COLONSPACE + +#ifndef D_STR_DAY +#define D_STR_DAY "天" +#endif // D_STR_DAY +#ifndef D_STR_DAYS +#define D_STR_DAYS D_STR_DAY "s" +#endif // D_STR_DAYS +#ifndef D_STR_HOUR +#define D_STR_HOUR "时" +#endif // D_STR_HOUR +#ifndef D_STR_HOURS +#define D_STR_HOURS D_STR_HOUR "s" +#endif // D_STR_HOURS +#ifndef D_STR_MINUTE +#define D_STR_MINUTE "分" +#endif // D_STR_MINUTE +#ifndef D_STR_MINUTES +#define D_STR_MINUTES D_STR_MINUTE "s" +#endif // D_STR_MINUTES +#ifndef D_STR_SECOND +#define D_STR_SECOND "秒" +#endif // D_STR_SECOND +#ifndef D_STR_SECONDS +#define D_STR_SECONDS D_STR_SECOND "s" +#endif // D_STR_SECONDS +#ifndef D_STR_NOW +#define D_STR_NOW "现在" +#endif // D_STR_NOW +/* This is not three letter days. Disabled. +#ifndef D_STR_THREELETTERDAYS +#define D_STR_THREELETTERDAYS "周一至周末" +#endif // D_STR_THREELETTERDAYS +*/ + +#ifndef D_STR_YES +#define D_STR_YES "是" +#endif // D_STR_YES +#ifndef D_STR_NO +#define D_STR_NO "否" +#endif // D_STR_NO +#ifndef D_STR_TRUE +#define D_STR_TRUE "正确" +#endif // D_STR_TRUE +#ifndef D_STR_FALSE +#define D_STR_FALSE "错误" +#endif // D_STR_FALSE + +#ifndef D_STR_REPEAT +#define D_STR_REPEAT "重复" +#endif // D_STR_REPEAT +#ifndef D_STR_CODE +#define D_STR_CODE "代码" +#endif // D_STR_CODE +#ifndef D_STR_BITS +#define D_STR_BITS "位" +#endif // D_STR_BITS + +// IRrecvDumpV2+ +#ifndef D_STR_TIMESTAMP +#define D_STR_TIMESTAMP "时间戳记" +#endif // D_STR_TIMESTAMP +#ifndef D_STR_LIBRARY +#define D_STR_LIBRARY "库文件" +#endif // D_STR_LIBRARY +#ifndef D_STR_MESGDESC +#define D_STR_MESGDESC "等等信息" +#endif // D_STR_MESGDESC +#ifndef D_STR_IRRECVDUMP_STARTUP +#define D_STR_IRRECVDUMP_STARTUP \ + "IRrecvDump 运行当中,等待红外信息输入位于引脚 %d" +#endif // D_STR_IRRECVDUMP_STARTUP +#ifndef D_WARN_BUFFERFULL +#define D_WARN_BUFFERFULL \ + "警告: 红外编码数组过大(>= %d). " \ + "在解决此问题之前,不应信任此结果. " \ + "编辑并增加 `kCaptureBufferSize` 变量." +#endif // D_WARN_BUFFERFULL + +#endif // LOCALE_ZH_CN_H_ diff --git a/src/libraries/IRremoteESP8266/src/minmax.h b/src/libraries/IRremoteESP8266/src/minmax.h new file mode 100644 index 000000000..d52361575 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/minmax.h @@ -0,0 +1,41 @@ +#ifndef __MINMAX_H__ +#define __MINMAX_H__ + +template A min(A a,B b) +{ + return a A min(A a,A b,A c) +{ + return min(min(a,b),c); +} + +template A min(A a,A b,A c,A d) +{ + return min(min(a,b,c),d); +} + +template A min(A a,A b,A c,A d,A e) +{ + return min(min(a,b,c,d),e); +} + +template A min(A a,A b,A c,A d,A e,A f) +{ + return min(min(a,b,c,d,e),f); +} + +template A min(A a,A b,A c,A d,A e,A f,A g) +{ + return min(min(a,b,c,d,e,f),g); +} + + +template A max(A a,B b) +{ + return a>b?a:b; +} + + +#endif //__MINMAX_H__ \ No newline at end of file From b498257199fdc04343017d08a4b6a6a0460ee49c Mon Sep 17 00:00:00 2001 From: openshwprojects Date: Thu, 23 Feb 2023 09:25:22 +0100 Subject: [PATCH 02/16] p2 - not working, receives garbage --- src/driver/drv_ir.cpp | 1373 +++++++++-------- src/libraries/IRremoteESP8266/src/IRrecv.cpp | 395 +++-- .../IRremoteESP8266/src/digitalWriteFast.h | 24 + 3 files changed, 1045 insertions(+), 747 deletions(-) create mode 100644 src/libraries/IRremoteESP8266/src/digitalWriteFast.h diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index 162421927..4d9c216c8 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -1,40 +1,40 @@ #if PLATFORM_BEKEN extern "C" { - // these cause error: conflicting declaration of 'int bk_wlan_mcu_suppress_and_sleep(unsigned int)' with 'C' linkage - #include "../new_common.h" - - #include "include.h" - #include "arm_arch.h" - #include "../new_pins.h" - #include "../new_cfg.h" - #include "../logging/logging.h" - #include "../obk_config.h" - #include "../cmnds/cmd_public.h" - #include "bk_timer_pub.h" - #include "drv_model_pub.h" - - // why can;t I call this? - #include "../mqtt/new_mqtt.h" - - #include - //#include "pwm.h" - #include "pwm_pub.h" - - #include "../../beken378/func/include/net_param_pub.h" - #include "../../beken378/func/user_driver/BkDriverPwm.h" - #include "../../beken378/func/user_driver/BkDriverI2c.h" - #include "../../beken378/driver/i2c/i2c1.h" - #include "../../beken378/driver/gpio/gpio.h" - - - unsigned long ir_counter = 0; - uint8_t gEnableIRSendWhilstReceive = 0; - uint32_t gIRProtocolEnable = 0xFFFFFFFF; - // 0 == active low. 1 = active hi - uint8_t gIRPinPolarity = 0; - - extern int my_strnicmp(const char* a, const char* b, int len); + // these cause error: conflicting declaration of 'int bk_wlan_mcu_suppress_and_sleep(unsigned int)' with 'C' linkage +#include "../new_common.h" + +#include "include.h" +#include "arm_arch.h" +#include "../new_pins.h" +#include "../new_cfg.h" +#include "../logging/logging.h" +#include "../obk_config.h" +#include "../cmnds/cmd_public.h" +#include "bk_timer_pub.h" +#include "drv_model_pub.h" + +// why can;t I call this? +#include "../mqtt/new_mqtt.h" + +#include +//#include "pwm.h" +#include "pwm_pub.h" + +#include "../../beken378/func/include/net_param_pub.h" +#include "../../beken378/func/user_driver/BkDriverPwm.h" +#include "../../beken378/func/user_driver/BkDriverI2c.h" +#include "../../beken378/driver/i2c/i2c1.h" +#include "../../beken378/driver/gpio/gpio.h" + + + unsigned long ir_counter = 0; + uint8_t gEnableIRSendWhilstReceive = 0; + uint32_t gIRProtocolEnable = 0xFFFFFFFF; + // 0 == active low. 1 = active hi + uint8_t gIRPinPolarity = 0; + + extern int my_strnicmp(const char* a, const char* b, int len); } @@ -55,33 +55,33 @@ typedef unsigned short uint16_t; #define __FlashStringHelper char // dummy functions -void noInterrupts(){} -void interrupts(){} +void noInterrupts() {} +void interrupts() {} -unsigned long millis(){ - return 0; +unsigned long millis() { + return 0; } -unsigned long micros(){ - return 0; +unsigned long micros() { + return 0; } -void delay(int n){ - return; +void delay(int n) { + return; } -void delayMicroseconds(int n){ - return; +void delayMicroseconds(int n) { + return; } class Print { - public: - void println(const char *p){ - return; - } - void print(...){ - return; - } +public: + void println(const char *p) { + return; + } + void print(...) { + return; + } }; Print Serial; @@ -94,23 +94,23 @@ Print Serial; void digitalToggleFast(unsigned char P) { - bk_gpio_output((GPIO_INDEX)P, !bk_gpio_input((GPIO_INDEX)P)); + bk_gpio_output((GPIO_INDEX)P, !bk_gpio_input((GPIO_INDEX)P)); } -unsigned char digitalReadFast(unsigned char P) { +unsigned char digitalReadFast(unsigned char P) { return bk_gpio_input((GPIO_INDEX)P); } void digitalWriteFast(unsigned char P, unsigned char V) { - //RAW_SetPinValue(P, V); - //HAL_PIN_SetOutputValue(index, iVal); - bk_gpio_output((GPIO_INDEX)P, V); + //RAW_SetPinValue(P, V); + //HAL_PIN_SetOutputValue(index, iVal); + bk_gpio_output((GPIO_INDEX)P, V); } void pinModeFast(unsigned char P, unsigned char V) { - if (V == INPUT){ - bk_gpio_config_input_pup((GPIO_INDEX)P); - } + if (V == INPUT) { + bk_gpio_config_input_pup((GPIO_INDEX)P); + } } @@ -127,66 +127,68 @@ void pinModeFast(unsigned char P, unsigned char V) { // # endif // #define ISR void IR_ISR extern "C" void DRV_IR_ISR(UINT8 t); +extern void IR_ISR(); static UINT32 ir_chan = BKTIMER0; static UINT32 ir_div = 1; static UINT32 ir_periodus = 50; -void timerConfigForReceive(){ - // nothing here` +void timerConfigForReceive() { + // nothing here` } void _timerConfigForReceive() { - ir_counter = 0; - - timer_param_t params = { - (unsigned char) ir_chan, - (unsigned char) ir_div, // div - ir_periodus, // us - DRV_IR_ISR - }; - //GLOBAL_INT_DECLARATION(); - - - UINT32 res; - // test what error we get with an invalid command - res = sddev_control((char *)TIMER_DEV_NAME, -1, nullptr); - - if (res == 1){ - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"bk_timer already initialised"); - } else { - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"bk_timer driver not initialised?"); - if ((int)res == -5){ - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"bk_timer sddev not found - not initialised?"); - return; - } - return; - } + ir_counter = 0; + + timer_param_t params = { + (unsigned char)ir_chan, + (unsigned char)ir_div, // div + ir_periodus, // us + DRV_IR_ISR + }; + //GLOBAL_INT_DECLARATION(); + + + UINT32 res; + // test what error we get with an invalid command + res = sddev_control((char *)TIMER_DEV_NAME, -1, nullptr); + + if (res == 1) { + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"bk_timer already initialised"); + } + else { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"bk_timer driver not initialised?"); + if ((int)res == -5) { + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"bk_timer sddev not found - not initialised?"); + return; + } + return; + } //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"ir timer init"); - // do not need to do this - //bk_timer_init(); + // do not need to do this + //bk_timer_init(); //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"ir timer init done"); ADDLOG_INFO(LOG_FEATURE_IR, (char *)"will ir timer setup %u", res); - res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_INIT_PARAM_US, ¶ms); + res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_INIT_PARAM_US, ¶ms); ADDLOG_INFO(LOG_FEATURE_IR, (char *)"ir timer setup %u", res); - res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_UNIT_ENABLE, &ir_chan); + res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_UNIT_ENABLE, &ir_chan); ADDLOG_INFO(LOG_FEATURE_IR, (char *)"ir timer enabled %u", res); } -static void timer_enable(){ +static void timer_enable() { } -static void timer_disable(){ +static void timer_disable() { } -static void _timer_enable(){ - UINT32 res; - res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_UNIT_ENABLE, &ir_chan); +static void _timer_enable() { + UINT32 res; + res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_UNIT_ENABLE, &ir_chan); ADDLOG_INFO(LOG_FEATURE_IR, (char *)"ir timer enabled %u", res); } -static void _timer_disable(){ - UINT32 res; - res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_UNIT_DISABLE, &ir_chan); +static void _timer_disable() { + UINT32 res; + res = sddev_control((char *)TIMER_DEV_NAME, CMD_TIMER_UNIT_DISABLE, &ir_chan); ADDLOG_INFO(LOG_FEATURE_IR, (char *)"ir timer disabled %u", res); } @@ -196,10 +198,10 @@ static void _timer_disable(){ ////////////////////////////////////////// class SpoofIrReceiver { - public: - static void restartAfterSend(){ +public: + static void restartAfterSend() { - } + } }; SpoofIrReceiver IrReceiver; @@ -212,7 +214,7 @@ SpoofIrReceiver IrReceiver; #include "../libraries/IRremoteESP8266/src/IRproto.h" -extern "C" int PIN_GetPWMIndexForPinIndex(int pin) ; +extern "C" int PIN_GetPWMIndexForPinIndex(int pin); // override aspects of sending for our own interrupt driven sends // basically, IRsend calls mark(us) and space(us) to send. @@ -222,611 +224,679 @@ extern "C" int PIN_GetPWMIndexForPinIndex(int pin) ; #define SEND_MAXBITS 128 class myIRsend : public IRsend { - public: - myIRsend(uint_fast8_t aSendPin):IRsend(aSendPin){ - our_us = 0; - our_ms = 0; - resetsendqueue(); - } - ~myIRsend() { } - - - uint32_t millis(){ - return our_ms; - } - - void delay(long int ms){ - // add a pure delay to our queue - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Delay %dms", ms); - space(ms*1000); - } - - uint16_t mark(uint16_t aMarkMicros){ - // sends a high for aMarkMicros - uint32_t newtimein = (timein + 1)%(SEND_MAXBITS * 2); - if (newtimein != timeout){ - // store mark bits in highest +ve bit of count - times[timein] = aMarkMicros | 0x10000000; - timein = newtimein; - timecount++; - timecounttotal++; - } else { - overflows++; - } - return 1; - } - - void space(uint32_t aMarkMicros){ - // sends a low for aMarkMicros - uint32_t newtimein = (timein + 1)%(SEND_MAXBITS * 2); - if (newtimein != timeout){ - times[timein] = aMarkMicros; - timein = newtimein; - timecount++; - timecounttotal++; - } else { - overflows++; - } - } - - void enableIROut(uint32_t freq, uint8_t duty){ - //uint_fast8_t aFrequencyKHz - if (freq < 1000) // Were we given kHz? Supports the old call usage. - freq *= 1000; - - // just setup variables for use in ISR - //pwmfrequency = ((uint32_t)aFrequencyKHz) * 1000; - pwmperiod = (26000000 / freq); - pwmduty = pwmperiod/2; - } - - void resetsendqueue(){ - // sends a low for aMarkMicros - timein = timeout = 0; - timecount = 0; - overflows = 0; - currentsendtime = 0; - currentbitval = 0; - timecounttotal = 0; - } - int32_t times[SEND_MAXBITS * 2]; // enough for 128 bits - unsigned short timein; - unsigned short timeout; - unsigned short timecount; - unsigned short overflows; - uint32_t timecounttotal; - - int32_t getsendqueue(){ - int32_t val = 0; - if (timein != timeout){ - val = times[timeout]; - timeout = (timeout + 1)%(SEND_MAXBITS * 2); - timecount --; - } - return val; - } - - int currentsendtime; - int currentbitval; - - uint8_t sendPin; - uint8_t pwmIndex; - uint32_t pwmfrequency; - uint32_t pwmperiod; - uint32_t pwmduty; - - uint32_t our_ms; - uint32_t our_us; +public: + myIRsend(uint_fast8_t aSendPin) :IRsend(aSendPin) { + our_us = 0; + our_ms = 0; + resetsendqueue(); + } + ~myIRsend() { } + + + uint32_t millis() { + return our_ms; + } + + void delay(long int ms) { + // add a pure delay to our queue + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Delay %dms", ms); + space(ms * 1000); + } + + uint16_t mark(uint16_t aMarkMicros) { + // sends a high for aMarkMicros + uint32_t newtimein = (timein + 1) % (SEND_MAXBITS * 2); + if (newtimein != timeout) { + // store mark bits in highest +ve bit of count + times[timein] = aMarkMicros | 0x10000000; + timein = newtimein; + timecount++; + timecounttotal++; + } + else { + overflows++; + } + return 1; + } + + void space(uint32_t aMarkMicros) { + // sends a low for aMarkMicros + uint32_t newtimein = (timein + 1) % (SEND_MAXBITS * 2); + if (newtimein != timeout) { + times[timein] = aMarkMicros; + timein = newtimein; + timecount++; + timecounttotal++; + } + else { + overflows++; + } + } + + void enableIROut(uint32_t freq, uint8_t duty) { + //uint_fast8_t aFrequencyKHz + if (freq < 1000) // Were we given kHz? Supports the old call usage. + freq *= 1000; + + // just setup variables for use in ISR + //pwmfrequency = ((uint32_t)aFrequencyKHz) * 1000; + pwmperiod = (26000000 / freq); + pwmduty = pwmperiod / 2; + } + + void resetsendqueue() { + // sends a low for aMarkMicros + timein = timeout = 0; + timecount = 0; + overflows = 0; + currentsendtime = 0; + currentbitval = 0; + timecounttotal = 0; + } + int32_t times[SEND_MAXBITS * 2]; // enough for 128 bits + unsigned short timein; + unsigned short timeout; + unsigned short timecount; + unsigned short overflows; + uint32_t timecounttotal; + + int32_t getsendqueue() { + int32_t val = 0; + if (timein != timeout) { + val = times[timeout]; + timeout = (timeout + 1) % (SEND_MAXBITS * 2); + timecount--; + } + return val; + } + + int currentsendtime; + int currentbitval; + + uint8_t sendPin; + uint8_t pwmIndex; + uint32_t pwmfrequency; + uint32_t pwmperiod; + uint32_t pwmduty; + + uint32_t our_ms; + uint32_t our_us; }; // our send/receive instances myIRsend *pIRsend = NULL; -//IRrecv *ourReceiver = NULL; +IRrecv *ourReceiver = NULL; // this is our ISR. // it is called every 50us, so we need to work on making it as efficient as possible. -extern "C" void DRV_IR_ISR(UINT8 t){ - int sending = 0; - if (pIRsend && (pIRsend->pwmIndex >= 0)){ - pIRsend->our_us += 50; - if (pIRsend->our_us > 1000){ - pIRsend->our_ms ++; - pIRsend->our_us -= 1000; - } - - int pinval = 0; - if (pIRsend->currentsendtime){ - sending = 1; - pIRsend->currentsendtime -= ir_periodus; - if (pIRsend->currentsendtime <= 0){ - int32_t remains = pIRsend->currentsendtime; - int32_t newtime = pIRsend->getsendqueue(); - if (0 == newtime){ - // if it was the last one - pIRsend->currentsendtime = 0; - pIRsend->currentbitval = 0; - } else { - // we got a new time - // store mark bits in highest +ve bit of count - pIRsend->currentbitval = (newtime & 0x10000000)? 1:0; - pIRsend->currentsendtime = (newtime & 0xfffffff); - // adjust the us value to keep the running accuracy - // and avoid a running error? - // note remains is -ve - pIRsend->currentsendtime += remains; - } - } - } else { - int32_t newtime = pIRsend->getsendqueue(); - if (!newtime){ - pIRsend->currentsendtime = 0; - pIRsend->currentbitval = 0; - } else { - sending = 1; - pIRsend->currentsendtime = (newtime & 0xfffffff); - pIRsend->currentbitval = (newtime & 0x10000000)? 1:0; - } - } - pinval = pIRsend->currentbitval; - - uint32_t duty = pIRsend->pwmduty; - if (!pinval){ - if (gIRPinPolarity){ - duty = pIRsend->pwmperiod; - } else { - duty = 0; - } - } +extern "C" void DRV_IR_ISR(UINT8 t) { + int sending = 0; + if (pIRsend && (pIRsend->pwmIndex >= 0)) { + pIRsend->our_us += 50; + if (pIRsend->our_us > 1000) { + pIRsend->our_ms++; + pIRsend->our_us -= 1000; + } + + int pinval = 0; + if (pIRsend->currentsendtime) { + sending = 1; + pIRsend->currentsendtime -= ir_periodus; + if (pIRsend->currentsendtime <= 0) { + int32_t remains = pIRsend->currentsendtime; + int32_t newtime = pIRsend->getsendqueue(); + if (0 == newtime) { + // if it was the last one + pIRsend->currentsendtime = 0; + pIRsend->currentbitval = 0; + } + else { + // we got a new time + // store mark bits in highest +ve bit of count + pIRsend->currentbitval = (newtime & 0x10000000) ? 1 : 0; + pIRsend->currentsendtime = (newtime & 0xfffffff); + // adjust the us value to keep the running accuracy + // and avoid a running error? + // note remains is -ve + pIRsend->currentsendtime += remains; + } + } + } + else { + int32_t newtime = pIRsend->getsendqueue(); + if (!newtime) { + pIRsend->currentsendtime = 0; + pIRsend->currentbitval = 0; + } + else { + sending = 1; + pIRsend->currentsendtime = (newtime & 0xfffffff); + pIRsend->currentbitval = (newtime & 0x10000000) ? 1 : 0; + } + } + pinval = pIRsend->currentbitval; + + uint32_t duty = pIRsend->pwmduty; + if (!pinval) { + if (gIRPinPolarity) { + duty = pIRsend->pwmperiod; + } + else { + duty = 0; + } + } #if PLATFORM_BK7231N - bk_pwm_update_param((bk_pwm_t)pIRsend->pwmIndex, pIRsend->pwmperiod, duty,0,0); + bk_pwm_update_param((bk_pwm_t)pIRsend->pwmIndex, pIRsend->pwmperiod, duty, 0, 0); #else - bk_pwm_update_param((bk_pwm_t)pIRsend->pwmIndex, pIRsend->pwmperiod, duty); + bk_pwm_update_param((bk_pwm_t)pIRsend->pwmIndex, pIRsend->pwmperiod, duty); #endif - } - - // is someone really wants rx and TX at the same time, then allow it. - if (gEnableIRSendWhilstReceive){ - sending = 0; - } - - // don't receive if we are currently sending - //if (ourReceiver && !sending){ - //IR_ISR(); - //TODO: implement reading from the recieve pin here - //} - ir_counter++; + } + + // is someone really wants rx and TX at the same time, then allow it. + if (gEnableIRSendWhilstReceive) { + sending = 0; + } + + // don't receive if we are currently sending + if (ourReceiver && !sending){ + IR_ISR(); + //TODO: implement reading from the recieve pin here + } + ir_counter++; } -decode_type_t find_protocol_by_name(const char* args,int ournamelen) +decode_type_t find_protocol_by_name(const char* args, int ournamelen) { - decode_type_t protocol = decode_type_t::UNKNOWN; // UNKNOW? - - for (int i = 0; i < numProtocols; i++) { - const char *name = ProtocolNames[i]; - int namelen = strlen(name); - if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)){ - protocol = (decode_type_t)i; - break; - } - } - return protocol; + decode_type_t protocol = decode_type_t::UNKNOWN; // UNKNOW? + + for (int i = 0; i < numProtocols; i++) { + const char *name = ProtocolNames[i]; + int namelen = strlen(name); + if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)) { + protocol = (decode_type_t)i; + break; + } + } + return protocol; } extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { - if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; - char args[64]; - strncpy(args, args_in, sizeof(args)-1); - args[sizeof(args)-1] = 0; - - // split arg at hyphen; - char *p = args; - while (*p && (*p != '-') && (*p != ' ')){ - p++; - } - - if ((*p != '-') && (*p != ' ')) { - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); - return CMD_RES_BAD_ARGUMENT; - } - - int ournamelen = (p - args); - decode_type_t protocol = find_protocol_by_name(args,ournamelen); - - p++; - int addr = strtol(p, &p, 16); - if ((*p != '-') && (*p != ' ')) { - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); - return CMD_RES_BAD_ARGUMENT; - } - p++; - int command = strtol(p, &p, 16); - - int repeats = 0; - - if ((*p == '-') || (*p == ' ')) { - p++; - repeats = strtol(p, &p, 16); - } - - if (pIRsend){ - bool success = true; // Assume success. - - success = pIRsend->send(protocol, addr, command, repeats); - // add a 100ms delay after command - // NOTE: this is NOT a delay here. it adds 100ms 'space' in the TX queue - pIRsend->delay(100); - - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR send %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)protocol, (int)addr, (int)command, (int)repeats); - return CMD_RES_OK; - } else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR NOT send (no IRsend running) %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)protocol, (int)addr, (int)command, (int)repeats); - } - return CMD_RES_ERROR; + if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; + char args[64]; + strncpy(args, args_in, sizeof(args) - 1); + args[sizeof(args) - 1] = 0; + + // split arg at hyphen; + char *p = args; + while (*p && (*p != '-') && (*p != ' ')) { + p++; + } + + if ((*p != '-') && (*p != ' ')) { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); + return CMD_RES_BAD_ARGUMENT; + } + + int ournamelen = (p - args); + decode_type_t protocol = find_protocol_by_name(args, ournamelen); + + p++; + int addr = strtol(p, &p, 16); + if ((*p != '-') && (*p != ' ')) { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); + return CMD_RES_BAD_ARGUMENT; + } + p++; + int command = strtol(p, &p, 16); + + int repeats = 0; + + if ((*p == '-') || (*p == ' ')) { + p++; + repeats = strtol(p, &p, 16); + } + + if (pIRsend) { + bool success = true; // Assume success. + + success = pIRsend->send(protocol, addr, command, repeats); + // add a 100ms delay after command + // NOTE: this is NOT a delay here. it adds 100ms 'space' in the TX queue + pIRsend->delay(100); + + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR send %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)protocol, (int)addr, (int)command, (int)repeats); + return CMD_RES_OK; + } + else { + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR NOT send (no IRsend running) %s protocol %d addr 0x%X cmd 0x%X repeats %d", args, (int)protocol, (int)addr, (int)command, (int)repeats); + } + return CMD_RES_ERROR; } extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const char *args_in, int cmdFlags) { - if (!args_in || !args_in[0]) { - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IREnable expects arguments"); - return CMD_RES_NOT_ENOUGH_ARGUMENTS; - } - - char args[20]; - strncpy(args, args_in, 19); - args[19] = 0; - char *p = args; - int enable = 1; - if (!my_strnicmp(p, "RXTX", 4)){ - p += 4; - if (*p == ' '){ - p++; - if (*p){ - enable = atoi(p); - } - } - gEnableIRSendWhilstReceive = enable; - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable RX whilst TX enable set %d", enable); - return CMD_RES_OK; - } - - if (!my_strnicmp(p, "invert", 6)){ - // default normal. - enable = 0; - p += 6; - if (*p == ' '){ - p++; - if (*p){ - enable = atoi(p); - } - } - gIRPinPolarity = enable; - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable invert set %d", enable); - return CMD_RES_OK; - } - - - // find length of first arg. - while (*p && (*p != ' ')){ - p++; - } - - //int numProtocols = sizeof(ProtocolNames)/sizeof(*ProtocolNames); - int numProtocols=0; - int ournamelen = (p - args); - int protocol = -1; - for (int i = 0; i < numProtocols; i++){ - const char *name = "Unknown" ; //= ProtocolNames[i]; - int namelen = strlen(name); - if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)){ - protocol = i; - break; - } - } - if (*p == ' '){ - p++; - if (*p){ - enable = atoi(p); - } - } - - uint32_t thisbit = (1 << protocol); - if (protocol < 0){ - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable invalid protocol %s", args); - return CMD_RES_BAD_ARGUMENT; - } else { - //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable found protocol %s(%d), enable %d from %s, bitmask 0x%08X", ProtocolNames[protocol], protocol, enable, p, thisbit); - } - if (enable) { - gIRProtocolEnable = gIRProtocolEnable | thisbit; - } else { - gIRProtocolEnable = gIRProtocolEnable & (~thisbit); - } - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable Protocol mask now 0x%08X", gIRProtocolEnable); - return CMD_RES_OK; - + if (!args_in || !args_in[0]) { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IREnable expects arguments"); + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + + char args[20]; + strncpy(args, args_in, 19); + args[19] = 0; + char *p = args; + int enable = 1; + if (!my_strnicmp(p, "RXTX", 4)) { + p += 4; + if (*p == ' ') { + p++; + if (*p) { + enable = atoi(p); + } + } + gEnableIRSendWhilstReceive = enable; + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable RX whilst TX enable set %d", enable); + return CMD_RES_OK; + } + + if (!my_strnicmp(p, "invert", 6)) { + // default normal. + enable = 0; + p += 6; + if (*p == ' ') { + p++; + if (*p) { + enable = atoi(p); + } + } + gIRPinPolarity = enable; + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable invert set %d", enable); + return CMD_RES_OK; + } + + + // find length of first arg. + while (*p && (*p != ' ')) { + p++; + } + + //int numProtocols = sizeof(ProtocolNames)/sizeof(*ProtocolNames); + int numProtocols = 0; + int ournamelen = (p - args); + int protocol = -1; + for (int i = 0; i < numProtocols; i++) { + const char *name = "Unknown"; //= ProtocolNames[i]; + int namelen = strlen(name); + if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)) { + protocol = i; + break; + } + } + if (*p == ' ') { + p++; + if (*p) { + enable = atoi(p); + } + } + + uint32_t thisbit = (1 << protocol); + if (protocol < 0) { + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable invalid protocol %s", args); + return CMD_RES_BAD_ARGUMENT; + } + else { + //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable found protocol %s(%d), enable %d from %s, bitmask 0x%08X", ProtocolNames[protocol], protocol, enable, p, thisbit); + } + if (enable) { + gIRProtocolEnable = gIRProtocolEnable | thisbit; + } + else { + gIRProtocolEnable = gIRProtocolEnable & (~thisbit); + } + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable Protocol mask now 0x%08X", gIRProtocolEnable); + return CMD_RES_OK; + } extern "C" commandResult_t IR_AC_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { - if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; + if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; - char args[64]; - strncpy(args, args_in, sizeof(args)-1); - args[sizeof(args)-1] = 0; + char args[64]; + strncpy(args, args_in, sizeof(args) - 1); + args[sizeof(args) - 1] = 0; - // split arg at hyphen; - char *p = args; - while (*p && (*p != '-') && (*p != ' ')){ - p++; - } - int ournamelen = (p - args); - if ((*p != '-') && (*p != ' ')) { - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); - return CMD_RES_BAD_ARGUMENT; - } + // split arg at hyphen; + char *p = args; + while (*p && (*p != '-') && (*p != ' ')) { + p++; + } + int ournamelen = (p - args); + if ((*p != '-') && (*p != ' ')) { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); + return CMD_RES_BAD_ARGUMENT; + } - decode_type_t protocol = find_protocol_by_name(args,ournamelen); + decode_type_t protocol = find_protocol_by_name(args, ournamelen); - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRAC cmnd not implemented yet", args); + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRAC cmnd not implemented yet", args); - return CMD_RES_OK; + return CMD_RES_OK; } // test routine to start IR RX and TX // currently fixed pins for testing. -extern "C" void DRV_IR_Init(){ +extern "C" void DRV_IR_Init() { ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Log from extern C CPP"); int pin = -1; //9;// PWM3/25 - int txpin = -1; //24;// PWM3/25 + int txpin = -1; //24;// PWM3/25 // allow user to change them - pin = PIN_FindPinIndexForRole(IOR_IRRecv,pin); - txpin = PIN_FindPinIndexForRole(IOR_IRSend,txpin); - - // if (ourReceiver){ - // IRrecv *temp = ourReceiver; - // ourReceiver = NULL; - // delete temp; - // } - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"DRV_IR_Init: recv pin %i",pin); - if ((pin > 0) || (txpin > 0)){ - } else { - _timer_disable(); - } - - - if (pin > 0){ - // setup IRrecv pin as input - bk_gpio_config_input_pup((GPIO_INDEX)pin); - - // ourReceiver = new IRrecv(pin); - // ourReceiver->enableIRIn(); - } - - if (pIRsend){ - myIRsend *pIRsendTemp = pIRsend; - pIRsend = NULL; - delete pIRsendTemp; - } - - if (txpin > 0){ - int pwmIndex = PIN_GetPWMIndexForPinIndex(txpin); - // is this pin capable of PWM? - if(pwmIndex != -1) { - uint32_t pwmfrequency = 38000; - uint32_t period = (26000000 / pwmfrequency); - uint32_t duty = period/2; - #if PLATFORM_BK7231N - // OSStatus bk_pwm_initialize(bk_pwm_t pwm, uint32_t frequency, uint32_t duty_cycle); - bk_pwm_initialize((bk_pwm_t)pwmIndex, period, duty, 0, 0); - #else - bk_pwm_initialize((bk_pwm_t)pwmIndex, period, duty); - #endif - bk_pwm_start((bk_pwm_t)pwmIndex); - myIRsend *pIRsendTemp = new myIRsend((uint_fast8_t) txpin); - pIRsendTemp->resetsendqueue(); - pIRsendTemp->pwmIndex = pwmIndex; - pIRsendTemp->pwmfrequency = pwmfrequency; - pIRsendTemp->pwmperiod = period; - pIRsendTemp->pwmduty = duty; - - pIRsend = pIRsendTemp; - //bk_pwm_stop((bk_pwm_t)pIRsend->pwmIndex); + pin = PIN_FindPinIndexForRole(IOR_IRRecv, pin); + txpin = PIN_FindPinIndexForRole(IOR_IRSend, txpin); + + if (ourReceiver){ + IRrecv *temp = ourReceiver; + ourReceiver = NULL; + delete temp; + } + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"DRV_IR_Init: recv pin %i", pin); + if ((pin > 0) || (txpin > 0)) { + } + else { + _timer_disable(); + } + + + if (pin > 0) { + // setup IRrecv pin as input + bk_gpio_config_input_pup((GPIO_INDEX)pin); + + ourReceiver = new IRrecv(pin); + ourReceiver->enableIRIn(); + } + + if (pIRsend) { + myIRsend *pIRsendTemp = pIRsend; + pIRsend = NULL; + delete pIRsendTemp; + } + + if (txpin > 0) { + int pwmIndex = PIN_GetPWMIndexForPinIndex(txpin); + // is this pin capable of PWM? + if (pwmIndex != -1) { + uint32_t pwmfrequency = 38000; + uint32_t period = (26000000 / pwmfrequency); + uint32_t duty = period / 2; +#if PLATFORM_BK7231N + // OSStatus bk_pwm_initialize(bk_pwm_t pwm, uint32_t frequency, uint32_t duty_cycle); + bk_pwm_initialize((bk_pwm_t)pwmIndex, period, duty, 0, 0); +#else + bk_pwm_initialize((bk_pwm_t)pwmIndex, period, duty); +#endif + bk_pwm_start((bk_pwm_t)pwmIndex); + myIRsend *pIRsendTemp = new myIRsend((uint_fast8_t)txpin); + pIRsendTemp->resetsendqueue(); + pIRsendTemp->pwmIndex = pwmIndex; + pIRsendTemp->pwmfrequency = pwmfrequency; + pIRsendTemp->pwmperiod = period; + pIRsendTemp->pwmduty = duty; + + pIRsend = pIRsendTemp; + //bk_pwm_stop((bk_pwm_t)pIRsend->pwmIndex); //cmddetail:{"name":"IRSend","args":"[PROT-ADDR-CMD-REP]", //cmddetail:"descr":"Sends IR commands in the form PROT-ADDR-CMD-REP, e.g. NEC-1-1A-0", //cmddetail:"fn":"IR_Send_Cmd","file":"driver/drv_ir.cpp","requires":"", //cmddetail:"examples":""} - CMD_RegisterCommand("IRSend",NULL,IR_Send_Cmd, NULL, NULL); - CMD_RegisterCommand("IRAC",NULL,IR_AC_Cmd, NULL, NULL); - //cmddetail:{"name":"IREnable","args":"[Str][1or0]", - //cmddetail:"descr":"Enable/disable aspects of IR. IREnable RXTX 0/1 - enable Rx whilst Tx. IREnable [protocolname] 0/1 - enable/disable a specified protocol", - //cmddetail:"fn":"IR_Enable","file":"driver/drv_ir.cpp","requires":"", - //cmddetail:"examples":""} - CMD_RegisterCommand("IREnable", "", IR_Enable, NULL, NULL); - } - } - if ((pin > 0) || (txpin > 0)){ - // both tx and rx need the interrupt - _timerConfigForReceive(); - _timer_enable(); - } + CMD_RegisterCommand("IRSend", IR_Send_Cmd, NULL); + CMD_RegisterCommand("IRAC", IR_AC_Cmd, NULL); + //cmddetail:{"name":"IREnable","args":"[Str][1or0]", + //cmddetail:"descr":"Enable/disable aspects of IR. IREnable RXTX 0/1 - enable Rx whilst Tx. IREnable [protocolname] 0/1 - enable/disable a specified protocol", + //cmddetail:"fn":"IR_Enable","file":"driver/drv_ir.cpp","requires":"", + //cmddetail:"examples":""} + CMD_RegisterCommand("IREnable",IR_Enable, NULL); + } + } + if ((pin > 0) || (txpin > 0)) { + // both tx and rx need the interrupt + _timerConfigForReceive(); + _timer_enable(); + } } +void dump(decode_results *results) { + // Dumps out the decode_results structure. + // Call this after IRrecv::decode() + uint16_t count = results->rawlen; + if (results->decode_type == UNKNOWN) { + ADDLOG_INFO(LOG_FEATURE_IR,"Unknown encoding: "); + } + else if (results->decode_type == NEC) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded NEC: "); + } + else if (results->decode_type == SONY) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded SONY: "); + } + else if (results->decode_type == RC5) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RC5: "); + } + else if (results->decode_type == RC5X) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RC5X: "); + } + else if (results->decode_type == RC6) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RC6: "); + } + else if (results->decode_type == RCMM) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RCMM: "); + } + else if (results->decode_type == PANASONIC) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded PANASONIC - Address: %i Value: ", results->address); + } + else if (results->decode_type == LG) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded LG: "); + } + else if (results->decode_type == JVC) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded JVC: "); + } + else if (results->decode_type == AIWA_RC_T501) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded AIWA RC T501: "); + } + else if (results->decode_type == WHYNTER) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded Whynter: "); + } + else if (results->decode_type == NIKAI) { + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded Nikai: "); + } + //serialPrintUint64(results->value, 16); + ADDLOG_INFO(LOG_FEATURE_IR,"%i (%i bits) Raw (%i)",(int)results->value, results->bits, count); + +} + // log the received IR -void PrintIRData(decode_results *aIRDataPtr){ - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %d", (int)aIRDataPtr->decode_type); +void PrintIRData(decode_results *aIRDataPtr) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %d", (int)aIRDataPtr->decode_type); #if 0 // TODO: disabled for now, using routines from IRremoteESP8266 - if (aIRDataPtr->decode_type == UNKNOWN) { + if (aIRDataPtr->decode_type == UNKNOWN) { #if defined(DECODE_HASH) - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Hash=0x%X", (int)aIRDataPtr->decodedRawData); + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Hash=0x%X", (int)aIRDataPtr->decodedRawData); #endif - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"%d bits (incl. gap and start) received", (int)((aIRDataPtr->rawlen) / 2)); - } else { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"%d bits (incl. gap and start) received", (int)((aIRDataPtr->rawlen) / 2)); + } + else { #if defined(DECODE_DISTANCE) - if(aIRDataPtr->protocol != PULSE_DISTANCE) { + if (aIRDataPtr->protocol != PULSE_DISTANCE) { #endif - /* - * New decoders have address and command - */ - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Address=0x%X Command=0x%X", (int)aIRDataPtr->address, (int)aIRDataPtr->command); - - if (aIRDataPtr->flags & IRDATA_FLAGS_EXTRA_INFO) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Extra=0x%X", (int)aIRDataPtr->extra); - } - - if (aIRDataPtr->flags & IRDATA_FLAGS_PARITY_FAILED) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Parity fail"); - } - - if (aIRDataPtr->flags & IRDATA_TOGGLE_BIT_MASK) { - if (aIRDataPtr->protocol == NEC) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Special repeat"); - } else { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Toggle=1"); - } - } + /* + * New decoders have address and command + */ + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Address=0x%X Command=0x%X", (int)aIRDataPtr->address, (int)aIRDataPtr->command); + + if (aIRDataPtr->flags & IRDATA_FLAGS_EXTRA_INFO) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Extra=0x%X", (int)aIRDataPtr->extra); + } + + if (aIRDataPtr->flags & IRDATA_FLAGS_PARITY_FAILED) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Parity fail"); + } + + if (aIRDataPtr->flags & IRDATA_TOGGLE_BIT_MASK) { + if (aIRDataPtr->protocol == NEC) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Special repeat"); + } + else { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Toggle=1"); + } + } #if defined(DECODE_DISTANCE) - } + } #endif - if (aIRDataPtr->flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) { - if (aIRDataPtr->flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Auto-Repeat"); - } else { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Repeat"); - } - if (1) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Gap %uus", (uint32_t)aIRDataPtr->rawDataPtr->rawbuf[0] * MICROS_PER_TICK); - } - } - - /* - * Print raw data - */ - if (!(aIRDataPtr->flags & IRDATA_FLAGS_IS_REPEAT) || aIRDataPtr->decodedRawData != 0) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Raw-Data=0x%X", aIRDataPtr->decodedRawData); - /* - * Print number of bits processed - */ - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" %d bits", aIRDataPtr->numberOfBits); - - if (aIRDataPtr->flags & IRDATA_FLAGS_IS_MSB_FIRST) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" MSB first", aIRDataPtr->numberOfBits); - } else { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" LSB first", aIRDataPtr->numberOfBits); - } - } - } - #endif //0 - // TODO: reimplement ? - //ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)resultToHumanReadableBasic(aIRDataPtr).c_str()); - String description = IRAcUtils::resultAcToString(aIRDataPtr); - if (description.length()) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)description.c_str()); + if (aIRDataPtr->flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) { + if (aIRDataPtr->flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Auto-Repeat"); + } + else { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Repeat"); + } + if (1) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Gap %uus", (uint32_t)aIRDataPtr->rawDataPtr->rawbuf[0] * MICROS_PER_TICK); + } + } + + /* + * Print raw data + */ + if (!(aIRDataPtr->flags & IRDATA_FLAGS_IS_REPEAT) || aIRDataPtr->decodedRawData != 0) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Raw-Data=0x%X", aIRDataPtr->decodedRawData); + /* + * Print number of bits processed + */ + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" %d bits", aIRDataPtr->numberOfBits); + + if (aIRDataPtr->flags & IRDATA_FLAGS_IS_MSB_FIRST) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" MSB first", aIRDataPtr->numberOfBits); + } + else { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" LSB first", aIRDataPtr->numberOfBits); + } + } + } +#endif //0 + // TODO: reimplement ? + //ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)resultToHumanReadableBasic(aIRDataPtr).c_str()); + String description = IRAcUtils::resultAcToString(aIRDataPtr); + if (description.length()) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)description.c_str()); } //////////////////////////////////////////////////// // this polls the IR receive to see if there was any IR received -extern "C" void DRV_IR_RunFrame(){ +extern "C" void DRV_IR_RunFrame() { // Debug-only check to see if the timer interrupt is running - if (ir_counter){ - //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR counter: %u", ir_counter); - } - if (pIRsend){ - if (pIRsend->overflows){ - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"##### IR send overflows %d", (int)pIRsend->overflows); - pIRsend->resetsendqueue(); - } else { - //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR send count %d remains %d currentus %d", (int)pIRsend->timecounttotal, (int)pIRsend->timecount, (int)pIRsend->currentsendtime); - } - } - - #if 0 - if (ourReceiver){ - decode_results results; - if (ourReceiver->decode(&results)) { + if (ir_counter) { + //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR counter: %u", ir_counter); + } + if (pIRsend) { + if (pIRsend->overflows) { + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"##### IR send overflows %d", (int)pIRsend->overflows); + pIRsend->resetsendqueue(); + } + else { + //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR send count %d remains %d currentus %d", (int)pIRsend->timecounttotal, (int)pIRsend->timecount, (int)pIRsend->currentsendtime); + } + } + + + if (ourReceiver) { + decode_results results; + if (ourReceiver->decode(&results)) { //const char *name = ProtocolNames[ourReceiver->decodedIRData.protocol]; - const char *name ="TODO"; - if (!(gIRProtocolEnable & (1 << (int)results.decode_type))){ + const char *name = "TODO"; + if (!(gIRProtocolEnable & (1 << (int)results.decode_type))) { ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", name, (int)results.decode_type, gIRProtocolEnable); - } + } + dump(&results); // 'UNKNOWN' protocol is by default disabled in flags // This is because I am getting a lot of 'UNKNOWN' spam with no IR signals in room - if (((results.decode_type != UNKNOWN) || - (results.decode_type == UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) && - // only process if this protocol is enabled. all by default. - (gIRProtocolEnable & (1 << (int)results.decode_type)) - ) { + if (((results.decode_type != UNKNOWN) || + (results.decode_type == UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) && + // only process if this protocol is enabled. all by default. + (gIRProtocolEnable & (1 << (int)results.decode_type)) + ) { - #if 0 //TODO +#if 0 //TODO char out[128]; PrintIRData(&results); ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %s (%d)", name, (int)results.decode_type); - int repeat = 0; - - if (ourReceiver->decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) { - if (ourReceiver->decodedIRData.flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { - repeat = 2; - } else { - repeat = 1; - } - } - - if (ourReceiver->decodedIRData.protocol == UNKNOWN){ - snprintf(out, sizeof(out), "IR_%s 0x%lX %d", name, (unsigned long)ourReceiver->decodedIRData.decodedRawData, repeat); - } else { - snprintf(out, sizeof(out), "IR_%s 0x%X 0x%X %d", name, ourReceiver->decodedIRData.address, ourReceiver->decodedIRData.command, repeat); - } - #endif - - String lastIrReceived = String(capture.decode_type) + "," + resultToHexidecimal(&results); - if (!hasACState(capture.decode_type)) - lastIrReceived += "," + String(results.bits); + int repeat = 0; + + if (ourReceiver->decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) { + if (ourReceiver->decodedIRData.flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { + repeat = 2; + } + else { + repeat = 1; + } + } + + if (ourReceiver->decodedIRData.protocol == UNKNOWN) { + snprintf(out, sizeof(out), "IR_%s 0x%lX %d", name, (unsigned long)ourReceiver->decodedIRData.decodedRawData, repeat); + } + else { + snprintf(out, sizeof(out), "IR_%s 0x%X 0x%X %d", name, ourReceiver->decodedIRData.address, ourReceiver->decodedIRData.command, repeat); + } + + String lastIrReceived = String(capture.decode_type) + "," + resultToHexidecimal(&results); + if (!hasACState(capture.decode_type)) + lastIrReceived += "," + String(results.bits); // if user wants us to publish every received IR data, do it now - if(CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED)) { - - // another flag required? - int publishrepeats = 1; - - if (publishrepeats || !repeat){ - //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR MQTT publish %s", out); - - uint32_t counter_in = ir_counter; - MQTT_PublishMain_StringString("ir",lastIrReceived.c_str(), 0); - uint32_t counter_dur = ((ir_counter - counter_in)*50)/1000; - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR MQTT publish %s took %dms", out, counter_dur); - } else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", out); - } - } else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", lastIrReceived.c_str()); - } + if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED)) { + + // another flag required? + int publishrepeats = 1; + + if (publishrepeats || !repeat) { + //ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR MQTT publish %s", out); + + uint32_t counter_in = ir_counter; + MQTT_PublishMain_StringString("ir", lastIrReceived.c_str(), 0); + uint32_t counter_dur = ((ir_counter - counter_in) * 50) / 1000; + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR MQTT publish %s took %dms", out, counter_dur); + } + else { + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", out); + } + } + else { + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", lastIrReceived.c_str()); + } +#endif if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED_IN_JSON)) { // {"IrReceived":{"Protocol":"RC_5","Bits":0x1,"Data":"0xC"}} // - - #if 0 // TODO: + +#if 0 // TODO: snprintf(out, sizeof(out), "{\"IrReceived\":{\"Protocol\":\"%s\",\"Bits\":%i,\"Data\":\"0x%lX\"}}", name, (int)ourReceiver->decodedIRData.numberOfBits, (unsigned long)ourReceiver->decodedIRData.decodedRawData); MQTT_PublishMain_StringString("RESULT", out, OBK_PUBLISH_FLAG_FORCE_REMOVE_GET); - #endif //TODO +#endif //TODO } - #if 0 - if(ourReceiver->decodedIRData.protocol != UNKNOWN) { +#if 0 + if (ourReceiver->decodedIRData.protocol != UNKNOWN) { snprintf(out, sizeof(out), "%X", ourReceiver->decodedIRData.command); int tgType = 0; - switch(ourReceiver->decodedIRData.protocol) + switch (ourReceiver->decodedIRData.protocol) { case NEC: tgType = CMD_EVENT_IR_NEC; @@ -846,27 +916,26 @@ extern "C" void DRV_IR_RunFrame(){ case SONY: tgType = CMD_EVENT_IR_SONY; break; - default: - break; + default: + break; } - // we should include repeat here? - // e.g. on/off button should not toggle on repeats, but up/down probably should eat them. - uint32_t counter_in = ir_counter; - EventHandlers_FireEvent2(tgType,ourReceiver->decodedIRData.address,ourReceiver->decodedIRData.command); - uint32_t counter_dur = ((ir_counter - counter_in)*50)/1000; - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR fire event took %dms", counter_dur); + // we should include repeat here? + // e.g. on/off button should not toggle on repeats, but up/down probably should eat them. + uint32_t counter_in = ir_counter; + EventHandlers_FireEvent2(tgType, ourReceiver->decodedIRData.address, ourReceiver->decodedIRData.command); + uint32_t counter_dur = ((ir_counter - counter_in) * 50) / 1000; + ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR fire event took %dms", counter_dur); } - #endif //0 TODO +#endif //0 TODO } /* * !!!Important!!! Enable receiving of the next value, * since receiving has stopped after the end of the current received data packet. */ ourReceiver->resume(); // Enable receiving of the next value - } - } - #endif // TODO + } + } } @@ -876,32 +945,32 @@ extern "C" void DRV_IR_RunFrame(){ #ifdef TEST_CPP // routines to test C++ class cpptest2 { - public: - int initialised; - cpptest2(){ - // remove else static class may kill us!!!ADDLOG_INFO(LOG_FEATURE_IR, "Log from Class constructor"); - initialised = 42; - }; - ~cpptest2(){ - initialised = 24; - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Log from Class destructor"); - } - - void print(){ - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Log from Class %d", initialised); - } +public: + int initialised; + cpptest2() { + // remove else static class may kill us!!!ADDLOG_INFO(LOG_FEATURE_IR, "Log from Class constructor"); + initialised = 42; + }; + ~cpptest2() { + initialised = 24; + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Log from Class destructor"); + } + + void print() { + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Log from Class %d", initialised); + } }; cpptest2 staticclass; -void cpptest(){ +void cpptest() { ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Log from CPP"); - cpptest2 test; - test.print(); - cpptest2 *test2 = new cpptest2(); - test2->print(); + cpptest2 test; + test.print(); + cpptest2 *test2 = new cpptest2(); + test2->print(); ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Log from static class (is it initialised?):"); - staticclass.print(); + staticclass.print(); } #endif diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.cpp b/src/libraries/IRremoteESP8266/src/IRrecv.cpp index b3a1b6714..3ae765dac 100644 --- a/src/libraries/IRremoteESP8266/src/IRrecv.cpp +++ b/src/libraries/IRremoteESP8266/src/IRrecv.cpp @@ -3,7 +3,7 @@ // Copyright 2015 Sebastien Warin // Copyright 2017, 2019 David Conran -#if !PLATFORM_BEKEN +#define USE_IRAM_ATTR #include "IRrecv.h" #include @@ -28,6 +28,18 @@ extern "C" { #define ICACHE_RAM_ATTR #endif +#include "digitalWriteFast.h" + + +// To change this value, you simply can add a line #define "RECORD_GAP_MICROS " in your ino file before the line "#include " +#define RECORD_GAP_MICROS 5000 // FREDRICH28AC / LG2 header space is 9700, NEC header space is 4500 +#define MICROS_PER_TICK 50 + +/** Minimum gap between IR transmissions, in MICROS_PER_TICK */ +#define RECORD_GAP_TICKS (RECORD_GAP_MICROS / MICROS_PER_TICK) // 221 for 1100 + + + #ifndef USE_IRAM_ATTR #if defined(ESP8266) #if defined(IRAM_ATTR) @@ -50,6 +62,7 @@ extern "C" { // Updated by markszabo (https://github.com/crankyoldgit/IRremoteESP8266) for // sending IR code on ESP8266 + // Globals #ifndef UNIT_TEST #if defined(ESP8266) @@ -139,7 +152,7 @@ namespace _IRrecv { static hw_timer_t * timer = NULL; } // namespace _IRrecv #endif // ESP32 -using _IRrecv::timer; +//using _IRrecv::timer; #endif // UNIT_TEST namespace _IRrecv { // Namespace extension @@ -157,98 +170,91 @@ using _IRrecv::params; using _IRrecv::params_save; #ifndef UNIT_TEST -#if defined(ESP8266) -/// Interrupt handler for when the timer runs out. -/// It signals to the library that capturing of IR data has stopped. -/// @param[in] arg Unused. (ESP8266 Only) -static void USE_IRAM_ATTR read_timeout(void *arg __attribute__((unused))) { - os_intr_lock(); -#endif // ESP8266 -/// @cond IGNORE -#if defined(ESP32) + /// Interrupt handler for when the timer runs out. /// It signals to the library that capturing of IR data has stopped. /// @note ESP32 version static void USE_IRAM_ATTR read_timeout(void) { /// @endcond - portENTER_CRITICAL(&mux); -#endif // ESP32 - if (params.rawlen) params.rcvstate = kStopState; + // portENTER_CRITICAL(&mux); + + if (params.rawlen) + params.rcvstate = kStopState; #if defined(ESP8266) os_intr_unlock(); #endif // ESP8266 #if defined(ESP32) - portEXIT_CRITICAL(&mux); + //portEXIT_CRITICAL(&mux); #endif // ESP32 } /// Interrupt handler for changes on the GPIO pin handling incoming IR messages. -static void USE_IRAM_ATTR gpio_intr() { - uint32_t now = micros(); - static uint32_t start = 0; - -#if defined(ESP8266) - uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); - os_timer_disarm(&timer); - GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); -#endif // ESP8266 - - // Grab a local copy of rawlen to reduce instructions used in IRAM. - // This is an ugly premature optimisation code-wise, but we do everything we - // can to save IRAM. - // It seems referencing the value via the structure uses more instructions. - // Less instructions means faster and less IRAM used. - // N.B. It saves about 13 bytes of IRAM. - uint16_t rawlen = params.rawlen; - - if (rawlen >= params.bufsize) { - params.overflow = true; - params.rcvstate = kStopState; - } - - if (params.rcvstate == kStopState) return; - - if (params.rcvstate == kIdleState) { - params.rcvstate = kMarkState; - params.rawbuf[rawlen] = 1; - } else { - if (now < start) - params.rawbuf[rawlen] = (UINT32_MAX - start + now) / kRawTick; - else - params.rawbuf[rawlen] = (now - start) / kRawTick; - } - params.rawlen++; - - start = now; - -#if defined(ESP8266) - os_timer_arm(&timer, params.timeout, ONCE); -#endif // ESP8266 -#if defined(ESP32) - // Reset the timeout. - // -#if _ESP32_IRRECV_TIMER_HACK - // The following three lines of code are the equiv of: - // `timerWrite(timer, 0);` - // We can't call that routine safely from inside an ISR as that procedure - // is not stored in IRAM. Hence, we do it manually so that it's covered by - // USE_IRAM_ATTR in this ISR. - // @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1350 - // @see https://github.com/espressif/arduino-esp32/blob/6b0114366baf986c155e8173ab7c22bc0c5fcedc/cores/esp32/esp32-hal-timer.c#L106-L110 - timer->dev->load_high = (uint32_t) 0; - timer->dev->load_low = (uint32_t) 0; - timer->dev->reload = 1; - // The next line is the same, but instead replaces: - // `timerAlarmEnable(timer);` - // @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1350 - // @see https://github.com/espressif/arduino-esp32/blob/6b0114366baf986c155e8173ab7c22bc0c5fcedc/cores/esp32/esp32-hal-timer.c#L176-L178 - timer->dev->config.alarm_en = 1; -#else // _ESP32_IRRECV_TIMER_HACK - timerWrite(timer, 0); - timerAlarmEnable(timer); -#endif // _ESP32_IRRECV_TIMER_HACK -#endif // ESP32 -} +//static void USE_IRAM_ATTR gpio_intr() { +// uint32_t now = micros(); +// static uint32_t start = 0; +// +//#if defined(ESP8266) +// uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); +// os_timer_disarm(&timer); +// GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); +//#endif // ESP8266 +// +// // Grab a local copy of rawlen to reduce instructions used in IRAM. +// // This is an ugly premature optimisation code-wise, but we do everything we +// // can to save IRAM. +// // It seems referencing the value via the structure uses more instructions. +// // Less instructions means faster and less IRAM used. +// // N.B. It saves about 13 bytes of IRAM. +// uint16_t rawlen = params.rawlen; +// +// if (rawlen >= params.bufsize) { +// params.overflow = true; +// params.rcvstate = kStopState; +// } +// +// if (params.rcvstate == kStopState) return; +// +// if (params.rcvstate == kIdleState) { +// params.rcvstate = kMarkState; +// params.rawbuf[rawlen] = 1; +// } else { +// if (now < start) +// params.rawbuf[rawlen] = (UINT32_MAX - start + now) / kRawTick; +// else +// params.rawbuf[rawlen] = (now - start) / kRawTick; +// } +// params.rawlen++; +// +// start = now; +// +//#if defined(ESP8266) +// os_timer_arm(&timer, params.timeout, ONCE); +//#endif // ESP8266 +//#if defined(ESP32) +// // Reset the timeout. +// // +//#if _ESP32_IRRECV_TIMER_HACK +// // The following three lines of code are the equiv of: +// // `timerWrite(timer, 0);` +// // We can't call that routine safely from inside an ISR as that procedure +// // is not stored in IRAM. Hence, we do it manually so that it's covered by +// // USE_IRAM_ATTR in this ISR. +// // @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1350 +// // @see https://github.com/espressif/arduino-esp32/blob/6b0114366baf986c155e8173ab7c22bc0c5fcedc/cores/esp32/esp32-hal-timer.c#L106-L110 +// timer->dev->load_high = (uint32_t) 0; +// timer->dev->load_low = (uint32_t) 0; +// timer->dev->reload = 1; +// // The next line is the same, but instead replaces: +// // `timerAlarmEnable(timer);` +// // @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1350 +// // @see https://github.com/espressif/arduino-esp32/blob/6b0114366baf986c155e8173ab7c22bc0c5fcedc/cores/esp32/esp32-hal-timer.c#L176-L178 +// timer->dev->config.alarm_en = 1; +//#else // _ESP32_IRRECV_TIMER_HACK +// timerWrite(timer, 0); +// timerAlarmEnable(timer); +//#endif // _ESP32_IRRECV_TIMER_HACK +//#endif // ESP32 +//} #endif // UNIT_TEST // Start of IRrecv class ------------------- @@ -297,14 +303,17 @@ IRrecv::IRrecv(const uint16_t recvpin, const uint16_t bufsize, params.bufsize = bufsize; // Ensure we are going to be able to store all possible values in the // capture buffer. - params.timeout = ::min(timeout, (uint8_t)kMaxTimeoutMs); + params.timeout = timeout; + if (kMaxTimeoutMs < params.timeout) + params.timeout = kMaxTimeoutMs; params.rawbuf = new uint16_t[bufsize]; if (params.rawbuf == NULL) { DPRINTLN( "Could not allocate memory for the primary IR buffer.\n" "Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!"); #ifndef UNIT_TEST - ESP.restart(); // Mem alloc failure. Reboot. + // ESP.restart(); // Mem alloc failure. Reboot. + return; #endif } // If we have been asked to use a save buffer (for decoding), then create one. @@ -317,7 +326,8 @@ IRrecv::IRrecv(const uint16_t recvpin, const uint16_t bufsize, "Could not allocate memory for the second IR buffer.\n" "Try a smaller size for CAPTURE_BUFFER_SIZE.\nRebooting!"); #ifndef UNIT_TEST - ESP.restart(); // Mem alloc failure. Reboot. + // ESP.restart(); // Mem alloc failure. Reboot. + return; #endif } } else { @@ -353,9 +363,9 @@ void IRrecv::enableIRIn(const bool pullup) { // This wasn't required on the ESP8266s, but it shouldn't hurt to make sure. if (pullup) { #ifndef UNIT_TEST - pinMode(params.recvpin, INPUT_PULLUP); + pinModeFast(params.recvpin, INPUT_PULLUP); } else { - pinMode(params.recvpin, INPUT); + pinModeFast(params.recvpin, INPUT); #endif // UNIT_TEST } #if defined(ESP32) @@ -388,7 +398,7 @@ void IRrecv::enableIRIn(const bool pullup) { NULL); #endif // ESP8266 // Attach Interrupt - attachInterrupt(params.recvpin, gpio_intr, CHANGE); + //attachInterrupt(params.recvpin, gpio_intr, CHANGE); #endif // UNIT_TEST } @@ -403,7 +413,7 @@ void IRrecv::disableIRIn(void) { timerAlarmDisable(timer); timerEnd(timer); #endif // ESP32 - detachInterrupt(params.recvpin); + //detachInterrupt(params.recvpin); #endif // UNIT_TEST } @@ -476,7 +486,9 @@ void IRrecv::setUnknownThreshold(const uint16_t length) { /// Set the base tolerance percentage for matching incoming IR messages. /// @param[in] percent An integer percentage. (0-100) void IRrecv::setTolerance(const uint8_t percent) { - _tolerance = ::min(percent, (uint8_t)100); + _tolerance = percent; + if (_tolerance > 100) + _tolerance = 100; } /// Get the base tolerance percentage for matching incoming IR messages. @@ -1209,9 +1221,11 @@ uint8_t IRrecv::_validTolerance(const uint8_t percentage) { uint32_t IRrecv::ticksLow(const uint32_t usecs, const uint8_t tolerance, const uint16_t delta) { // max() used to ensure the result can't drop below 0 before the cast. - return ((uint32_t)::max( - (int32_t)(usecs * (1.0 - _validTolerance(tolerance) / 100.0) - delta), - (int32_t)0)); + float res; + res = usecs * (1.0 - _validTolerance(tolerance) / 100.0) - delta; + if (res < 0) + res = 0; + return res; } /// Calculate the upper bound of the nr. of ticks. @@ -1294,8 +1308,10 @@ bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired, // We really should never get a value of 0, except as the last value // in the buffer. If that is the case, then assume infinity and return true. if (measured == 0) return true; - return measured >= ticksLow(::min(desired, - (uint32_t)MS_TO_USEC(params.timeout)), + uint32_t mins = MS_TO_USEC(params.timeout); + if (mins > desired) + mins = desired; + return measured >= ticksLow(mins, tolerance, delta); } @@ -2064,6 +2080,196 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, return offset; } + +/********************************************************************************************************************** + * Interrupt Service Routine - Called every 50 us + * + * Duration in ticks of 50 us of alternating SPACE, MARK are recorded in irparams.rawbuf array. + * 'rawlen' counts the number of entries recorded so far. + * First entry is the SPACE between transmissions. + * + * As soon as one SPACE entry gets longer than RECORD_GAP_TICKS, state switches to STOP (frame received). Timing of SPACE continues. + * A call of resume() switches from STOP to IDLE. + * As soon as first MARK arrives in IDLE, gap width is recorded and new logging starts. + * + * With digitalRead and Feedback LED + * 15 pushs, 1 in, 1 eor before start of code = 2 us @16MHz + * 7.2 us computation time (6us idle time) + * pop + reti = 2.25 us @16MHz => 10.3 to 11.5 us @16MHz + * With portInputRegister and mask and Feedback LED code commented + * 9 pushs, 1 in, 1 eor before start of code = 1.25 us @16MHz + * 2.25 us computation time + * pop + reti = 1.5 us @16MHz => 5 us @16MHz + * => Minimal CPU frequency is 4 MHz + * + **********************************************************************************************************************/ + /* + * Activate this line if your receiver has an external output driver transistor / "inverted" output + */ + //#define IR_INPUT_IS_ACTIVE_HIGH +#if defined(IR_INPUT_IS_ACTIVE_HIGH) +// IR detector output is active high +#define INPUT_MARK 1 ///< Sensor output for a mark ("flash") +#else +// IR detector output is active low +#define INPUT_MARK 0 ///< Sensor output for a mark ("flash") +#endif + + +uint32_t now = 0; +uint_fast8_t old; +static uint32_t start = 0; + + void IR_ISR() { + now += 50; + uint_fast8_t tIRInputLevel = (uint_fast8_t)digitalReadFast(params.recvpin); + if (tIRInputLevel == old) + return; + old = tIRInputLevel; + // Grab a local copy of rawlen to reduce instructions used in IRAM. + // This is an ugly premature optimisation code-wise, but we do everything we + // can to save IRAM. + // It seems referencing the value via the structure uses more instructions. + // Less instructions means faster and less IRAM used. + // N.B. It saves about 13 bytes of IRAM. + uint16_t rawlen = params.rawlen; + + if (rawlen >= params.bufsize) { + params.overflow = true; + params.rcvstate = kStopState; + } + + if (params.rcvstate == kStopState) return; + + if (params.rcvstate == kIdleState) { + params.rcvstate = kMarkState; + params.rawbuf[rawlen] = 1; + } + else { + if (now < start) + params.rawbuf[rawlen] = (UINT32_MAX - start + now) / kRawTick; + else + params.rawbuf[rawlen] = (now - start) / kRawTick; + } + params.rawlen++; + + start = now; + +} + //#define _IR_MEASURE_TIMING + //#define _IR_TIMING_TEST_PIN 7 // do not forget to execute: "pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);" if activated by line above +#if defined(TIMER_INTR_NAME) +ISR(TIMER_INTR_NAME) // for ISR definitions +#else +void IR_ISRqq() // for functions definitions which are called by separate (board specific) ISR +#endif +{ +#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) + digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles +#endif +// 7 - 8.5 us for ISR body (without pushes and pops) for ATmega328 @16MHz + + //TIMER_RESET_INTR_PENDING;// reset timer interrupt flag if required (currently only for Teensy and ATmega4809) + +// Read if IR Receiver -> SPACE [xmt LED off] or a MARK [xmt LED on] +#if defined(__AVR__) + uint8_t tIRInputLevel = *params.IRReceivePinPortInputRegister & params.IRReceivePinMask; +#else + uint_fast8_t tIRInputLevel = (uint_fast8_t)digitalReadFast(params.recvpin); +#endif + + /* + * Increase TickCounter and clip it at maximum 0xFFFF / 3.2 seconds at 50 us ticks + */ + if (params.timer < UINT16_MAX) { + params.timer++; // One more 50uS tick + } +#define MY_TIMER_MULT_FOR_BUFF 25 + /* + * Due to a ESP32 compiler bug https://github.com/espressif/esp-idf/issues/1552 no switch statements are possible for ESP32 + * So we change the code to if / else if + */ + // switch (params.rcvstate) { + //...................................................................... + if (params.rcvstate == kIdleState) { // In the middle of a gap or just resumed (and maybe in the middle of a transmission + if (tIRInputLevel == INPUT_MARK) { + // check if we did not start in the middle of a transmission by checking the minimum length of leading space + if (params.timer > RECORD_GAP_TICKS) { + // Gap just ended; Record gap duration + start recording transmission + // Initialize all state machine variables +#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) +// digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles +#endif + params.overflow = false; + params.rawbuf[0] = params.timer*MY_TIMER_MULT_FOR_BUFF; + params.rawlen = 1; + params.rcvstate = kMarkState; + } // otherwise stay in idle state + params.timer = 0;// reset counter in both cases + } + + } + else if (params.rcvstate == kMarkState) { // Timing mark + if (tIRInputLevel != INPUT_MARK) { // Mark ended; Record time +#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) +// digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles +#endif + params.rawbuf[params.rawlen++] = params.timer*MY_TIMER_MULT_FOR_BUFF; + params.rcvstate = kSpaceState; + params.timer = 0; + } + + } + else if (params.rcvstate == kSpaceState) { // Timing space + if (tIRInputLevel == INPUT_MARK) { // Space just ended; Record time + if (params.rawlen >=params.bufsize) // RAW_BUFFER_LENGTH) + { + // Flag up a read overflow; Stop the state machine + params.overflow = true; + params.rcvstate = kStopState; + } + else { +#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) + // digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles +#endif + params.rawbuf[params.rawlen++] = params.timer*MY_TIMER_MULT_FOR_BUFF; + params.rcvstate = kMarkState; + } + params.timer = 0; + + } + else if (params.timer > RECORD_GAP_TICKS) { + /* + * Current code is ready for processing! + * We received a long space, which indicates gap between codes. + * Switch to kStopState + * Don't reset timer; keep counting width of next leading space + */ + params.rcvstate = kStopState; + } + } + else if (params.rcvstate == kStopState) { + /* + * Complete command received + * stay here until resume() is called, which switches state to kIdleState + */ +#if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) + // digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles +#endif + if (tIRInputLevel == INPUT_MARK) { + // Reset gap timer, to prepare for detection if we are in the middle of a transmission after call of resume() + params.timer = 0; + } + } + +#if !defined(NO_LED_FEEDBACK_CODE) +/// if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_RECEIVE) { +/// setFeedbackLED(tIRInputLevel == INPUT_MARK); +// } +#endif + +#ifdef _IR_MEASURE_TIMING + digitalWriteFast(_IR_TIMING_TEST_PIN, LOW); // 2 clock cycles +#endif +} + + #if UNIT_TEST /// Unit test helper to get access to the params structure. volatile irparams_t *IRrecv::_getParamsPtr(void) { @@ -2072,4 +2278,3 @@ volatile irparams_t *IRrecv::_getParamsPtr(void) { #endif // UNIT_TEST // End of IRrecv class ------------------- -#endif \ No newline at end of file diff --git a/src/libraries/IRremoteESP8266/src/digitalWriteFast.h b/src/libraries/IRremoteESP8266/src/digitalWriteFast.h new file mode 100644 index 000000000..2ff24dbb4 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/digitalWriteFast.h @@ -0,0 +1,24 @@ +/* + * digitalWriteFast.h + * + * Optimized digital functions for AVR microcontrollers + * by Watterott electronic (www.watterott.com) + * based on https://code.google.com/p/digitalwritefast + * + * License: BSD 3-Clause License (https://opensource.org/licenses/BSD-3-Clause) + */ + +#ifndef __digitalWriteFast_h_ +#define __digitalWriteFast_h_ 1 + +#define INPUT_PULLUP 1 +#define INPUT 1 + +void digitalToggleFast(unsigned char P); +unsigned char digitalReadFast(unsigned char P); +void digitalWriteFast(unsigned char P, unsigned char V); +void pinModeFast(unsigned char P, unsigned char V); + +#endif //__digitalWriteFast_h_ + + From 5bee311fd7202aaba33c5abf201bf0ada5b989ca Mon Sep 17 00:00:00 2001 From: "Vladimir S. FONOV" Date: Sun, 26 Feb 2023 11:30:22 -0500 Subject: [PATCH 03/16] Debugging IRRecv --- .vscode/extensions.json | 6 - .vscode/settings.json | 19 ---- src/driver/drv_ir.cpp | 106 ++++++++++++------ src/libraries/IRremoteESP8266/src/IRrecv.cpp | 65 ++++++----- .../IRremoteESP8266/src/IRremoteESP8266.h | 14 +++ src/libraries/IRremoteESP8266/src/IRsend.cpp | 18 +-- .../IRremoteESP8266/src/digitalWriteFast.cpp | 35 ++++++ .../IRremoteESP8266/src/digitalWriteFast.h | 28 ++++- .../IRremoteESP8266/src/ir_Corona.cpp | 23 ++-- .../IRremoteESP8266/src/ir_Doshisha.cpp | 6 +- .../IRremoteESP8266/src/ir_Goodweather.cpp | 7 +- .../IRremoteESP8266/src/ir_Lutron.cpp | 16 +-- src/libraries/IRremoteESP8266/src/ir_MWM.cpp | 23 ++-- .../IRremoteESP8266/src/ir_Magiquest.cpp | 13 ++- .../IRremoteESP8266/src/ir_MilesTag2.cpp | 3 +- .../IRremoteESP8266/src/ir_RC5_RC6.cpp | 4 +- 16 files changed, 233 insertions(+), 153 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json create mode 100644 src/libraries/IRremoteESP8266/src/digitalWriteFast.cpp diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 09744810c..000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recommendations": [ - "ms-vscode.cpptools", - "esbenp.prettier-vscode" - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d9094df0b..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "C_Cpp.formatting": "vcFormat", - "C_Cpp.codeAnalysis.runAutomatically": false, - "C_Cpp.clang_format_style": "{ BasedOnStyle: LLVM, UseTab: Always, IndentWidth: 4, TabWidth: 4, BreakBeforeBraces: Attach, AllowShortIfStatementsOnASingleLine: false, IndentCaseLabels: false, ColumnLimit: 0, AccessModifierOffset: -4, NamespaceIndentation: All, FixNamespaceComments: false }", - "C_Cpp.autocompleteAddParentheses": true, - "files.exclude": { - "**/*.o": true, - "node_modules": true - }, - "editor.insertSpaces": false, - "editor.tabSize": 4, - "editor.detectIndentation": false, - "C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: LLVM, UseTab: Always, IndentWidth: 4, TabWidth: 4, BreakBeforeBraces: Attach, AllowShortIfStatementsOnASingleLine: false, IndentCaseLabels: false, ColumnLimit: 0, AccessModifierOffset: -4, NamespaceIndentation: All, FixNamespaceComments: false }", - "prettier.tabWidth": 4, - "prettier.useTabs": true, - "prettier.printWidth": 120 -} \ No newline at end of file diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index 4d9c216c8..98a35356f 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -87,31 +87,6 @@ class Print { Print Serial; -#define INPUT 0 -#define OUTPUT 1 -#define HIGH 1 -#define LOW 1 - - -void digitalToggleFast(unsigned char P) { - bk_gpio_output((GPIO_INDEX)P, !bk_gpio_input((GPIO_INDEX)P)); -} - -unsigned char digitalReadFast(unsigned char P) { - return bk_gpio_input((GPIO_INDEX)P); -} - -void digitalWriteFast(unsigned char P, unsigned char V) { - //RAW_SetPinValue(P, V); - //HAL_PIN_SetOutputValue(index, iVal); - bk_gpio_output((GPIO_INDEX)P, V); -} - -void pinModeFast(unsigned char P, unsigned char V) { - if (V == INPUT) { - bk_gpio_config_input_pup((GPIO_INDEX)P); - } -} #define EXTERNAL_IR_TIMER_ISR @@ -126,6 +101,8 @@ void pinModeFast(unsigned char P, unsigned char V) { // #undef ISR // # endif // #define ISR void IR_ISR + +// THIS function is defined in src/libraries/IRremoteESP8266/src/IRrecv.cpp extern "C" void DRV_IR_ISR(UINT8 t); extern void IR_ISR(); @@ -212,7 +189,7 @@ SpoofIrReceiver IrReceiver; #include "../libraries/IRremoteESP8266/src/IRutils.h" #include "../libraries/IRremoteESP8266/src/IRac.h" #include "../libraries/IRremoteESP8266/src/IRproto.h" - +#include "../libraries/IRremoteESP8266/src/digitalWriteFast.h" extern "C" int PIN_GetPWMIndexForPinIndex(int pin); @@ -559,6 +536,51 @@ extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const } +extern "C" commandResult_t IR_Param(const void *context, const char *cmd, const char *args_in, int cmdFlags) { + if (!args_in || !args_in[0]) { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRParam expects two arguments"); + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + + if(!ourReceiver) + { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRParam: IR reciever disabled"); + return CMD_RES_BAD_ARGUMENT; + } + + // Set higher if you get lots of random short UNKNOWN messages when nothing + // should be sending a message. + // Set lower if you are sure your setup is working, but it doesn't see messages + // from your device. (e.g. Other IR remotes work.) + // NOTE: Set this value very high to effectively turn off UNKNOWN detection. + int kMinUnknownSize = 12; + + // How much percentage lee way do we give to incoming signals in order to match + // it? + // e.g. +/- 25% (default) to an expected value of 500 would mean matching a + // value between 375 & 625 inclusive. + // Note: Default is 25(%). Going to a value >= 50(%) will cause some protocols + // to no longer match correctly. In normal situations you probably do not + // need to adjust this value. Typically that's when the library detects + // your remote's message some of the time, but not all of the time. + int kTolerancePercentage = 25; // kTolerance is normally 25% + + int res = sscanf(args_in, "%d %d", &kMinUnknownSize, &kTolerancePercentage); + + if(res!=2) + { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRParam invalid parameters %s", args_in); + return CMD_RES_BAD_ARGUMENT; + } + ourReceiver->setUnknownThreshold(kMinUnknownSize); + ourReceiver->setTolerance(kTolerancePercentage); + + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IRParam MinUnknownSize: %d Noice tolerance: %d%%", kMinUnknownSize,kTolerancePercentage); + return CMD_RES_OK; +} + + + extern "C" commandResult_t IR_AC_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; @@ -612,10 +634,10 @@ extern "C" void DRV_IR_Init() { if (pin > 0) { // setup IRrecv pin as input - bk_gpio_config_input_pup((GPIO_INDEX)pin); + //bk_gpio_config_input_pup((GPIO_INDEX)pin); // enabled by enableIRIn ourReceiver = new IRrecv(pin); - ourReceiver->enableIRIn(); + ourReceiver->enableIRIn(true);// try with pullup } if (pIRsend) { @@ -648,17 +670,26 @@ extern "C" void DRV_IR_Init() { pIRsend = pIRsendTemp; //bk_pwm_stop((bk_pwm_t)pIRsend->pwmIndex); - //cmddetail:{"name":"IRSend","args":"[PROT-ADDR-CMD-REP]", - //cmddetail:"descr":"Sends IR commands in the form PROT-ADDR-CMD-REP, e.g. NEC-1-1A-0", - //cmddetail:"fn":"IR_Send_Cmd","file":"driver/drv_ir.cpp","requires":"", - //cmddetail:"examples":""} + //cmddetail:{"name":"IRSend","args":"[PROT-ADDR-CMD-REP]", + //cmddetail:"descr":"Sends IR commands in the form PROT-ADDR-CMD-REP, e.g. NEC-1-1A-0", + //cmddetail:"fn":"IR_Send_Cmd","file":"driver/drv_ir.cpp","requires":"", + //cmddetail:"examples":""} CMD_RegisterCommand("IRSend", IR_Send_Cmd, NULL); + //cmddetail:{"name":"IRAC","args":"[TODO]", + //cmddetail:"descr":"Sends IR commands for HVAC control (TODO)", + //cmddetail:"fn":"IR_AC_Cmd","file":"driver/drv_ir.cpp","requires":"", + //cmddetail:"examples":""} CMD_RegisterCommand("IRAC", IR_AC_Cmd, NULL); //cmddetail:{"name":"IREnable","args":"[Str][1or0]", //cmddetail:"descr":"Enable/disable aspects of IR. IREnable RXTX 0/1 - enable Rx whilst Tx. IREnable [protocolname] 0/1 - enable/disable a specified protocol", //cmddetail:"fn":"IR_Enable","file":"driver/drv_ir.cpp","requires":"", //cmddetail:"examples":""} CMD_RegisterCommand("IREnable",IR_Enable, NULL); + //cmddetail:{"name":"IRParam","args":"[MinSize] [Noise Threshold]", + //cmddetail:"descr":"Set minimal size of the message and noise threshold", + //cmddetail:"fn":"IR_Enable","file":"driver/drv_ir.cpp","requires":"", + //cmddetail:"examples":""} + CMD_RegisterCommand("IRParam",IR_Param, NULL); } } if ((pin > 0) || (txpin > 0)) { @@ -672,6 +703,7 @@ extern "C" void DRV_IR_Init() { void dump(decode_results *results) { // Dumps out the decode_results structure. // Call this after IRrecv::decode() + #if 0 uint16_t count = results->rawlen; if (results->decode_type == UNKNOWN) { ADDLOG_INFO(LOG_FEATURE_IR,"Unknown encoding: "); @@ -695,7 +727,7 @@ void dump(decode_results *results) { ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RCMM: "); } else if (results->decode_type == PANASONIC) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded PANASONIC - Address: %i Value: ", results->address); + ADDLOG_INFO(LOG_FEATURE_IR,"Decoded PANASONIC: "); } else if (results->decode_type == LG) { ADDLOG_INFO(LOG_FEATURE_IR,"Decoded LG: "); @@ -713,8 +745,9 @@ void dump(decode_results *results) { ADDLOG_INFO(LOG_FEATURE_IR,"Decoded Nikai: "); } //serialPrintUint64(results->value, 16); - ADDLOG_INFO(LOG_FEATURE_IR,"%i (%i bits) Raw (%i)",(int)results->value, results->bits, count); - + ADDLOG_INFO(LOG_FEATURE_IR,"Address: %i Value: %i (%i bits) Raw (%i)",(int)results->address, (int)results->value, results->bits, count); + #endif + ADDLOG_INFO(LOG_FEATURE_IR,resultToHumanReadableBasic(results).c_str()); } // log the received IR @@ -817,10 +850,12 @@ extern "C" void DRV_IR_RunFrame() { decode_results results; if (ourReceiver->decode(&results)) { //const char *name = ProtocolNames[ourReceiver->decodedIRData.protocol]; + #if 0 const char *name = "TODO"; if (!(gIRProtocolEnable & (1 << (int)results.decode_type))) { ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", name, (int)results.decode_type, gIRProtocolEnable); } + #endif dump(&results); // 'UNKNOWN' protocol is by default disabled in flags @@ -831,7 +866,6 @@ extern "C" void DRV_IR_RunFrame() { (gIRProtocolEnable & (1 << (int)results.decode_type)) ) { - #if 0 //TODO char out[128]; PrintIRData(&results); diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.cpp b/src/libraries/IRremoteESP8266/src/IRrecv.cpp index 3ae765dac..4d3e2fe5e 100644 --- a/src/libraries/IRremoteESP8266/src/IRrecv.cpp +++ b/src/libraries/IRremoteESP8266/src/IRrecv.cpp @@ -29,7 +29,7 @@ extern "C" { #endif #include "digitalWriteFast.h" - +#include "minmax.h" // To change this value, you simply can add a line #define "RECORD_GAP_MICROS " in your ino file before the line "#include " #define RECORD_GAP_MICROS 5000 // FREDRICH28AC / LG2 header space is 9700, NEC header space is 4500 @@ -1248,13 +1248,14 @@ uint32_t IRrecv::ticksHigh(const uint32_t usecs, const uint8_t tolerance, /// @return A Boolean. true if it matches, false if it doesn't. bool IRrecv::match(uint32_t measured, uint32_t desired, uint8_t tolerance, uint16_t delta) { + char tmp[24]; measured *= kRawTick; // Convert to uSecs. DPRINT("Matching: "); - DPRINT(ticksLow(desired, tolerance, delta)); + DPRINT(itoa(ticksLow(desired, tolerance, delta),tmp,10)); DPRINT(" <= "); - DPRINT(measured); + DPRINT(itoa(measured,tmp,10)); DPRINT(" <= "); - DPRINTLN(ticksHigh(desired, tolerance, delta)); + DPRINTLN(itoa(ticksHigh(desired, tolerance, delta),tmp,10)); #ifdef UNIT_TEST // Sanity checks that we don't have values that cause integer over/underflow. // Only performed during testing so there is no performance hit in normal @@ -1279,20 +1280,21 @@ bool IRrecv::match(uint32_t measured, uint32_t desired, uint8_t tolerance, /// @return A Boolean. true if it matches, false if it doesn't. bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired, uint8_t tolerance, uint16_t delta) { + char tmp[24]; measured *= kRawTick; // Convert to uSecs. DPRINT("Matching ATLEAST "); - DPRINT(measured); + DPRINT(itoa(measured,tmp,10)); DPRINT(" vs "); - DPRINT(desired); + DPRINT(itoa(desired,tmp,10)); DPRINT(". Matching: "); - DPRINT(measured); + DPRINT(itoa(measured,tmp,10)); DPRINT(" >= "); - DPRINT(ticksLow(::min(desired, (uint32_t)MS_TO_USEC(params.timeout)), - tolerance, delta)); + DPRINT(itoa(ticksLow(::min(desired, (uint32_t)MS_TO_USEC(params.timeout)), + tolerance, delta),tmp,10)); DPRINT(" [min("); - DPRINT(ticksLow(desired, tolerance, delta)); + DPRINT(itoa(ticksLow(desired, tolerance, delta),tmp,10)); DPRINT(", "); - DPRINT(ticksLow(MS_TO_USEC(params.timeout), tolerance, delta)); + DPRINT(itoa(ticksLow(MS_TO_USEC(params.timeout), tolerance, delta),tmp,10)); DPRINTLN(")]"); #ifdef UNIT_TEST // Sanity checks that we don't have values that cause integer over/underflow. @@ -1324,12 +1326,13 @@ bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired, /// @return A Boolean. true if it matches, false if it doesn't. bool IRrecv::matchMark(uint32_t measured, uint32_t desired, uint8_t tolerance, int16_t excess) { + char tmp[24]; DPRINT("Matching MARK "); - DPRINT(measured * kRawTick); + DPRINT(itoa(measured * kRawTick,tmp,10)); DPRINT(" vs "); - DPRINT(desired); + DPRINT(itoa(desired,tmp,10)); DPRINT(" + "); - DPRINT(excess); + DPRINT(itoa(excess,tmp,10)); DPRINT(". "); return match(measured, desired + excess, tolerance); } @@ -1344,12 +1347,13 @@ bool IRrecv::matchMark(uint32_t measured, uint32_t desired, uint8_t tolerance, /// @return A Boolean. true if it matches, false if it doesn't. bool IRrecv::matchMarkRange(const uint32_t measured, const uint32_t desired, const uint16_t range, const int16_t excess) { + char tmp[24]; DPRINT("Matching MARK "); - DPRINT(measured * kRawTick); + DPRINT(itoa(measured * kRawTick,tmp,10)); DPRINT(" vs "); - DPRINT(desired); + DPRINT(itoa(desired,tmp,10)); DPRINT(" + "); - DPRINT(excess); + DPRINT(itoa(excess,tmp,10)); DPRINT(". "); return match(measured, desired + excess, 0, range); } @@ -1363,12 +1367,13 @@ bool IRrecv::matchMarkRange(const uint32_t measured, const uint32_t desired, /// @return A Boolean. true if it matches, false if it doesn't. bool IRrecv::matchSpace(uint32_t measured, uint32_t desired, uint8_t tolerance, int16_t excess) { + char tmp[24]; DPRINT("Matching SPACE "); - DPRINT(measured * kRawTick); + DPRINT(itoa(measured * kRawTick,tmp,10)); DPRINT(" vs "); - DPRINT(desired); + DPRINT(itoa(desired,tmp,10)); DPRINT(" - "); - DPRINT(excess); + DPRINT(itoa(excess,tmp,10)); DPRINT(". "); return match(measured, desired - excess, tolerance); } @@ -1383,12 +1388,13 @@ bool IRrecv::matchSpace(uint32_t measured, uint32_t desired, uint8_t tolerance, /// @return A Boolean. true if it matches, false if it doesn't. bool IRrecv::matchSpaceRange(const uint32_t measured, const uint32_t desired, const uint16_t range, const int16_t excess) { + char tmp[24]; DPRINT("Matching SPACE "); - DPRINT(measured * kRawTick); + DPRINT(itoa(measured * kRawTick,tmp,10)); DPRINT(" vs "); - DPRINT(desired); + DPRINT(itoa(desired,tmp,10)); DPRINT(" - "); - DPRINT(excess); + DPRINT(itoa(excess,tmp,10)); DPRINT(". "); return match(measured, desired - excess, 0, range); } @@ -1972,6 +1978,7 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, const bool MSBfirst, const bool GEThomas) { DPRINTLN("DEBUG: Entered matchManchesterData"); + char tmp[24]; uint16_t offset = 0; uint64_t data = 0; uint16_t nr_half_periods = 0; @@ -2012,10 +2019,10 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, nr_half_periods < expected_half_periods) { // Get the next entry if we haven't anything existing to process. DPRINT("DEBUG: Offset = "); - DPRINTLN(offset); + DPRINTLN(itoa(offset,tmp,10)); if (!bank) bank = *(data_ptr + offset++); DPRINT("DEBUG: Bank = "); - DPRINTLN(bank * kRawTick); + DPRINTLN(itoa(bank * kRawTick,tmp,10)); // Check if we don't have a short interval. DPRINTLN("DEBUG: Checking for short interval"); if (!match(bank, half_period, tolerance, excess)) { @@ -2025,13 +2032,13 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, // We've succeeded in matching half a period, so count it. nr_half_periods++; DPRINT("DEBUG: Half Periods = "); - DPRINTLN(nr_half_periods); + DPRINTLN(itoa(nr_half_periods,tmp,10)); // We've now used up our bank, so refill it with the next item, unless we // are at the end of the capture buffer. // If we are assume a single half period of "space". if (offset < remaining) { DPRINT("DEBUG: Offset = "); - DPRINTLN(offset); + DPRINTLN(itoa(offset,tmp,10)); bank = *(data_ptr + offset++); } else if (offset == remaining) { bank = raw_half_period; @@ -2039,7 +2046,7 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, return 0; // We are out of buffer, so abort! } DPRINT("DEBUG: Bank = "); - DPRINTLN(bank * kRawTick); + DPRINTLN(itoa(bank * kRawTick,tmp,10)); // Shift the data along and add our new bit. DPRINT("DEBUG: Adding bit: "); @@ -2116,7 +2123,7 @@ uint32_t now = 0; uint_fast8_t old; static uint32_t start = 0; - void IR_ISR() { +void IR_ISR() { now += 50; uint_fast8_t tIRInputLevel = (uint_fast8_t)digitalReadFast(params.recvpin); if (tIRInputLevel == old) diff --git a/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h b/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h index 76a4d1370..bca524beb 100644 --- a/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h +++ b/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h @@ -52,6 +52,7 @@ //#include #endif // UNIT_TEST + // Library Version Information // Major version number (X.x.x) #define _IRREMOTEESP8266_VERSION_MAJOR 2 @@ -1494,8 +1495,21 @@ const uint16_t kClimaButlerBits = 52; #define DPRINTLN(x) do { Serial.println(x); } while (0) #endif // ARDUINO #else // DEBUG + +#if PLATFORM_BEKEN + +// ADD Logging macro +// For debug messages only +extern "C" { +#include "../../../logging/logging.h" +} + +#define DPRINT(x) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)x); +#define DPRINTLN(x) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)x); +#else #define DPRINT(x) #define DPRINTLN(x) +#endif #endif // DEBUG #ifdef UNIT_TEST diff --git a/src/libraries/IRremoteESP8266/src/IRsend.cpp b/src/libraries/IRremoteESP8266/src/IRsend.cpp index 24cba1ffe..34b25c766 100644 --- a/src/libraries/IRremoteESP8266/src/IRsend.cpp +++ b/src/libraries/IRremoteESP8266/src/IRsend.cpp @@ -16,23 +16,7 @@ #endif #include "IRtimer.h" -#if PLATFORM_BEKEN -//TODO: check these? -typedef enum { - LOW = 0, - HIGH = 1, - CHANGE = 2, - FALLING = 3, - RISING = 4, -} PinStatus; - -typedef enum { - INPUT = 0x0, - OUTPUT = 0x1, - INPUT_PULLUP = 0x2, - INPUT_PULLDOWN = 0x3, -} PinMode; -#endif +#include "digitalWriteFast.h" /// Constructor for an IRsend object. diff --git a/src/libraries/IRremoteESP8266/src/digitalWriteFast.cpp b/src/libraries/IRremoteESP8266/src/digitalWriteFast.cpp new file mode 100644 index 000000000..6c393c968 --- /dev/null +++ b/src/libraries/IRremoteESP8266/src/digitalWriteFast.cpp @@ -0,0 +1,35 @@ +extern "C" { +#include +} + +#include "digitalWriteFast.h" + + +void digitalToggleFast(unsigned char P) { + bk_gpio_output((GPIO_INDEX)P, !bk_gpio_input((GPIO_INDEX)P)); +} + +unsigned char digitalReadFast(unsigned char P) { + return bk_gpio_input((GPIO_INDEX)P); +} + +void digitalWriteFast(unsigned char P, unsigned char V) { + //RAW_SetPinValue(P, V); + //HAL_PIN_SetOutputValue(index, iVal); + bk_gpio_output((GPIO_INDEX)P, V); +} + +void pinModeFast(unsigned char P, unsigned char V) { + if (V == INPUT_PULLUP) { + bk_gpio_config_input_pup((GPIO_INDEX)P); + } + else if (V == INPUT_PULLDOWN) { + bk_gpio_config_input_pdwn((GPIO_INDEX)P); + } + else if (V == INPUT) { + bk_gpio_config_input((GPIO_INDEX)P); + } + else if (V == OUTPUT) { + bk_gpio_config_output((GPIO_INDEX)P); + } +} diff --git a/src/libraries/IRremoteESP8266/src/digitalWriteFast.h b/src/libraries/IRremoteESP8266/src/digitalWriteFast.h index 2ff24dbb4..f56e85544 100644 --- a/src/libraries/IRremoteESP8266/src/digitalWriteFast.h +++ b/src/libraries/IRremoteESP8266/src/digitalWriteFast.h @@ -8,11 +8,35 @@ * License: BSD 3-Clause License (https://opensource.org/licenses/BSD-3-Clause) */ +/** + * + * Emulation layer for the OpenBK7231T + * +*/ + + #ifndef __digitalWriteFast_h_ #define __digitalWriteFast_h_ 1 -#define INPUT_PULLUP 1 -#define INPUT 1 + +#if PLATFORM_BEKEN +//TODO: check these? +typedef enum { + LOW = 0, + HIGH = 1, + CHANGE = 2, + FALLING = 3, + RISING = 4, +} PinStatus; + +typedef enum { + INPUT = 0x0, + OUTPUT = 0x1, + INPUT_PULLUP = 0x2, + INPUT_PULLDOWN = 0x3, +} PinMode; +#endif + void digitalToggleFast(unsigned char P); unsigned char digitalReadFast(unsigned char P); diff --git a/src/libraries/IRremoteESP8266/src/ir_Corona.cpp b/src/libraries/IRremoteESP8266/src/ir_Corona.cpp index c0e369702..c44d55c8c 100644 --- a/src/libraries/IRremoteESP8266/src/ir_Corona.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_Corona.cpp @@ -106,7 +106,7 @@ bool IRrecv::decodeCoronaAc(decode_results *results, uint16_t offset, // #2 *(results->state + pos) = {0x28, 0x61, 0x6D, 0xFF, 0x00, 0xFF, 0x00}; // #3 *(results->state + pos) = {0x28, 0x61, 0xCD, 0xFF, 0x00, 0xFF, 0x00}; for (uint8_t section = 0; section < kCoronaAcSections; section++) { - DPRINT(uint64ToString(section)); + DPRINT(uint64ToString(section).c_str()); used = matchGeneric(results->rawbuf + offset, results->state + pos, results->rawlen - offset, kCoronaAcBitsShort, kCoronaAcHdrMark, kCoronaAcHdrSpace, @@ -190,29 +190,30 @@ bool IRCoronaAc::validSection(const uint8_t state[], const uint16_t pos, // sanity check, pos must match section, section 4 is at pos 0 if ((section % kCoronaAcSections) * kCoronaAcSectionBytes != pos) return false; + char tmp[24]; // all individual sections has the same prefix const CoronaSection *p = reinterpret_cast(state + pos); if (p->Header0 != kCoronaAcSectionHeader0) { DPRINT("State "); - DPRINT(&(p->Header0) - state); + DPRINT(itoa(&(p->Header0) - state,tmp,10)); DPRINT(" expected 0x28 was "); - DPRINTLN(uint64ToString(p->Header0, 16)); + DPRINTLN(uint64ToString(p->Header0, 16).c_str()); return false; } if (p->Header1 != kCoronaAcSectionHeader1) { DPRINT("State "); - DPRINT(&(p->Header1) - state); + DPRINT(itoa(&(p->Header1) - state,tmp,10)); DPRINT(" expected 0x61 was "); - DPRINTLN(uint64ToString(p->Header1, 16)); + DPRINTLN(uint64ToString(p->Header1, 16).c_str()); return false; } // checking section byte if (p->Label != getSectionByte(section)) { DPRINT("check 2 not matching, got "); - DPRINT(uint64ToString(p->Label, 16)); + DPRINT(uint64ToString(p->Label, 16).c_str()); DPRINT(" expected "); - DPRINTLN(uint64ToString(getSectionByte(section), 16)); + DPRINTLN(uint64ToString(getSectionByte(section), 16).c_str()); return false; } @@ -220,17 +221,17 @@ bool IRCoronaAc::validSection(const uint8_t state[], const uint16_t pos, uint8_t d0invinv = ~p->Data0Inv; if (p->Data0 != d0invinv) { DPRINT("inverted 3 - 4 not matching, got "); - DPRINT(uint64ToString(p->Data0, 16)); + DPRINT(uint64ToString(p->Data0, 16).c_str()); DPRINT(" vs "); - DPRINTLN(uint64ToString(p->Data0Inv, 16)); + DPRINTLN(uint64ToString(p->Data0Inv, 16).c_str()); return false; } uint8_t d1invinv = ~p->Data1Inv; if (p->Data1 != d1invinv) { DPRINT("inverted 5 - 6 not matching, got "); - DPRINT(uint64ToString(p->Data1, 16)); + DPRINT(uint64ToString(p->Data1, 16).c_str()); DPRINT(" vs "); - DPRINTLN(uint64ToString(p->Data1Inv, 16)); + DPRINTLN(uint64ToString(p->Data1Inv, 16).c_str()); return false; } return true; diff --git a/src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp b/src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp index 0a5fadedb..2627f440c 100644 --- a/src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_Doshisha.cpp @@ -104,11 +104,11 @@ bool IRrecv::decodeDoshisha(decode_results *results, uint16_t offset, // RCZ01 remote commands starts with a lead bit set if ((data & kRcz01SignatureMask) != kRcz01Signature) { DPRINT(" decodeDoshisha data "); - DPRINT(uint64ToString(data, 16)); + DPRINT(uint64ToString(data, 16).c_str()); DPRINT(" masked "); - DPRINT(uint64ToString(data & kRcz01SignatureMask, 16)); + DPRINT(uint64ToString(data & kRcz01SignatureMask, 16).c_str()); DPRINT(" not matching "); - DPRINT(uint64ToString(kRcz01Signature, 16)); + DPRINT(uint64ToString(kRcz01Signature, 16).c_str()); DPRINTLN(" ."); return false; // expected lead bits not matching } diff --git a/src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp b/src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp index 00da7e309..d3e750e80 100644 --- a/src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_Goodweather.cpp @@ -444,8 +444,9 @@ bool IRrecv::decodeGoodweather(decode_results* results, uint16_t offset, // Data for (; offset <= results->rawlen - 32 && dataBitsSoFar < nbits; dataBitsSoFar += 8) { + char tmp[24]; DPRINT("DEBUG: Attempting Byte #"); - DPRINTLN(dataBitsSoFar / 8); + DPRINTLN(itoa(dataBitsSoFar / 8,tmp,10)); // Read in a byte at a time. // Normal first. data_result = matchData(&(results->rawbuf[offset]), 8, @@ -468,9 +469,9 @@ bool IRrecv::decodeGoodweather(decode_results* results, uint16_t offset, offset += data_result.used; uint8_t inverted = (uint8_t)data_result.data; DPRINT("DEBUG: data = "); - DPRINTLN((uint16_t)data); + DPRINTLN(itoa(data,tmp,10)); DPRINT("DEBUG: inverted = "); - DPRINTLN((uint16_t)inverted); + DPRINTLN(itoa(inverted,tmp,10)); if (data != (inverted ^ 0xFF)) return false; // Data integrity failed. dataSoFar |= (uint64_t)data << dataBitsSoFar; } diff --git a/src/libraries/IRremoteESP8266/src/ir_Lutron.cpp b/src/libraries/IRremoteESP8266/src/ir_Lutron.cpp index ecbf0151f..993656a2d 100644 --- a/src/libraries/IRremoteESP8266/src/ir_Lutron.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_Lutron.cpp @@ -73,6 +73,8 @@ bool IRrecv::decodeLutron(decode_results *results, uint16_t offset, uint64_t data = 0; int16_t bitsSoFar = -1; + char tmp[24];//DEBUG + if (nbits > sizeof(data) * 8) return false; // To large to store the data. for (; bitsSoFar < nbits && offset < results->rawlen; offset++) { uint16_t entry = results->rawbuf[offset]; @@ -85,7 +87,7 @@ bool IRrecv::decodeLutron(decode_results *results, uint16_t offset, while (entry != 0 && matchAtLeast(entry, kLutronTick, 0, kLutronDelta)) { bitsSoFar++; DPRINT("Bit: "); - DPRINT(bitsSoFar); + DPRINT(itoa(bitsSoFar,tmp,10)); if (offset % 2) { // Is Odd? data = (data << 1) + 1; // Append a '1'. DPRINTLN(" is a 1."); @@ -101,22 +103,22 @@ bool IRrecv::decodeLutron(decode_results *results, uint16_t offset, } if (offset % 2 && !match(entry, kLutronDelta, 0, kLutronDelta)) { DPRINT("offset = "); - DPRINTLN(offset); + DPRINTLN(itoa(offset,tmp,10)); DPRINT("rawlen = "); - DPRINTLN(results->rawlen); + DPRINTLN(itoa(results->rawlen,tmp,10)); DPRINT("entry = "); - DPRINTLN(entry); + DPRINTLN(itoa(entry,tmp,10)); DPRINTLN("Odd Entry has too much left over. Aborting."); return false; // Too much left over to be a good value. Reject it. } if (offset % 2 == 0 && offset <= results->rawlen - 1 && !matchAtLeast(entry, kLutronDelta, 0, kLutronDelta)) { DPRINT("offset = "); - DPRINTLN(offset); + DPRINTLN(itoa(offset,tmp,10)); DPRINT("rawlen = "); - DPRINTLN(results->rawlen); + DPRINTLN(itoa(results->rawlen,tmp,10)); DPRINT("entry = "); - DPRINTLN(entry); + DPRINTLN(itoa(entry,tmp,10)); DPRINTLN("Entry has too much left over. Aborting."); return false; // Too much left over to be a good value. Reject it. } diff --git a/src/libraries/IRremoteESP8266/src/ir_MWM.cpp b/src/libraries/IRremoteESP8266/src/ir_MWM.cpp index 757696db6..0589b180a 100644 --- a/src/libraries/IRremoteESP8266/src/ir_MWM.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_MWM.cpp @@ -92,15 +92,16 @@ bool IRrecv::decodeMWM(decode_results *results, uint16_t offset, uint64_t data = 0; uint16_t frame_bits = 0; uint16_t data_bits = 0; - + char tmp[24]; //DEBUG // No Header // Data uint8_t bits_per_frame = 10; for (; offset < results->rawlen && results->bits < 8 * kStateSizeMax; frame_bits++) { + DPRINT("DEBUG: decodeMWM: offset = "); - DPRINTLN(offset); + DPRINTLN(itoa(offset,tmp,10)); int16_t level = getRClevel(results, &offset, &used, kMWMTick, kMWMTolerance, kMWMExcess, kMWMDelta, kMWMMaxWidth); if (level < 0) { @@ -122,9 +123,9 @@ bool IRrecv::decodeMWM(decode_results *results, uint16_t offset, return false; } else { DPRINT("DEBUG: decodeMWM: data_bits = "); - DPRINTLN(data_bits); + DPRINTLN(itoa(data_bits,tmp,10)); DPRINT("DEBUG: decodeMWM: Finished byte: "); - DPRINTLN(uint64ToString(data)); + DPRINTLN(uint64ToString(data).c_str()); results->state[data_bits / 8 - 1] = data & 0xFF; results->bits = data_bits; data = 0; @@ -133,7 +134,7 @@ bool IRrecv::decodeMWM(decode_results *results, uint16_t offset, default: // Data bits DPRINT("DEBUG: decodeMWM: Storing bit: "); - DPRINTLN((level == kSpace)); + DPRINTLN(itoa((level == kSpace),tmp,10)); // Transmission is LSB-first, space=1 data |= ((level == kSpace)) << 8; data >>= 1; @@ -147,12 +148,12 @@ bool IRrecv::decodeMWM(decode_results *results, uint16_t offset, // Compliance DPRINT("DEBUG: decodeMWM: frame_bits = "); - DPRINTLN(frame_bits); + DPRINTLN(itoa(frame_bits,tmp,10)); DPRINT("DEBUG: decodeMWM: data_bits = "); - DPRINTLN(data_bits); + DPRINTLN(itoa(data_bits,tmp,10)); if (data_bits < nbits) { DPRINT("DEBUG: decodeMWM: too few bits; expected "); - DPRINTLN(nbits); + DPRINTLN(itoa(nbits,tmp,10)); return false; // Less data than we expected. } @@ -163,7 +164,7 @@ bool IRrecv::decodeMWM(decode_results *results, uint16_t offset, // Normal commands payload_length = results->state[0] & 0x0f; DPRINT("DEBUG: decodeMWM: payload_length = "); - DPRINTLN(payload_length); + DPRINTLN(itoa(payload_length,tmp,10)); break; default: if (strict) { @@ -176,13 +177,13 @@ bool IRrecv::decodeMWM(decode_results *results, uint16_t offset, } if (data_bits < (payload_length + 3) * 8) { DPRINT("DEBUG: decodeMWM: too few bytes; expected "); - DPRINTLN((payload_length + 3)); + DPRINTLN(itoa(payload_length + 3,tmp,10)); return false; } if (strict) { if (payload_length && (data_bits > (payload_length + 3) * 8)) { DPRINT("DEBUG: decodeMWM: too many bytes; expected "); - DPRINTLN((payload_length + 3)); + DPRINTLN(itoa(payload_length + 3,tmp,10)); return false; } } diff --git a/src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp b/src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp index b459949cd..f682db0ea 100644 --- a/src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_Magiquest.cpp @@ -70,12 +70,13 @@ bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { uint16_t bits = 0; uint64_t data = 0; + char tmp[24]; if (results->rawlen < (2 * kMagiquestBits) + offset - 1) { DPRINT("Not enough bits to be Magiquest - Rawlen: "); - DPRINT(results->rawlen); + DPRINT(itoa(results->rawlen,tmp,10)); DPRINT(" Expected: "); - DPRINTLN(2 * kMagiquestBits + offset - 1); + DPRINTLN(itoa(2 * kMagiquestBits + offset - 1,tmp,10)); return false; } @@ -93,13 +94,13 @@ bool IRrecv::decodeMagiQuest(decode_results *results, uint16_t offset, uint16_t space = results->rawbuf[offset + 1]; if (!matchMark(mark + space, kMagiQuestTotalUsec)) { DPRINT("Not enough time to be Magiquest - Mark: "); - DPRINT(mark); + DPRINT(itoa(mark,tmp,10)); DPRINT(" Space: "); - DPRINT(space); + DPRINT(itoa(space,tmp,10)); DPRINT(" Total: "); - DPRINT(mark + space); + DPRINT(itoa(mark + space,tmp,10)); DPRINT("Expected: "); - DPRINTLN(kMagiQuestTotalUsec); + DPRINTLN(itoa(kMagiQuestTotalUsec,tmp,10)); return false; } diff --git a/src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp b/src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp index 1d1f4899e..90c0c8ac8 100644 --- a/src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_MilesTag2.cpp @@ -63,6 +63,7 @@ void IRsend::sendMilestag2(const uint64_t data, const uint16_t nbits, bool IRrecv::decodeMilestag2(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { uint64_t data = 0; + char tmp[24];//DEBUG // Header + Data + Optional Footer if (!matchGeneric(results->rawbuf + offset, &data, results->rawlen - offset, nbits, @@ -86,7 +87,7 @@ bool IRrecv::decodeMilestag2(decode_results *results, uint16_t offset, break; default: DPRINT("incorrect nbits:"); - DPRINTLN(nbits); + DPRINTLN(itoa(nbits,tmp,10)); return false; // The request doesn't strictly match the protocol defn. } } diff --git a/src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp b/src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp index e522a370f..6a3f1c5c7 100644 --- a/src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp +++ b/src/libraries/IRremoteESP8266/src/ir_RC5_RC6.cpp @@ -245,9 +245,9 @@ int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset, const uint8_t tolerance, const int16_t excess, const uint16_t delta, const uint8_t maxwidth) { DPRINT("DEBUG: getRClevel: offset = "); - DPRINTLN(uint64ToString(*offset)); + DPRINTLN(uint64ToString(*offset).c_str()); DPRINT("DEBUG: getRClevel: rawlen = "); - DPRINTLN(uint64ToString(results->rawlen)); + DPRINTLN(uint64ToString(results->rawlen).c_str()); if (*offset >= results->rawlen) { DPRINTLN("DEBUG: getRClevel: SPACE, past end of rawbuf"); return kSpace; // After end of recorded buffer, assume SPACE. From 57cfdd04b5f65ff65ac721f2a7e5bbd8880267e7 Mon Sep 17 00:00:00 2001 From: "Vladimir S. FONOV" Date: Sun, 26 Feb 2023 13:55:16 -0500 Subject: [PATCH 04/16] implemented timeout condition and increased buffer size --- src/libraries/IRremoteESP8266/src/IRrecv.cpp | 78 ++++++++++++-------- src/libraries/IRremoteESP8266/src/IRrecv.h | 2 +- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.cpp b/src/libraries/IRremoteESP8266/src/IRrecv.cpp index 4d3e2fe5e..1c360a8da 100644 --- a/src/libraries/IRremoteESP8266/src/IRrecv.cpp +++ b/src/libraries/IRremoteESP8266/src/IRrecv.cpp @@ -2120,44 +2120,58 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, uint32_t now = 0; -uint_fast8_t old; +uint_fast8_t old = 0; static uint32_t start = 0; void IR_ISR() { - now += 50; - uint_fast8_t tIRInputLevel = (uint_fast8_t)digitalReadFast(params.recvpin); - if (tIRInputLevel == old) - return; - old = tIRInputLevel; - // Grab a local copy of rawlen to reduce instructions used in IRAM. - // This is an ugly premature optimisation code-wise, but we do everything we - // can to save IRAM. - // It seems referencing the value via the structure uses more instructions. - // Less instructions means faster and less IRAM used. - // N.B. It saves about 13 bytes of IRAM. - uint16_t rawlen = params.rawlen; - - if (rawlen >= params.bufsize) { - params.overflow = true; - params.rcvstate = kStopState; - } - - if (params.rcvstate == kStopState) return; + // TODO: is there a lock or a mutex to prevent race condition ??? + + now += 50; // timer is supposed to be running at 50us ? + if (params.rcvstate == kStopState) return; + uint_fast8_t tIRInputLevel = (uint_fast8_t)digitalReadFast(params.recvpin); + uint16_t time_since_last_change; + // check if timeout is reached and stop recieving + if (now < start) + time_since_last_change = (UINT32_MAX - start + now); + else + time_since_last_change = (now - start); + + // Timeout is in mS + if((time_since_last_change/1000) >= params.timeout && params.rawlen) // timed out + { + params.rcvstate = kStopState; + return; + } - if (params.rcvstate == kIdleState) { - params.rcvstate = kMarkState; - params.rawbuf[rawlen] = 1; - } - else { - if (now < start) - params.rawbuf[rawlen] = (UINT32_MAX - start + now) / kRawTick; - else - params.rawbuf[rawlen] = (now - start) / kRawTick; - } - params.rawlen++; + if (tIRInputLevel == old) + return; + + old = tIRInputLevel; + // Grab a local copy of rawlen to reduce instructions used in IRAM. + // This is an ugly premature optimisation code-wise, but we do everything we + // can to save IRAM. + // It seems referencing the value via the structure uses more instructions. + // Less instructions means faster and less IRAM used. + // N.B. It saves about 13 bytes of IRAM. + uint16_t rawlen = params.rawlen; + + if (rawlen >= params.bufsize) { + params.overflow = true; + params.rcvstate = kStopState; + return; + } - start = now; + if (params.rcvstate == kIdleState) { + params.rcvstate = kMarkState; + params.rawbuf[rawlen] = 1; + } + else { + // buffer stores in units of 2uS for some reason? + params.rawbuf[rawlen] = time_since_last_change/ kRawTick; + } + params.rawlen++; + start = now; } //#define _IR_MEASURE_TIMING //#define _IR_TIMING_TEST_PIN 7 // do not forget to execute: "pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);" if activated by line above diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.h b/src/libraries/IRremoteESP8266/src/IRrecv.h index 019791a32..36d12181f 100644 --- a/src/libraries/IRremoteESP8266/src/IRrecv.h +++ b/src/libraries/IRremoteESP8266/src/IRrecv.h @@ -22,7 +22,7 @@ const uint16_t kStartOffset = 1; // Usual rawbuf entry to start from. // Marks tend to be 100us too long, and spaces 100us too short // when received due to sensor lag. const uint16_t kMarkExcess = 50; -const uint16_t kRawBuf = 100; // Default length of raw capture buffer +const uint16_t kRawBuf = 1024; // Default length of raw capture buffer const uint64_t kRepeat = UINT64_MAX; // Default min size of reported UNKNOWN messages. const uint16_t kUnknownThreshold = 6; From 1e949a2f2e710c0b9594575460bb43f312a1633c Mon Sep 17 00:00:00 2001 From: "Vladimir S. FONOV" Date: Sun, 26 Feb 2023 15:28:53 -0500 Subject: [PATCH 05/16] Disabled verbose debug messages for IRrecv, added decoding of AC modes in the log --- src/driver/drv_ir.cpp | 49 ++----------------- .../IRremoteESP8266/src/IRremoteESP8266.h | 7 ++- 2 files changed, 7 insertions(+), 49 deletions(-) diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index 98a35356f..1bf8381d3 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -378,7 +378,6 @@ extern "C" void DRV_IR_ISR(UINT8 t) { // don't receive if we are currently sending if (ourReceiver && !sending){ IR_ISR(); - //TODO: implement reading from the recieve pin here } ir_counter++; } @@ -703,51 +702,11 @@ extern "C" void DRV_IR_Init() { void dump(decode_results *results) { // Dumps out the decode_results structure. // Call this after IRrecv::decode() - #if 0 - uint16_t count = results->rawlen; - if (results->decode_type == UNKNOWN) { - ADDLOG_INFO(LOG_FEATURE_IR,"Unknown encoding: "); - } - else if (results->decode_type == NEC) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded NEC: "); - } - else if (results->decode_type == SONY) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded SONY: "); - } - else if (results->decode_type == RC5) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RC5: "); - } - else if (results->decode_type == RC5X) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RC5X: "); - } - else if (results->decode_type == RC6) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RC6: "); - } - else if (results->decode_type == RCMM) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded RCMM: "); - } - else if (results->decode_type == PANASONIC) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded PANASONIC: "); - } - else if (results->decode_type == LG) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded LG: "); - } - else if (results->decode_type == JVC) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded JVC: "); - } - else if (results->decode_type == AIWA_RC_T501) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded AIWA RC T501: "); - } - else if (results->decode_type == WHYNTER) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded Whynter: "); - } - else if (results->decode_type == NIKAI) { - ADDLOG_INFO(LOG_FEATURE_IR,"Decoded Nikai: "); + ADDLOG_INFO(LOG_FEATURE_IR, resultToHumanReadableBasic(results).c_str()); + if (hasACState(results->decode_type)) + { + ADDLOG_INFO(LOG_FEATURE_IR, IRAcUtils::resultAcToString(results).c_str()); } - //serialPrintUint64(results->value, 16); - ADDLOG_INFO(LOG_FEATURE_IR,"Address: %i Value: %i (%i bits) Raw (%i)",(int)results->address, (int)results->value, results->bits, count); - #endif - ADDLOG_INFO(LOG_FEATURE_IR,resultToHumanReadableBasic(results).c_str()); } // log the received IR diff --git a/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h b/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h index bca524beb..64f50dda3 100644 --- a/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h +++ b/src/libraries/IRremoteESP8266/src/IRremoteESP8266.h @@ -1496,17 +1496,16 @@ const uint16_t kClimaButlerBits = 52; #endif // ARDUINO #else // DEBUG -#if PLATFORM_BEKEN - +#if 0 // PLATFORM_BEKEN // ADD Logging macro // For debug messages only extern "C" { #include "../../../logging/logging.h" } - #define DPRINT(x) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)x); #define DPRINTLN(x) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)x); -#else + +#else // PLATFORM_BEKEN #define DPRINT(x) #define DPRINTLN(x) #endif From 9768f442f4e6a7dfddd6d8a7e02679448380999d Mon Sep 17 00:00:00 2001 From: "Vladimir S. FONOV" Date: Mon, 27 Feb 2023 00:43:00 -0500 Subject: [PATCH 06/16] Sending is working for a limited set of protocols: RC5,RC5,NEC,PANASONIC,JVC,SAMSUNG,LG --- src/driver/drv_ir.cpp | 236 +++++++------------ src/libraries/IRremoteESP8266/src/IRsend.cpp | 2 - src/libraries/IRremoteESP8266/src/IRsend.h | 2 +- 3 files changed, 85 insertions(+), 155 deletions(-) diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index 1bf8381d3..be6e19090 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -216,7 +216,6 @@ class myIRsend : public IRsend { void delay(long int ms) { // add a pure delay to our queue - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"Delay %dms", ms); space(ms * 1000); } @@ -250,15 +249,19 @@ class myIRsend : public IRsend { } } - void enableIROut(uint32_t freq, uint8_t duty) { + void enableIROut(uint32_t freq, uint8_t duty=50) { //uint_fast8_t aFrequencyKHz if (freq < 1000) // Were we given kHz? Supports the old call usage. freq *= 1000; - + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"enableIROut %d freq %d duty",(int)freq, (int)duty); + if(duty<1) + duty=1; + if(duty>100) + duty=100; // just setup variables for use in ISR //pwmfrequency = ((uint32_t)aFrequencyKHz) * 1000; pwmperiod = (26000000 / freq); - pwmduty = pwmperiod / 2; + pwmduty = pwmperiod / (100/duty); } void resetsendqueue() { @@ -382,21 +385,6 @@ extern "C" void DRV_IR_ISR(UINT8 t) { ir_counter++; } -decode_type_t find_protocol_by_name(const char* args, int ournamelen) -{ - decode_type_t protocol = decode_type_t::UNKNOWN; // UNKNOW? - - for (int i = 0; i < numProtocols; i++) { - const char *name = ProtocolNames[i]; - int namelen = strlen(name); - if (!my_strnicmp(name, args, namelen) && (ournamelen == namelen)) { - protocol = (decode_type_t)i; - break; - } - } - return protocol; -} - extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; @@ -415,9 +403,13 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con return CMD_RES_BAD_ARGUMENT; } - int ournamelen = (p - args); - decode_type_t protocol = find_protocol_by_name(args, ournamelen); - + *p='\0'; + decode_type_t protocol = strToDecodeType(args); + if(hasACState(protocol)) + { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend can't send AC commands", args); + return CMD_RES_BAD_ARGUMENT; + } p++; int addr = strtol(p, &p, 16); if ((*p != '-') && (*p != ' ')) { @@ -437,7 +429,35 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con if (pIRsend) { bool success = true; // Assume success. - success = pIRsend->send(protocol, addr, command, repeats); + switch(protocol) + { + case decode_type_t::RC5: + pIRsend->sendRC5((uint64_t)pIRsend->encodeRC5(addr,command)); + break; + case decode_type_t::RC6: + pIRsend->sendRC6((uint64_t)pIRsend->encodeRC6(addr,command)); + break; + case decode_type_t::NEC: + pIRsend->sendNEC((uint64_t)pIRsend->encodeNEC(addr,command)); + break; + case decode_type_t::PANASONIC: + pIRsend->sendPanasonic((uint16_t)addr,(uint32_t)command); + break; + case decode_type_t::JVC: + pIRsend->sendJVC((uint64_t)pIRsend->encodeJVC(addr,command)); + break; + case decode_type_t::SAMSUNG: + pIRsend->sendSAMSUNG((uint64_t)pIRsend->encodeSAMSUNG(addr,command)); + break; + case decode_type_t::LG: + pIRsend->sendLG((uint64_t)pIRsend->encodeLG(addr,command)); + break; + default: + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IR send %s protocol not supported", args); + return CMD_RES_ERROR; + break; + }; + // add a 100ms delay after command // NOTE: this is NOT a delay here. it adds 100ms 'space' in the TX queue pIRsend->delay(100); @@ -497,6 +517,8 @@ extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const } //int numProtocols = sizeof(ProtocolNames)/sizeof(*ProtocolNames); + #if 0 // number of protocols now is 125 + // TODO: reimpleemnt this using bigger mask int numProtocols = 0; int ournamelen = (p - args); int protocol = -1; @@ -530,8 +552,8 @@ extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const gIRProtocolEnable = gIRProtocolEnable & (~thisbit); } ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IREnable Protocol mask now 0x%08X", gIRProtocolEnable); + #endif //TODO return CMD_RES_OK; - } @@ -595,11 +617,10 @@ extern "C" commandResult_t IR_AC_Cmd(const void *context, const char *cmd, const } int ournamelen = (p - args); if ((*p != '-') && (*p != ' ')) { - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRAC cmnd not valid [%s] ", args); return CMD_RES_BAD_ARGUMENT; } - - decode_type_t protocol = find_protocol_by_name(args, ournamelen); + // decode_type_t protocol = strToDecodeType(args); ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRAC cmnd not implemented yet", args); @@ -630,7 +651,6 @@ extern "C" void DRV_IR_Init() { _timer_disable(); } - if (pin > 0) { // setup IRrecv pin as input //bk_gpio_config_input_pup((GPIO_INDEX)pin); // enabled by enableIRIn @@ -661,10 +681,8 @@ extern "C" void DRV_IR_Init() { bk_pwm_start((bk_pwm_t)pwmIndex); myIRsend *pIRsendTemp = new myIRsend((uint_fast8_t)txpin); pIRsendTemp->resetsendqueue(); + pIRsendTemp->enableIROut(pwmfrequency,50); pIRsendTemp->pwmIndex = pwmIndex; - pIRsendTemp->pwmfrequency = pwmfrequency; - pIRsendTemp->pwmperiod = period; - pIRsendTemp->pwmduty = duty; pIRsend = pIRsendTemp; //bk_pwm_stop((bk_pwm_t)pIRsend->pwmIndex); @@ -709,82 +727,6 @@ void dump(decode_results *results) { } } -// log the received IR -void PrintIRData(decode_results *aIRDataPtr) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %d", (int)aIRDataPtr->decode_type); - -#if 0 // TODO: disabled for now, using routines from IRremoteESP8266 - if (aIRDataPtr->decode_type == UNKNOWN) { -#if defined(DECODE_HASH) - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Hash=0x%X", (int)aIRDataPtr->decodedRawData); -#endif - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"%d bits (incl. gap and start) received", (int)((aIRDataPtr->rawlen) / 2)); - } - else { -#if defined(DECODE_DISTANCE) - if (aIRDataPtr->protocol != PULSE_DISTANCE) { -#endif - /* - * New decoders have address and command - */ - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Address=0x%X Command=0x%X", (int)aIRDataPtr->address, (int)aIRDataPtr->command); - - if (aIRDataPtr->flags & IRDATA_FLAGS_EXTRA_INFO) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Extra=0x%X", (int)aIRDataPtr->extra); - } - - if (aIRDataPtr->flags & IRDATA_FLAGS_PARITY_FAILED) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Parity fail"); - } - - if (aIRDataPtr->flags & IRDATA_TOGGLE_BIT_MASK) { - if (aIRDataPtr->protocol == NEC) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Special repeat"); - } - else { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Toggle=1"); - } - } -#if defined(DECODE_DISTANCE) - } -#endif - if (aIRDataPtr->flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) { - if (aIRDataPtr->flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Auto-Repeat"); - } - else { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"Repeat"); - } - if (1) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Gap %uus", (uint32_t)aIRDataPtr->rawDataPtr->rawbuf[0] * MICROS_PER_TICK); - } - } - - /* - * Print raw data - */ - if (!(aIRDataPtr->flags & IRDATA_FLAGS_IS_REPEAT) || aIRDataPtr->decodedRawData != 0) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" Raw-Data=0x%X", aIRDataPtr->decodedRawData); - /* - * Print number of bits processed - */ - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" %d bits", aIRDataPtr->numberOfBits); - - if (aIRDataPtr->flags & IRDATA_FLAGS_IS_MSB_FIRST) { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" MSB first", aIRDataPtr->numberOfBits); - } - else { - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)" LSB first", aIRDataPtr->numberOfBits); - } - } - } -#endif //0 - // TODO: reimplement ? - //ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)resultToHumanReadableBasic(aIRDataPtr).c_str()); - String description = IRAcUtils::resultAcToString(aIRDataPtr); - if (description.length()) ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)description.c_str()); - -} //////////////////////////////////////////////////// @@ -808,48 +750,46 @@ extern "C" void DRV_IR_RunFrame() { if (ourReceiver) { decode_results results; if (ourReceiver->decode(&results)) { - //const char *name = ProtocolNames[ourReceiver->decodedIRData.protocol]; - #if 0 - const char *name = "TODO"; + // TODO: find a better way? + const char *proto_name = typeToString(results.decode_type, results.repeat).c_str(); + + #if 0 // TODO: implement different masking if (!(gIRProtocolEnable & (1 << (int)results.decode_type))) { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", name, (int)results.decode_type, gIRProtocolEnable); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", proto_name, (int)results.decode_type, gIRProtocolEnable); } #endif - dump(&results); + //dump(&results); // 'UNKNOWN' protocol is by default disabled in flags // This is because I am getting a lot of 'UNKNOWN' spam with no IR signals in room - if (((results.decode_type != UNKNOWN) || - (results.decode_type == UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) && + if (((results.decode_type != decode_type_t::UNKNOWN) || + (results.decode_type == decode_type_t::UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) && // only process if this protocol is enabled. all by default. (gIRProtocolEnable & (1 << (int)results.decode_type)) ) { + String lastIrReceived = String((int)results.decode_type,16) + "," + resultToHexidecimal(&results); -#if 0 //TODO char out[128]; - PrintIRData(&results); - ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR decode returned true, protocol %s (%d)", name, (int)results.decode_type); int repeat = 0; - if (ourReceiver->decodedIRData.flags & (IRDATA_FLAGS_IS_AUTO_REPEAT | IRDATA_FLAGS_IS_REPEAT)) { - if (ourReceiver->decodedIRData.flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { - repeat = 2; - } - else { - repeat = 1; - } + if (results.repeat) { //? + repeat = 2; + } + else { + repeat = 1; } - if (ourReceiver->decodedIRData.protocol == UNKNOWN) { - snprintf(out, sizeof(out), "IR_%s 0x%lX %d", name, (unsigned long)ourReceiver->decodedIRData.decodedRawData, repeat); + if (results.decode_type == decode_type_t::UNKNOWN) { + //snprintf(out, sizeof(out), "IR_RAW 0x%lX %d", (unsigned long)results.decodedRawData, repeat); + // TODO: print RAW hexedecimal value } else { - snprintf(out, sizeof(out), "IR_%s 0x%X 0x%X %d", name, ourReceiver->decodedIRData.address, ourReceiver->decodedIRData.command, repeat); + snprintf(out, sizeof(out), "IR %s %X %X %d", proto_name, results.address, results.command, repeat); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)out); } - String lastIrReceived = String(capture.decode_type) + "," + resultToHexidecimal(&results); - if (!hasACState(capture.decode_type)) - lastIrReceived += "," + String(results.bits); + if (!hasACState(results.decode_type)) + lastIrReceived += "," + String((int)results.bits); // if user wants us to publish every received IR data, do it now if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED)) { @@ -866,47 +806,43 @@ extern "C" void DRV_IR_RunFrame() { ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR MQTT publish %s took %dms", out, counter_dur); } else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", out); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)out); } } else { ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", lastIrReceived.c_str()); } -#endif if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED_IN_JSON)) { // {"IrReceived":{"Protocol":"RC_5","Bits":0x1,"Data":"0xC"}} // - -#if 0 // TODO: - snprintf(out, sizeof(out), "{\"IrReceived\":{\"Protocol\":\"%s\",\"Bits\":%i,\"Data\":\"0x%lX\"}}", - name, (int)ourReceiver->decodedIRData.numberOfBits, (unsigned long)ourReceiver->decodedIRData.decodedRawData); + String _data=resultToHexidecimal(&results); + snprintf(out, sizeof(out), "{\"IrReceived\":{\"Protocol\":\"%s\",\"Bits\":%i,\"Data\":\"%s\"}}", + proto_name, (int)results.bits, _data.c_str()); MQTT_PublishMain_StringString("RESULT", out, OBK_PUBLISH_FLAG_FORCE_REMOVE_GET); -#endif //TODO } -#if 0 - if (ourReceiver->decodedIRData.protocol != UNKNOWN) { - snprintf(out, sizeof(out), "%X", ourReceiver->decodedIRData.command); + if (results.decode_type != decode_type_t::UNKNOWN) { + snprintf(out, sizeof(out), "%X", results.command); int tgType = 0; - switch (ourReceiver->decodedIRData.protocol) + switch (results.decode_type) { - case NEC: + case decode_type_t::NEC: tgType = CMD_EVENT_IR_NEC; break; - case SAMSUNG: + case decode_type_t::SAMSUNG: tgType = CMD_EVENT_IR_SAMSUNG; break; - case SHARP: + case decode_type_t::SHARP: tgType = CMD_EVENT_IR_SHARP; break; - case RC5: + case decode_type_t::RC5: tgType = CMD_EVENT_IR_RC5; break; - case RC6: + case decode_type_t::RC6: tgType = CMD_EVENT_IR_RC6; break; - case SONY: + case decode_type_t::SONY: tgType = CMD_EVENT_IR_SONY; break; default: @@ -916,11 +852,10 @@ extern "C" void DRV_IR_RunFrame() { // we should include repeat here? // e.g. on/off button should not toggle on repeats, but up/down probably should eat them. uint32_t counter_in = ir_counter; - EventHandlers_FireEvent2(tgType, ourReceiver->decodedIRData.address, ourReceiver->decodedIRData.command); + EventHandlers_FireEvent2(tgType, results.address, results.command); uint32_t counter_dur = ((ir_counter - counter_in) * 50) / 1000; ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR fire event took %dms", counter_dur); } -#endif //0 TODO } /* * !!!Important!!! Enable receiving of the next value, @@ -932,9 +867,6 @@ extern "C" void DRV_IR_RunFrame() { } - - - #ifdef TEST_CPP // routines to test C++ class cpptest2 { diff --git a/src/libraries/IRremoteESP8266/src/IRsend.cpp b/src/libraries/IRremoteESP8266/src/IRsend.cpp index 34b25c766..bae2a5046 100644 --- a/src/libraries/IRremoteESP8266/src/IRsend.cpp +++ b/src/libraries/IRremoteESP8266/src/IRsend.cpp @@ -94,7 +94,6 @@ uint32_t IRsend::calcUSecPeriod(uint32_t hz, bool use_offset) { /// microseconds timing. Thus minor changes to the freq & duty values may have /// limited effect. You've been warned. void IRsend::enableIROut(uint32_t freq, uint8_t duty) { - #if 0 // Set the duty cycle to use if we want freq. modulation. if (modulation) { _dutycycle = ::min(duty, kDutyMax); @@ -111,7 +110,6 @@ void IRsend::enableIROut(uint32_t freq, uint8_t duty) { onTimePeriod = (period * _dutycycle) / kDutyMax; // Nr. of uSeconds the LED will be off per pulse. offTimePeriod = period - onTimePeriod; - #endif //0 } #if ALLOW_DELAY_CALLS diff --git a/src/libraries/IRremoteESP8266/src/IRsend.h b/src/libraries/IRremoteESP8266/src/IRsend.h index 74becd66d..67b1cd8a7 100644 --- a/src/libraries/IRremoteESP8266/src/IRsend.h +++ b/src/libraries/IRremoteESP8266/src/IRsend.h @@ -238,7 +238,7 @@ class IRsend { explicit IRsend(uint16_t IRsendPin, bool inverted = false, bool use_modulation = true); void begin(); - void enableIROut(uint32_t freq, uint8_t duty = kDutyDefault); + virtual void enableIROut(uint32_t freq, uint8_t duty = kDutyDefault); virtual void _delayMicroseconds(uint32_t usec); virtual uint16_t mark(uint16_t usec); From c65a606b0ceac4216637971d302cc02708287887 Mon Sep 17 00:00:00 2001 From: "Vladimir S. FONOV" Date: Sat, 11 Mar 2023 13:48:59 -0500 Subject: [PATCH 07/16] Added Denon --- src/driver/drv_ir.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index be6e19090..d0feb3f31 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -452,6 +452,9 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con case decode_type_t::LG: pIRsend->sendLG((uint64_t)pIRsend->encodeLG(addr,command)); break; + case decode_type_t::DENON: + pIRsend->sendDenon((uint64_t)pIRsend->encodeDenon(addr,command)); + break; default: ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IR send %s protocol not supported", args); return CMD_RES_ERROR; From 9791bc8c478caae60a931f995ce4525d6eb39b0e Mon Sep 17 00:00:00 2001 From: "Vladimir S. FONOV" Date: Sat, 11 Mar 2023 22:16:16 -0500 Subject: [PATCH 08/16] Debugged sending and reciving basic IR commands, added a new format for IRsend --- src/driver/drv_ir.cpp | 104 +++++++++++++------ src/libraries/IRremoteESP8266/src/IRrecv.cpp | 26 ++--- src/libraries/IRremoteESP8266/src/IRrecv.h | 2 +- 3 files changed, 90 insertions(+), 42 deletions(-) diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index d0feb3f31..ea71e8cc5 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -388,7 +388,7 @@ extern "C" void DRV_IR_ISR(UINT8 t) { extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; - char args[64]; + char args[128]; strncpy(args, args_in, sizeof(args) - 1); args[sizeof(args) - 1] = 0; @@ -399,8 +399,55 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con } if ((*p != '-') && (*p != ' ')) { - ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1].", args); - return CMD_RES_BAD_ARGUMENT; + // try to decode "new" format, separated by comma + // the format is bits,0xDATA[,repeat] + char *p = args; + while (*p && (*p != ',')) { + p++; + } + if(*p==',') + { + *p='\0'; + decode_type_t protocol = strToDecodeType(args); + p++; + char *_bits=p; + while (*p && (*p != ',')) { + p++; + } + if(*p==',') + { + *p='\0'; + uint16_t bits = (uint16_t)strtol(_bits,NULL,10); + p++; + if(bits<=64) + { + char *_data=p; + uint64_t data = strtoll(_data,&p,16); + if(protocol!=decode_type_t::UNKNOWN) + { + int repeats=1; + if(*p==',') + repeats=strtol(p+1,NULL,10); + + if( pIRsend->send(protocol,data,bits,repeats) ) + { + pIRsend->delay(100); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR send %s: protocol %d bits %d data 0x%llX repeats %d", args, (int)protocol, (int)bits, (long long int)data, (int)repeats); + return CMD_RES_OK; + } else { + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IR can't send %s: protocol %d bits data 0x%llX repeats %d", args, (int)protocol, (long long int)data, (int)repeats); + return CMD_RES_BAD_ARGUMENT; + } + } + } else { + // TODO: implement longer protocols + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend currently only protocol with up to 64bits are supported", args); + return CMD_RES_BAD_ARGUMENT; + } + } + } + ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IRSend cmnd not valid [%s] not like [NEC-0-1A] or [NEC 0 1A 1] or [NEC,bits,0xDATA,[repeat]]", args); + return CMD_RES_BAD_ARGUMENT; } *p='\0'; @@ -452,9 +499,6 @@ extern "C" commandResult_t IR_Send_Cmd(const void *context, const char *cmd, con case decode_type_t::LG: pIRsend->sendLG((uint64_t)pIRsend->encodeLG(addr,command)); break; - case decode_type_t::DENON: - pIRsend->sendDenon((uint64_t)pIRsend->encodeDenon(addr,command)); - break; default: ADDLOG_ERROR(LOG_FEATURE_IR, (char *)"IR send %s protocol not supported", args); return CMD_RES_ERROR; @@ -658,6 +702,7 @@ extern "C" void DRV_IR_Init() { // setup IRrecv pin as input //bk_gpio_config_input_pup((GPIO_INDEX)pin); // enabled by enableIRIn + //TODO: we should specify buffer size (now set to 1024), timeout (now 90ms) and tolerance ourReceiver = new IRrecv(pin); ourReceiver->enableIRIn(true);// try with pullup } @@ -754,11 +799,11 @@ extern "C" void DRV_IR_RunFrame() { decode_results results; if (ourReceiver->decode(&results)) { // TODO: find a better way? - const char *proto_name = typeToString(results.decode_type, results.repeat).c_str(); + String proto_name = typeToString(results.decode_type, results.repeat).c_str(); #if 0 // TODO: implement different masking if (!(gIRProtocolEnable & (1 << (int)results.decode_type))) { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", proto_name, (int)results.decode_type, gIRProtocolEnable); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR decode ignore masked protocol %s (%d) - mask 0x%08X", proto_name.c_str(), (int)results.decode_type, gIRProtocolEnable); } #endif @@ -766,34 +811,36 @@ extern "C" void DRV_IR_RunFrame() { // 'UNKNOWN' protocol is by default disabled in flags // This is because I am getting a lot of 'UNKNOWN' spam with no IR signals in room if (((results.decode_type != decode_type_t::UNKNOWN) || - (results.decode_type == decode_type_t::UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) && + (results.decode_type == decode_type_t::UNKNOWN && CFG_HasFlag(OBK_FLAG_IR_ALLOW_UNKNOWN))) //&& // only process if this protocol is enabled. all by default. - (gIRProtocolEnable & (1 << (int)results.decode_type)) + //(gIRProtocolEnable & (1 << (int)results.decode_type) ) { - String lastIrReceived = String((int)results.decode_type,16) + "," + resultToHexidecimal(&results); + String lastIrReceived = String((int)results.decode_type, 16) + "," + resultToHexidecimal(&results); + + if (!hasACState(results.decode_type)) + lastIrReceived += "," + String((int)results.bits); + else + ADDLOG_INFO(LOG_FEATURE_IR, "Recieved AC code:%s",proto_name.c_str()); char out[128]; - int repeat = 0; - if (results.repeat) { //? - repeat = 2; - } - else { - repeat = 1; - } + int repeat = results.repeat?0:1; // not sure how to deal with this if (results.decode_type == decode_type_t::UNKNOWN) { //snprintf(out, sizeof(out), "IR_RAW 0x%lX %d", (unsigned long)results.decodedRawData, repeat); - // TODO: print RAW hexedecimal value + snprintf(out, sizeof(out), "IR %s %s", "Unknown", lastIrReceived.c_str()); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)out); } - else { - snprintf(out, sizeof(out), "IR %s %X %X %d", proto_name, results.address, results.command, repeat); + else if (!hasACState(results.decode_type)) { + snprintf(out, sizeof(out), "IR %s %lX %lX %d", proto_name.c_str(), (long int)results.address, (long int)results.command, repeat); ADDLOG_INFO(LOG_FEATURE_IR, (char *)out); + // show new format too + snprintf(out, sizeof(out), "IR %s,%d,%s", proto_name.c_str(), (int)results.bits, resultToHexidecimal(&results).c_str()); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)out); + } else { + String description = IRAcUtils::resultAcToString(&results); + ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IRAC %s", description.c_str()); } - - if (!hasACState(results.decode_type)) - lastIrReceived += "," + String((int)results.bits); - // if user wants us to publish every received IR data, do it now if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED)) { @@ -812,16 +859,13 @@ extern "C" void DRV_IR_RunFrame() { ADDLOG_INFO(LOG_FEATURE_IR, (char *)out); } } - else { - ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IR %s", lastIrReceived.c_str()); - } if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED_IN_JSON)) { // {"IrReceived":{"Protocol":"RC_5","Bits":0x1,"Data":"0xC"}} // String _data=resultToHexidecimal(&results); snprintf(out, sizeof(out), "{\"IrReceived\":{\"Protocol\":\"%s\",\"Bits\":%i,\"Data\":\"%s\"}}", - proto_name, (int)results.bits, _data.c_str()); + proto_name.c_str(), (int)results.bits, _data.c_str()); MQTT_PublishMain_StringString("RESULT", out, OBK_PUBLISH_FLAG_FORCE_REMOVE_GET); } @@ -859,6 +903,8 @@ extern "C" void DRV_IR_RunFrame() { uint32_t counter_dur = ((ir_counter - counter_in) * 50) / 1000; ADDLOG_DEBUG(LOG_FEATURE_IR, (char *)"IR fire event took %dms", counter_dur); } + } else { + ADDLOG_INFO(LOG_FEATURE_IR, "Recieved Unknown IR "); } /* * !!!Important!!! Enable receiving of the next value, diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.cpp b/src/libraries/IRremoteESP8266/src/IRrecv.cpp index 1c360a8da..bf3bb292b 100644 --- a/src/libraries/IRremoteESP8266/src/IRrecv.cpp +++ b/src/libraries/IRremoteESP8266/src/IRrecv.cpp @@ -2118,23 +2118,25 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr, #define INPUT_MARK 0 ///< Sensor output for a mark ("flash") #endif - -uint32_t now = 0; -uint_fast8_t old = 0; -static uint32_t start = 0; +// current time in us +static uint32_t ir_now = 0; +// last value of the IR detector +static uint_fast8_t ir_old = 0; +// start time of the condition +static uint32_t ir_start = 0; void IR_ISR() { // TODO: is there a lock or a mutex to prevent race condition ??? - now += 50; // timer is supposed to be running at 50us ? + ir_now += 50; // timer is supposed to be running at 50us ? if (params.rcvstate == kStopState) return; uint_fast8_t tIRInputLevel = (uint_fast8_t)digitalReadFast(params.recvpin); - uint16_t time_since_last_change; + uint32_t time_since_last_change; // check if timeout is reached and stop recieving - if (now < start) - time_since_last_change = (UINT32_MAX - start + now); + if (ir_now < ir_start) + time_since_last_change = (UINT32_MAX - ir_start + ir_now); else - time_since_last_change = (now - start); + time_since_last_change = (ir_now - ir_start); // Timeout is in mS if((time_since_last_change/1000) >= params.timeout && params.rawlen) // timed out @@ -2143,10 +2145,10 @@ void IR_ISR() { return; } - if (tIRInputLevel == old) + if (tIRInputLevel == ir_old) return; - old = tIRInputLevel; + ir_old = tIRInputLevel; // Grab a local copy of rawlen to reduce instructions used in IRAM. // This is an ugly premature optimisation code-wise, but we do everything we // can to save IRAM. @@ -2171,7 +2173,7 @@ void IR_ISR() { } params.rawlen++; - start = now; + ir_start = ir_now; } //#define _IR_MEASURE_TIMING //#define _IR_TIMING_TEST_PIN 7 // do not forget to execute: "pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);" if activated by line above diff --git a/src/libraries/IRremoteESP8266/src/IRrecv.h b/src/libraries/IRremoteESP8266/src/IRrecv.h index 36d12181f..4f476cd4b 100644 --- a/src/libraries/IRremoteESP8266/src/IRrecv.h +++ b/src/libraries/IRremoteESP8266/src/IRrecv.h @@ -44,7 +44,7 @@ const uint16_t kRawTick = 2; // Capture tick to uSec factor. // before we need to start capturing a possible new message. // Typically 15ms suits most applications. However, some protocols demand a // higher value. e.g. 90ms for XMP-1 and some aircon units. -const uint8_t kTimeoutMs = 15; // In MilliSeconds. +const uint8_t kTimeoutMs = 90; // In MilliSeconds. #define TIMEOUT_MS kTimeoutMs // For legacy documentation. const uint16_t kMaxTimeoutMs = kRawTick * (UINT16_MAX / MS_TO_USEC(1)); From 9b0cc66a7ba18de151b3db532041f4004a96b796 Mon Sep 17 00:00:00 2001 From: "Vladimir S. FONOV" Date: Mon, 24 Apr 2023 00:17:53 -0400 Subject: [PATCH 09/16] Temporarely disabled linking with IRAc to save flash space --- src/driver/drv_ir.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir.cpp index ea71e8cc5..abf559db6 100644 --- a/src/driver/drv_ir.cpp +++ b/src/driver/drv_ir.cpp @@ -187,7 +187,9 @@ SpoofIrReceiver IrReceiver; #include "../libraries/IRremoteESP8266/src/IRsend.h" #include "../libraries/IRremoteESP8266/src/IRrecv.h" #include "../libraries/IRremoteESP8266/src/IRutils.h" +#ifdef ENABLE_IRAC #include "../libraries/IRremoteESP8266/src/IRac.h" +#endif #include "../libraries/IRremoteESP8266/src/IRproto.h" #include "../libraries/IRremoteESP8266/src/digitalWriteFast.h" @@ -649,7 +651,7 @@ extern "C" commandResult_t IR_Param(const void *context, const char *cmd, const - +#ifdef ENABLE_IRAC extern "C" commandResult_t IR_AC_Cmd(const void *context, const char *cmd, const char *args_in, int cmdFlags) { if (!args_in) return CMD_RES_NOT_ENOUGH_ARGUMENTS; @@ -673,6 +675,8 @@ extern "C" commandResult_t IR_AC_Cmd(const void *context, const char *cmd, const return CMD_RES_OK; } +#endif //ENABLE_IRAC + // test routine to start IR RX and TX // currently fixed pins for testing. @@ -744,7 +748,9 @@ extern "C" void DRV_IR_Init() { //cmddetail:"descr":"Sends IR commands for HVAC control (TODO)", //cmddetail:"fn":"IR_AC_Cmd","file":"driver/drv_ir.cpp","requires":"", //cmddetail:"examples":""} + #ifdef ENABLE_IRAC CMD_RegisterCommand("IRAC", IR_AC_Cmd, NULL); + #endif //ENABLE_IRAC //cmddetail:{"name":"IREnable","args":"[Str][1or0]", //cmddetail:"descr":"Enable/disable aspects of IR. IREnable RXTX 0/1 - enable Rx whilst Tx. IREnable [protocolname] 0/1 - enable/disable a specified protocol", //cmddetail:"fn":"IR_Enable","file":"driver/drv_ir.cpp","requires":"", @@ -769,10 +775,13 @@ void dump(decode_results *results) { // Dumps out the decode_results structure. // Call this after IRrecv::decode() ADDLOG_INFO(LOG_FEATURE_IR, resultToHumanReadableBasic(results).c_str()); + + #ifdef ENABLE_IRAC if (hasACState(results->decode_type)) { ADDLOG_INFO(LOG_FEATURE_IR, IRAcUtils::resultAcToString(results).c_str()); } + #endif } @@ -838,8 +847,10 @@ extern "C" void DRV_IR_RunFrame() { snprintf(out, sizeof(out), "IR %s,%d,%s", proto_name.c_str(), (int)results.bits, resultToHexidecimal(&results).c_str()); ADDLOG_INFO(LOG_FEATURE_IR, (char *)out); } else { + #ifdef ENABLE_IRAC String description = IRAcUtils::resultAcToString(&results); ADDLOG_INFO(LOG_FEATURE_IR, (char *)"IRAC %s", description.c_str()); + #endif //ENABLE_IRAC } // if user wants us to publish every received IR data, do it now if (CFG_HasFlag(OBK_FLAG_IR_PUBLISH_RECEIVED)) { From c849ce9839f433fd117d628a768661802e735caa Mon Sep 17 00:00:00 2001 From: openshwprojects <85486843+openshwprojects@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:24:09 +0200 Subject: [PATCH 10/16] Update README.md --- src/libraries/IRremoteESP8266/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/IRremoteESP8266/README.md b/src/libraries/IRremoteESP8266/README.md index b15ef1abe..6851d3ecd 100644 --- a/src/libraries/IRremoteESP8266/README.md +++ b/src/libraries/IRremoteESP8266/README.md @@ -1,5 +1,6 @@ ![IRremoteESP8266 Library](./assets/images/banner.svg) + [![Build Status](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Build.yml/badge.svg)](../../actions/workflows/Build.yml) [![Code Lint](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/Lint.yml/badge.svg)](../../actions/workflows/Lint.yml) [![Tests](https://github.com/crankyoldgit/IRremoteESP8266/actions/workflows/UnitTests.yml/badge.svg)](../../actions/workflows/UnitTests.yml) From 0a36160af683bc089dd40a683364d464ad52d186 Mon Sep 17 00:00:00 2001 From: Tester23 Date: Sun, 31 Dec 2023 15:43:43 +0100 Subject: [PATCH 11/16] rename drv_ir.cpp to drv_ir_new.cpp --- src/driver/{drv_ir.cpp => drv_ir_new.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/driver/{drv_ir.cpp => drv_ir_new.cpp} (100%) diff --git a/src/driver/drv_ir.cpp b/src/driver/drv_ir_new.cpp similarity index 100% rename from src/driver/drv_ir.cpp rename to src/driver/drv_ir_new.cpp From b7809f9bb9e20af7f19a454482c41a8f792a58a4 Mon Sep 17 00:00:00 2001 From: Tester23 Date: Sun, 31 Dec 2023 15:47:37 +0100 Subject: [PATCH 12/16] comment/trigger build --- src/driver/drv_ir_new.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/driver/drv_ir_new.cpp b/src/driver/drv_ir_new.cpp index abf559db6..faaaea394 100644 --- a/src/driver/drv_ir_new.cpp +++ b/src/driver/drv_ir_new.cpp @@ -1,4 +1,5 @@ #if PLATFORM_BEKEN +// drv_ir_new.cpp - IRremoteESP8266 extern "C" { // these cause error: conflicting declaration of 'int bk_wlan_mcu_suppress_and_sleep(unsigned int)' with 'C' linkage From 1c87c3c80e4f01ec05331da59d3f361b28e24fe5 Mon Sep 17 00:00:00 2001 From: Tester23 Date: Sun, 31 Dec 2023 16:57:46 +0100 Subject: [PATCH 13/16] update sdks --- sdk/OpenBK7231N | 2 +- sdk/OpenBK7231T | 2 +- sdk/OpenBL602 | 2 +- sdk/OpenW600 | 2 +- sdk/OpenW800 | 2 +- sdk/OpenXR809 | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/OpenBK7231N b/sdk/OpenBK7231N index 7a65b01f6..b340a75ae 160000 --- a/sdk/OpenBK7231N +++ b/sdk/OpenBK7231N @@ -1 +1 @@ -Subproject commit 7a65b01f637203c1c6c1f7a7ec630aa16ea5df4b +Subproject commit b340a75ae2ff7d27a23ef6858b04096ec3f75046 diff --git a/sdk/OpenBK7231T b/sdk/OpenBK7231T index 12c68122b..7a68cca84 160000 --- a/sdk/OpenBK7231T +++ b/sdk/OpenBK7231T @@ -1 +1 @@ -Subproject commit 12c68122b61da686df8b77a6577857ac05b55cbf +Subproject commit 7a68cca84e473d98d5af600a5e2e39348ee8d148 diff --git a/sdk/OpenBL602 b/sdk/OpenBL602 index e5769160c..50ddebdf0 160000 --- a/sdk/OpenBL602 +++ b/sdk/OpenBL602 @@ -1 +1 @@ -Subproject commit e5769160cfc91a5fe36f040b3d5314e51eda3a28 +Subproject commit 50ddebdf0088aa59b1fd531948f0a7c7f9f01d7f diff --git a/sdk/OpenW600 b/sdk/OpenW600 index ad8f51d06..e052a7b17 160000 --- a/sdk/OpenW600 +++ b/sdk/OpenW600 @@ -1 +1 @@ -Subproject commit ad8f51d064a78465ec1845287cf19c3a24c655b8 +Subproject commit e052a7b17a32a79a87fd803384c714b684bf4d6b diff --git a/sdk/OpenW800 b/sdk/OpenW800 index bc2c41e02..b7a83300c 160000 --- a/sdk/OpenW800 +++ b/sdk/OpenW800 @@ -1 +1 @@ -Subproject commit bc2c41e0271f1018bdb66cebddf592de7b7a571d +Subproject commit b7a83300c4854db1246246710abdbf07ddeb961d diff --git a/sdk/OpenXR809 b/sdk/OpenXR809 index 3e8cd2f98..9e59b6b03 160000 --- a/sdk/OpenXR809 +++ b/sdk/OpenXR809 @@ -1 +1 @@ -Subproject commit 3e8cd2f98075aa89bb1fbafd7871a8bb61360612 +Subproject commit 9e59b6b03eca3294bb6e67e33204ed3863aa348d From 401598dc1d69185d5d0e52e6b42c254c655fe059 Mon Sep 17 00:00:00 2001 From: Tester23 Date: Sun, 31 Dec 2023 19:11:34 +0100 Subject: [PATCH 14/16] test --- src/driver/drv_ir_new.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/driver/drv_ir_new.cpp b/src/driver/drv_ir_new.cpp index faaaea394..73a6ecb02 100644 --- a/src/driver/drv_ir_new.cpp +++ b/src/driver/drv_ir_new.cpp @@ -736,6 +736,8 @@ extern "C" void DRV_IR_Init() { pIRsendTemp->resetsendqueue(); pIRsendTemp->enableIROut(pwmfrequency,50); pIRsendTemp->pwmIndex = pwmIndex; + pIRsendTemp->pwmduty = duty; + pIRsendTemp->pwmperiod = period; pIRsend = pIRsendTemp; //bk_pwm_stop((bk_pwm_t)pIRsend->pwmIndex); From 7f1098c12096ecbb86f931f5a457e74ed63924ab Mon Sep 17 00:00:00 2001 From: Tester23 Date: Sun, 31 Dec 2023 20:09:02 +0100 Subject: [PATCH 15/16] test --- src/driver/drv_ir_new.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/driver/drv_ir_new.cpp b/src/driver/drv_ir_new.cpp index 73a6ecb02..7187134c2 100644 --- a/src/driver/drv_ir_new.cpp +++ b/src/driver/drv_ir_new.cpp @@ -265,6 +265,13 @@ class myIRsend : public IRsend { //pwmfrequency = ((uint32_t)aFrequencyKHz) * 1000; pwmperiod = (26000000 / freq); pwmduty = pwmperiod / (100/duty); + + +#if PLATFORM_BK7231N + bk_pwm_update_param((bk_pwm_t)this->pwmIndex, this->pwmperiod, pwmduty, 0, 0); +#else + bk_pwm_update_param((bk_pwm_t)this->pwmIndex, this->pwmperiod, pwmduty); +#endif } void resetsendqueue() { From 85e5c424093d715c006afcd27f75116140e4fcc8 Mon Sep 17 00:00:00 2001 From: Tester23 Date: Thu, 1 Feb 2024 11:53:28 +0100 Subject: [PATCH 16/16] per user request - larger buffer --- src/driver/drv_ir_new.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/driver/drv_ir_new.cpp b/src/driver/drv_ir_new.cpp index 7187134c2..1b90181a0 100644 --- a/src/driver/drv_ir_new.cpp +++ b/src/driver/drv_ir_new.cpp @@ -534,9 +534,9 @@ extern "C" commandResult_t IR_Enable(const void *context, const char *cmd, const return CMD_RES_NOT_ENOUGH_ARGUMENTS; } - char args[20]; - strncpy(args, args_in, 19); - args[19] = 0; + char args[128]; + strncpy(args, args_in, sizeof(args)-1); + args[sizeof(args)-1] = 0; char *p = args; int enable = 1; if (!my_strnicmp(p, "RXTX", 4)) {