/
clockServer.c
2870 lines (2477 loc) · 123 KB
/
clockServer.c
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
/*
RedClock Copyright © 2021 Craige Hales
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
thanks to many people that posted bits of information!
*/
// the original server example was modified, a lot, for RedClock. It has this license...
/* HTTP File Server Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/* DIRECTORY
redirect_to_setup_html_handler - redirect to the setup page, used by save/cancel/... commands
bufferedhttpd_resp_sendstr_chunk - buffer send responses - maybe not really needed - moved to clockHtml.c
status_json_handler - AJAX JSON response
status_html_handler HTML for above
recent_wifi_json_handler AJAX JSON response
recent_wifi_html_handler HTML for above
setup_html_handler_real main setup HTML page
directory_html_handler - show the spiffs dir links for downloading, include a script for uploading
set_content_type_from_file - downloads with content type "do the right thing"
get_path_from_uri - URL splitter
download_get_handler - choose what page to deliver based on URL, update parms in NVS, or down load a file
upload_post_handler - upload file...specialize this to handle the timezone update file outside of spiffs
delete_post_handler - delete file
start_file_server - ADD MORE HANDLERS for splitting up pages
urldecode2 - params with < > etc encoded by browser
loadNVSparms - loop through the form data for NVS parm addresses
struct FormData - define the setup page contents
struct KEYVAL - ditto
struct FormPanel - ditto
struct CharStarParm...Uint8Parm - etc,etc - lists of same data-type parms for a for-loop
define emitjson(x) - helpers to emit html and json via *httpd_resp_send*
define emit(x) - ditto
define emitpairnocomma(key,val) - ditto
define emitpair(key,val) - ditto
define emittime - ditto
static const char cBlueTable - css for the tables on the html pages
*/
#include "clockMain.h"
/* Max length a file path can have on storage */
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN) // 15 + 32
/* Max size of an individual file. Make sure this
* value is same as that set in upload_script.html */
#define MAX_FILE_SIZE (1024*1024) // 200 KB. no, 1M
#define MAX_FILE_SIZE_STR "1MB"
/* Scratch buffer size */
#define SCRATCH_BUFSIZE 8192
//////// used by the json writers
#define emitjson(x) do {bufferedhttpd_resp_sendjson_chunk(req,(x));} while(0)
#define emit(x) do {bufferedhttpd_resp_sendstr_chunk(req,(x));} while(0)
#define emitpairnocomma(key,val) do {emit("\n\"");emit((key));emit("\":\"");emitjson((val));emit("\"");} while(0)
#define emitpair(key,val) do {emit(",\n\"");emit((key));emit("\":\"");emitjson((val));emit("\"");} while(0)
#define emittime(unit,code) do {strftime(strftime_buf, sizeof(strftime_buf), (code), &sTimeinfo);emitpair((unit),strftime_buf);} while(0)
////////
/*
I could not figure out how to get the number of items in a static initializer list for an array in a structure.
There are two examples of that here, FormData.vals and FormPanel.keyVals . FormData uses nvals, in the structure,
just before the array. It is hand counted in the initializer. FormPanel is similar. look for the [] in the struct.
how to add new config parms to the HTML screen:
clockmain.c and .h define globals like sClockState.cfgWiFiName . Add to the asprintf/mallocs in main.
Use FormData to associate a key name with the global; the keynames need to be unique and
become parameters after the url/? For text box formdata with zero vals specified, the
textbox value is the value. For radio buttons, with >0 vals, one of those vals (radio button names)
should match the sClockState value. Next, make a KEYVAL to represent a line in a form that holds the
name/value you just made. if it is a radio, the radio will be on multiple lines and needs multiple KEYVALs.
Next, make a FormPanel to combine related stuff; the FormPanel will create the ordering of the KEYVALs.
Finally, add the FormPanel to formPanels. Done!
how to add json dict entries to the HTML status screen:
look for emitpair in status_json_handler. use one of the existing lists of typed variables.
how to add a file to the list of files in the HTML directory screen:
any file in the spiffsdata is copied in by "clear;idf.py -p /dev/ttyUSB0 clean flash monitor" from the shell
(don't upload via the html screen, too easy to forget!)
*/
struct FormData { // represents either a name/value input field (router password) or a named radio group
char const key[8]; // 7 character key, unique across all forms, inserted into form and looked up when form recvd back; a bit human readable
char *(* const currentValue)[2]; // this is malloc memory; the sClockState holds the ptr to the malloc block
short nvals; // this is needed for walking the structure for lookups
char const vals[][8]; // only exist for the radio values; nvals=0 is for a text box
};
static struct FormData const textWifiName = {"wfiname",&sClockState.cfgWiFiName,0,{}};
static struct FormData const textWifiPassword = {"wfipass",&sClockState.cfgWiFiPass,0,{}};
static struct FormData const textMyFiName = {"myname",&sClockState.cfgMyFiName,0,{}};
static struct FormData const textMyFiPassword = {"mypass",&sClockState.cfgMyFiPass,0,{}};
static struct FormData const textBrightHigh = {"brthigh",&sClockState.cfgBrthigh,0,{}}; // goes with bright radio
static struct FormData const textBrightLow = {"brtlow",&sClockState.cfgBrtlow,0,{}};
static struct FormData const textBrightDawn = {"brtdawn",&sClockState.cfgBrtdawn,0,{}};
static struct FormData const textBrightDusk = {"brtdusk",&sClockState.cfgBrtdusk,0,{}};
static struct FormData const textTimeSourceURL= {"sntpurl",&sClockState.cfgSntpUrl,0,{}}; // goes with tzone radio
//static struct FormData const textTimezoneOffset = {"tzoffse",&sClockState.cfgTzoffset,0,{}}; // goes with tzone radio
static struct FormData const textTimezoneRule = {"tzrule",&sClockState.cfgTzrule,0,{}};
static struct FormData const radioWifi = {"wifi",&sClockState.cfgWiFi, 2,{"on","off"} }; // radio to disable wifi
static struct FormData const radioBright = {"bright",&sClockState.cfgBright, 4,{"sense","time","max","min"} };
static struct FormData const radioClock = {"clock",&sClockState.cfgClock, 3,{"gps","sntp","open"} }; // could add fields for sntp server and neighbor wifi
static struct FormData const radioTZone = {"tzone",&sClockState.cfgTzone, 2,{"lookup","rule"} };
static struct FormData const radioDisplay = {"display",&sClockState.cfgDisplay, 2,{"civ","mil"} };
static struct FormData const textLocationNote = {"locnote",&sClockState.cfgLocationNote,0,{}};
static struct FormData const radioNeighborHoodWatch = {"nwatch",&sClockState.cfgNeighborHoodWatch, 8,{"0","1","2","4","8","16","24","32"} };
// this list of FormData is used for save/resore/POST(save,reset,cancel) which all need to
// work through the pairs, once. I started out using the formPanels[], but it struggles
// because the radios are in there multiple times (and has an extra loop over panels.)
static struct FormData const * const formDatas[] = {&textWifiName,&textWifiPassword,&textMyFiName,&textMyFiPassword,
&textBrightHigh,&textBrightLow,&textBrightDawn,&textBrightDusk,
&textTimeSourceURL,/*&textTimezoneOffset,*/&textTimezoneRule,&textLocationNote,
&radioWifi,&radioBright,&radioClock,&radioTZone,&radioNeighborHoodWatch,&radioDisplay
};
enum INPUTTYPE {inputText,inputPassword,inputRadio,inputNothing};// nothing is informative text without input capability
struct KEYVAL { // this is a line in a form, either a single text box, or a single radio button
enum INPUTTYPE const inputType;
short const nthRadioLine; // zero for text, zero for 1st radio line
short const textSize; // width of text input field in characters
struct FormData const * const formData; // a text box or a radio button. nthRadioLine chooses the button.
char const * const title; // tool tip
char const * const label; // visible instructions
};
static struct KEYVAL const kvWifiName = { inputText,0,15,&textWifiName,"Using a wifi is needed if SNTP (or HTML date header) needs to fetch the time. 31 chars max.","Router" };
static struct KEYVAL const kvWifiPass = { inputPassword,0,15,&textWifiPassword,"WPA passwords are 8-63 chars long. The single dot represents the current pw. Leave empty for open WIFI with no WPA password. That is typical of insecure public WIFI routers with a sign-on screen. They might still give the time if HTML date header is selected for Time Source.","Password" };
static struct KEYVAL const kvMyFiName = { inputText,0,15,&textMyFiName,"My internal access point (router) name, 31 chars max. Used for configuration or downloads; it is not bridged to your router.", "AccessPoint" };
static struct KEYVAL const kvMyFiPass = { inputPassword,0,15,&textMyFiPassword,"WPA passwords are 8-63 chars long. The single dot represents the current pw.","Password" };
static struct KEYVAL const kvBrightMax = { inputText,0,5,&textBrightHigh,"Maximum brightness when sensor maxed out or during dawn to dusk.",
"High (prefer 7000)" };
static struct KEYVAL const kvBrightMin = { inputText,0,5,&textBrightLow,"Minimum brightness when sensor dark or during dusk to dawn.",
"Low (prefer 3000)" };
static struct KEYVAL const kvBrightDawn = { inputText,0,5,&textBrightDawn,"Pick Dawn/Dusk above, set the High and Low values. Go bright (high) at this time.",
"Dawn (typically 0700)" };
static struct KEYVAL const kvBrightDusk = { inputText,0,5,&textBrightDusk,"Go dim (low) at this time",
"Dusk (typically 1900)" };
//static struct KEYVAL const kvTzOffset = { inputText,0,5,&textTimezoneOffset,"Offset in minutes: EST is 300 for 5 hours.",
//"Offset minutes (0 = UTC)" };
static struct KEYVAL const kvTzRule = { inputText,0,30,&textTimezoneRule,"Best bet is to look this up. RedClock is using rules from 2020 era. This value is used before the GPS gets a position, so good idea to specify it correctly anyway.","Rule" };
static struct KEYVAL const kvUrlSNTP = { inputText,0,15,&textTimeSourceURL,"pool.ntp.org is a good choice in 2020.",
"Server" };
static struct KEYVAL const kvLocationNote = { inputText,0,30,&textLocationNote,"A small note, typically like \"Room 720\" or \"Kitchen Clock\"","Location Note" };
// the radios, defined here, but not positioned yet
static struct KEYVAL const kvRadioWifi[] = {
{ inputRadio,0,0,&radioWifi,"The Simple Network Time Protocol can get the time from the internet. Turning wifi on also allows configuration via your router, below.","on - allow SNTP or HTML" },
{ inputRadio,1,0,&radioWifi,"Turning off isolates RedClock from internet. The GPS will try to get the time.","off - only GPS" }
};
static struct KEYVAL const kvRadioBright[] = {
{ inputRadio,0,0,&radioBright,"There is a CDS photo sensor that RedClock can use to automatically dim the clock when the lights are dim, etc. The numbers below determine how dim/bright RedClock is when the sensor is at its limits. 3000 to 7000 is linear and covers almost all of the range. 0 to 10000 is the total range and adds a little more on the ends. The clock may flicker in the extended range.","High to Low using sensor" },
{ inputRadio,1,0,&radioBright,"RedClock can dim the clock at night (dusk to dawn) and brighten in the day (dawn to dusk). Set the time, below, and the brightness, above.","Use dawn and dusk..." },
{ inputRadio,2,0,&radioBright,"Clock intensity will always be the high value, above. >5000 would be pretty intense in the dark.","Always use High" },
{ inputRadio,3,0,&radioBright,"Clock intensity will always be the low value, above. <3000 won't be visible in the day time.","Always use Low" }
};
static struct KEYVAL const kvRadioNeighborHoodWatch[] = {
{ inputRadio,0,0,&radioNeighborHoodWatch,"Turn off the scan for SSIDs -- the WIFIs in your neighborhood.","0" },
{ inputRadio,1,0,&radioNeighborHoodWatch,"Scan 1/32. Allows more time for sending status and recent web page updates.","1" },
{ inputRadio,2,0,&radioNeighborHoodWatch,"Scan 2/32. Default. Eventually catches most SSIDs.","2" },
{ inputRadio,3,0,&radioNeighborHoodWatch,"Scan 4/32. Spend a little more time watching for WIFIs.","4" },
{ inputRadio,4,0,&radioNeighborHoodWatch,"Scan 8/32. The more time looking for WIFIs, the less time for updating recent/status.","8" },
{ inputRadio,5,0,&radioNeighborHoodWatch,"Scan 16/32. Catches most SSIDs sooner. Rare finds may dribble in for days.","16" },
{ inputRadio,6,0,&radioNeighborHoodWatch,"Scan 24/32. May impact clock performance; Status/etc page may lag behind.","24" },
{ inputRadio,7,0,&radioNeighborHoodWatch,"scan 32/32. May impact clock performance; Recent/etc page may lag behind.","32" }
};
static struct KEYVAL const kvRadioClock[] = {
{ inputRadio,0,0,&radioClock,"RedClock always uses GPS if there is a signal. Updates once a second. Set this to prevent SNTP and HTML date header.",
"GPS only" },
{ inputRadio,1,0,&radioClock,"RedClock uses SNTP if signed in to a router. Updates every two hours. GPS will override immediately if available. Set this to allow GPS and SNTP and prevent HTML date header. Recommended/default.",
"GPS or SNTP" },
{ inputRadio,2,0,&radioClock,"RedClock might be able to get the time from the sign-in page on a non-WPA WIFI router; it might not be accurate and RedClock only re-asks once an hour. Set this to allow using HTML date header; SNTP or GPS will set a more accurate time if available.",
"GPS, SNTP or HTML date header" }
};
//
// https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
// remove the offset and just use the rule.
//
static struct KEYVAL const kvRadioTZone[] = {
{ inputRadio,0,0,&radioTZone,"RedClock has an internal table to turn GPS locations into posix timezone strings. The table may be wrong within a few miles of a timezone boundary; you might need to specify below. Data from 2019-2020 era.",
"GPS lookup (best if it works)" },
// { inputRadio,1,0,&radioTZone,"If you want UTC time, or a fixed offset from UTC, but NO daylight time change, use this.",
// "Offset from UTC, no daylight" },
{ inputRadio,1,0,&radioTZone,"The rule string is complicated because it specifies when daylight time starts and ends (nth sunday at 2am, etc). Clock firmware is from 2020 era. Select this and specify the rule string below if the built in GPS table gets the wrong answer.",
"<a target='_blank' href='https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv'>Posix Timezone Rule</a>" }
};
static struct KEYVAL const kvRadioDisplay[] = {
{ inputRadio,0,0,&radioDisplay,"There is no AM/PM indicator; look out a window.","12 hour civilian" },
{ inputRadio,1,0,&radioDisplay,"00:01 is just after midnight; 23:59 is just before midnight.","24 hour military" }
};
// the FormPanels are assembled here, with the text and radio lines in desired interleaved order...
struct FormPanel { // the panel box around a form
char const * const legend;
int const nKeyVals;
struct KEYVAL const * const keyVals[];
};
static struct FormPanel const fpWIFI = {"WIFI",4, {&kvRadioWifi[1],&kvRadioWifi[0],&kvWifiName,&kvWifiPass}};
static struct FormPanel const fpMYFI = {"MYFI",2, {&kvMyFiName,&kvMyFiPass}};
static struct FormPanel const fpBright = {"Brightness",8, {
&kvRadioBright[0],// radio
&kvBrightMax, // text
&kvBrightMin,
&kvRadioBright[1],
&kvBrightDawn,
&kvBrightDusk,
&kvRadioBright[2],
&kvRadioBright[3]
}};
static struct FormPanel const fpNeighborHoodWatch = {"Recent WIFI/SSID watcher",8, {
&kvRadioNeighborHoodWatch[0],
&kvRadioNeighborHoodWatch[1],
&kvRadioNeighborHoodWatch[2],
&kvRadioNeighborHoodWatch[3],
&kvRadioNeighborHoodWatch[4],
&kvRadioNeighborHoodWatch[5],
&kvRadioNeighborHoodWatch[6],
&kvRadioNeighborHoodWatch[7]
}};
static struct FormPanel const fpTimeSource = {"Time source",4, {&kvRadioClock[0],&kvRadioClock[1],&kvUrlSNTP,&kvRadioClock[2]}};
static struct FormPanel const fpTimeZone = {"Timezone",3,{&kvRadioTZone[0],&kvRadioTZone[1],&kvTzRule}};
static struct FormPanel const fpDisplay = {"Display",2,{&kvRadioDisplay[0],&kvRadioDisplay[1]}};
static struct FormPanel const fpLocationNote = {"Location",1,{&kvLocationNote}};
// put the form panels in an array for processing
static struct FormPanel const * const formPanels[] = {&fpMYFI,&fpWIFI,&fpBright,&fpDisplay,&fpTimeSource,&fpTimeZone,&fpNeighborHoodWatch,&fpLocationNote};
////////
struct file_server_data {
/* Base path of file storage */
char base_path[ESP_VFS_PATH_MAX + 1];
/* Scratch buffer for temporary storage during file transfer */
char scratch[SCRATCH_BUFSIZE];
};
static const char *TAG = "file_server";
// redirect back to setup page
static esp_err_t redirect_to_setup_html_handler(httpd_req_t *req)
{
httpd_resp_set_status(req, "307 Temporary Redirect");
httpd_resp_set_hdr(req, "Location", "/setup.html");
httpd_resp_send(req, NULL, 0); // Response body can be empty
return ESP_OK;
}
//static char sEpochTime[16];
static char sUpTime[16];
struct CharStarParm {
char const *name;
char **variable;
};
static struct CharStarParm sCharStarParm[] = {
{"router_name",&(sClockState.cfgWiFiName[0])},
{"access_point_name",&(sClockState.cfgMyFiName[0])},
{"location_note",&(sClockState.cfgLocationNote[0])},
{"timezone_build_date",(char **)&sClockState.timezoneBuildDate},
{"chip_model",&sClockState.chip_infoModel}
};
struct CharParm {
char const *name;
char *variable;
};
static struct CharParm sCharParm[] = {
{"clock_up",sUpTime},
{"station_ip",sClockState.ip_addr_txt},
{"base_mac_addr",sClockState.baseMacAddress},
{"access_point_gateway", sClockState.accesspoint_gateway_txt},
{"gps_last_reported_time",sClockState.gpstime},
{"gps_last_reported_date",sClockState.gpsdate},
{"serial_number",sClockState.serialnumber},
{"app_desc_project_name",sClockState.app_desc.project_name},
{"app_desc_version",sClockState.app_desc.version},
{"app_desc_time",sClockState.app_desc.time},
{"app_desc_date",sClockState.app_desc.date},
{"app_desc_idf_ver",sClockState.app_desc.idf_ver},
{"gps_firmware",sClockState.gpsGPTXT},
{"GPGLL",sClockState.gpsGPGLL},
{"GPRMC",sClockState.gpsGPRMC},
{"GPVTG",sClockState.gpsGPVTG},
{"GPGGA",sClockState.gpsGPGGA},
{"GPGSA",sClockState.gpsGPGSA}
};
struct DoubleParm {
char const *name;
char const format[8];
double *variable;
};
static struct DoubleParm sDoubleParm[] = {
{"lat","%1.6f",&sClockState.lat},
{"lon","%1.6f",&sClockState.lon},
{"ambient","%1.1f",&sClockState.smoothTrueAmbient},
{"temperature","%1.1f",&sClockState.temperature},
{"pressure","%1.1f",&sClockState.pressure},
{"humidity","%1.1f",&sClockState.humidity}
};
struct UintParm {
char const *name;
char const format[4];
unsigned int *variable;
};
static struct UintParm sUintParm[] = {
{"router_disconnects","%u",&sClockState.nDisconnects},
{"chip_features","%u",&sClockState.chip_info.features},
{"cpu_frequency","%u",&sClockState.rcf.freq_mhz}
};
struct IntParm {
char const *name;
char const format[4];
int *variable;
};
static struct IntParm sIntParm[] = {
{"ondelay","%d",&sClockState.onDelay},
{"offdelay","%d",&sClockState.offDelay},
{"crystal_frequency","%d",&sClockState.xtalfreq}
};
struct Uint8Parm {
char const *name;
char const format[4];
uint8_t *variable;
};
static struct Uint8Parm sUint8Parm[] = {
{"chip_revision","%u",&sClockState.chip_info.revision},
{"chip_cores","%u",&sClockState.chip_info.cores}
};
static unsigned int getChannel(){
uint8_t primary;
wifi_second_chan_t second;
esp_wifi_get_channel(&primary, &second);
return primary;
}
static struct timeval sTime_now;
static unsigned int epochtime() {
gettimeofday(&sTime_now, NULL);
// https://www.delftstack.com/howto/cpp/how-to-get-time-in-milliseconds-cpp/
// time_t msecs_time = (sTime_now.tv_sec * 1000) + (sTime_now.tv_usec / 1000);
// cout << "seconds since epoch: " << sTime_now.tv_sec << endl;
// cout << "milliseconds since epoch: " << msecs_time << endl << endl;
// printf("gettimeofday=%ld+%ld/1e6 time=%ld\n",sTime_now.tv_sec,sTime_now.tv_usec,time(NULL));
return sTime_now.tv_sec;
// was: return (unsigned int)time(NULL); // the 32-bit *signed* time, unsigned for the CallParm data structure below
}
static unsigned int epochmicro() {
return sTime_now.tv_usec;
}
// rework avail memory https://www.esp32.com/viewtopic.php?f=19&t=23609
// total_free_bytes must be called first to init...
static multi_heap_info_t sMHIT;
static unsigned int total_free_bytes() { // Total free bytes in the heap. Equivalent to multi_free_heap_size().
heap_caps_get_info(&sMHIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
return sMHIT.total_free_bytes;
}
static unsigned int total_allocated_bytes() { // Total bytes allocated to data in the heap.
return sMHIT.total_allocated_bytes;
}
static unsigned int largest_free_block() { // Size of largest free block in the heap. This is the largest malloc-able size.
return sMHIT.largest_free_block;
}
static unsigned int minimum_free_bytes() { // Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size().
return sMHIT.minimum_free_bytes;
}
static unsigned int allocated_blocks() { // Number of (variable size) blocks allocated in the heap.
return sMHIT.allocated_blocks;
}
static unsigned int free_blocks() { // Number of (variable size) free blocks in the heap.
return sMHIT.free_blocks;
}
static unsigned int total_blocks() { // Total number of (variable size) blocks in the heap.
return sMHIT.total_blocks;
}
struct CallParm {
char const *name;
char const format[4];
unsigned int (*callee)();
};
static struct CallParm sCallParm[] = { // https://www.esp32.com/viewtopic.php?f=19&t=23609&p=84887#p84887 has heap_caps_get_info which includes largest free block; that should replace esp_get_free_heap_size (I only have internal heap?)
{"available_heap","%u",esp_get_free_heap_size},
{"available_internal_heap","%u",esp_get_free_internal_heap_size},
{"minimum_heap","%u",esp_get_minimum_free_heap_size},
{"heap_caps_total_free_bytes","%u",total_free_bytes}, // must be before the following to init the static struct
{"heap_caps_total_allocated_bytes","%u",total_allocated_bytes},
{"heap_caps_largest_free_block","%u",largest_free_block},
{"heap_caps_minimum_free_bytes","%u",minimum_free_bytes},
{"heap_caps_allocated_blocks","%u",allocated_blocks},
{"heap_caps_free_blocks","%u",free_blocks},
{"heap_caps_total_blocks","%u",total_blocks},
{"wifi_channel","%u",getChannel},
{"epoch_time","%u",epochtime}, // this is a time_t, but unsigned *might* go past 2038
{"epoch_micro","%d",epochmicro} // this is a long and 'could' be negative
};
struct FlagParm {
char const *name;
char const format[4];
unsigned int *variable;
unsigned int flag;
};
static struct FlagParm sFlagParm[] = {
{"chip_features_embedded_flash","%u",&sClockState.chip_info.features,CHIP_FEATURE_EMB_FLASH},
{"chip_features_wifi_bgn","%u",&sClockState.chip_info.features,CHIP_FEATURE_WIFI_BGN},
{"chip_features_bluetooth_le","%u",&sClockState.chip_info.features,CHIP_FEATURE_BLE},
{"chip_features_bluetooth_classic","%u",&sClockState.chip_info.features,CHIP_FEATURE_BT}
};
struct TimeParm {
char const *name;
char const *format;
};
static struct TimeParm sTimeParm[] = {
{"time","%l:%M:%S %p %Z"},
{"day_of_week_name","%A"},
{"day_of_week_number","%w"},
{"month_name","%B"},
{"month_number","%m"},
{"day_of_month","%d"},
{"year","%Y"},
{"hour_24","%H"},//leading zero
{"hour_12","%l"},//leading blank
{"AM_PM","%p"},
{"minutes","%M"},
{"seconds","%S"},
{"timezone","%Z"},
{"timezone_offset","%z"},
{"day_of_year","%j"},
{"ISO_week","%V"}
};
struct ScaledTimeParm {
char const *name;
char const format[8];
uint64_t *prevGetTime;
};
static struct ScaledTimeParm sScaledTimeParm[] = {
{"seconds_since_definitive","%llu",&sClockState.tickSecondOfDefinitiveTime},
{"seconds_since_definitiveGPS","%llu",&sClockState.tickSecondOfDefinitiveTimeGPS},
{"seconds_since_definitiveSNTP","%llu",&sClockState.tickSecondOfDefinitiveTimeSNTP}
};
static struct tm sTimeinfo;
static char *fixupZero(char *in,bool comma) {
static char out[16];
strcpy(out,comma?",":"");// out is initialized, either to ,nul or nul
if(strlen(in)){// there is some non-nul text
while(*in == '0')
in+=1; // json chokes on leading zeros
if(strlen(in))
strlcat(out,in,sizeof out);// there is text remaining after removing leading zeros
else
strlcat(out,"0",sizeof out);// restore a zero
}
else {// the txt was nul, missing value...
strlcat(out,"0",sizeof out);// I think this is OK; snr is ~20 for low in sky, ~40 for high, "" for very low. change to zero.
}
return out;
}
static void emitsats(httpd_req_t *req){
// the GPS satellite structure in struct SATRECORD satRecords[SatsPerGSV*MaxGSVRecs] sClockState.satRecords
// prn,el,az,snr
const int BUFSIZE=16+DIM(sClockState.satRecords)*8;
char *prnbuffer = (char *)malloc(BUFSIZ);// 8 is 6 and a comma and ?
strcpy(prnbuffer,",\n\"sat_prn\":[");
char *elbuffer = (char *)malloc(BUFSIZE);
strcpy(elbuffer,",\n\"sat_el\":[");
char *azbuffer = (char *)malloc(BUFSIZE);
strcpy(azbuffer,",\n\"sat_az\":[");
char *snrbuffer = (char *)malloc(BUFSIZE);
strcpy(snrbuffer,",\n\"sat_snr\":[");
// bool gotit = xSemaphoreTake(sClockState.satSemaphore, 1000 / portTICK_RATE_MS/*portMAX_DELAY block forever*/ ) == pdTRUE;
// if(!gotit) printf("clockServer.c did not get satSemaphore\n"); else printf("clockServer.c running\n");
for(int i=0;i<DIM(sClockState.satRecords);i+=1){
if(sClockState.satRecords[i].prn[0]==0)
break;
strlcat(prnbuffer,fixupZero(sClockState.satRecords[i].prn,i>0),BUFSIZE);
strlcat(elbuffer,fixupZero(sClockState.satRecords[i].el,i>0),BUFSIZE);
strlcat(azbuffer,fixupZero(sClockState.satRecords[i].az,i>0),BUFSIZE);
strlcat(snrbuffer,fixupZero(sClockState.satRecords[i].snr,i>0),BUFSIZE);
}
// printf("clockServer.c done\n");
// xSemaphoreGive(sClockState.satSemaphore);
strlcat(prnbuffer,"]",BUFSIZE);
strlcat(elbuffer,"]",BUFSIZE);
strlcat(azbuffer,"]",BUFSIZE);
strlcat(snrbuffer,"]",BUFSIZE);
emit(prnbuffer);
emit(elbuffer);
emit(azbuffer);
emit(snrbuffer);
free(prnbuffer);
free(elbuffer);
free(azbuffer);
free(snrbuffer);
}
static esp_err_t status_json_handler( httpd_req_t *req ) {
const char *quest = strchr(req->uri, '?'); // status.json?time
bool onlyTime = quest && strcmp(quest,"?time")==0;
char strftime_buf[64];
httpd_resp_set_type(req, "application/json");
emit( "{\n\"version\":\"1\""); // after this, all pairs begin with a comma
time_t now;
time(&now); // TZ was set in main, once
localtime_r(&now, &sTimeinfo); // _r is the threadsafe version, now, a time_t, unpacked into sTimeinfo, a tm
for(int i = 0; i < DIM(sTimeParm); i+=1 ){
emittime(sTimeParm[i].name, sTimeParm[i].format);
}
//sprintf(sEpochTime,"%d",(int)time(NULL));
uint64_t t = esp_timer_get_time()/1000000;
int d = (int)(t/(24*60*60));
t = t - d * 24*60*60;
int h = (int)(t/(60*60));
t = t - h * 60*60;
int m = (int)(t/60);
t = t - m * 60;
int s = (int)t;
sprintf(sUpTime,"%dd %dh %dm %ds",d,h,m,s);
if(onlyTime){
emitpair(sCharParm[0].name,sCharParm[0].variable);//uptime
}
else {
for(int i = 0; i<DIM(sCharStarParm); i+=1){
emitpair(sCharStarParm[i].name,*sCharStarParm[i].variable);
}
for(int i = 0; i<DIM(sCharParm); i+=1){
//DumpHex(sCharParm[i].variable, 0, sizeof(sClockState.gpsGPGLL));
emitpair(sCharParm[i].name,sCharParm[i].variable);
}
for(int i = 0; i<DIM(sDoubleParm); i+=1){
sprintf(strftime_buf,sDoubleParm[i].format,*sDoubleParm[i].variable);
emitpair(sDoubleParm[i].name,strftime_buf);
}
for(int i = 0; i<DIM(sUintParm); i+=1){
sprintf(strftime_buf,sUintParm[i].format,*sUintParm[i].variable);
emitpair(sUintParm[i].name,strftime_buf);
}
for(int i = 0; i<DIM(sIntParm); i+=1){
sprintf(strftime_buf,sIntParm[i].format,*sIntParm[i].variable);
emitpair(sIntParm[i].name,strftime_buf);
}
for(int i = 0; i<DIM(sUint8Parm); i+=1){
sprintf(strftime_buf,sUint8Parm[i].format,*sUint8Parm[i].variable);
emitpair(sUint8Parm[i].name,strftime_buf);
}
for(int i = 0; i<DIM(sFlagParm); i+=1){
sprintf(strftime_buf,sFlagParm[i].format,( *sFlagParm[i].variable & sFlagParm[i].flag ) != 0);
emitpair(sFlagParm[i].name,strftime_buf);
}
for(int i = 0; i<DIM(sCallParm); i+=1){
sprintf(strftime_buf,sCallParm[i].format,sCallParm[i].callee());
emitpair(sCallParm[i].name,strftime_buf);
}
for(int i = 0; i<DIM(sScaledTimeParm); i+=1){
sprintf(strftime_buf,sScaledTimeParm[i].format,esp_timer_get_time()/1000000 - *sScaledTimeParm[i].prevGetTime);
emitpair(sScaledTimeParm[i].name,strftime_buf);
}
emitsats(req);
/*
typedef enum {
RTC_CPU_FREQ_XTAL = 0, //!< Main XTAL frequency
RTC_CPU_FREQ_80M = 1, //!< 80 MHz
RTC_CPU_FREQ_160M = 2, //!< 160 MHz
RTC_CPU_FREQ_240M = 3, //!< 240 MHz
RTC_CPU_FREQ_2M = 4, //!< 2 MHz
} rtc_cpu_freq_t;
*/
//static char frqtab[][4] = {"XTL","80","160","240","2Mh"};
//rtc_xtal_freq_t freq = rtc_clk_xtal_freq_get();// 40
// sprintf(strftime_buf,"%d",sClockState.xtalfreq);
// emitpair("crystal_frequency",strftime_buf); // the rtc_xtal_freq_t enum is the value sClockState.freq
//
/*
typedef struct rtc_cpu_freq_config_s {
rtc_cpu_freq_src_t source; //!< The clock from which CPU clock is derived
uint32_t source_freq_mhz; //!< Source clock frequency
uint32_t div; //!< Divider, freq_mhz = source_freq_mhz / div
uint32_t freq_mhz; //!< CPU clock frequency
} rtc_cpu_freq_config_t;
*/
}
emit( "\n}");
emit( NULL);
return ESP_OK;
}
static const char cBlueTable[] = {
"<style>"
// "form {"
// "vertical-align: top;" // allow the forms to flow across and
// "display: inline-block;" // stick to top
// "}"
// "input {"
// "margin-top: 5px;" // space between the text fields
// "margin-bottom: 1px;"
// "}"
// "button {"
// "margin-top: 15px;" // space above and
// "margin-left: 5px;" // between the
// "margin-right: 5px;" // button pair
// "}"
// ".tg {border-collapse:collapse;border-spacing:0;}"
// ".tg td{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;"
// " overflow:hidden;padding:10px 5px;word-break:normal;}"
// ".tg th{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;"
// " font-weight:normal;overflow:hidden;padding:10px 5px;word-break:normal;}"
// ".tg .tg-0lax{text-align:left;vertical-align:top}"
// ".tg .tg-sjuo{background-color:#C2FFD6;text-align:left;vertical-align:top}"
// https://html-css-js.com/html/generator/form/ -- not using for status
// https://divtable.com/table-styler/ -- the blue table...pretty cool with even/odd support and minimal gradient
"table.blueTable {"
" font-family: Arial, Helvetica, sans-serif;"
" border: 1px solid #1C6EA4;"
" background-color: #FFFFFF;"
//" width: 100%;"
" text-align: left;"
" border-collapse: collapse;"
"}"
"table.blueTable th {"
" border: 1px solid #AAAAAA;"
" padding: 3px 3px;" // more
"}"
"table.blueTable td {"
" border: 1px solid #AAAAAA;"
" padding: 1px 3px;" // less
" white-space: pre;" // for the gps_firmware glob with embedded \n (maybe should be in tbody?)
"}"
"table.blueTable tbody td {"
" font-size: 13px;"
" color: #000000;"
"}"
"table.blueTable tr:nth-child(even) {"
" background: #E0F4FF;"
"}"
"table.blueTable thead {"
" background: #1C6EA4;"
" background: -moz-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%);"
" background: -webkit-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%);"
" background: linear-gradient(to bottom, #5592bb 0%, #327cad 66%, #1C6EA4 100%);"
" border-bottom: 2px solid #444444;"
"}"
"table.blueTable thead th {"
" font-size: 15px;"
" font-weight: bold;"
" color: #FFFFFF;"
" text-align: left;"
" border-left: 2px solid #D0E4F5;"
"}"
"table.blueTable thead th:first-child {"
" border-left: none;"
"}"
"table.blueTable tfoot td {"
" font-size: 14px;"
"}"
"table.blueTable tfoot .links {"
" text-align: right;"
"}"
"table.blueTable tfoot .links a{"
" display: inline-block;"
" background: #1C6EA4;"
" color: #FFFFFF;"
" padding: 2px 8px;"
" border-radius: 5px;"
"}"
"</style>"
};
static esp_err_t status_html_handler( httpd_req_t *req ) {
// char strftime_buf[64];
/* Send HTML file header */
emit( "<!DOCTYPE html>"
"<head><meta charset='UTF-8'><title>RedClock Status</title>"
"<script src='js/jquery-3.5.1.min.js' integrity='sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==' crossorigin='anonymous'></script>"
);
emit(cBlueTable);
emit(
"</head><html>"
"<body style='background-color: white'>"
"<span style='font-size:300%;'>RedClock Status <span id='clocktime' style='color: #cc3333;'>clocktime</span> </span>"
);
emit("<div>");
//#define ADDROW1(xxx) "<tr><td class='tg-0lax'>" #xxx "</td><td class='tg-0lax'><span id='" #xxx "'>" "000000" "</span> </td></tr>"
//#define ADDROW0(xxx) "<tr><td class='tg-sjuo'>" #xxx "</td><td class='tg-sjuo'><span id='" #xxx "'>" "000000" "</span> </td></tr>"
emit(
"<br>"
"<div>" // extras that also slide about
"<span style='font-size:75%;'>"
"Lat(<span id='clocklat'>clocklat</span>) " // labels on link <a> tag
"Lon(<span id='clocklon'>clocklon</span>) "
"</span>"
"<a id='linkToGoogle' target='_blank' href='https://www.google.com/maps/@0,0,5z'>"
// );
// sprintf(strftime_buf,"%1.6f,%1.6f,17z'>",sClockState.lat,sClockState.lon); // fill in the parms in the google url, end the open <a> tag
// emit(strftime_buf);
// emit(
" map</a> " // finsh label, close </a> tag
"<br><span style='font-size:150%;'>up <span id='clock_up'>clock_up</span></span>"
"<br><span style='font-size:100%;'><a target='_blank' href='https://www.epochconverter.com/'>epoch</a> <span id='epoch_time'>epoch_time</span></span>"
"<br><span style='font-size:100%;'><span id='router_name'>router</span> "
"<a target='_blank' href='xx' id='station_ip1'><span id='station_ip2'>station_ip</span></a>"
"</span>"
"<br><span style='font-size:100%;'><span id='access_point_name'>my fi name</span>/<span id='serial_number'>serial_number</span> "
"<a target='_blank' href='xx' id='access_point_gateway1'><span id='access_point_gateway2'>access_point_gateway</span></a>"
"</span>"
"<br><br>"
"</div>" // end of the extras
"<div>This data is available <a href='/status.json' target='_blank'>here</a></div><br><br>"
"<table class='blueTable'>"
"<thead>"
"<tr><th>Key</th><th>Value</th></tr>"
"</thead>"
"<tbody id='status'>"
// "<tr><td>some</td><td>content</td></tr>"
// "<tr><td>to be</td><td>removed</td></tr>"
"</tbody>"
"</table>"
);
emit(
"</div>" // end of the .grid
);
emit(
"<script>"
"var interval = 1000; // 1000 = 1 second"
"\n"
"function doAjax() { // https://stackoverflow.com/questions/20371695/execute-an-ajax-request-every-second"
"\n" // https://stackoverflow.com/users/157247/t-j-crowder
//"console.log('started');"
"\n"
"$.ajax({"
"\n"
"type: 'GET',"
"\n"
"url: 'status.json',"
"\n"
"//data: $(this).serialize(),"
"\n"
"dataType: 'json',"
"\n"
"success: function (data) {"
"\n"
"$('#status').empty();"
"\n"
// "let even = 0;"
// "\n"
"for (let key in data) {"
"\n"
"$('#status').append('<tr><td>' + key + '</td><td>' + data[key] + '</td></tr>');"
// "if (even==0) {"
// "\n"
// "even=1;"
// "\n"
// "$('#status').append('<tr><td class=\"tg-0lax\">' + key + '</td><td class=\"tg-0lax\">' + data[key] + '</td></tr>');"
// "\n"
// "} else {"
// "\n"
// "even=0;"
// "\n"
// "$('#status').append('<tr><td class=\"tg-sjuo\">' + key + '</td><td class=\"tg-sjuo\">' + data[key] + '</td></tr>');"
// "\n"
// "}"
"\n"
"}"
"\n"
"$('#clocktime').html(data.time);"
"\n"
"$('#clocklat').html(data.lat);"
"\n"
"$('#clocklon').html(data.lon);"
"\n"
"$('#linkToGoogle').attr('href','https://www.google.com/maps/@'+data.lat+','+data.lon+',17z');"
"\n"
"$('#clock_up').html(data.clock_up);"
"\n"
"$('#epoch_time').html(data.epoch_time);"
"\n"
"$('#router_name').html(data.router_name);"
"\n"
"$('#station_ip1').attr('href', 'http://' + data.station_ip);"
"\n"
"$('#station_ip2').html(data.station_ip);"
"\n"
"$('#access_point_name').html(data.access_point_name);"
"$('#serial_number').html(data.serial_number);"
"\n"
"$('#access_point_gateway1').attr('href', 'http://' + data.access_point_gateway);"
"\n"
"$('#access_point_gateway2').html(data.access_point_gateway);"
"\n"
"},"
"\n"
"complete: function (data) {"
"\n"
"setTimeout(doAjax, interval);"
"\n"
"}"
"\n"
"});"
"\n"
"}"
"\n"
"console.log('starting');"
"\n"
"setTimeout(doAjax, interval);"
"\n"
"</script>"
);
// end of a chunk, the commented out bit is the file server code. way below is the close...
/* Send remaining chunk of HTML file to complete it */
emit( "</body></html>");
/* Send empty chunk to signal HTTP response completion */
emit( NULL);
return ESP_OK;
}
static esp_err_t recent_wifi_json_handler( httpd_req_t *req ) {
char strftime_buf[64];
httpd_resp_set_type(req, "application/json");
emit( "{\n");
emitpairnocomma("version","1");
emitpair("scanrate",sClockState.cfgNeighborHoodWatch[0]);
emitpair("location_note",sClockState.cfgLocationNote[0]);
//sCallParm ("available_heap","available_internal_heap","minimum_heap"), sDoubleParm ("ambient","temperature","pressure","humidity")
for(int i = 0; i<DIM(sCallParm); i+=1){
sprintf(strftime_buf,sCallParm[i].format,sCallParm[i].callee());
emitpair(sCallParm[i].name,strftime_buf);
}
for(int i = 0; i<DIM(sDoubleParm); i+=1){
sprintf(strftime_buf,sDoubleParm[i].format,*sDoubleParm[i].variable);
emitpair(sDoubleParm[i].name,strftime_buf);
}
emitsats(req);
emit(",\n\"wifi\":[\n");
uint64_t now = esp_timer_get_time()/1000000;
int ndone = 0;
for(int i = 0; i<DIM(sClockState.scanRecords); i+=1){
char buf[32];
static uint8_t emptybssid[6] = {0};
if(memcmp(emptybssid,sClockState.scanRecords[i].r.bssid,sizeof(emptybssid))!=0 ||
strlen((char *)sClockState.scanRecords[i].r.ssid)>0 ){
if(ndone) {
emit(",");
}
ndone+=1;
emit("{");
snprintf(buf,sizeof(buf),"%d",ndone);
emitpairnocomma("n",buf);
/*
struct wifi_ap_record_t // Description of a WiFi AP.
// Public Members
uint8_t bssid[6] // MAC address of AP
uint8_t ssid[33] // SSID of AP
uint8_t primary // channel of AP
wifi_second_chan_t second // secondary channel of AP
int8_t rssi // signal strength of AP
wifi_auth_mode_t authmode // authmode of AP
wifi_cipher_type_t pairwise_cipher // pairwise cipher of AP
wifi_cipher_type_t group_cipher // group cipher of AP
wifi_ant_t ant // antenna used to receive beacon from AP
uint32_t phy_11b : 1 // bit: 0 flag to identify if 11b mode is enabled or not
uint32_t phy_11g : 1 // bit: 1 flag to identify if 11g mode is enabled or not
uint32_t phy_11n : 1 // bit: 2 flag to identify if 11n mode is enabled or not
uint32_t phy_lr : 1 // bit: 3 flag to identify if low rate is enabled or not
uint32_t wps : 1 // bit: 4 flag to identify if WPS is supported or not
uint32_t ftm_responder : 1 // bit: 5 flag to identify if FTM is supported in responder mode
uint32_t ftm_initiator : 1 // bit: 6 flag to identify if FTM is supported in initiator mode
uint32_t reserved : 25 //bit: 7..31 reserved