forked from letscontrolit/ESPEasyPluginPlayground
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path_P137_ESP32_IR.ino
295 lines (264 loc) · 12.1 KB
/
_P137_ESP32_IR.ino
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
#ifdef USES_P137
//#######################################################################################################
//#################################### Plugin 137: Input IR for ESP32###################################
//#######################################################################################################
#include <IRremote.h>
#include <IRutils.h>
IRrecv *irReceiver;
decode_results results;
#define PLUGIN_137
#define PLUGIN_ID_137 137
#define PLUGIN_NAME_137 "ESP32 Communication - TSOP4838"
#define PLUGIN_VALUENAME1_137 "IR"
// A lot of the following code has been taken directly (with permission) from the IRrecvDumpV2.ino example code
// of the Arduino-IRremote library. (https://github.com/ExploreEmbedded/Arduino-IRremote)
// ==================== start of TUNEABLE PARAMETERS ====================
// As this program is a special purpose capture/decoder, let us use a larger
// than normal buffer so we can handle Air Conditioner remote codes.
//#define CAPTURE_BUFFER_SIZE 1024
// P016_TIMEOUT is the Nr. of milli-Seconds of no-more-data before we consider a
// message ended.
// This parameter is an interesting trade-off. The longer the timeout, the more
// complex a message it can capture. e.g. Some device protocols will send
// multiple message packets in quick succession, like Air Conditioner remotes.
// Air Coniditioner protocols often have a considerable gap (20-40+ms) between
// packets.
// The downside of a large timeout value is a lot of less complex protocols
// send multiple messages when the remote's button is held down. The gap between
// them is often also around 20+ms. This can result in the raw data be 2-3+
// times larger than needed as it has captured 2-3+ messages in a single
// capture. Setting a low timeout value can resolve this.
// So, choosing the best P016_TIMEOUT value for your use particular case is
// quite nuanced. Good luck and happy hunting.
// NOTE: Don't exceed MAX_P016_TIMEOUT_MS. Typically 130ms.
/*
#if DECODE_AC
#define P016_TIMEOUT 50U // Some A/C units have gaps in their protocols of ~40ms.
// e.g. Kelvinator
// A value this large may swallow repeats of some protocols
#else // DECODE_AC
#define P016_TIMEOUT 15U // Suits most messages, while not swallowing many repeats.
#endif // DECODE_AC
*/
// Alternatives:
// #define P016_TIMEOUT 90U // Suits messages with big gaps like XMP-1 & some aircon
// units, but can accidentally swallow repeated messages
// in the rawData[] output.
// #define P016_TIMEOUT MAX_P016_TIMEOUT_MS // This will set it to our currently allowed
// maximum. Values this high are problematic
// because it is roughly the typical boundary
// where most messages repeat.
// e.g. It will stop decoding a message and
// start sending it to serial at precisely
// the time when the next message is likely
// to be transmitted, and may miss it.
// Set the smallest sized "UNKNOWN" message packets we actually care about.
// This value helps reduce the false-positive detection rate of IR background
// noise as real messages. The chances of background IR noise getting detected
// as a message increases with the length of the P016_TIMEOUT value. (See above)
// The downside of setting this message too large is you can miss some valid
// short messages for protocols that this library doesn't yet decode.
//
// 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.
//#define MIN_UNKNOWN_SIZE 12
// ==================== end of TUNEABLE PARAMETERS ====================
boolean Plugin_137(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_137;
Device[deviceCount].Type = DEVICE_TYPE_SINGLE;
Device[deviceCount].VType = SENSOR_TYPE_LONG;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = true;
Device[deviceCount].InverseLogicOption = true;
Device[deviceCount].FormulaOption = false;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = false;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_137);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_137));
break;
}
case PLUGIN_INIT:
{
int irPin = Settings.TaskDevicePin1[event->TaskIndex];
if (irReceiver == 0 && irPin != -1)
{
Serial.println(F("IR Init"));
// irReceiver = new IRrecv(irPin, CAPTURE_BUFFER_SIZE, P016_TIMEOUT, true);
irReceiver = new IRrecv(irPin);
// irReceiver->setUnknownThreshold(MIN_UNKNOWN_SIZE); // Ignore messages with less than minimum on or off pulses.
irReceiver->enableIRIn(); // Start the receiver
}
if (irReceiver != 0 && irPin == -1)
{
Serial.println(F("IR Removed"));
// irReceiver->disableIRIn();
delete irReceiver;
irReceiver = 0;
}
success = true;
break;
}
case PLUGIN_TEN_PER_SECOND:
{
if (irReceiver->decode(&results))
{
unsigned long IRcode = results.value;
irReceiver->resume();
UserVar[event->BaseVarIndex] = (IRcode & 0xFFFF);
UserVar[event->BaseVarIndex + 1] = ((IRcode >> 16) & 0xFFFF);
String log = "IR: ";
if (results.overflow)
log += F("WARNING: IR code is too big for buffer. This result shouldn't be trusted until this is resolved. Edit IRremoteInt.h & increase RAWBUF.\n");
// Display the basic output of what we found.
log += resultToHumanReadableBasic(&results);
addLog(LOG_LEVEL_INFO, log);
displayRawToReadableB32Hex();
// Display any extra A/C info if we have it.
// Display the human readable state of an A/C message if we can.
log = "";
// If we got a human-readable description of the message, display it.
if (log != "") addLog(LOG_LEVEL_INFO, log);
// Output RAW timing info of the result.
//log += resultToTimingInfo(&results); //not showing up nicely in the web log... Maybe send them to serial?
// Output the results as source code
// log += resultToSourceCode(&results); //not showing up nicely in the web log... Maybe send them to serial?
sendData(event);
}
success = true;
break;
}
}
return success;
}
#define PCT_TOLERANCE 7u
#define pct_tolerance(v) ((v) / (100u / PCT_TOLERANCE))
//#define MIN_TOLERANCE 10u
//#define get_tolerance(v) (pct_tolerance(v) > MIN_TOLERANCE? pct_tolerance(v) : MIN_TOLERANCE)
#define get_tolerance(v) (pct_tolerance(v))
#define MIN_VIABLE_DIV 40u
#define to_32hex(c) ((c) < 10 ? (c) + '0' : (c) + 'A' - 10)
// This function attempts to convert the raw IR timings buffer to a short string that can be sent over as
// an IRSEND HTTP/MQTT command. It analyzes the timings, and searches for a common denominator which can be
// used to compress the values. If found, it then produces a string consisting of B32 Hex digit for each
// timing value, appended by the denominators for Pulse and Blank. This string can then be used in an
// IRSEND command. An important advantage of this string over the current IRSEND RAW B32 format implemented
// by GusPS is that it allows easy inspections and modifications after the code is constructed.
//
// Author: Gilad Raz (jazzgil) 23sep2018
void displayRawToReadableB32Hex() {
String line;
uint16_t div[2];
// print the values: either pulses or blanks
for (uint16_t i = 1; i < results.rawlen; i++)
line += uint64ToString(results.rawbuf[i] * RAWTICK, 10) + ",";
addLog(LOG_LEVEL_DEBUG, line);
// Find a common denominator divisor for odd indexes (pulses) and then even indexes (blanks).
for (uint16_t p = 0; p < 2; p++) {
uint16_t cd = 0xFFFFU; // current divisor
// find the lowest value to start the divisor with.
for (uint16_t i = 1 + p; i < results.rawlen; i += 2) {
uint16_t val = results.rawbuf[i] * RAWTICK;
if (cd > val) cd = val;
}
uint16_t bstDiv = -1, bstAvg = 0xFFFFU;
float bstMul = 5000;
cd += get_tolerance(cd) + 1;
//Serial.println(String("p="+ uint64ToString(p, 10) + " start cd=" + uint64ToString(cd, 10)).c_str());
// find the best divisor based on lowest avg err, within allowed tolerance.
while (--cd >= MIN_VIABLE_DIV) {
uint32_t avg = 0;
uint16_t totTms = 0;
// calculate average error for current divisor, and verify it's within tolerance for all timings.
for (uint16_t i = 1 + p; i < results.rawlen; i += 2) {
uint16_t val = results.rawbuf[i] * RAWTICK;
uint16_t rmdr = val >= cd ? val % cd : cd - val;
if (rmdr > get_tolerance(val)) { avg = 0xFFFFU; break; }
avg += rmdr;
totTms += val / cd + (cd > val? 1 : 0);
}
if (avg == 0xFFFFU) continue;
avg /= results.rawlen / 2;
float avgTms = (float)totTms / (results.rawlen / 2);
if (avgTms <= bstMul && avg < bstAvg) {
bstMul = avgTms;
bstAvg = avg;
bstDiv = cd;
//Serial.println(String("p="+ uint64ToString(p, 10) + " cd=" + uint64ToString(cd, 10) +" avgErr=" + uint64ToString(avg, 10) + " totTms="+ uint64ToString(totTms, 10) + " avgTms="+ uint64ToString((uint16_t)(avgTms*10), 10) ).c_str());
}
}
if (bstDiv == 0xFFFFU) {
addLog(LOG_LEVEL_INFO, "No proper divisor found. Try again...");
return;
}
div[p] = bstDiv;
line = String(p? "Blank: " : "Pulse: ") + " divisor=" + uint64ToString(bstDiv, 10)
+" avgErr=" + uint64ToString(bstAvg, 10) + " avgMul="+ uint64ToString((uint16_t)bstMul, 10)
+'.'+ ((char)((bstMul - (uint16_t)bstMul) * 10 )+ '0');
addLog(LOG_LEVEL_DEBUG, line);
}
// Generate the B32 Hex string, per the divisors found.
uint16_t total = results.rawlen - 1, tmOut[total];
//line = "Timing muls ("+ uint64ToString(total, 10) + "): ";
for (unsigned int i = 0; i < total; i++) {
uint16_t val = results.rawbuf[i+1] * RAWTICK;
unsigned int dv = div[(i) & 1];
unsigned int tm = val / dv + (val % dv > dv / 2? 1 : 0);
tmOut[i] = tm;
//line += uint64ToString(tm, 10) + ",";
}
//Serial.println(line);
char out[total];
unsigned int iOut = 0, s = 2, d = 0;
for (; s+1 < total; d = s, s += 2) {
unsigned int vals = 2;
while (s+1 < total && tmOut[s] == tmOut[d] && tmOut[s+1] == tmOut[d+1]) {
vals += 2;
s += 2;
}
if (iOut + 5 > sizeof(out) || tmOut[d] >= 32*32 || tmOut[d+1] >= 32*32 || vals >= 64) {
addLog(LOG_LEVEL_INFO, "Raw code too long. Try again...");
return;
}
if (vals > 4 || (vals == 4 && (tmOut[d] >= 32 || tmOut[d+1] >= 32))) {
out[iOut++] = '*';
out[iOut++] = to_32hex(vals / 2);
vals = 2;
}
while (vals--)
iOut = storeB32Hex(out, iOut, tmOut[d++]);
}
while (d < total)
iOut = storeB32Hex(out, iOut, tmOut[d++]);
out[iOut] = 0;
line = "IRSEND,RAW2," + String(out) + ",38," + uint64ToString(div[0], 10) +','+ uint64ToString(div[1], 10);
addLog(LOG_LEVEL_INFO, line);
}
unsigned int storeB32Hex(char out[], unsigned int iOut, unsigned int val) {
if (val >= 32) {
out[iOut++] = '^';
out[iOut++] = to_32hex(val/32);
val %= 32;
}
out[iOut++] = to_32hex(val);
return iOut;
}
#endif // USES_P137