-
Notifications
You must be signed in to change notification settings - Fork 6
/
script.js
1993 lines (1771 loc) · 99.9 KB
/
script.js
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
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// « non verbum e verbo sed sensum de sensu »
// https://github.com/pasthev/sensus
/* Todo:
- All '// return false;' at the end of ui_ functions to be deleted / corrected (was initially meant for Google Sites embedding)
*/
const MAX_FACTOR = 5; // defines max factor value between zeros and ones in commands
const FREQ36 = 36045; // Most common IR frequencies
const FREQ38 = 38029; //
const FREQ40 = 40244; //
const BRDLNK_UNIT = 269 / 8192; // Broadlink 32.84ms time units, or 2^-15s ;
const NEC = 'Nec', ONEHOT = 'One-Hot'; // Just to make function calls a bit more understandable
var output = ''; // Buffer for output message in log window
const activeDebug = false
// Interface functions --------------------------------------------------------------------------------------------------->
function search(e){
// https://stackoverflow.com/questions/20998541/get-the-value-of-input-text-when-enter-key-pressed
if (e.which === 13) {info('coucou'); return;};
let unicode= e.which;
info(unicode + ' / ' + e);
}
function setField(name, val) { // Fills textarea named 'name' with 'val'
if (typeof val === 'undefined') {
document.getElementById(name).value = '';
} else {
document.getElementById(name).value = val;
}
}
function clrField(name) { // Clears textarea named 'name'
document.getElementById(name).value = '';
}
function getField(name) { // Returns content of 'name' html field
return document.getElementById(name).value
}
function setFocus(name) { // Returns content of 'name' html field
document.getElementById(name).focus();
}
function cleanOnClick() { // Cleanup tasks on button clicks, i.e. close msg popup if any
output = '';
closeMsg();
}
function ui_clear(fieldId) { // Clears field with id string fieldId and sets focus on it
cleanOnClick();
if (fieldId == 'Commands') {
clrField('cPreField');
clrField('cHeaderField');
clrField('cOneField');
clrField('cZeroField');
clrField('cPtrailField');
clrField('cGapField');
clrField('cFreqField');
clrField('cShortField');
clrField('cCommandField');
setFocus('cShortField');
} else {
clrField(fieldId);
setFocus(fieldId);
}
// return false;
}
function ui_copy(fieldId) { // Copies string from field with id string fieldId to clipboard
cleanOnClick();
copyToClipboard(getField(fieldId));
// return false;
}
function ui_copyPrefixed(fieldId) { // Copies string from field with id string fieldId to clipboard, adding prefix 'hex2send:'
cleanOnClick();
let jeedom = 'hex2send:' + getField(fieldId);
copyToClipboard(jeedom);
// return false;
}
function ui_copyLirc() { // Turns Commands into a Lirc txt, and copies it to clipboard
cleanOnClick();
let header = cleanStringSeps(getField('cHeaderField'), ' ');
let one = cleanStringSeps(getField('cOneField'), ' ');
let zero = cleanStringSeps(getField('cZeroField'), ' ');
let ptrail = cleanStringSeps(getField('cPtrailField'), ' ');
let gap = cleanStringSeps(getField('cGapField'), ' ');
let pre = cleanStringSeps(getField('cPreField'), '');
let preBits = 0;
let freq = getField('cFreqField');
let lircCommand = stripHex(getField('cCommandField'),0);
let bits = Math.ceil(lircCommand.length / 2) * 8;
if (lircCommand == '') {
shortToCommand();
lircCommand = getField('cCommandField');
}
if (!one || !zero || !lircCommand) {
message('Need at least Command or Short, as well as Zero and One values to generate Lirc');
return
}
if (pre) { // Pre value usually is stored in hex, with 0x prefix
let preVal = parseInt(pre); // in case Pre would have been typed in decimal
preVal = isNaN(preVal) ? parseInt(pre,16) : preVal; // if above failed, tries direct Hex in case 0x would be missing in field
pre = isNaN(preVal) ? '' : '0x' + hexPairPad(preVal); // Converts to 0x-prefixed hex string or empty string
preBits = isNaN(preVal) ? 0 : Math.ceil(pre.length / 2) * 8 - 8; // pre length - 0x
}
let lirc = ``;
lirc += `begin remote\n`
lirc += ` name Sensus-generated Lirc file # https://pasthev.github.io/sensus/\n`
lirc += ` bits ${bits}\n`
lirc += ` flags SPACE_ENC|CONST_LENGTH\n`
lirc += ` eps 30\n`
lirc += ` aeps 100\n`
lirc += ` header ${header}\n`
lirc += ` one ${one}\n`
lirc += ` zero ${zero}\n`
lirc += ` ptrail ${ptrail}\n`
lirc += ` pre_data_bits ${preBits}\n`
lirc += ` pre_data ${pre}\n`
lirc += ` gap ${gap}\n`
lirc += ` toggle_bit_mask 0x0\n`
lirc += ` frequency ${freq}\n`
lirc += `\n`
lirc += ` begin codes\n`
lirc += ` KEY_MAIN 0x${lircCommand}\n`
lirc += ` end codes\n`
lirc += `end remote\n`
copyToClipboard(lirc);
output += 'Lirc file copied to clipboard, ready to be pasted into a new text file:\n\n' + lirc;
write(output);
// return false;
}
function copyToClipboard(string) { // _Copies %string to clipboard
if (!string) {return};
if (!navigator.clipboard) {
message('Can not copy to clipboard');
return
}
navigator.clipboard
.writeText(string)
.then( () => { info('Copied to clipboard: ' + string) } )
.catch( () => { message('Failed to copy') } );
}
function ui_paste(fieldId) { // Pastes clipboard to field with id string fieldId
cleanOnClick();
if (!navigator.clipboard) {
message('Can not paste from clipboard');
return
}
let string;
navigator.clipboard
.readText()
.then( clipText => setField(fieldId, clipText));
// return false;
}
function formatShort(str, space, plus) { // Formats a string, adding space every %space, and + every %plus position i.e. 'a55a 9a65 + a55a 40bf'
// let command = command.replace(/.{4}/g, '$& '); // might exploit regEx, but boring with imbricated shifts
if (!str) {return ''};
let i, formated = '';
str = str.replace(/[+]| /g, ''); // i.e. 'A559+A502' to 'a55a 9a65 + a55a 40bf'
for (i in str) {
if (i > 0) {
if (plus && !(i % plus)) { // If i counter has reached a multiple of %plus
formated += ' + ';
} else if (space && !(i % space)) { // If i counter has reached a multiple of %min
formated += ' ';
}
}
formated += str.charAt(i);
}
return formated;
}
// Top toolbars functions
function ui_formToggle(formName) { // Buttons to toggle panels on / off
let form=document.getElementById(formName);
if (form.hidden == true) {
formShow(formName)
} else {
formHide(formName)
}
}
function formHide(formName) { // Hides given panel (called both from ui and internally)
let form=document.getElementById(formName);
form.hidden = true;
let button=document.getElementById('b' + formName); // Buttons are named after corresponding panel (form) with a 'b' prefix
let body = document.querySelector('body');
let shadowColor = getComputedStyle(body).getPropertyValue('--col-s1');
button.style.boxShadow = '-1px -1px 1px ' + shadowColor; // Inverts button's shadow for crappy 3D effect
button.style.fontSize = '0.4em'; // Reduces font size in button to help crappy 3D effect
}
function formShow(formName) { // Shows given panel (called both from ui and internally)
let form=document.getElementById(formName);
form.hidden = false;
let button=document.getElementById('b' + formName); // Buttons are named after corresponding panel (form) with a 'b' prefix
let body = document.querySelector('body');
let shadowColor = getComputedStyle(body).getPropertyValue('--col-s1');
button.style.boxShadow = '1px 1px 3px ' + shadowColor; // Restores button's shadow for crappy 3D effect
button.style.fontSize = '0.6em'; // Restores font size in button to help crappy 3D effect
}
function ui_colorsToggle(theme) { // Changes interface colors
// ref: https://webdesign.tutsplus.com/tutorials/color-schemes-with-css-variables-and-javascript--cms-36989
// const setTheme = theme => document.documentElement.className = 'bnw';
cleanOnClick();
document.documentElement.className = theme;
// return false;
}
function ui_fontSize(opt) { // Changes text size in code fields
cleanOnClick();
let fontSize = window.getComputedStyle(document.getElementById("prontoField")).fontSize // Just for info, gets size in px
let fntSize = getComputedStyle(document.documentElement).getPropertyValue('--fntSize'); // Reads CSS variable
let em = parseFloat(fntSize);
if (opt) {em += .2} else {em -= .2};
if ((em<5) && (em > .1)) {
let newem = '' + em + 'em';
document.documentElement.style.setProperty('--fntSize', newem);
info('New font size: ' + fontSize + ' / ' + newem);
} else {
message('🙂 + 👓 = 😎 / 🙂 + 👓👓 = 🤓')
}
// return false;
}
// Message popup functions
function message(text){ // Displays a ⚠ warning message at the bottom of the screen
popup('⚠ ' + text, '--col-sb')
}
function info(text){ // Displays an 🛈 info message at the bottom of the screen
popup('🛈 ' + text, '--col-a1')
}
function popup(text, colCSSname){ // _Generates the popup window for message() and info() functions
// https://www.cssscript.com/alert-msg/
closeMsg();
let body = document.querySelector('body');
let color = getComputedStyle(body).getPropertyValue(colCSSname);
let textColor = getComputedStyle(body).getPropertyValue('--col-st');
let div=document.createElement('div')
let len = text.length;
if (len > 99) {
text = text.slice(0,79) + '…'
}
div.setAttribute('id', 'div_Msg'); // ID will be needed to close later through getElementById()
div.style='font-family: Verdana, Tahoma, sans-serif';
div.style.color = textColor;
div.style.padding = '5';
div.style.margin = '5';
div.style.animation = 'messageAnim 0.3s linear';
div.style.backgroundColor = color;
div.style.position = 'fixed';
div.style.bottom = '10';
div.style.right = '50';
div.style.left = '50';
div.style.boxShadow = '5px 5px 10px ' + getComputedStyle(body).getPropertyValue('--col-s1');
div.style.borderRadius = '8px';
div.innerHTML = ' <center><button onclick="closeMsg()" class="msg-text" style="cursor: pointer; background-color: transparent; border: 0px; outline: none; color: ' + textColor + '; position: absolute; top: 2; right: 2;">ⓧ</button><span>'+text+'</span></center>';
document.body.appendChild(div);
}
function closeMsg(){
let messagePopup=document.getElementById('div_Msg');
if (messagePopup) {messagePopup.remove()};
}
function refreshAll(includeComm) { // Triggers refreshes on all Frenquency and Repeat fields, and on IR/RF radio buttons
freqReadPronto();
freqReadDecimal();
freqReadRaw();
hexReadRepeats();
b64ReadRepeats();
freqHexCheck();
freqB64Check();
if (includeComm) { // includeCom flag is set to true when we want return from a conversion command to
let tmpOutput = output; // also trigger a raw analysis attempt in order to generate the commands values,
readRaw(); // but in this case, we don't want to display the long output from raw analysis,
output = tmpOutput; // which would hide other possible warnings or logs. Hence saving and restoring
} // initial log output, thus deleting the part that comes from raw analysis.
if (output) { // Throws log, if any
write(output);
output = '';
message('See Raw analysis panel for conversion logs')
}
}
function write(text){ // Sends text message to Raw Analysis field
setField('testField', text);
}
/* Raw Panel functions ***************************************************************************************************************************
*/
// String Checks and repeats cleaning functions ---------------------------------------------------------------------------->
function ui_clearAll() {
cleanOnClick();
clrField('cPreField');
clrField('cHeaderField');
clrField('cOneField');
clrField('cZeroField');
clrField('cPtrailField');
clrField('cGapField');
clrField('cFreqField');
clrField('cShortField');
clrField('cCommandField');
setFocus('cShortField');
clrField('prontoField');
clrField('decimalField');
clrField('rawField');
clrField('broadlinkField');
clrField('broadB64Field');
clrField('testField');
setFocus('rawField');
}
function ui_check() { // 'Check' button in Raw Analysis panel : pulls figures out of figures
cleanOnClick();
let i, str = '', raw = getField('rawField');
let hex, dec;
let obj = stringType(raw);
switch (obj.type) { //
case 'binary':
// 0011001100101101010011 abc123ab1bc123
// 001100110010110101001100101100110100101010110010101010101100101100 (Hex command: 53a5 8409, no short)
// 01000100000100010001000101000100000100010100000001000100000101010 (generates short a51a)
// abc123ab23acb321abc111fbc123a (decimal: 171 193 35 171 35 172 179 33 171 193 17 251 193 35 10)
output += 'Trying ' + formatShort(obj.cleaned, 8, 0) + '\n';
let binObj = searchBibits(obj.cleaned);
if (!binObj.type) {info('nothing found'); break}
str = obj.cleaned // Prepares a more readable 0011 [01100110 10010101 01100101 01010101 10010110] 00
str = insertChars(str, ' [', binObj.shiftL); //
str = insertChars(str, '] ', -binObj.shiftR); //
i = binObj.shiftL + 10; // 2 chars for ' [' + 8 first bits
while (i < str.length - 4 - binObj.shiftR) { //
str = insertChars(str, ' ', i) //
i += 9; // 8 bits + 1 space
}
output += 'Bibits section: ' + str + '\n';
output += 'Cleaned string length: ' + obj.cleaned.length + ' / ';
output += 'Type detected: ' + binObj.type +' / ';
output += 'Shift L: ' + binObj.shiftL +' / ';
output += 'Shift R: ' + binObj.shiftR +' / ';
output += 'Hex command: ' + formatShort(binObj.hex, 4, 8);
if (binObj.short) {output += ' / Short: ' + binObj.short};
hex = chunk(binObj.hex, 2) // '53a58409' => {'53', 'a5', '84', '09'}
dec = hex.map(byte => parseInt(byte,16)) // {'53', 'a5', '84', '09'} => {83, 165, 132, 9}
let lsb = dec.map(byte => toLsb(byte).toString(16).padStart(2, '0')); // lsb: array of Less Significant bits first in hex format
let inv = dec.map(byte => invert(byte).toString(16).padStart(2, '0')); // inv: array of inverted decimals in hex format
let inl = dec.map(byte => invert(toLsb(byte)).toString(16).padStart(2, '0')); // inl: array of LSB & inverted decimals in hex format
output += '\nBibits : ' + formatShort(binObj.bin,8 ,0);
output += '\nDecimal : ' + dec.join(' ');
output += '\nHexa : ' + hex.join(' ');
output += '\nLSB : ' + lsb.join(' ');
output += '\ninvert : ' + inv.join(' ');
output += '\nLSB inv : ' + inl.join(' ');
break;
case 'decimal':
dec = obj.cleaned.split(',')
output += 'Cleaned string length: ' + obj.cleaned.length + ' / ';
output += 'Type detected: decimal values. / ';
output += 'Raw field contains ' + dec.length + ' values\n\n';
output += 'Cleaned string: ' + dec.join(', ') + '\n';
break;
case 'hexa':
hex = chunk(obj.cleaned, 2);
dec = hex.map(byte => parseInt(byte, 16).toString().padStart(3, ' ')); // Translatess hex array to array of decimal strings with padding
output += 'Type detected: hexadecimal values. / ';
output += 'Cleaned string characters length: ' + obj.cleaned.length + ' / ';
output += 'Number of values: ' + hex.length + ' / ';
output += 'Cleaned string: ' + obj.cleaned + '\n';
output += 'Hexa: ' + hex.join(' ') + '\n';
output += 'decimal: ' + dec.join(' ') + '\n';
break;
default:
output += 'insert list of binary, decimal or hex values in the Raw panel, and try again';
}
output += '\nLongest repeat identified: ' + repeatFind(raw) + '\n';
write(output);
}
function stringType(str) { // _Tells if str contains binary, decimal, or hex data - returns object
if (!str.replace(/ /g, '')) {return 'empty string'};
let cln = str.replace(/[L ;,.()\+\-\r\n]/g, ''); // test string after removing ' L;,()+-' chars
if (/^[0-1]+$/.test(cln)) { // Case binary
return { // Returns an object - Sample values:
'type': 'binary', // 'binary'
'original': str, // '00 11 0011; 00101 101010011'
'cleaned': cln, // '0011001100101101010011'
};
}
if (/^\d+$/.test(cln)) { // Case decimal (\d is exactly equivalent to [0-9] in js)
cln = str.replace(/[L ;()\+]/g, '')
return { // Returns an object - Sample values:
'type': 'decimal', // 'decimal'
'original': str, // '26L, 12, +15, (12-2)'
'cleaned': cln, // '26,12,15,8' (had to make some choices here)
};
}
if (/^[0-9A-Fa-f]+$/.test(cln)) { // Case hexadecimal
return { // Returns an object - Sample values:
'type': 'hexa', // 'hexa'
'original': str, // '2600, 1a 00,1d, 1d'
'cleaned': cln, // '26001a001d1d' (means hex strings will always be converted to single byte values here)
};
}
return { 'type': 'invalid' };
//str = str.replace(/[ ;\+\-]/g, ','); // Replaces spaces, ; + or - with commas if any
//str = str.replace(/(\,){2,}/g, '$1'); // Replaces multiple ",,," with single "," separator
}
function chunk(str, size) { // _Cuts a string in slices of %size length, returns an array of strings
return str.match(new RegExp('.{1,' + size + '}', 'g'));
}
function insertChars(str, char, pos) { // _Inserts char at pos in str string
if (str.length < pos) {return ''};
return str.slice(0, pos) + char + str.slice(pos);
}
function ui_repeats() { // Identifies longest duplicated sustrings and removes last occurrence of the dup
cleanOnClick();
let raw = getField('rawField');
let str = repeatFind(raw);
let len = str.length;
let separator = '';
if (str) {
switch (str.charAt()) { // Boring checks to try and preserve list of values separated by , or ; etc.
case ',': // else '15, 12, 13, 4, 12, 13, 3' would become '15, 12, 13, 43',
separator = ', ' // which would be correct, but probably not the desired effect
break; //
case ';', ' ', "/": //
separator = 'str.charAt()' //
break; //
} //
let i = raw.length; // Extracts last occurrence of the detected repeat
while (i >= 0) {
i--;
if (raw.slice(i, i + len) == str) {
let newRaw = raw.slice(0, i)
newRaw += separator
newRaw += raw.slice(i + len);
setField('rawField', newRaw)
break;
}
}
output += 'Longest repeat spotted: ' + str + '\n\n'
output += 'Content in Raw panel has been purged from last occurrence. Original string was:\n' + raw;
} else {
output += 'No repeated string found.'
}
write(output);
}
function repeatFind(str) { // In: string => Out: Last longest repeated substring
// Inspired from https://stackoverflow.com/questions/65380016/the-longest-repeating-substrings-javascript
let suffixes = [], len = str.length, i, string, pos1, pos2;
for (i = 0; i < len; i++) {
suffixes.push(str.slice(i, len));
}
suffixes.sort();
let longestSubStr = '';
for (i = 0; i < len - 1; i++) {
string = commonSubstr(suffixes[i], suffixes[i + 1]);
if (string.length >= longestSubStr.length) {
// This test is here to avoid overlapping repeated substring
pos1 = str.indexOf(string); // i.e. '123123123' would show repeat of '123' without this test
pos2 = str.slice(pos1 + string.length).indexOf(string); // searches new occurrence of string after first occurrence
if (pos2 != -1) {longestSubStr = string}; // (if needed, function could be modified with a 'acceptOverlap' yes/no flag)
}
}
return longestSubStr;
}
function commonSubstr(str1, str2) { // _Returns the longest common identical substring from the beginning of str1 & str2
let shortest = Math.min(str1.length, str2.length);
for (let i = 0; i < shortest; i++) {
if (str1.charAt(i) != str2.charAt(i)) {
return str1.slice(0, i);
}
}
return str1.slice(0, shortest);
}
// Raw Analysis functions -------------------------------------------------------------------------------------------------->
function ui_readRaw() {
cleanOnClick();
readRaw();
setFocus('rawField');
write(output); output = ''; // Because we won't call refreshAll(), as we don't want Raw Analysis to modify any other fields
// return false;
}
function readRaw() { // * Main Raw analysis function
/* headerLength & trailerLength entered by user will be completely ignored in this function if their actual values can be identified in the process
at the end, these two values will only be considered in the case of a raw strings that contains no obvious headers or trailers
ie: 8548,4125,530,1220,530,530,530, [...] 1220,530,530,530,530,530,1220,530,25817 will force headerLength = 2 & trailerLength = 1
while: 530,1220,530,530,530,1220,530,530,530, [...] 1220,530,530,530,530,530,1220,530 will rely on user input, and show retained values afterward */
let i, seqStart, sequence;
let headerLength = parseInt(getField('headerLength'));
let trailerLength = parseInt(getField('trailerLength')); //
let raw = getField('rawField');
if (raw.length < 8) {return false}; // Assume smallest string accepted is one stripped byte
let str = raw.split(',').map(Number); // String of comma-separated values ==> integer array
str = stripRaw(str); // Removes pronto header if any, to get bare raw
let highest = findHighest(str);
if (!highest) {return false};
let zero = findZero(str, 0, highest); // Gets smallest identified zero
zero = averageZero(str, zero); // Gets averaged zero
let one = findOne(str, zero, 2); // Gets averaged one
output += 'Values: ' + str.length + ' / ';
output += ' Averaged zero: ' + zero;
output += ' Averaged one: ' + one + ' / ';
let header = findFullHeader(str, zero, one, headerLength);
if (header.length) {
firstHeaderPos = header.pop(); // First header position had been pushed to header table by findFullHeader()
headerLength = header.length; // *** Replaces user-defined header length if a header has been identified***
output += 'Header: ' + header.join(',');
output += ' @pos. ' + firstHeaderPos + ' / ';
seqStart = locateSequences(str, zero, one, firstHeaderPos, header);
} else {
seqStart = locateSequences(str, zero, one, 0, -1);
}
output += 'Sequences: ' + (seqStart.length - 1)
output += ' - positions: ' + seqStart.toString() + ' / ';
output += '\n\n';
let gap = checkGap(str, highest); // Gets gap string or '' (gap being the closing long value)
if (gap) {trailerLength = 1} else {trailerLength = 0}; // *** Replaces user-defined trailer length if a trailer has been identified or not ***
//let pTrail = checkPtrail(str, seqStart); // Gets pTrail string or '' ***** can hardly be sure of pTrail, so, first proposal here ***
let seqQty = seqStart.length - 1; // Nombre de séquences
let seq;
let binCommand = []; // These will store the different burst values that will be found
let seqCommand = []; //
let burstCommand = []; //
let burstShort = []; //
let lastType, lastBin, lastHex, lastShort;
let lastShiftL = 0, lastShiftR = 0, lastCommand = '';
// * Sequences loop ------------------------------------------------------------------------------------------------------------
for (i = 0; i < seqQty; i ++) { // Packets reading sequences (can't (for in) here)
// Function could be optimized by not re-running hexCommand() & searchBibits()
// when binCommand == previous binCommand, but keep as is for readability
seq = str.slice(seqStart[i] + headerLength, // i.e. seq = '75,549,549,275,275,549,549,275,275,549,275'
seqStart[i+1] + 1 - trailerLength); // +1 because slice excludes last, - trailerLength' as search parses groups of 8 from beginning)
binCommand[i] = payloadRead(seq, zero, one); // i.e. binCommand = '011001100101101010011001011001...'
seqCommand[i] = hexCommand(binCommand[i]); // i.e. seqCommand = '665a996695655596'
output += "* Sequence " + i + ': '; // Start of sequence output
output += "Binary length: " + binCommand[i].length + " / "; // Outputs binary count
output += "Rough command: "; // Very rough:
output += formatShort(seqCommand[i], 4, 8) + "\n"; // stupidly printing bibits as if they were bits (but who knows?)
output += seq + "\n"; // Outputs decimal slice
output += "Binary: *" + binCommand[i] + "*\n"; // Outputs binary slice
sequence = searchBibits(binCommand[i]); // Bibits search for this sequence; receives sequence object
if (sequence.type != '') { // Bibits! :)
lastType = sequence.type; // Function will assume the last sequence is the good one,
lastShiftL = sequence.shiftL; // so let's store the last good values that have been read here
lastShiftR = sequence.shiftR; // in case the last sequence that will be read would return an empty object
lastBin = sequence.bin; //
lastHex = formatShort(sequence.hex, 4, 8); //
lastShort = formatShort(sequence.short, 0, 4); //
lastCommand = seqCommand[i]; // Also stores identified rough hex commands[]
lastPtrail = str.at(-1 - trailerLength);
burstCommand[i] = lastHex; // Stores found hex and shorts in arrays
if (lastShort) {burstShort[i] = lastShort}; // to avoid cases where formatShort would generate a ' ' false positive
output += '> ' + lastType + ' encoding detected '; // Outputs type (One-Hot or Nec)
output += ' with shift value: ' // Outputs left & right shifts used,
output += lastShiftL + '/' + lastShiftR ; // in order to correct header / trailer Lengths
output += ' *' + formatShort(lastBin, 8, 16) + '* '; // Outputs decoded bibits binary values
output += '- hex: *** ' + lastHex + ' *** '; // Outputs the hex (Lirc) command, i.e. *** a55a + 58a7 ***
output += '- short: < ' + lastShort + ' > '; // Outputs the short command, i.e. <A51A>, or <>
output += '\n';
} else {
output += 'No bi-bits encoding found. \n'; // No bibits. :(
}
output += "\n";
}
// Now to recap or correct what has been found from sequences ------------------------------------------------------------------
let flag = false; // if value is same for all sequences
for (i in seqCommand) { // result will be i.e. 'ab2c'
if (seqCommand[i].split('/')[0] != lastCommand.split('/')[0]) { // (splits chars after /, where exceeding values might have been shown)
flag =true // but if at least one differs, raises flag
};
}
zero = zero.toString(); // So far, zero and one were used to store the "raw bit" values, or high & lows
one = one.toString(); // but if bibits types have been identified, we'll need to properly assign these
let zeroString, oneString;
switch (lastType) { //
case 'One-Hot': // One-Hot bibits (01b=zero / 10b=one)
zeroString = zero + ', ' + one; // i.e. zero = '275, 549'
oneString = one + ', ' + zero; // one = '549, 275'
break;
case 'Nec': // Nec bibits (00b=zero / 01b=one)
zeroString = zero + ', ' + zero; // i.e. zero = '275, 275'
oneString = zero + ', ' + one; // one = '275, 549'
break;
default: // No bibits but raw bits
zeroString = zero; // i.e. zero = 275
oneString = one; // one = 549
}
headerLength += lastShiftL; // Corrects user-defined Header and Trailer Lengths,
trailerLength += lastShiftR; // in case function would have shifted these values
let pTrail = ''; // Defaults Ptrail to empty,
if (lastShiftR == 1) {pTrail = lastPtrail}; // but validates last value if right shift == 1
if (lastHex) {lastCommand = lastHex} // If applicable, replaces raw hex with Nec or One-Hot real hex lastCommand
// * Reports to log window -----------------------------------------------------------------------------------------------------
let prefix = '';
if (burstCommand.length) {
lastCommand = burstCommand.join(' + ');
prefix += lastCommand;
if (burstShort.length) {
lastShort = burstShort.join(' + ');
prefix += ' - Short: ' + lastShort;
} else {
prefix += ' (no short identified)';
}
prefix += '\n\n';
}
if (flag) {
prefix += 'Multiple command bursts identified. '
} else {
prefix += 'Unique command burst identified. ';
}
prefix += '\nHeader: ' + header.join(', ') + ' / Ptrail: ' + pTrail
if (lastShiftR > 1) {prefix += 'invalid (multiple values)'}
if (lastShiftR < 1) {prefix += 'none'}
prefix += ' / Gap: ' + gap
prefix += ' / Full analysis below.';
output = prefix + '\n\n' + output;
// * Reports to Command panel --------------------------------------------------------------------------------------------------
if (sequence.type != '') { // Bibits! :)
setField('cOneField', oneString); // Injects Lirc values in Commands form
setField('cZeroField', zeroString);
setField('cCommandField', lastCommand);
setField('cShortField', lastShort);
setField('cGapField', gap);
setField('cHeaderField', header.join(', '));
setField('headerLength', headerLength);
setField('trailerLength', trailerLength); //
setField('cPtrailField', pTrail);
document.getElementById('cPtrailField').placeholder = pTrail;
setField('cPreField', '');
document.getElementById('cPreField').placeholder = '';
}
// -----------------------------------------------------------------------------------------------------------------------------
}
function searchBibits(rawBin) { // _Searches %rawBin string for One-Hot or Nec sequence, shifting start if not multiple of 8
/* For RC6, check http://www.pcbheaven.com/userpages/The_Philips_RC6_Protocol/index.php?topic=worklog&p=0
rawBin might be of any length above 15, since 16 bibits = 1 byte
i.e for 276,276,554,554,276,554,554,276,276,554,276,554,554,276,554,276,554,276,276,554,554,276
00110110 01011010 100110
rawBin length = 22 (len % 16=6), function will parse string like this:
i
0: [0011011001011010] 100110 > no bibit
1: 0 [0110110010110101] 00110 > no bibit
2: 00 [1101100101101010] 0110 > no bibit
3: 001 [1011001011010100] 110 > no bibit
4: 0011 [0110010110101001] 10 > One-Hot bibit: 4e
5: 00110 [1100101101010011] 0 > no bibit
6: 001101 [1001011010100110] > Another One-Hot bibit: 9d (hence the need for user variable Header length)
*/
let i, bin, result, hex, shift;
let found = '', short = '';
let len = rawBin.length;
let toClip = len % 16; // Clip to byte; 16 bibits = 1 byte
let debug = ''; // Debug option in case of new encoding types study
for (i=0; i <= toClip; i++) {
bin = rawBin.slice(i, len - toClip + i);
debug += '\n\n>>>>>> i=' + i + ' ' + bin.toString() + ' -> ';
result = checkBinPairs(bin, 1, 2); // Search for One-Hot bibits (01b=zero / 10b=one)
if (result) { // If result is not an empty string
debug += 'One-Hot';
found = 'One-Hot'; //
break; // And exit for loop
}
result = checkBinPairs(bin, 0, 1); // Search for Nec bibits (00b=zero / 01b=one)
if (result) { // If result is not an empty string
found = 'Nec'; //
break; // And exit for loop
}
}
if (activeDebug) {output += activeDebug};
if (found != '') {
hex = parseInt(result, 2).toString(16); // i.e. hex = 53a58409
if (hex.length % 2) {hex = '0' + hex}; // padStart simply based on length parity
shift = len -i - result.length * 2; // Calculates Right shift, while left shift is i
if (found == 'Nec') { // Only Nec type is worth searching for a search command,
short = getShortFromCommand(hex, false); // since only Nec needs 0/1s to be equilibrated
} // short = '' if no short can be found
} else {
return { 'type': '' }; // Returns an object with only one empty element
}
return { // Returns an object - Sample values:
'type': found, // Nec
'shiftL': i, // 2
'shiftR': shift, // 1
'bin': result, // 10100101 01011010 + 01011000 10100111 (after formatShort(sequence.bin, 8, 16))
'hex': hex, // a55a + 58a7 (after formatShort(sequence.hex, 0, 4))
'short': short // a51a (might formatShort(sequence.short, 0, 4))
};
}
function checkBinPairs(string, zero, one) { // __checks if binary %string contains bibits sequence and returns it as decoded bin string
// Function can either be used to search for One-Hot pairs (01b=zero / 10b=one) - see https://fr.wikipedia.org/wiki/Encodage_one-hot
// or for Nec pairs (00b=zero / 01b=one), in which case the whole sequence should later be found inverted to maintain constant signal duration
let len = 2; // Making len a variable here, because function might be used to search tribits or more,
let val, i; // although only bibits seem to be interesting here, as they guarantee constant signal duration
let debug ='';
debug += 'String: ' + string + ' (string length=' + string.length + ') \n';
if (string.length % len) {return ''} // length division error};
let chunk = '';
for (i = 0; i < string.length - len + 1 ; i += len) { // Temporarily hard-coded for bibits only
val = parseInt(string.substr(i, len),2); // Parses the string pair by pair,
if (val == zero) { // comparing bin pair value to decimal value received as argument
chunk += '0'; // and feeding chunk string with resulting bit
} else if (val == one) {
chunk += '1';
} else { // Invalid bibit encountered : assume end of sequence has been reached
len = chunk.length; // Recycles len variable to save the planet
debug += ' chunk: ' + chunk + ' (chunk length=' + len + ') \n';
if (len > 6) { // if at least one byte has been decoded (first 16 bits)
chunk = chunk.slice(0, Math.floor(len / 8) * 8); // stick to bytes only (looks like irScrutinizer coders wrongly used math.round here)
return chunk; // and return the partial finding
}
return ''; // No significant bibits string found
}
}
if (activeDebug) {output += activeDebug};
return chunk;
}
function stripRaw(seq) { // _Returns raw values stripped from Pronto header
if (seq.length < 4) {return seq}; // usual length of a Pronto header
if (seq[0] == 0) { // Pronto header always begin with a 0
if ((seq[2] + seq[3]) * 2 == seq.length - 4) { // Pronto header bytes 2 & 3 contain paquets A & B length
output += '*Pronto header removed* /';
return seq.slice(4);
}
}
return seq;
}
function findHighest(seq) { // _Identifies highest value in seq array
let tmp;
let highest = 0;
for (let i in seq) { // Identifies smallest value above min
tmp = seq[i]
if (tmp > highest) {highest = tmp};
}
return highest;
}
function findZero(seq, min, highest) { // _Identifies zero value above min and under highest in seq array
let smallest = 0xffff;
let i, tmp;
for (i in seq) { // Identifies smallest value above min
tmp = seq[i]
if ((tmp > min) && (tmp < smallest)) {smallest = tmp};
}
if (smallest == highest) { // safeguard for recursive call
output += "smallest = highest! " + highest + "\n";
return 0
}
let count = 0;
for (i in seq) { // Counts occurences of ~smallest
tmp = seq[i];
if (Math.round(tmp / smallest) == 1) {count ++};
}
output += 'highest: ' + highest + ' / smallest: ';
output += smallest + ' / Count: ' + count + ' / ';
if (count < seq.length / 3) { // *** at least 1/3 of the values should be zeros
smallest = findZero(seq, smallest, highest); // recursive call
}
return smallest;
}
function averageZero(seq, zero) { // _Identifies zero value above min and under highest in seq array
let tmp, count = 0, sum = 0;
for (let i in seq) { // Identifies all zeros
tmp = seq[i]
if (tmp >= zero) { // since zero is min zero when called, excludes other small values
if (is(tmp, zero)) {
count ++;
sum += tmp
}
}
}
output += 'Second pass zero count: ' + count + ' / ';
return Math.round(sum / count);
}
function findOne(seq, zero, factor) { // _Identifies one values as zero factors in seq array
let i, tmp, count = 0, sum = 0;
for (i in seq) { // Identifies smallest value above min
tmp = Math.round(seq[i] / zero)
if (tmp >= factor && tmp <= factor + 1) { // keep some margin; i.e. ones can be *2 or *3
count ++;
sum += seq[i];
};
}
if (count){
return Math.round(sum / count);
}
output += 'Could not find any ones with factor ' + factor + ', '
if (factor < MAX_FACTOR) { // safeguard for recursive call
output += 'increasing factor. ';
factor ++
return findOne(seq, zero, factor); // recursive call
}
return 0;
}
function findFullHeader(seq, zero, one, headerLength) { // _Identifies zero value above min and under highest in seq array
let tmp;
let firstHeaderPos = findFirstHeader(seq, zero, one, 0);
let header = new Array()
if (firstHeaderPos == -1) {return header};
header[0] = seq[firstHeaderPos];
for (let i = 1; i < headerLength; i ++) { // Look for consecutive non-0s or 1s following first header value found
tmp = seq[firstHeaderPos + i]
if (!isZeroOrOne(tmp, zero, one)) {
header[i] = tmp; // Found: stores candidate value and loops
} else { // Not found : header values ending before defined header length)
header.push(firstHeaderPos); // returned header[] array will contain values, and first header pos as last element
return header;
}
}
header.push(firstHeaderPos); // returned header[] array will contain values, + first header pos as last element
return header;
}
function findFirstHeader(seq, zero, one, start) { // __Returns position of first non-zero/one value found after start in seq array
let tmp, count = 0, sum = 0;
for (i = start; i < seq.length -17; i ++) { // **** -17 because header found at the end would have no interest
tmp = seq[i]
if (!isZeroOrOne(tmp, zero, one)) {
output += '1st header found at pos. '
output += i + ' / ';
return i;
}
}
if (start == 0) {
output += 'Could not find a first header. / ';
}
return -1;
}
function locateSequences(dec, zero, one, firstHeaderPos, header) { // _input raw decimals[], output array of pos[0,132,264] from header[]
let index = 0;
let seqStart = []; // seqStart will store Seq milestones positions, e.g. [0,132,264]
if (header != -1) {
for (i = firstHeaderPos; i < dec.length - 1; i ++) { //
tmp = dec[i] //
if (is(tmp, header[0])) { // Search for approximate first header value
let check = 1 // Resets check and mark first check
if (i < dec.length - header.length) { // In case end of string would contain a cropped header
for (j = 1; j < header.length; j ++) { // Check all header values against found values
if (is(dec[i+j], header[j])) { // Still only looking for approximations of header
check ++
}
}
if (check = header.length) { // If all headers check have passed
seqStart[index] = i;
index ++;
i = i + header.length;
}
}
}
} // At this stage, seqStart = e.g. [0,132,264],
seqStart[index] = i // last element being the end marker
} else { // If function was called without an identified header,
seqStart[0] = firstHeaderPos; // seqStart = e.g. {0, 264}
seqStart[1] = dec.length - 1 ; //
}
return seqStart;
}
function checkGap(str, highest) { // _Returns it as string if last value in string is a possible Gap
if (str[str.length-1] == highest) { // If very last value of string is equal to highest value
// for (i in str) { // (removed as too restrictive in case of RF)
// if (str[i] >= highest) {return ''}; // If another value is as high as highest, then not a Gap, return ''
// } //
return highest.toString(); // Gap!
}
return ''; // no Gap.
}
function payloadRead(seq, zero, one) { // _input: array of values, output string of binary values
let zeroCount = 0;
let oneCount = 0;
let value, i;
let command = '';
for (i = 0; i < seq.length; i ++) { //
value = seq[i];
if (is(value, zero)) {
command += '0';
zeroCount ++;
} else if (isClose(value, one)){
command += '1';
oneCount ++;
} else {
output += "\n Encountered out-of-range value: " + value + ' at step ' + i + ' in sequence:\n' + seq.join(', ') + '\n';
break;;
}
}
output += " / Zeros: " + zeroCount + " / Ones: " + oneCount + " - ";
return command;
}
function hexCommand(binCommand) { // _input: '101001101011001011000100...' output : 'a6b2c4...'
let len = binCommand.length / 8;
let hex = '';