/
ir_Fujitsu.cpp
797 lines (737 loc) · 26.6 KB
/
ir_Fujitsu.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
// Copyright 2017 Jonny Graham
// Copyright 2017-2019 David Conran
#include "ir_Fujitsu.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"
// Fujitsu A/C support added by Jonny Graham & David Conran
// Equipment it seems compatible with:
// * Fujitsu ASYG30LFCA with remote AR-RAH2E
// * Fujitsu AST9RSGCW with remote AR-DB1
// * Fujitsu ASYG7LMCA with remote AR-REB1E
// * Fujitsu AR-RAE1E remote.
// * Fujitsu General with remote AR-JW2
// * <Add models (A/C & remotes) you've gotten it working with here>
// Ref:
// These values are based on averages of measurements
const uint16_t kFujitsuAcHdrMark = 3324;
const uint16_t kFujitsuAcHdrSpace = 1574;
const uint16_t kFujitsuAcBitMark = 448;
const uint16_t kFujitsuAcOneSpace = 1182;
const uint16_t kFujitsuAcZeroSpace = 390;
const uint16_t kFujitsuAcMinGap = 8100;
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addModelToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::setBit;
using irutils::setBits;
#if SEND_FUJITSU_AC
// Send a Fujitsu A/C message.
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. Typically one of:
// kFujitsuAcStateLength
// kFujitsuAcStateLength - 1
// kFujitsuAcStateLengthShort
// kFujitsuAcStateLengthShort - 1
// repeat: Nr. of times the message is to be repeated.
// (Default = kFujitsuAcMinRepeat).
//
// Status: STABLE / Known Good.
//
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.
// Initialise the object.
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) {
this->setModel(model);
this->stateReset();
}
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;
}
}
fujitsu_ac_remote_model_t IRFujitsuAC::getModel(void) { return _model; }
// Reset the state of the remote to a known good state/sequence.
void IRFujitsuAC::stateReset(void) {
_temp = 24;
_fanSpeed = kFujitsuAcFanHigh;
_mode = kFujitsuAcModeCool;
_swingMode = kFujitsuAcSwingBoth;
_cmd = kFujitsuAcCmdTurnOn;
_filter = false;
_clean = false;
this->buildState();
}
// Configure the pin for output.
void IRFujitsuAC::begin(void) { _irsend.begin(); }
#if SEND_FUJITSU_AC
// Send the current desired state to the IR LED.
void IRFujitsuAC::send(const uint16_t repeat) {
this->buildState();
_irsend.sendFujitsuAC(remote_state, getStateLength(), repeat);
}
#endif // SEND_FUJITSU_AC
void IRFujitsuAC::buildState(void) {
remote_state[0] = 0x14;
remote_state[1] = 0x63;
remote_state[2] = 0x00;
remote_state[3] = 0x10;
remote_state[4] = 0x10;
bool fullCmd = false;
switch (_cmd) {
case kFujitsuAcCmdTurnOff: // 0x02
case kFujitsuAcCmdEcono: // 0x09
case kFujitsuAcCmdPowerful: // 0x39
case kFujitsuAcCmdStepVert: // 0x6C
case kFujitsuAcCmdToggleSwingVert: // 0x6D
case kFujitsuAcCmdStepHoriz: // 0x79
case kFujitsuAcCmdToggleSwingHoriz: // 0x7A
remote_state[5] = _cmd;
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:
remote_state[5] = 0xFE;
break;
case fujitsu_ac_remote_model_t::ARDB1:
case fujitsu_ac_remote_model_t::ARJW2:
remote_state[5] = 0xFC;
break;
}
fullCmd = true;
break;
}
if (fullCmd) { // long codes
uint8_t tempByte = _temp - kFujitsuAcMinTemp;
// Nr. of bytes in the message after this byte.
remote_state[6] = _state_length - 7;
remote_state[7] = 0x30;
remote_state[8] = (_cmd == kFujitsuAcCmdTurnOn) | (tempByte << 4);
remote_state[9] = _mode | 0 << 4; // timer off
remote_state[10] = _fanSpeed;
remote_state[11] = 0; // timerOff values
remote_state[12] = 0; // timerOff/On values
remote_state[13] = 0; // timerOn values
switch (_model) {
case fujitsu_ac_remote_model_t::ARRY4:
remote_state[14] = _filter << 3;
remote_state[9] |= (_clean << 3);
break;
default:
remote_state[14] = 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:
checksum = sumBytes(remote_state, _state_length - 1);
checksum_complement = 0x9B;
break;
case fujitsu_ac_remote_model_t::ARREB1E:
setBit(&remote_state[14], kFujitsuAcOutsideQuietOffset, _outsideQuiet);
// FALL THRU
case fujitsu_ac_remote_model_t::ARRAH2E:
case fujitsu_ac_remote_model_t::ARRY4:
setBit(&remote_state[14], 5); // |= 0b00100000
setBits(&remote_state[10], kHighNibble, kFujitsuAcSwingSize,
_swingMode);
// FALL THRU
default:
checksum = sumBytes(remote_state + _state_length_short,
_state_length - _state_length_short - 1);
}
// and negate the checksum and store it in the last byte.
remote_state[_state_length - 1] = checksum_complement - checksum;
} else { // short codes
switch (_model) {
case fujitsu_ac_remote_model_t::ARRY4:
case fujitsu_ac_remote_model_t::ARRAH2E:
case fujitsu_ac_remote_model_t::ARREB1E:
// The last byte is the inverse of penultimate byte
remote_state[_state_length_short - 1] =
~remote_state[_state_length_short - 2];
break;
default:
{}; // We don't need to do anything for the others.
}
// Zero the rest of the state.
for (uint8_t i = _state_length_short; i < kFujitsuAcStateLength; i++)
remote_state[i] = 0;
}
}
uint8_t IRFujitsuAC::getStateLength(void) {
this->buildState(); // Force an update of the internal state.
if (((_model == fujitsu_ac_remote_model_t::ARRAH2E ||
_model == fujitsu_ac_remote_model_t::ARREB1E ||
_model == fujitsu_ac_remote_model_t::ARRY4) &&
remote_state[5] != 0xFE) ||
((_model == fujitsu_ac_remote_model_t::ARDB1 ||
_model == fujitsu_ac_remote_model_t::ARJW2) && remote_state[5] != 0xFC))
return _state_length_short;
else
return _state_length;
}
// Return a pointer to the internal state date of the remote.
uint8_t* IRFujitsuAC::getRaw(void) {
this->buildState();
return remote_state;
}
void IRFujitsuAC::buildFromState(const uint16_t length) {
switch (length) {
case kFujitsuAcStateLength - 1:
case kFujitsuAcStateLengthShort - 1:
this->setModel(fujitsu_ac_remote_model_t::ARDB1);
// ARJW2 has horizontal swing.
if (this->getSwing(true) > kFujitsuAcSwingVert)
this->setModel(fujitsu_ac_remote_model_t::ARJW2);
break;
default:
switch (this->getCmd(true)) {
case kFujitsuAcCmdEcono:
case kFujitsuAcCmdPowerful:
this->setModel(fujitsu_ac_remote_model_t::ARREB1E);
break;
default:
this->setModel(fujitsu_ac_remote_model_t::ARRAH2E);
}
}
switch (remote_state[6]) {
case 8:
if (this->getModel() != fujitsu_ac_remote_model_t::ARJW2)
this->setModel(fujitsu_ac_remote_model_t::ARDB1);
break;
case 9:
if (this->getModel() != fujitsu_ac_remote_model_t::ARREB1E)
this->setModel(fujitsu_ac_remote_model_t::ARRAH2E);
break;
}
setTemp((remote_state[8] >> 4) + kFujitsuAcMinTemp);
if (GETBIT8(remote_state[8], 0))
setCmd(kFujitsuAcCmdTurnOn);
else
setCmd(kFujitsuAcCmdStayOn);
setMode(GETBITS8(remote_state[9], kLowNibble, kModeBitsSize));
setFanSpeed(GETBITS8(remote_state[10], kLowNibble, kFujitsuAcFanSize));
setSwing(GETBITS8(remote_state[10], kHighNibble, kFujitsuAcSwingSize));
setClean(getClean(true));
setFilter(getFilter(true));
// Currently the only way we know how to tell ARRAH2E & ARRY4 apart is if
// either the raw Filter or Clean setting is on.
if (getModel() == fujitsu_ac_remote_model_t::ARRAH2E && (getFilter(true) ||
getClean(true)))
setModel(fujitsu_ac_remote_model_t::ARRY4);
switch (remote_state[5]) {
case kFujitsuAcCmdTurnOff:
case kFujitsuAcCmdStepHoriz:
case kFujitsuAcCmdToggleSwingHoriz:
case kFujitsuAcCmdStepVert:
case kFujitsuAcCmdToggleSwingVert:
case kFujitsuAcCmdEcono:
case kFujitsuAcCmdPowerful:
setCmd(remote_state[5]);
break;
}
_outsideQuiet = this->getOutsideQuiet(true);
}
bool IRFujitsuAC::setRaw(const uint8_t newState[], const uint16_t length) {
if (length > kFujitsuAcStateLength) return false;
for (uint16_t i = 0; i < kFujitsuAcStateLength; i++) {
if (i < length)
remote_state[i] = newState[i];
else
remote_state[i] = 0;
}
buildFromState(length);
return true;
}
void IRFujitsuAC::stepHoriz(void) { this->setCmd(kFujitsuAcCmdStepHoriz); }
void IRFujitsuAC::toggleSwingHoriz(const bool update) {
// Toggle the current setting.
if (update) this->setSwing(this->getSwing() ^ kFujitsuAcSwingHoriz);
// and set the appropriate special command.
this->setCmd(kFujitsuAcCmdToggleSwingHoriz);
}
void IRFujitsuAC::stepVert(void) { this->setCmd(kFujitsuAcCmdStepVert); }
void IRFujitsuAC::toggleSwingVert(const bool update) {
// Toggle the current setting.
if (update) this->setSwing(this->getSwing() ^ kFujitsuAcSwingVert);
// and set the appropriate special command.
this->setCmd(kFujitsuAcCmdToggleSwingVert);
}
// Set the requested command of the A/C.
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:
_cmd = cmd;
break;
default:
_cmd = kFujitsuAcCmdStayOn;
}
break;
default:
_cmd = kFujitsuAcCmdStayOn;
}
}
// Get the special command part of the message.
// Args:
// raw: Do we need to get it from first principles from the raw data?
// Returns:
// A uint8_t containing the contents of the special command byte.
uint8_t IRFujitsuAC::getCmd(const bool raw) {
if (raw) return remote_state[5];
return _cmd;
}
// Set the requested power state of the A/C.
void IRFujitsuAC::setPower(const bool on) {
this->setCmd(on ? kFujitsuAcCmdTurnOn : kFujitsuAcCmdTurnOff);
}
// Set the requested power state of the A/C to off.
void IRFujitsuAC::off(void) { this->setPower(false); }
// Set the requested power state of the A/C to on.
void IRFujitsuAC::on(void) { this->setPower(true); }
bool IRFujitsuAC::getPower(void) { return _cmd != kFujitsuAcCmdTurnOff; }
void IRFujitsuAC::setOutsideQuiet(const bool on) {
_outsideQuiet = on;
this->setCmd(kFujitsuAcCmdStayOn); // No special command involved.
}
// Get the status of the Outside Quiet setting.
// Args:
// raw: Do we get the result from base data?
// Returns:
// A boolean for if it is set or not.
bool IRFujitsuAC::getOutsideQuiet(const bool raw) {
if (_state_length == kFujitsuAcStateLength && raw) {
_outsideQuiet = GETBIT8(remote_state[14], kFujitsuAcOutsideQuietOffset);
// Only ARREB1E seems to have this mode.
if (_outsideQuiet) this->setModel(fujitsu_ac_remote_model_t::ARREB1E);
}
return _outsideQuiet;
}
// Set the temp. in deg C
void IRFujitsuAC::setTemp(const uint8_t temp) {
_temp = std::max((uint8_t)kFujitsuAcMinTemp, temp);
_temp = std::min((uint8_t)kFujitsuAcMaxTemp, _temp);
this->setCmd(kFujitsuAcCmdStayOn); // No special command involved.
}
uint8_t IRFujitsuAC::getTemp(void) { return _temp; }
// Set the speed of the fan
void IRFujitsuAC::setFanSpeed(const uint8_t fanSpeed) {
if (fanSpeed > kFujitsuAcFanQuiet)
_fanSpeed = kFujitsuAcFanHigh; // Set the fan to maximum if out of range.
else
_fanSpeed = fanSpeed;
this->setCmd(kFujitsuAcCmdStayOn); // No special command involved.
}
uint8_t IRFujitsuAC::getFanSpeed(void) { return _fanSpeed; }
// Set the requested climate operation mode of the a/c unit.
void IRFujitsuAC::setMode(const uint8_t mode) {
if (mode > kFujitsuAcModeHeat)
_mode = kFujitsuAcModeHeat; // Set the mode to maximum if out of range.
else
_mode = mode;
this->setCmd(kFujitsuAcCmdStayOn); // No special command involved.
}
uint8_t IRFujitsuAC::getMode(void) { return _mode; }
// Set the requested swing operation mode of the a/c unit.
void IRFujitsuAC::setSwing(const uint8_t swingMode) {
_swingMode = swingMode;
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) _swingMode = 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) _swingMode = kFujitsuAcSwingBoth;
}
this->setCmd(kFujitsuAcCmdStayOn); // No special command involved.
}
// Get what the swing part of the message should be.
// Args:
// raw: Do we need to get it from first principles from the raw data?
// Returns:
// A uint8_t containing the contents of the swing state.
uint8_t IRFujitsuAC::getSwing(const bool raw) {
if (raw) _swingMode = GETBITS8(remote_state[10], kHighNibble,
kFujitsuAcSwingSize);
return _swingMode;
}
void IRFujitsuAC::setClean(const bool on) {
_clean = on;
this->setCmd(kFujitsuAcCmdStayOn); // No special command involved.
}
bool IRFujitsuAC::getClean(const bool raw) {
if (raw) {
return GETBIT8(remote_state[9], kFujitsuAcCleanOffset);
} else {
switch (getModel()) {
case fujitsu_ac_remote_model_t::ARRY4: return _clean;
default: return false;
}
}
}
void IRFujitsuAC::setFilter(const bool on) {
_filter = on;
this->setCmd(kFujitsuAcCmdStayOn); // No special command involved.
}
bool IRFujitsuAC::getFilter(const bool raw) {
if (raw) {
return GETBIT8(remote_state[14], kFujitsuAcFilterOffset);
} else {
switch (getModel()) {
case fujitsu_ac_remote_model_t::ARRY4: return _filter;
default: return 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?
}
// Convert a standard A/C mode into its native mode.
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 standard A/C Fan speed into its native fan speed.
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 to it's common equivalent.
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 to it's common equivalent.
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 A/C state to it's common equivalent.
stdAc::state_t IRFujitsuAC::toCommon(void) {
stdAc::state_t result;
result.protocol = decode_type_t::FUJITSU_AC;
result.model = this->getModel();
result.power = this->getPower();
result.mode = this->toCommonMode(this->getMode());
result.celsius = true;
result.degrees = this->getTemp();
result.fanspeed = this->toCommonFanSpeed(this->getFanSpeed());
uint8_t swing = this->getSwing();
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 = (this->getFanSpeed() == kFujitsuAcFanQuiet);
result.turbo = this->getCmd() == kFujitsuAcCmdPowerful;
result.econo = this->getCmd() == 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 internal state into a human readable string.
String IRFujitsuAC::toString(void) {
String result = "";
result.reserve(100); // Reserve some heap for the string to reduce fragging.
fujitsu_ac_remote_model_t model = this->getModel();
result += addModelToString(decode_type_t::FUJITSU_AC, model, false);
result += addBoolToString(getPower(), kPowerStr);
result += addModeToString(getMode(), kFujitsuAcModeAuto, kFujitsuAcModeCool,
kFujitsuAcModeHeat, kFujitsuAcModeDry,
kFujitsuAcModeFan);
result += addTempToString(getTemp());
result += addFanToString(getFanSpeed(), 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;
default: // Assume everything else does.
result += addBoolToString(getClean(), kCleanStr);
result += addBoolToString(getFilter(), kFilterStr);
result += addIntToString(this->getSwing(), kSwingStr);
result += kSpaceLBraceStr;
switch (this->getSwing()) {
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 (this->getCmd()) {
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 (this->getModel() == fujitsu_ac_remote_model_t::ARREB1E)
result += addBoolToString(getOutsideQuiet(), kOutsideQuietStr);
return result;
}
#if DECODE_FUJITSU_AC
// Decode a Fujitsu AC IR message if possible.
// Places successful decode information in the results pointer.
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// offset: The starting index to use when attempting to decode the raw data.
// Typically/Defaults to kStartOffset.
// nbits: The number of data bits to expect. Typically kFujitsuAcBits.
// strict: Flag to indicate if we strictly adhere to the specification.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: STABLE / Working.
//
// Ref:
//
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
if (!matchMark(results->rawbuf[offset++], kFujitsuAcHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kFujitsuAcHdrSpace)) return false;
// Data (Fixed signature)
match_result_t data_result =
matchData(&(results->rawbuf[offset]), kFujitsuAcMinBits - 8,
kFujitsuAcBitMark, kFujitsuAcOneSpace, kFujitsuAcBitMark,
kFujitsuAcZeroSpace, _tolerance, kMarkExcess, false);
if (data_result.success == false) return false; // Fail
if (data_result.data != 0x1010006314) return false; // Signature failed.
dataBitsSoFar += kFujitsuAcMinBits - 8;
offset += data_result.used;
results->state[0] = 0x14;
results->state[1] = 0x63;
results->state[2] = 0x00;
results->state[3] = 0x10;
results->state[4] = 0x10;
// Keep reading bytes until we either run out of message or state to fill.
for (uint16_t i = 5;
offset <= results->rawlen - 16 && i < kFujitsuAcStateLength;
i++, dataBitsSoFar += 8, offset += data_result.used) {
data_result = matchData(
&(results->rawbuf[offset]), 8, kFujitsuAcBitMark, kFujitsuAcOneSpace,
kFujitsuAcBitMark, kFujitsuAcZeroSpace, _tolerance, kMarkExcess, false);
if (data_result.success == false) break; // Fail
results->state[i] = data_result.data;
}
// Footer
if (offset > results->rawlen ||
!matchMark(results->rawbuf[offset++], kFujitsuAcBitMark))
return false;
// The space is optional if we are out of capture.
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], kFujitsuAcMinGap))
return false;
// Compliance
if (strict) {
if (dataBitsSoFar != nbits) return false;
}
results->decode_type = FUJITSU_AC;
results->bits = dataBitsSoFar;
// Compliance
switch (dataBitsSoFar) {
case kFujitsuAcMinBits:
// Check if this values indicate that this should have been a long state
// message.
if (results->state[5] == 0xFC) return false;
return true; // Success
case kFujitsuAcMinBits + 8:
// Check if this values indicate that this should have been a long state
// message.
if (results->state[5] == 0xFE) return false;
// The last byte needs to be the inverse of the penultimate byte.
if (results->state[5] != (uint8_t)~results->state[6]) return false;
return true; // Success
case kFujitsuAcBits - 8:
// Long messages of this size require this byte be correct.
if (results->state[5] != 0xFC) return false;
break;
case kFujitsuAcBits:
// Long messages of this size require this byte be correct.
if (results->state[5] != 0xFE) return false;
break;
default:
return false; // Unexpected size.
}
if (!IRFujitsuAC::validChecksum(results->state, dataBitsSoFar / 8))
return false;
// Success
return true; // All good.
}
#endif // DECODE_FUJITSU_AC