9-29 commit
This commit is contained in:
parent
0040c64da8
commit
660fa5f1c7
14
.clang-format
Normal file
14
.clang-format
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 100
|
||||||
|
BraceWrapping:
|
||||||
|
AfterFunction: false
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterClass: false
|
||||||
|
AfterEnum: false
|
||||||
|
AfterStruct: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterUnion: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
IndentBraces: false
|
||||||
10
ToDo.txt
10
ToDo.txt
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
1) OTA switch to Minio
|
|
||||||
|
|
||||||
2) Global variable ( Circular or Linear LEDS)
|
|
||||||
|
|
||||||
3) Long Hold to switch
|
|
||||||
|
|
||||||
4) Fix white flasing at booth
|
|
||||||
|
|
||||||
5)
|
|
||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"profile": "big-roam",
|
||||||
"mode": "roamer",
|
"mode": "roamer",
|
||||||
|
"limited-mode": false,
|
||||||
"button": 0,
|
"button": 0,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
@ -58,7 +60,7 @@
|
|||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0,
|
"button-index": 0,
|
||||||
"min": 0.0,
|
"min": 1.0,
|
||||||
"max": 100.0,
|
"max": 100.0,
|
||||||
"step": 1.5,
|
"step": 1.5,
|
||||||
"rate": 30.0,
|
"rate": 30.0,
|
||||||
@ -66,10 +68,10 @@
|
|||||||
"vision": true
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": false,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1,
|
"button-index": 1,
|
||||||
"min": 0.0,
|
"min": 1.0,
|
||||||
"max": 100.0,
|
"max": 100.0,
|
||||||
"step": 1.5,
|
"step": 1.5,
|
||||||
"rate": 30.0,
|
"rate": 30.0,
|
||||||
@ -106,9 +108,9 @@
|
|||||||
"size": 168,
|
"size": 168,
|
||||||
"chip": "SK6812",
|
"chip": "SK6812",
|
||||||
"rgb-order": "rgb",
|
"rgb-order": "rgb",
|
||||||
"shift":-5,
|
"shift":-42,
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"bright": 200,
|
"bright": 240,
|
||||||
"power-div": 0,
|
"power-div": 0,
|
||||||
"i2s-ch": 0,
|
"i2s-ch": 0,
|
||||||
"core": 1
|
"core": 1
|
||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"id": "custom",
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
|
"limited-mode": false,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -54,14 +56,26 @@
|
|||||||
"ramp-lights":
|
"ramp-lights":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mode": 0,
|
"id": "flare-posh",
|
||||||
"buttons":
|
"mode": 0,
|
||||||
|
"limited-mode": false,
|
||||||
|
"buttons":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"en": true
|
"en": true
|
||||||
@ -56,12 +58,24 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": false,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
@ -1,124 +0,0 @@
|
|||||||
{
|
|
||||||
"buttons":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"en": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"en": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"en": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pwmout":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"en": true,
|
|
||||||
"freq": 250,
|
|
||||||
"min": 0,
|
|
||||||
"max": 100,
|
|
||||||
"default": 0,
|
|
||||||
"vision": false,
|
|
||||||
"deltarate": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"en": true,
|
|
||||||
"freq": 250,
|
|
||||||
"min": 0,
|
|
||||||
"max": 100,
|
|
||||||
"default": 0,
|
|
||||||
"vision": false,
|
|
||||||
"deltarate": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"en": true,
|
|
||||||
"freq": 250,
|
|
||||||
"min": 0,
|
|
||||||
"max": 100,
|
|
||||||
"default": 0,
|
|
||||||
"vision": true,
|
|
||||||
"deltarate": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"en": true,
|
|
||||||
"freq": 250,
|
|
||||||
"min": 0,
|
|
||||||
"max": 100,
|
|
||||||
"default": 0,
|
|
||||||
"vision": true,
|
|
||||||
"deltarate": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ramp-lights":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"en": true,
|
|
||||||
"relay-index": 0,
|
|
||||||
"button-index": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"en": false,
|
|
||||||
"relay-index": 1,
|
|
||||||
"button-index": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"oled": {
|
|
||||||
"en": false,
|
|
||||||
"height": 64,
|
|
||||||
"width": 128
|
|
||||||
},
|
|
||||||
"t-sensor": {
|
|
||||||
"en": true,
|
|
||||||
"addr": 72,
|
|
||||||
"sp1": 85.0,
|
|
||||||
"fan-pwr1": 50.0,
|
|
||||||
"sp2": 90.0,
|
|
||||||
"fan-pwr2": 100.0,
|
|
||||||
"hyst": 1.0,
|
|
||||||
"relay": 3,
|
|
||||||
"interval": 5000
|
|
||||||
},
|
|
||||||
"adc": {
|
|
||||||
"ain1_factor": 1.0
|
|
||||||
},
|
|
||||||
"status-led":{
|
|
||||||
"interval": 250
|
|
||||||
},
|
|
||||||
"strips":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"en": true,
|
|
||||||
"size": 138,
|
|
||||||
"chip": "SK6812",
|
|
||||||
"rgb-order": "rgb",
|
|
||||||
"shift":-27,
|
|
||||||
"offset": 0,
|
|
||||||
"power-div": 0,
|
|
||||||
"i2s-ch": 0,
|
|
||||||
"core": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"en": true,
|
|
||||||
"size": 30,
|
|
||||||
"chip": "SK6812",
|
|
||||||
"rgb-order": "rgb",
|
|
||||||
"shift":-27,
|
|
||||||
"offset": 0,
|
|
||||||
"power-div": 0,
|
|
||||||
"i2s-ch": 0,
|
|
||||||
"core": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rx433": {
|
|
||||||
"en": "true"
|
|
||||||
},
|
|
||||||
"tx433": {
|
|
||||||
"en": "true"
|
|
||||||
},
|
|
||||||
"ble":{
|
|
||||||
"en": true,
|
|
||||||
"name": "Ata_Lights",
|
|
||||||
"core": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"id": "m-lumia",
|
||||||
|
"limited-mode": false,
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
@ -56,12 +58,24 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"id": "m1",
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
@ -56,12 +57,24 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"id": "marquee",
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
|
"limited-mode": false,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -56,12 +58,24 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"id": "orig-roam",
|
||||||
"mode": "roamer",
|
"mode": "roamer",
|
||||||
|
"limited-mode": false,
|
||||||
"button": 0,
|
"button": 0,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
@ -58,19 +60,21 @@
|
|||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0,
|
"button-index": 0,
|
||||||
"min": 0.0,
|
"min": 1.0,
|
||||||
"max": 100.0,
|
"max": 100.0,
|
||||||
"step": 1.5,
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
"skip-count": 5,
|
"skip-count": 5,
|
||||||
"vision": true
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": false,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1,
|
"button-index": 1,
|
||||||
"min": 0.0,
|
"min": 1.0,
|
||||||
"max": 100.0,
|
"max": 100.0,
|
||||||
"step": 1.5,
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
"skip-count": 5,
|
"skip-count": 5,
|
||||||
"vision": true
|
"vision": true
|
||||||
}
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"id": "spectra",
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
|
"limited-mode": false,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -56,12 +58,24 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": false,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"id": "sport",
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
|
"limited-mode": false,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -56,12 +58,24 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"profile": "stik",
|
||||||
"mode": "stik",
|
"mode": "stik",
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
@ -56,13 +57,25 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
}
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
"en": false,
|
"en": false,
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"profile": "testbooth",
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
@ -54,24 +55,26 @@
|
|||||||
"ramp-lights":
|
"ramp-lights":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"en": false,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0,
|
"button-index": 0,
|
||||||
"min": 5.0,
|
"min": 1.0,
|
||||||
"max": 100.0,
|
"max": 100.0,
|
||||||
"step": 1.5,
|
"step": 1.5,
|
||||||
"skip-count": 5,
|
"rate": 30.0,
|
||||||
"vision": true
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": false,
|
"en": true,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1,
|
"button-index": 1,
|
||||||
"min": 5.0,
|
"min": 1.0,
|
||||||
"max": 100.0,
|
"max": 100.0,
|
||||||
"step": 1.5,
|
"step": 1.5,
|
||||||
"skip-count": 5,
|
"rate": 30.0,
|
||||||
"vision": true
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"id": "xl-lumia",
|
||||||
"mode": 0,
|
"mode": 0,
|
||||||
|
"limited-mode": false,
|
||||||
"buttons":
|
"buttons":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -56,12 +58,24 @@
|
|||||||
{
|
{
|
||||||
"en": true,
|
"en": true,
|
||||||
"relay-index": 0,
|
"relay-index": 0,
|
||||||
"button-index": 0
|
"button-index": 0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"en": true,
|
"en": false,
|
||||||
"relay-index": 1,
|
"relay-index": 1,
|
||||||
"button-index": 1
|
"button-index": 1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 100.0,
|
||||||
|
"step": 1.5,
|
||||||
|
"rate": 30.0,
|
||||||
|
"skip-count": 5,
|
||||||
|
"vision": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"oled": {
|
"oled": {
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "ATALIGHTS",
|
"name": "SP110E-ATA",
|
||||||
"unique": true,
|
"unique": true,
|
||||||
"lights-service": "FFE0",
|
"lights-service": "FFE0",
|
||||||
"lights-char": "FFE1",
|
"lights-char": "FFE1",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"boardfile": "/boards/board15.json",
|
"boardfile": "/boards/board15.json",
|
||||||
"configfile": "/booths/roamer-big.json"
|
"configfile": "/booths/big-roam.json"
|
||||||
}
|
}
|
||||||
@ -589,67 +589,67 @@ IMPORTANT NOTES:
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
bleDevice = await navigator.bluetooth.requestDevice({
|
bleDevice = await navigator.bluetooth.requestDevice({
|
||||||
filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }],
|
filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }],
|
||||||
optionalServices:[BLE_SERVICE_UUID]
|
optionalServices:[BLE_SERVICE_UUID]
|
||||||
});
|
});
|
||||||
|
|
||||||
const server = await bleDevice.gatt.connect();
|
const server = await bleDevice.gatt.connect();
|
||||||
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
|
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
|
||||||
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
|
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
|
||||||
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
|
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
|
||||||
await bleCharacteristic2.startNotifications();
|
await bleCharacteristic2.startNotifications();
|
||||||
let prevProgressFound = false;
|
let prevProgressFound = false;
|
||||||
bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
|
bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
|
||||||
try{
|
try{
|
||||||
//const view = e.target.value; // DataView
|
//const view = e.target.value; // DataView
|
||||||
//const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
//const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
||||||
|
|
||||||
// Debug info
|
// Debug info
|
||||||
//let debugInfo = `Received ${bytes.length} bytes: `;
|
//let debugInfo = `Received ${bytes.length} bytes: `;
|
||||||
//for (let i = 0; i < bytes.length; i++) {
|
//for (let i = 0; i < bytes.length; i++) {
|
||||||
// debugInfo += bytes[i].toString(16).padStart(2, '0') + ' ';
|
// debugInfo += bytes[i].toString(16).padStart(2, '0') + ' ';
|
||||||
// }
|
// }
|
||||||
//console.log(debugInfo);
|
//console.log(debugInfo);
|
||||||
|
|
||||||
// Try to decode as text
|
// Try to decode as text
|
||||||
let txt = '';
|
let txt = '';
|
||||||
try {
|
try {
|
||||||
//txt = new TextDecoder().decode(bytes);
|
//txt = new TextDecoder().decode(bytes);
|
||||||
txt = new TextDecoder().decode(e.target.value);
|
txt = new TextDecoder().decode(e.target.value);
|
||||||
// Remove null terminators and trim
|
// Remove null terminators and trim
|
||||||
const nullIdx = txt.indexOf('\0');
|
const nullIdx = txt.indexOf('\0');
|
||||||
if (nullIdx !== -1) txt = txt.slice(0, nullIdx);
|
if (nullIdx !== -1) txt = txt.slice(0, nullIdx);
|
||||||
txt = txt.trim();
|
txt = txt.trim();
|
||||||
} catch (decodeErr) {
|
} catch (decodeErr) {
|
||||||
console.error('Text decode error:', decodeErr);
|
console.error('Text decode error:', decodeErr);
|
||||||
// Fallback to showing hex if text decode fails
|
// Fallback to showing hex if text decode fails
|
||||||
txt = '[Binary data]';
|
txt = '[Binary data]';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show both raw and processed data in console
|
||||||
|
//console.log('--> Raw bytes:', bytes);
|
||||||
|
//console.log('--> As text:', txt);
|
||||||
|
|
||||||
|
//logMessage(`--> (${bytes.length} bytes) ${txt}`);
|
||||||
|
// progress messages handling variable captured from outer scope
|
||||||
|
|
||||||
|
if(prevProgressFound && txt.includes('progress')){
|
||||||
|
logMessage(`${txt}`, true); // overwrite last line for progress updates
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
logMessage(`${txt}`);
|
||||||
|
prevProgressFound = false; // reset if current message isn't progress
|
||||||
|
}
|
||||||
|
|
||||||
|
if(txt.includes('progress')){
|
||||||
|
prevProgressFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch(err) {
|
||||||
|
console.error('Processing error', err);
|
||||||
|
logMessage('--> Error processing message: ' + err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show both raw and processed data in console
|
|
||||||
//console.log('--> Raw bytes:', bytes);
|
|
||||||
//console.log('--> As text:', txt);
|
|
||||||
|
|
||||||
//logMessage(`--> (${bytes.length} bytes) ${txt}`);
|
|
||||||
// progress messages handling variable captured from outer scope
|
|
||||||
|
|
||||||
if(prevProgressFound && txt.includes('progress')){
|
|
||||||
logMessage(`${txt}`, true); // overwrite last line for progress updates
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
logMessage(`${txt}`);
|
|
||||||
prevProgressFound = false; // reset if current message isn't progress
|
|
||||||
}
|
|
||||||
|
|
||||||
if(txt.includes('progress')){
|
|
||||||
prevProgressFound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
console.error('Processing error', err);
|
|
||||||
logMessage('--> Error processing message: ' + err.message);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bleConnected=true;
|
bleConnected=true;
|
||||||
|
|||||||
1324
data/www/usercfg.html
Normal file
1324
data/www/usercfg.html
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/SP110E Info.xlsx
Normal file
BIN
docs/SP110E Info.xlsx
Normal file
Binary file not shown.
24
docs/ToDo.txt
Normal file
24
docs/ToDo.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
Board to do:
|
||||||
|
2) make sure no fixed indexes to pwmOutput and other peripherals
|
||||||
|
|
||||||
|
3) Implement sending packet with current values to ble appearance
|
||||||
|
|
||||||
|
4) figure out how to compare profile comparison before savoing to system.json
|
||||||
|
|
||||||
|
5) made mode string instead of number.
|
||||||
|
|
||||||
|
6) Comments on json file Enable?
|
||||||
|
#define ARDUINOJSON_ENABLE_COMMENTS 1
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
permits: // or /* lsslslslslsl */
|
||||||
|
|
||||||
|
|
||||||
|
7) Use same struct of setting for to and from in ble
|
||||||
|
|
||||||
|
8) learn about Blob in BLE
|
||||||
|
|
||||||
|
######### Configuration Page ############
|
||||||
|
done
|
||||||
|
|
||||||
BIN
docs/~$SP110E Info.xlsx
Normal file
BIN
docs/~$SP110E Info.xlsx
Normal file
Binary file not shown.
@ -2,9 +2,6 @@
|
|||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"path": "."
|
"path": "."
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "../esp32s3_module_8mb"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
@ -5,13 +5,21 @@
|
|||||||
#include "ColorPalettes.h"
|
#include "ColorPalettes.h"
|
||||||
#include "PWM_Output.h"
|
#include "PWM_Output.h"
|
||||||
|
|
||||||
#define PIXEL_INDEX -3
|
#define SHIFT_INDEX -3
|
||||||
#define SOLID_COLOR_INDEX -2
|
#define SOLID_COLOR_INDEX -2
|
||||||
#define OFF_INDEX -1
|
#define OFF_INDEX -1
|
||||||
|
|
||||||
#define WITH_GAPS true
|
#define WITH_GAPS true
|
||||||
#define NO_GAPS false
|
#define NO_GAPS false
|
||||||
|
|
||||||
|
enum CHIP_TYPE {
|
||||||
|
CHIP_WS2812B = 0,
|
||||||
|
CHIP_SK6812,
|
||||||
|
CHIP_WS2811,
|
||||||
|
CHIP_WS2815,
|
||||||
|
CHIP_UNKNOWN
|
||||||
|
};
|
||||||
|
|
||||||
extern uint32_t whiteTimeout;
|
extern uint32_t whiteTimeout;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -39,19 +47,53 @@ typedef struct {
|
|||||||
int shift;
|
int shift;
|
||||||
int offset;
|
int offset;
|
||||||
int powerDiv;
|
int powerDiv;
|
||||||
int effSize;
|
//int effSize;
|
||||||
uint8_t bright;
|
uint8_t bright;
|
||||||
uint8_t i2sCh;
|
uint8_t i2sCh;
|
||||||
uint8_t core;
|
uint8_t core;
|
||||||
uint8_t pin;
|
uint8_t pin;
|
||||||
}LEDSTRIP_SETTINGS;
|
}LEDSTRIP_SETTINGS;
|
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint8_t cmd;
|
||||||
|
uint8_t limitedMode;
|
||||||
|
float temperature;
|
||||||
|
float vIn;
|
||||||
|
char profile[10];
|
||||||
|
|
||||||
|
uint8_t ledChipset1;
|
||||||
|
char rgbOrder1[4]; // 3 chars + null terminator
|
||||||
|
uint8_t ledCount1;
|
||||||
|
uint8_t ledShift1;
|
||||||
|
uint8_t ledBrightness1;
|
||||||
|
|
||||||
|
uint8_t ledChipset2;
|
||||||
|
char rgbOrder2[4]; // 3 chars + null terminator
|
||||||
|
uint8_t ledCount2;
|
||||||
|
uint8_t ledShift2;
|
||||||
|
uint8_t ledBrightness2;
|
||||||
|
|
||||||
|
uint8_t frontLightMin;
|
||||||
|
uint8_t frontLightMax;
|
||||||
|
uint8_t rearLightMin;
|
||||||
|
uint8_t rearLightMax;
|
||||||
|
uint8_t fanLowerTemp;
|
||||||
|
uint8_t fanUpperTemp;
|
||||||
|
} USER_SETTINGS;
|
||||||
|
|
||||||
|
enum BOOTH_PROFILE {
|
||||||
|
PROFILE_CUSTOM=0,
|
||||||
|
PROFILE_ROAMER,
|
||||||
|
PROFILE_STIK,
|
||||||
|
PROFILE_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
extern LEDSTRIP_SETTINGS ledSettings[2];
|
extern LEDSTRIP_SETTINGS ledSettings[2];
|
||||||
|
|
||||||
|
|
||||||
void RGB_Lights_Control_Task(void *parameters);
|
void RGB_Lights_Control_Task(void *parameters);
|
||||||
|
|
||||||
void Init_RGB_Lights_Task(void);
|
void Init_RGB_Lights_Task(bool upgrade);
|
||||||
|
|
||||||
void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright);
|
void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright);
|
||||||
|
|
||||||
@ -72,6 +114,8 @@ void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack);
|
|||||||
|
|
||||||
void loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src);
|
void loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src);
|
||||||
|
|
||||||
|
void SetAndSaveUserSettings(USER_SETTINGS &userSettings);
|
||||||
|
|
||||||
|
|
||||||
//void Init_Ramp_Front_Light_Task(void);
|
//void Init_Ramp_Front_Light_Task(void);
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include "AppVersion.h"
|
#include "AppVersion.h"
|
||||||
|
|
||||||
|
|
||||||
//#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/"
|
//#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/"
|
||||||
#define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/"
|
#define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/"
|
||||||
#define BUFFER_SIZE 2048 // Reduced from 4096 to use less memory
|
#define BUFFER_SIZE 2048 // Reduced from 4096 to use less memory
|
||||||
@ -171,6 +172,7 @@ class AppUpdater {
|
|||||||
fs::FS& fileSystem;
|
fs::FS& fileSystem;
|
||||||
UpdateStatus status;
|
UpdateStatus status;
|
||||||
std::unique_ptr<uint8_t[]> downloadBuffer;
|
std::unique_ptr<uint8_t[]> downloadBuffer;
|
||||||
|
size_t downloadBufferSize = 0; /**< Actual size allocated for downloadBuffer */
|
||||||
bool updateAvailable = false;
|
bool updateAvailable = false;
|
||||||
UpdateMode updateMode = UpdateMode::UPDATE_BOTH; // Default to updating both files and firmware
|
UpdateMode updateMode = UpdateMode::UPDATE_BOTH; // Default to updating both files and firmware
|
||||||
|
|
||||||
@ -223,7 +225,7 @@ void firmwareUpdateTask(void* param);
|
|||||||
|
|
||||||
void startFirmwareUpdateTask(AsyncEventSource* eventSrc);
|
void startFirmwareUpdateTask(AsyncEventSource* eventSrc);
|
||||||
|
|
||||||
void loadUpdateJson(void);
|
bool loadUpdateJson(void);
|
||||||
|
|
||||||
void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message);
|
void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message);
|
||||||
|
|
||||||
@ -243,3 +245,5 @@ void setUpdateModeFilesOnly();
|
|||||||
void setUpdateModeFirmwareOnly();
|
void setUpdateModeFirmwareOnly();
|
||||||
void setUpdateModeBoth();
|
void setUpdateModeBoth();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,11 @@
|
|||||||
#define SET_SPEED 0x03
|
#define SET_SPEED 0x03
|
||||||
#define SET_AUTO_MODE 0x06
|
#define SET_AUTO_MODE 0x06
|
||||||
|
|
||||||
|
// My custom commands
|
||||||
|
#define SET_USER_SETTINGS 0xB0
|
||||||
|
#define SET_SHIFT 0xB1
|
||||||
|
|
||||||
|
|
||||||
// Initializes the BLE server, services, and advertising
|
// Initializes the BLE server, services, and advertising
|
||||||
void Init_BLE_SP110E(NimBLEServer* pServer);
|
void Init_BLE_SP110E(NimBLEServer* pServer);
|
||||||
|
|
||||||
|
|||||||
@ -28,9 +28,9 @@ const COLOR_PACK colorPack_RED_WHITE PROGMEM = { 2, { CRGB::Red, CRGB::White } }
|
|||||||
const COLOR_PACK colorPack_ORANGE_WHITE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::White } };
|
const COLOR_PACK colorPack_ORANGE_WHITE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::White } };
|
||||||
const COLOR_PACK colorPack_GREEN_WHITE PROGMEM = { 2, { CRGB::Green, CRGB::White } };
|
const COLOR_PACK colorPack_GREEN_WHITE PROGMEM = { 2, { CRGB::Green, CRGB::White } };
|
||||||
const COLOR_PACK colorPack_BLUE_WHITE PROGMEM = { 2, { CRGB::Blue, CRGB::White } };
|
const COLOR_PACK colorPack_BLUE_WHITE PROGMEM = { 2, { CRGB::Blue, CRGB::White } };
|
||||||
const COLOR_PACK colorPack_PINK_WHITE PROGMEM = { 2, { CRGB::Pink, CRGB::White } };
|
const COLOR_PACK colorPack_MAGENTA_WHITE PROGMEM = { 2, { CRGB::Magenta, CRGB::White } };
|
||||||
const COLOR_PACK colorPack_PURPLE_WHITE PROGMEM = { 2, { CRGB::DarkViolet, CRGB::White } };
|
const COLOR_PACK colorPack_PURPLE_WHITE PROGMEM = { 2, { CRGB::DarkViolet, CRGB::White } };
|
||||||
const COLOR_PACK colorPack_PURPLE_PINK PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Pink } };
|
const COLOR_PACK colorPack_PURPLE_MAGENTA PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Magenta } };
|
||||||
const COLOR_PACK colorPack_PURPLE_YELLOW PROGMEM = { 2, { CRGB::Purple, CRGB::Yellow } };
|
const COLOR_PACK colorPack_PURPLE_YELLOW PROGMEM = { 2, { CRGB::Purple, CRGB::Yellow } };
|
||||||
|
|
||||||
|
|
||||||
@ -45,11 +45,10 @@ const COLOR_PACK colorPack_BLUE_RED PROGMEM = { 2, { CRGB::Blue, CRGB::Red } };
|
|||||||
const COLOR_PACK colorPack_ORANGE_BLUE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Blue } };
|
const COLOR_PACK colorPack_ORANGE_BLUE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Blue } };
|
||||||
const COLOR_PACK colorPack_RED_GREEN PROGMEM = { 2, { CRGB::Red, CRGB::Green } };
|
const COLOR_PACK colorPack_RED_GREEN PROGMEM = { 2, { CRGB::Red, CRGB::Green } };
|
||||||
const COLOR_PACK colorPack_CYAN_RED PROGMEM = { 2, { CRGB::Cyan, CRGB::Red } };
|
const COLOR_PACK colorPack_CYAN_RED PROGMEM = { 2, { CRGB::Cyan, CRGB::Red } };
|
||||||
const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } };
|
|
||||||
|
|
||||||
// Warm/cool combinations
|
// Warm/cool combinations
|
||||||
const COLOR_PACK colorPack_ORANGE_CYAN PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Cyan } };
|
const COLOR_PACK colorPack_ORANGE_CYAN PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Cyan } };
|
||||||
const COLOR_PACK colorPack_PINK_GREEN PROGMEM = { 2, { CRGB::Pink, CRGB::Green } };
|
const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } };
|
||||||
const COLOR_PACK colorPack_VIOLET_LIME PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Lime } };
|
const COLOR_PACK colorPack_VIOLET_LIME PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Lime } };
|
||||||
|
|
||||||
// Analogous combinations
|
// Analogous combinations
|
||||||
@ -67,28 +66,28 @@ const COLOR_PACK double_colorPacks[] PROGMEM = {
|
|||||||
colorPack_RED_WHITE,
|
colorPack_RED_WHITE,
|
||||||
colorPack_RED_YELLOW,
|
colorPack_RED_YELLOW,
|
||||||
colorPack_BLUE_WHITE,
|
colorPack_BLUE_WHITE,
|
||||||
colorPack_PINK_WHITE,
|
|
||||||
colorPack_GREEN_WHITE,
|
|
||||||
colorPack_PURPLE_WHITE,
|
colorPack_PURPLE_WHITE,
|
||||||
colorPack_GREEN_YELLOW,
|
colorPack_MAGENTA_WHITE,
|
||||||
colorPack_PURPLE_PINK,
|
colorPack_GREEN_WHITE,
|
||||||
colorPack_CYAN_RED,
|
|
||||||
|
|
||||||
colorPack_ORANGE_WHITE,
|
colorPack_ORANGE_WHITE,
|
||||||
|
colorPack_PURPLE_MAGENTA,
|
||||||
|
colorPack_GREEN_YELLOW,
|
||||||
|
// 9 pack
|
||||||
|
|
||||||
colorPack_PURPLE_PINK,
|
|
||||||
|
colorPack_CYAN_RED,
|
||||||
|
colorPack_PURPLE_MAGENTA,
|
||||||
colorPack_PURPLE_YELLOW,
|
colorPack_PURPLE_YELLOW,
|
||||||
|
|
||||||
colorPack_BLUE_YELLOW,
|
colorPack_BLUE_YELLOW,
|
||||||
|
|
||||||
colorPack_BLUE_GREEN,
|
colorPack_BLUE_GREEN,
|
||||||
colorPack_BLUE_RED,
|
colorPack_BLUE_RED,
|
||||||
colorPack_ORANGE_BLUE,
|
colorPack_ORANGE_BLUE,
|
||||||
colorPack_RED_GREEN,
|
// 16 pack
|
||||||
|
|
||||||
|
|
||||||
|
colorPack_RED_GREEN,
|
||||||
colorPack_MAGENTA_GREEN,
|
colorPack_MAGENTA_GREEN,
|
||||||
colorPack_ORANGE_CYAN,
|
colorPack_ORANGE_CYAN,
|
||||||
colorPack_PINK_GREEN,
|
|
||||||
colorPack_VIOLET_LIME,
|
colorPack_VIOLET_LIME,
|
||||||
colorPack_RED_ORANGE,
|
colorPack_RED_ORANGE,
|
||||||
colorPack_BLUE_PURPLE,
|
colorPack_BLUE_PURPLE,
|
||||||
@ -101,12 +100,12 @@ const COLOR_PACK double_colorPacks[] PROGMEM = {
|
|||||||
|
|
||||||
// Sectors
|
// Sectors
|
||||||
|
|
||||||
const COLOR_PACK colorPack_RAINBOW PROGMEM = { 7, { CRGB::Red, CRGB::OrangeRed, CRGB::Yellow, CRGB::Green, CRGB::Blue, CRGB::BlueViolet, CRGB::MediumVioletRed } };
|
const COLOR_PACK colorPack_RAINBOW PROGMEM = { 7, { CRGB::Red, CRGB::DarkOrange, CRGB::LightYellow, CRGB::Green, CRGB::Blue, CRGB::DarkViolet, CRGB::MediumVioletRed } };
|
||||||
// Triple Color Packs, Common Flags
|
// Triple Color Packs, Common Flags
|
||||||
const COLOR_PACK colorPack_RED_WHITE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Blue } };
|
const COLOR_PACK colorPack_RED_WHITE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Blue } };
|
||||||
const COLOR_PACK colorPack_RED_WHITE_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Green } };
|
const COLOR_PACK colorPack_RED_WHITE_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Green } };
|
||||||
const COLOR_PACK colorPack_RED_YELLOW_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Blue } };
|
const COLOR_PACK colorPack_RED_YELLOW_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Blue } };
|
||||||
const COLOR_PACK colorPack_RED_ORANGE_YELLOW PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Yellow } };
|
const COLOR_PACK colorPack_RED_ORANGE_YELLOW PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Yellow } };
|
||||||
const COLOR_PACK colorPack_RED_YELLOW_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Green } };
|
const COLOR_PACK colorPack_RED_YELLOW_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Green } };
|
||||||
const COLOR_PACK colorPack_RED_PURPLE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Purple, CRGB::Blue } };
|
const COLOR_PACK colorPack_RED_PURPLE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Purple, CRGB::Blue } };
|
||||||
|
|
||||||
@ -114,13 +113,11 @@ const COLOR_PACK colorPack_GREEN_WHITE_RED PROGMEM = { 3, { CRGB::Green, CRGB::
|
|||||||
const COLOR_PACK colorPack_GREEN_WHITE_ORANGE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::DarkOrange } };
|
const COLOR_PACK colorPack_GREEN_WHITE_ORANGE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::DarkOrange } };
|
||||||
const COLOR_PACK colorPack_GREEN_WHITE_BLUE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Blue } };
|
const COLOR_PACK colorPack_GREEN_WHITE_BLUE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Blue } };
|
||||||
|
|
||||||
|
|
||||||
const COLOR_PACK colorPack_BLUE_WHITE_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::White, CRGB::Green } };
|
|
||||||
const COLOR_PACK colorPack_BLUE_YELLOW_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Green } };
|
const COLOR_PACK colorPack_BLUE_YELLOW_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Green } };
|
||||||
const COLOR_PACK colorPack_BLUE_YELLOW_RED PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Red } };
|
const COLOR_PACK colorPack_BLUE_YELLOW_RED PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Red } };
|
||||||
|
|
||||||
// Additional triple color combinations
|
// Additional triple color combinations
|
||||||
const COLOR_PACK colorPack_PURPLE_PINK_CYAN PROGMEM = { 3, { CRGB::Purple, CRGB::Pink, CRGB::Cyan } };
|
const COLOR_PACK colorPack_MAGENTA_WHITE_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::White, CRGB::Cyan } };
|
||||||
const COLOR_PACK colorPack_ORANGE_YELLOW_LIME PROGMEM = { 3, { CRGB::DarkOrange, CRGB::Yellow, CRGB::Lime } };
|
const COLOR_PACK colorPack_ORANGE_YELLOW_LIME PROGMEM = { 3, { CRGB::DarkOrange, CRGB::Yellow, CRGB::Lime } };
|
||||||
const COLOR_PACK colorPack_MAGENTA_YELLOW_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::Yellow, CRGB::Cyan } };
|
const COLOR_PACK colorPack_MAGENTA_YELLOW_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::Yellow, CRGB::Cyan } };
|
||||||
const COLOR_PACK colorPack_LIME_CYAN_MAGENTA PROGMEM = { 3, { CRGB::Lime, CRGB::Cyan, CRGB::Magenta } };
|
const COLOR_PACK colorPack_LIME_CYAN_MAGENTA PROGMEM = { 3, { CRGB::Lime, CRGB::Cyan, CRGB::Magenta } };
|
||||||
@ -149,27 +146,30 @@ const COLOR_PACK tripple_colorPacks[] PROGMEM = {
|
|||||||
colorPack_GREEN_WHITE_ORANGE,
|
colorPack_GREEN_WHITE_ORANGE,
|
||||||
colorPack_BLUE_YELLOW_GREEN,
|
colorPack_BLUE_YELLOW_GREEN,
|
||||||
colorPack_RED_GREEN_BLUE,
|
colorPack_RED_GREEN_BLUE,
|
||||||
colorPack_PURPLE_PINK_CYAN,
|
colorPack_MAGENTA_WHITE_CYAN,
|
||||||
colorPack_YELLOW_ORANGE_RED,
|
colorPack_YELLOW_ORANGE_RED,
|
||||||
colorPack_BLUE_CYAN_LIME,
|
colorPack_BLUE_CYAN_LIME,
|
||||||
colorPack_PINK_PURPLE_MAGENTA,
|
|
||||||
|
|
||||||
colorPack_RED_YELLOW_BLUE,
|
colorPack_RED_YELLOW_BLUE,
|
||||||
|
// 9 pack
|
||||||
|
|
||||||
|
|
||||||
colorPack_RED_ORANGE_YELLOW,
|
colorPack_RED_ORANGE_YELLOW,
|
||||||
colorPack_RED_YELLOW_GREEN,
|
colorPack_RED_YELLOW_GREEN,
|
||||||
colorPack_RED_PURPLE_BLUE,
|
colorPack_RED_PURPLE_BLUE,
|
||||||
colorPack_GREEN_WHITE_RED,
|
colorPack_GREEN_WHITE_RED,
|
||||||
colorPack_GREEN_WHITE_BLUE,
|
colorPack_GREEN_WHITE_BLUE,
|
||||||
colorPack_BLUE_WHITE_GREEN,
|
|
||||||
colorPack_BLUE_YELLOW_RED,
|
colorPack_BLUE_YELLOW_RED,
|
||||||
|
colorPack_GREEN_CYAN_BLUE,
|
||||||
|
// 16 pack
|
||||||
|
|
||||||
|
|
||||||
colorPack_ORANGE_YELLOW_LIME,
|
colorPack_ORANGE_YELLOW_LIME,
|
||||||
colorPack_MAGENTA_YELLOW_CYAN,
|
colorPack_MAGENTA_YELLOW_CYAN,
|
||||||
colorPack_LIME_CYAN_MAGENTA,
|
colorPack_LIME_CYAN_MAGENTA,
|
||||||
colorPack_RED_ORANGE_PINK,
|
colorPack_RED_ORANGE_PINK,
|
||||||
colorPack_PURPLE_BLUE_CYAN,
|
colorPack_PURPLE_BLUE_CYAN,
|
||||||
colorPack_GREEN_CYAN_BLUE,
|
|
||||||
colorPack_YELLOW_PURPLE_ORANGE,
|
colorPack_YELLOW_PURPLE_ORANGE,
|
||||||
colorPack_PINK_LIME_PURPLE
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR_PACK colorPack_grad_blueish PROGMEM = { 3, { CRGB::Blue, CRGB::Cyan, CRGB::Green } };
|
const COLOR_PACK colorPack_grad_blueish PROGMEM = { 3, { CRGB::Blue, CRGB::Cyan, CRGB::Green } };
|
||||||
@ -200,7 +200,7 @@ const COLOR_PACK colorPack_Single_Viloet PROGMEM = { 1, { CRGB::DarkViolet } };
|
|||||||
const COLOR_PACK colorPack_Single_Magenta PROGMEM = { 1, { CRGB::Magenta } };
|
const COLOR_PACK colorPack_Single_Magenta PROGMEM = { 1, { CRGB::Magenta } };
|
||||||
const COLOR_PACK colorPack_Single_White PROGMEM = { 1, { CRGB::White } };
|
const COLOR_PACK colorPack_Single_White PROGMEM = { 1, { CRGB::White } };
|
||||||
|
|
||||||
// 9 Single Color Packs
|
// 8 Single Color Packs
|
||||||
const COLOR_PACK single_colorPacks[] PROGMEM = {
|
const COLOR_PACK single_colorPacks[] PROGMEM = {
|
||||||
colorPack_Single_White,
|
colorPack_Single_White,
|
||||||
colorPack_Single_Red,
|
colorPack_Single_Red,
|
||||||
|
|||||||
@ -38,6 +38,8 @@ class PWM_Output {
|
|||||||
|
|
||||||
float standardFactor;
|
float standardFactor;
|
||||||
float visionFactor;
|
float visionFactor;
|
||||||
|
int8_t pin;
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ private:
|
|||||||
float currentValue;
|
float currentValue;
|
||||||
bool IsOn = false;
|
bool IsOn = false;
|
||||||
RAMP_STATE rampState;
|
RAMP_STATE rampState;
|
||||||
int tickCount;
|
int tickCount = 0;
|
||||||
|
|
||||||
void tick();
|
void tick();
|
||||||
void singleClick();
|
void singleClick();
|
||||||
|
|||||||
@ -60,3 +60,5 @@ void Log_CPU_Load(void);
|
|||||||
|
|
||||||
void print_task_watermarks(void);
|
void print_task_watermarks(void);
|
||||||
|
|
||||||
|
float updateLowpass(float currentValue, float newValue, float alpha);
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,10 @@ extern SYS_SETTINGS sys_settings;
|
|||||||
extern PWM_Output *pwmOutputs[4];
|
extern PWM_Output *pwmOutputs[4];
|
||||||
extern RAMP_LIGHT *rampLight1;
|
extern RAMP_LIGHT *rampLight1;
|
||||||
extern RAMP_LIGHT *rampLight2;
|
extern RAMP_LIGHT *rampLight2;
|
||||||
|
extern String booth_file_path;
|
||||||
|
extern float PowerVin;
|
||||||
|
extern float PowerVinAlpha;
|
||||||
|
extern float prevPowerVin;
|
||||||
|
|
||||||
void Init_ADC(void);
|
void Init_ADC(void);
|
||||||
float readBoardInputVoltage(void);
|
float readBoardInputVoltage(void);
|
||||||
|
|||||||
@ -23,7 +23,7 @@ void handleGET_Query(AsyncWebServerRequest *request);
|
|||||||
|
|
||||||
void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||||
|
|
||||||
void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&));
|
bool sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&));
|
||||||
String fileManagerHtmlProcessor(const String& var);
|
String fileManagerHtmlProcessor(const String& var);
|
||||||
String HomeHtmlProcessor(const String& var);
|
String HomeHtmlProcessor(const String& var);
|
||||||
String listDirAsHtml(String directoryList[], int count);
|
String listDirAsHtml(String directoryList[], int count);
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
#include <FastLED.h>
|
#include <FastLED.h>
|
||||||
#include "ATALights.h"
|
#include "ATALights.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum LED_CHIPSET { LED_CHIPSET_WS2812B=0, LED_CHIPSET_SK6812=1, LED_CHIPSET_WS2811_400=2, LED_CHIPSET_WS2815=3, LED_CHIPSET_NONE=255 };
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
}BTN_SETTINGS;
|
}BTN_SETTINGS;
|
||||||
@ -79,6 +82,8 @@ typedef struct {
|
|||||||
|
|
||||||
enum BOOTH_MODE { BOOTH_MODE_NONE=0, BOOTH_MODE_ROAMER=1, BOOTH_MODE_STIK=2 };
|
enum BOOTH_MODE { BOOTH_MODE_NONE=0, BOOTH_MODE_ROAMER=1, BOOTH_MODE_STIK=2 };
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
String profile;
|
||||||
|
bool limitedMode;
|
||||||
BOOTH_MODE mode;
|
BOOTH_MODE mode;
|
||||||
BOARD_PINS boardPins;
|
BOARD_PINS boardPins;
|
||||||
BTN_SETTINGS btnSettings[3];
|
BTN_SETTINGS btnSettings[3];
|
||||||
|
|||||||
@ -24,6 +24,7 @@ lib_deps =
|
|||||||
jeremycole/I2C Temperature Sensors derived from the LM75 @ ^1.0.3
|
jeremycole/I2C Temperature Sensors derived from the LM75 @ ^1.0.3
|
||||||
mathertel/OneButton @ ^2.6.1
|
mathertel/OneButton @ ^2.6.1
|
||||||
h2zero/NimBLE-Arduino @ ^1.4.1
|
h2zero/NimBLE-Arduino @ ^1.4.1
|
||||||
|
#h2zero/NimBLE-Arduino @ ^2.3.6
|
||||||
adafruit/Adafruit SSD1306 @ ^2.5.7
|
adafruit/Adafruit SSD1306 @ ^2.5.7
|
||||||
fastled/FastLED @ ^3.9.4
|
fastled/FastLED @ ^3.9.4
|
||||||
marian-craciunescu/ESP32Ping@^1.7
|
marian-craciunescu/ESP32Ping@^1.7
|
||||||
@ -35,6 +36,11 @@ build_flags =
|
|||||||
#-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
|
#-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
|
||||||
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO
|
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO
|
||||||
-D CONFIG_ARDUHAL_LOG_COLORS=1
|
-D CONFIG_ARDUHAL_LOG_COLORS=1
|
||||||
|
#-Os
|
||||||
|
#-ffunction-sections
|
||||||
|
#-fdata-sections
|
||||||
|
#-Wl,--gc-sections
|
||||||
|
|
||||||
upload_port = COM5
|
upload_port = COM5
|
||||||
debug_init_break = tbreak setup
|
debug_init_break = tbreak setup
|
||||||
monitor_port = COM5
|
monitor_port = COM5
|
||||||
|
|||||||
@ -9,16 +9,16 @@
|
|||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "ColorPalettes.h"
|
#include "ColorPalettes.h"
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
//#include <crgb.h>
|
|
||||||
#include "PWM_Output.h"
|
#include "PWM_Output.h"
|
||||||
#include "my_device.h"
|
#include "my_device.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "my_device.h"
|
#include "my_device.h"
|
||||||
|
#include "LittleFS.h"
|
||||||
|
|
||||||
#define FASTLED_CORE 0
|
#define FASTLED_CORE 0
|
||||||
static const char* tag = "strips";
|
static const char* tag = "strips";
|
||||||
|
|
||||||
uint32_t whiteTimeout = 0;
|
//uint32_t whiteTimeout = 0;
|
||||||
|
|
||||||
TaskHandle_t Animation_Task_Handle;
|
TaskHandle_t Animation_Task_Handle;
|
||||||
TaskHandle_t Ramp_Front_Light_Task_Handle;
|
TaskHandle_t Ramp_Front_Light_Task_Handle;
|
||||||
@ -27,7 +27,13 @@ volatile bool AnimationLooping = false;
|
|||||||
ANIM_EVENT prevAnimEvent = {0};
|
ANIM_EVENT prevAnimEvent = {0};
|
||||||
QueueHandle_t animationQueue = xQueueCreate( 4, sizeof( ANIM_EVENT ) );
|
QueueHandle_t animationQueue = xQueueCreate( 4, sizeof( ANIM_EVENT ) );
|
||||||
|
|
||||||
bool fillAnimationActive = false;
|
bool upgradeMode = false;
|
||||||
|
|
||||||
|
#define LIMITED_ANIMATION true
|
||||||
|
|
||||||
|
#define DEFAULT_ANIMATION 23
|
||||||
|
|
||||||
|
//bool fillAnimationActive = false;
|
||||||
|
|
||||||
void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){
|
void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){
|
||||||
|
|
||||||
@ -47,7 +53,8 @@ void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t b
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RGB_Animations_ON(){
|
void RGB_Animations_ON(){
|
||||||
RGB_Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu);
|
//RGB_Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu);
|
||||||
|
RGB_Lights_Set_Animation(DEFAULT_ANIMATION, 0, 0, 0); // set comet rainbow animation
|
||||||
}
|
}
|
||||||
|
|
||||||
void RGB_Animations_OFF(){
|
void RGB_Animations_OFF(){
|
||||||
@ -63,7 +70,9 @@ void Lights_Set_White(uint8_t val){
|
|||||||
//pwmOut[0]->setOutput(val / 2.5f);
|
//pwmOut[0]->setOutput(val / 2.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Init_RGB_Lights_Task(void){
|
|
||||||
|
void Init_RGB_Lights_Task(bool upgrade){
|
||||||
|
upgradeMode = upgrade;
|
||||||
ledSettings[0].leds = new CRGB[ledSettings[0].size];
|
ledSettings[0].leds = new CRGB[ledSettings[0].size];
|
||||||
Init_RGB_Strip(ledSettings[0].leds, ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip, ledSettings[0].bright);
|
Init_RGB_Strip(ledSettings[0].leds, ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip, ledSettings[0].bright);
|
||||||
ESP_LOGD(tag, "Initializing Strip1: Pin: %d, size: %d, order: %s, chip: %s", ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip);
|
ESP_LOGD(tag, "Initializing Strip1: Pin: %d, size: %d, order: %s, chip: %s", ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip);
|
||||||
@ -234,31 +243,61 @@ inline int calcPixelIndex(LEDSTRIP_SETTINGS& strip, int index) {
|
|||||||
return (x +strip.offset) % strip.effSize;
|
return (x +strip.offset) % strip.effSize;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
// ...existing code...
|
||||||
|
inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) {
|
||||||
|
// Guard against invalid configuration that can cause crashes (null pointer or zero size)
|
||||||
|
if (leds.size <= 0 || leds.leds == nullptr) {
|
||||||
|
ESP_LOGW(tag, "setPixel1: invalid strip (Size=%d, leds=%p)", leds.size, (void*)leds.leds);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a signed type so negative pixelIndex is preserved
|
||||||
|
int x = pixelIndex + leds.shift;
|
||||||
|
int n = leds.size;
|
||||||
|
|
||||||
|
// If strip.effSize is power of 2: (n & (n - 1)) == 0
|
||||||
|
int idx;
|
||||||
|
if ((n & (n - 1)) == 0) {
|
||||||
|
// Masking is safe if we treat x as unsigned for masking,
|
||||||
|
// this yields the correct wrapped index for two's-complement
|
||||||
|
idx = static_cast<int>( (static_cast<uint32_t>(x)) & (static_cast<uint32_t>(n) - 1u) );
|
||||||
|
} else {
|
||||||
|
// General modulo: C++ '%' can be negative, so normalize
|
||||||
|
idx = x % n;
|
||||||
|
if (idx < 0) idx += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize final position robustly (handles negative offsets)
|
||||||
|
int pos = (idx + leds.offset) % n;
|
||||||
|
if (pos < 0) pos += n;
|
||||||
|
|
||||||
|
leds.leds[pos] = col;
|
||||||
|
}
|
||||||
|
/*
|
||||||
inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) {
|
inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) {
|
||||||
register uint16_t x = pixelIndex + ledSettings[0].shift;
|
register uint16_t x = pixelIndex + ledSettings[0].shift;
|
||||||
// If strip.effSize is power of 2, use faster bit masking
|
// If strip.size is power of 2, use faster bit masking
|
||||||
if ((leds.effSize & (leds.effSize - 1)) == 0) {
|
if ((leds.size & (leds.size - 1)) == 0) {
|
||||||
x = (x < 0) ? ((x + leds.effSize) & (leds.effSize - 1)) : (x & (leds.effSize - 1));
|
x = (x < 0) ? ((x + leds.size) & (leds.size - 1)) : (x & (leds.size - 1));
|
||||||
leds.leds[(x + leds.offset) & (leds.effSize - 1)] = col;
|
leds.leds[(x + leds.offset) & (leds.size - 1)] = col;
|
||||||
} else {
|
} else {
|
||||||
// For non-power-of-2 sizes, still need modulo
|
// For non-power-of-2 sizes, still need modulo
|
||||||
x = (x < 0) ? ((x + ledSettings[0].effSize) % ledSettings[0].effSize) : (x % ledSettings[0].effSize);
|
x = (x < 0) ? ((x + ledSettings[0].size) % ledSettings[0].size) : (x % ledSettings[0].size);
|
||||||
ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].effSize] = col;
|
ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].size] = col;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
inline void setPixel2(int pixelIndex, const CRGB col) {
|
inline void setPixel2(int pixelIndex, const CRGB col) {
|
||||||
register uint16_t x = pixelIndex + ledSettings[1].shift;
|
register uint16_t x = pixelIndex + ledSettings[1].shift;
|
||||||
// If strip.effSize is power of 2, use faster bit masking
|
// If strip.size is power of 2, use faster bit masking
|
||||||
if ((ledSettings[1].effSize & (ledSettings[1].effSize - 1)) == 0) {
|
if ((ledSettings[1].size & (ledSettings[1].size - 1)) == 0) {
|
||||||
x = (x < 0) ? ((x + ledSettings[1].effSize) & (ledSettings[1].effSize - 1)) : (x & (ledSettings[1].effSize - 1));
|
x = (x < 0) ? ((x + ledSettings[1].size) & (ledSettings[1].size - 1)) : (x & (ledSettings[1].size - 1));
|
||||||
ledSettings[1].leds[(x + ledSettings[1].offset) & (ledSettings[1].effSize - 1)] = col;
|
ledSettings[1].leds[(x + ledSettings[1].offset) & (ledSettings[1].size - 1)] = col;
|
||||||
} else {
|
} else {
|
||||||
// For non-power-of-2 sizes, still need modulo
|
// For non-power-of-2 sizes, still need modulo
|
||||||
x = (x < 0) ? ((x + ledSettings[1].effSize) % ledSettings[1].effSize) : (x % ledSettings[1].effSize);
|
x = (x < 0) ? ((x + ledSettings[1].size) % ledSettings[1].size) : (x % ledSettings[1].size);
|
||||||
ledSettings[1].leds[(x + ledSettings[1].offset) % ledSettings[1].effSize] = col;
|
ledSettings[1].leds[(x + ledSettings[1].offset) % ledSettings[1].size] = col;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +325,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
|
|||||||
|
|
||||||
if(pin == 3){
|
if(pin == 3){
|
||||||
// First level: Chip type selection
|
// First level: Chip type selection
|
||||||
if (chipUpper == "WS2812B" || chipUpper == "SK6812") {
|
if (chipUpper == "WS2812B") {
|
||||||
switch(rgbOrder) {
|
switch(rgbOrder) {
|
||||||
case RGB: FastLED.addLeds<WS2812B, 3, RGB>(leds, size); break;
|
case RGB: FastLED.addLeds<WS2812B, 3, RGB>(leds, size); break;
|
||||||
case RBG: FastLED.addLeds<WS2812B, 3, RBG>(leds, size); break;
|
case RBG: FastLED.addLeds<WS2812B, 3, RBG>(leds, size); break;
|
||||||
@ -308,7 +347,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
|
|||||||
default: FastLED.addLeds<SK6812, 3, GRB>(leds, size); break;
|
default: FastLED.addLeds<SK6812, 3, GRB>(leds, size); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (chipUpper == "WS2811_400") {
|
else if (chipUpper == "WS2811") {
|
||||||
switch(rgbOrder) {
|
switch(rgbOrder) {
|
||||||
case RGB: FastLED.addLeds<WS2811_400, 3, RGB>(leds, size); break;
|
case RGB: FastLED.addLeds<WS2811_400, 3, RGB>(leds, size); break;
|
||||||
case RBG: FastLED.addLeds<WS2811_400, 3, RBG>(leds, size); break;
|
case RBG: FastLED.addLeds<WS2811_400, 3, RBG>(leds, size); break;
|
||||||
@ -361,7 +400,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
|
|||||||
default: FastLED.addLeds<SK6812, 9, GRB>(leds, size); break;
|
default: FastLED.addLeds<SK6812, 9, GRB>(leds, size); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (chipUpper == "WS2811_400") {
|
else if (chipUpper == "WS2811") {
|
||||||
switch(rgbOrder) {
|
switch(rgbOrder) {
|
||||||
case RGB: FastLED.addLeds<WS2811_400, 9, RGB>(leds, size); break;
|
case RGB: FastLED.addLeds<WS2811_400, 9, RGB>(leds, size); break;
|
||||||
case RBG: FastLED.addLeds<WS2811_400, 9, RBG>(leds, size); break;
|
case RBG: FastLED.addLeds<WS2811_400, 9, RBG>(leds, size); break;
|
||||||
@ -392,7 +431,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
|
|||||||
}
|
}
|
||||||
else if(pin == 46){
|
else if(pin == 46){
|
||||||
// First level: Chip type selection
|
// First level: Chip type selection
|
||||||
if (chipUpper == "WS2812B" || chipUpper == "SK6812") {
|
if (chipUpper == "WS2812B") {
|
||||||
switch(rgbOrder) {
|
switch(rgbOrder) {
|
||||||
case RGB: FastLED.addLeds<WS2812B, 46, RGB>(leds, size); break;
|
case RGB: FastLED.addLeds<WS2812B, 46, RGB>(leds, size); break;
|
||||||
case RBG: FastLED.addLeds<WS2812B, 46, RBG>(leds, size); break;
|
case RBG: FastLED.addLeds<WS2812B, 46, RBG>(leds, size); break;
|
||||||
@ -414,7 +453,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
|
|||||||
default: FastLED.addLeds<SK6812, 46, GRB>(leds, size); break;
|
default: FastLED.addLeds<SK6812, 46, GRB>(leds, size); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (chipUpper == "WS2811_400") {
|
else if (chipUpper == "WS2811") {
|
||||||
switch(rgbOrder) {
|
switch(rgbOrder) {
|
||||||
case RGB: FastLED.addLeds<WS2811_400, 46, RGB>(leds, size); break;
|
case RGB: FastLED.addLeds<WS2811_400, 46, RGB>(leds, size); break;
|
||||||
case RBG: FastLED.addLeds<WS2811_400, 46, RBG>(leds, size); break;
|
case RBG: FastLED.addLeds<WS2811_400, 46, RBG>(leds, size); break;
|
||||||
@ -439,7 +478,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
|
|||||||
else {
|
else {
|
||||||
// Default to WS2812B if unknown chip type
|
// Default to WS2812B if unknown chip type
|
||||||
ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str());
|
ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str());
|
||||||
FastLED.addLeds<WS2812B, 9, GRB>(leds, size);
|
FastLED.addLeds<WS2812B, 46, GRB>(leds, size);
|
||||||
}
|
}
|
||||||
ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin);
|
ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin);
|
||||||
}
|
}
|
||||||
@ -449,147 +488,392 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
|
|||||||
|
|
||||||
|
|
||||||
void RGB_Lights_Control_Task(void *parameters){
|
void RGB_Lights_Control_Task(void *parameters){
|
||||||
|
// Wait for other tasks to initialize
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200)); // wait for everything to settle
|
||||||
|
RGB_Lights_Set_Brightness(ledSettings[0].bright);
|
||||||
|
|
||||||
|
if(upgradeMode){
|
||||||
|
// fill with 3 colors equally magenta, cyan, lime
|
||||||
|
RGB_Lights_Set_Brightness(64);
|
||||||
|
fill_gradient_RGB(ledSettings[0].leds, ledSettings[0].size, CRGB(CRGB::Magenta), CRGB(CRGB::Cyan), CRGB(CRGB::Lime), CRGB(CRGB::Magenta));
|
||||||
|
FastLED.show();
|
||||||
|
}else{
|
||||||
|
RGB_Lights_Set_Animation(DEFAULT_ANIMATION, 0, 0, 0); // set comet rainbow animation
|
||||||
|
}
|
||||||
|
|
||||||
ANIM_EVENT AnimEvent;
|
ANIM_EVENT AnimEvent;
|
||||||
CRGB col;
|
|
||||||
COLOR_PACK colorPack;
|
|
||||||
CRGBPalette16 firePalette;
|
|
||||||
|
|
||||||
// Wait for other tasks to initialize (including front light task)
|
if (LIMITED_ANIMATION){
|
||||||
vTaskDelay(pdMS_TO_TICKS(500)); // wait for everything to settle
|
while (true) {
|
||||||
|
if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
|
||||||
|
ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
|
||||||
|
switch (AnimEvent.AnimationIndex) {
|
||||||
|
|
||||||
RGB_Lights_Set_Brightness(48);
|
case -3: // Set Shift
|
||||||
RGB_Lights_Set_Animation(23, 0, 0, 0); // set comet rainbow animation
|
if (AnimEvent.data.data[0] >= 0 && AnimEvent.data.data[0] < ledSettings[0].size) {
|
||||||
|
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
|
||||||
|
ledSettings[0].shift = AnimEvent.data.data[0];
|
||||||
while (true) {
|
vTaskDelay(25 / portTICK_PERIOD_MS);
|
||||||
if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
|
setPixel1(ledSettings[0], 0, CRGB::White * FastLED.getBrightness() / 255);
|
||||||
ESP_LOGI(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
|
FastLED.show();
|
||||||
switch (AnimEvent.AnimationIndex) {
|
ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift);
|
||||||
case -3: // Set Pixel by index
|
} else {
|
||||||
if (AnimEvent.data.data[7] >= 0 && AnimEvent.data.data[7] < ledSettings[0].size) {
|
ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[0]);
|
||||||
ledSettings[0].leds[AnimEvent.data.data[7]] = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu);
|
}
|
||||||
|
break;
|
||||||
|
case -2: // Fill Static Color
|
||||||
|
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu));
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
} else {
|
ESP_LOGD(tag, "Static Color");
|
||||||
ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[7]);
|
break;
|
||||||
|
case -1:
|
||||||
|
FastLED.clear();
|
||||||
|
FastLED.show();
|
||||||
|
ESP_LOGD(tag, "LEDs Off");
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
|
||||||
|
FastLED.show();
|
||||||
|
break;
|
||||||
|
case 1 ... 5: { // Timed Fill Animations
|
||||||
|
int timeDuration = AnimEvent.AnimationIndex * 1000 - 400;
|
||||||
|
int whiteDelay = timeDuration - 1000;
|
||||||
|
if (whiteDelay < 800) whiteDelay = 800; // minimum 8 seconds
|
||||||
|
Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 20000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 6:
|
||||||
case -2: // Fill Static Color
|
// RGB White and Dedicated White all on with timeout and brightness reduction
|
||||||
col = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu);
|
|
||||||
fill_solid(ledSettings[0].leds, ledSettings[0].size, col);
|
|
||||||
FastLED.show();
|
|
||||||
ESP_LOGD(tag, "Static Color");
|
|
||||||
break;
|
|
||||||
case -1:
|
|
||||||
FastLED.clear();
|
|
||||||
FastLED.show();
|
|
||||||
ESP_LOGD(tag, "LEDs Off");
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
|
|
||||||
FastLED.show();
|
|
||||||
break;
|
|
||||||
case 1: case 2: case 3: case 4: case 5:{ // Timed Fill Animations
|
|
||||||
int timeDuration = AnimEvent.AnimationIndex * 1000;
|
|
||||||
int whiteDelay = timeDuration - 1000;
|
|
||||||
Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 10000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
// RGB White and Dedicated White all on with timeout and brightness reduction
|
|
||||||
if (pwmOutputs[0] != nullptr) {
|
|
||||||
Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000);
|
Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60);
|
||||||
|
break;
|
||||||
|
case 8 ... 12:
|
||||||
|
Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80);
|
||||||
|
break;
|
||||||
|
case 13 ... 17: { // Fire Animations
|
||||||
|
COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable
|
||||||
|
CRGBPalette16 firePalette;
|
||||||
|
createFirePalette(firePalette, fp);
|
||||||
|
Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 18 ... 20: // Sparkle/Twinkle
|
||||||
case 7:
|
Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
|
||||||
Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60);
|
break;
|
||||||
break;
|
case 21:
|
||||||
case 8: case 9: case 10: case 11: case 12:
|
Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90);
|
||||||
Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80);
|
break;
|
||||||
break;
|
case 22: // Rain Animation
|
||||||
case 13: case 14: case 15: case 16: case 17: { // Fire Animations
|
Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40);
|
||||||
COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable
|
break;
|
||||||
createFirePalette(firePalette, fp);
|
|
||||||
Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift);
|
// Comets
|
||||||
break;
|
case 23:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1);
|
||||||
|
break;
|
||||||
|
case 24 ... 31:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6);
|
||||||
|
break;
|
||||||
|
case 32 ... 40:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3);
|
||||||
|
break;
|
||||||
|
case 41 ... 49:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 41], 40, 85, RANDOM_DECAY, 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Snakes
|
||||||
|
case 50:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50);
|
||||||
|
break;
|
||||||
|
case 51 ... 58:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 51], 60, 6);
|
||||||
|
break;
|
||||||
|
case 59 ... 67:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 59], 60, 3);
|
||||||
|
break;
|
||||||
|
case 68 ... 76:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 68], 60, 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Sectors
|
||||||
|
case 77:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90);
|
||||||
|
break;
|
||||||
|
case 78 ... 86:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 78], NO_GAPS, 3, 40, 90);
|
||||||
|
break;
|
||||||
|
case 87 ... 95:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 87], NO_GAPS, 2, 40, 90);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Dashes
|
||||||
|
case 96:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90);
|
||||||
|
break;
|
||||||
|
case 97 ... 104:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 97], WITH_GAPS, 6, 40, 90);
|
||||||
|
break;
|
||||||
|
case 105 ... 113:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 105], WITH_GAPS, 3, 40, 90);
|
||||||
|
break;
|
||||||
|
case 114 ... 122:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 114], WITH_GAPS, 2, 40, 90);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGW(tag, "Loop default");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 18: case 19: case 20:// Sparkle/Twinkle
|
|
||||||
Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
|
|
||||||
break;
|
|
||||||
case 21:
|
|
||||||
//Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90);
|
|
||||||
Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40);
|
|
||||||
break;
|
|
||||||
case 22: // Rain Animation
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
AnimationLooping = false;
|
||||||
// Comets
|
prevAnimEvent = AnimEvent;
|
||||||
case 23:
|
ESP_LOGD(tag, "Going to Queue to Wait");
|
||||||
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1);
|
|
||||||
break;
|
|
||||||
case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31:
|
|
||||||
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6);
|
|
||||||
break;
|
|
||||||
case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40:
|
|
||||||
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3);
|
|
||||||
break;
|
|
||||||
case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49:
|
|
||||||
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 41], 40, 85, RANDOM_DECAY, 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Snakes
|
|
||||||
case 50:
|
|
||||||
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50);
|
|
||||||
break;
|
|
||||||
case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58:
|
|
||||||
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 51], 60, 6);
|
|
||||||
break;
|
|
||||||
case 59: case 60: case 61: case 62: case 63: case 64: case 65: case 66: case 67:
|
|
||||||
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 59], 60, 3);
|
|
||||||
break;
|
|
||||||
case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76:
|
|
||||||
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 68], 60, 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Sectors
|
|
||||||
case 77:
|
|
||||||
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90);
|
|
||||||
break;
|
|
||||||
case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86:
|
|
||||||
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 78], NO_GAPS, 3, 40, 90);
|
|
||||||
break;
|
|
||||||
case 87: case 88: case 89: case 90: case 91: case 92: case 93: case 94: case 95:
|
|
||||||
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 87], NO_GAPS, 2, 40, 90);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Dashes
|
|
||||||
case 96:
|
|
||||||
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90);
|
|
||||||
break;
|
|
||||||
case 97:case 98:case 99: case 100: case 101: case 102: case 103: case 104:
|
|
||||||
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 97], WITH_GAPS, 6, 40, 90);
|
|
||||||
break;
|
|
||||||
case 105: case 106: case 107: case 108: case 109: case 110: case 111: case 112: case 113:
|
|
||||||
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 105], WITH_GAPS, 3, 40, 90);
|
|
||||||
break;
|
|
||||||
case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122:
|
|
||||||
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 114], WITH_GAPS, 2, 40, 90);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
ESP_LOGW(tag, "Loop default");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extended Animation Set
|
||||||
|
while (true) {
|
||||||
|
if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
|
||||||
|
ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
|
||||||
|
switch (AnimEvent.AnimationIndex) {
|
||||||
|
case -3: // Set Shift
|
||||||
|
if (AnimEvent.data.data[0] >= 0 && AnimEvent.data.data[0] < ledSettings[0].size) {
|
||||||
|
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
|
||||||
|
ledSettings[0].shift = AnimEvent.data.data[0];
|
||||||
|
|
||||||
AnimationLooping = false;
|
ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift);
|
||||||
prevAnimEvent = AnimEvent;
|
vTaskDelay(25 / portTICK_PERIOD_MS);
|
||||||
ESP_LOGD(tag, "Going to Queue to Wait");
|
setPixel1(ledSettings[0], 0, CRGB::White);
|
||||||
|
|
||||||
|
FastLED.show();
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[0]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case -2: // Fill Static Color
|
||||||
|
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu));
|
||||||
|
FastLED.show();
|
||||||
|
ESP_LOGD(tag, "Static Color");
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
FastLED.clear();
|
||||||
|
FastLED.show();
|
||||||
|
ESP_LOGD(tag, "LEDs Off");
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
|
||||||
|
FastLED.show();
|
||||||
|
break;
|
||||||
|
case 1 ... 5: { // Timed Fill Animations
|
||||||
|
int timeDuration = AnimEvent.AnimationIndex * 1000 - 400;
|
||||||
|
int whiteDelay = timeDuration - 1000;
|
||||||
|
if (whiteDelay < 800) whiteDelay = 800; // minimum 8 seconds
|
||||||
|
Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 10000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
// RGB White and Dedicated White all on with timeout and brightness reduction
|
||||||
|
Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60);
|
||||||
|
break;
|
||||||
|
case 8 ... 12:
|
||||||
|
Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80);
|
||||||
|
break;
|
||||||
|
case 13 ... 17: { // Fire Animations
|
||||||
|
COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable
|
||||||
|
CRGBPalette16 firePalette;
|
||||||
|
createFirePalette(firePalette, fp);
|
||||||
|
Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 18 ... 20: // Sparkle/Twinkle
|
||||||
|
Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
|
||||||
|
break;
|
||||||
|
case 21:
|
||||||
|
Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90);
|
||||||
|
break;
|
||||||
|
case 22: // Rain Animation
|
||||||
|
Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Comets
|
||||||
|
case 23:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1);
|
||||||
|
break;
|
||||||
|
case 24 ... 31:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6);
|
||||||
|
break;
|
||||||
|
case 32 ... 47:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3);
|
||||||
|
break;
|
||||||
|
case 48 ... 63:
|
||||||
|
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 48], 40, 85, RANDOM_DECAY, 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Snakes
|
||||||
|
case 64:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50);
|
||||||
|
break;
|
||||||
|
case 65 ... 72:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 65], 60, 6);
|
||||||
|
break;
|
||||||
|
case 73 ... 88:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 73], 60, 3);
|
||||||
|
break;
|
||||||
|
case 89 ... 104:
|
||||||
|
Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 89], 60, 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Sectors
|
||||||
|
case 105:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90);
|
||||||
|
break;
|
||||||
|
case 106 ... 121:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 106], NO_GAPS, 3, 40, 90);
|
||||||
|
break;
|
||||||
|
case 122 ... 137:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 122], NO_GAPS, 2, 40, 90);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Dashes
|
||||||
|
case 138:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90);
|
||||||
|
break;
|
||||||
|
case 139 ... 146:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 139], WITH_GAPS, 6, 40, 90);
|
||||||
|
break;
|
||||||
|
case 147 ... 162:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 147], WITH_GAPS, 3, 40, 90);
|
||||||
|
break;
|
||||||
|
case 163 ... 178:
|
||||||
|
Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 163], WITH_GAPS, 2, 40, 90);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGW(tag, "Loop default");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationLooping = false;
|
||||||
|
prevAnimEvent = AnimEvent;
|
||||||
|
ESP_LOGD(tag, "Going to Queue to Wait");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SetAndSaveUserSettings(USER_SETTINGS &userSettings) {
|
||||||
|
|
||||||
|
// Apply the settings to sys_settings
|
||||||
|
sys_settings.limitedMode = userSettings.limitedMode;
|
||||||
|
|
||||||
|
sys_settings.ledStripSettings[0]->chip = userSettings.ledChipset1;
|
||||||
|
sys_settings.ledStripSettings[0]->shift = userSettings.ledShift1;
|
||||||
|
sys_settings.ledStripSettings[0]->bright = userSettings.ledBrightness1;
|
||||||
|
RGB_Lights_Set_Brightness(userSettings.ledBrightness1);
|
||||||
|
|
||||||
|
sys_settings.ledStripSettings[0]->chip = userSettings.ledChipset2;
|
||||||
|
sys_settings.ledStripSettings[0]->shift = userSettings.ledShift2;
|
||||||
|
sys_settings.ledStripSettings[0]->bright = userSettings.ledBrightness2;
|
||||||
|
|
||||||
|
sys_settings.tSensorSettings.setpoint1 = userSettings.fanLowerTemp;
|
||||||
|
sys_settings.tSensorSettings.setpoint2 = userSettings.fanUpperTemp;
|
||||||
|
sys_settings.rampLightSettings[0].min = userSettings.frontLightMin;
|
||||||
|
sys_settings.rampLightSettings[0].max = userSettings.frontLightMax;
|
||||||
|
sys_settings.rampLightSettings[1].min = userSettings.rearLightMin;
|
||||||
|
sys_settings.rampLightSettings[1].max = userSettings.rearLightMax;
|
||||||
|
|
||||||
|
if (!LittleFS.begin()) {
|
||||||
|
ESP_LOGE(tag, "Failed to mount file system");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, open the existing file for reading so we preserve unrelated keys
|
||||||
|
File file = LittleFS.open(booth_file_path, "r");
|
||||||
|
JsonDocument jsonDocument;
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
// Try to deserialize existing content; if it fails, start with an empty document
|
||||||
|
DeserializationError err = deserializeJson(jsonDocument, file);
|
||||||
|
if (err) {
|
||||||
|
ESP_LOGW(tag, "Failed to parse existing JSON (%s), starting fresh", err.c_str());
|
||||||
|
jsonDocument.clear();
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(tag, "Settings file not found, a new one will be created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update only the keys we care about
|
||||||
|
jsonDocument["limied-mode"] = userSettings.limitedMode;
|
||||||
|
|
||||||
|
jsonDocument["ramp-lights"][0]["min"] = userSettings.frontLightMin;
|
||||||
|
jsonDocument["ramp-lights"][0]["max"] = userSettings.frontLightMax;
|
||||||
|
jsonDocument["ramp-lights"][1]["min"] = userSettings.rearLightMin;
|
||||||
|
jsonDocument["ramp-lights"][1]["max"] = userSettings.rearLightMax;
|
||||||
|
|
||||||
|
String chipName = "Unknown";
|
||||||
|
if (userSettings.ledChipset1 == CHIP_WS2812B) chipName = "WS2812B";
|
||||||
|
else if (userSettings.ledChipset1 == CHIP_SK6812) chipName = "SK6812";
|
||||||
|
else if (userSettings.ledChipset1 == CHIP_WS2811) chipName = "WS2811";
|
||||||
|
else if (userSettings.ledChipset1 == CHIP_WS2815) chipName = "WS2815";
|
||||||
|
jsonDocument["strips"][0]["chip"] = chipName;
|
||||||
|
userSettings.rgbOrder1[3] = '\0'; // Ensure null-terminated string
|
||||||
|
jsonDocument["strips"][0]["rgb-order"] = String(userSettings.rgbOrder1);
|
||||||
|
jsonDocument["strips"][0]["shift"] = userSettings.ledShift1;
|
||||||
|
jsonDocument["strips"][0]["bright"] = userSettings.ledBrightness1;
|
||||||
|
jsonDocument["strips"][0]["count"] = userSettings.ledCount1;
|
||||||
|
|
||||||
|
|
||||||
|
if (userSettings.ledChipset2 == CHIP_WS2812B) chipName = "WS2812B";
|
||||||
|
else if (userSettings.ledChipset2 == CHIP_SK6812) chipName = "SK6812";
|
||||||
|
else if (userSettings.ledChipset2 == CHIP_WS2811) chipName = "WS2811";
|
||||||
|
else if (userSettings.ledChipset2 == CHIP_WS2815) chipName = "WS2815";
|
||||||
|
jsonDocument["strips"][1]["chip"] = chipName;
|
||||||
|
userSettings.rgbOrder2[3] = '\0'; // Ensure null-terminated string
|
||||||
|
jsonDocument["strips"][1]["rgb-order"] = String(userSettings.rgbOrder2);
|
||||||
|
jsonDocument["strips"][1]["shift"] = userSettings.ledShift2;
|
||||||
|
jsonDocument["strips"][1]["bright"] = userSettings.ledBrightness2;
|
||||||
|
jsonDocument["strips"][1]["count"] = userSettings.ledCount2;
|
||||||
|
|
||||||
|
jsonDocument["t-sensor"]["sp1"] = userSettings.fanLowerTemp;
|
||||||
|
jsonDocument["t-sensor"]["sp2"] = userSettings.fanUpperTemp;
|
||||||
|
|
||||||
|
//jsonDocument["stripsr"][0]["chip"] = settings.ledChipset;
|
||||||
|
|
||||||
|
// Now open the file for writing (overwrite) and serialize back
|
||||||
|
File out = LittleFS.open(booth_file_path, "w");
|
||||||
|
if (!out) {
|
||||||
|
ESP_LOGE(tag, "Failed to open file for writing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serializeJson(jsonDocument, out) == 0) {
|
||||||
|
ESP_LOGE(tag, "Failed to write to file");
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(tag, "User settings saved successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
|
||||||
|
// Modify system.json with booth type
|
||||||
|
String sysProf = String(sys_settings.profile);
|
||||||
|
String userProf = String(userSettings.profile);
|
||||||
|
sysProf.toLowerCase();
|
||||||
|
userProf.toLowerCase();
|
||||||
|
ESP_LOGI(tag, "System Profile: '%s', User Profile: '%s'", sysProf.c_str(), userProf.c_str());
|
||||||
|
|
||||||
|
if (userProf.length() > 0 && sysProf.indexOf(userProf) >= 0) {
|
||||||
|
// userSettings.profile is contained in sys_settings.profile (case-insensitive)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack) {
|
void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack) {
|
||||||
for (uint8_t i = 0; i < 16; i++) {
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
if (i < 3) palette[i] = CRGB::Black;
|
if (i < 3) palette[i] = CRGB::Black;
|
||||||
|
|||||||
@ -27,6 +27,7 @@ void Animation_Init(void){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Animation Loop Template
|
// Animation Loop Template
|
||||||
|
/*
|
||||||
void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<int()> callback) {
|
void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<int()> callback) {
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
ESP_LOGE("Animation_Loop", "Invalid callback function");
|
ESP_LOGE("Animation_Loop", "Invalid callback function");
|
||||||
@ -65,7 +66,62 @@ void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<in
|
|||||||
|
|
||||||
loop_active_flag = false;
|
loop_active_flag = false;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<int()> callback) {
|
||||||
|
if (!callback) {
|
||||||
|
ESP_LOGE("Animation_Loop", "Invalid callback function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_active_flag = true;
|
||||||
|
speed = constrain(speed, 0, MaxSpeed);
|
||||||
|
|
||||||
|
// compute desired loop delay in milliseconds and clamp
|
||||||
|
int loopDelayMs = max(MaxSpeed - speed, MinLoopDelay);
|
||||||
|
const int MAX_LOOP_DELAY_MS = 60 * 1000; // 60s safety cap
|
||||||
|
if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
|
||||||
|
|
||||||
|
// Convert ms -> RTOS ticks
|
||||||
|
TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
|
||||||
|
|
||||||
|
ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications
|
||||||
|
|
||||||
|
TickType_t xLastWakeTime;
|
||||||
|
TickType_t elapsedTicks;
|
||||||
|
TickType_t delayTicks;
|
||||||
|
int retVal = 0;
|
||||||
|
while(!retVal && loop_active_flag) {
|
||||||
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
|
try {
|
||||||
|
retVal = callback(); // Call animation function
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what());
|
||||||
|
break;
|
||||||
|
} catch (...) {
|
||||||
|
ESP_LOGE("Animation_Loop", "Callback unknown exception");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!loop_active_flag) break;
|
||||||
|
|
||||||
|
// compute elapsed ticks since callback start and remaining ticks
|
||||||
|
elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
|
||||||
|
delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
|
||||||
|
|
||||||
|
if (delayTicks == 0) {
|
||||||
|
// yield a tick to avoid busy-spin
|
||||||
|
vTaskDelay(1);
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, 0)) break; // notified -> exit
|
||||||
|
} else {
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; } // notified -> exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_active_flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int()> callback) {
|
void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int()> callback) {
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
ESP_LOGE("Animation_Loop", "Invalid callback function");
|
ESP_LOGE("Animation_Loop", "Invalid callback function");
|
||||||
@ -105,8 +161,74 @@ void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int(
|
|||||||
|
|
||||||
loop_active_flag = false;
|
loop_active_flag = false;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int()> callback) {
|
||||||
|
if (!callback) {
|
||||||
|
ESP_LOGE("Animation_Loop", "Invalid callback function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_active_flag = true;
|
||||||
|
|
||||||
|
// clear any pending notification
|
||||||
|
ulTaskNotifyTake(pdTRUE, 0);
|
||||||
|
|
||||||
|
TickType_t xLastWakeTime;
|
||||||
|
TickType_t elapsedTicks;
|
||||||
|
TickType_t delayTicks;
|
||||||
|
|
||||||
|
// Define sensible upper bound for delay (ms) to avoid indefinite blocking due to bad callback
|
||||||
|
const int MAX_LOOP_DELAY_MS = 60 * 1000; // 60 seconds
|
||||||
|
|
||||||
|
while (loop_active_flag) {
|
||||||
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
|
int loopDelayMs = 0;
|
||||||
|
try {
|
||||||
|
loopDelayMs = callback(); // callback returns desired delay in milliseconds
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what());
|
||||||
|
break;
|
||||||
|
} catch (...) {
|
||||||
|
ESP_LOGE("Animation_Loop", "Callback unknown exception");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loop_active_flag) break;
|
||||||
|
|
||||||
|
// sanitize returned ms value and enforce minimum/maximum
|
||||||
|
if (loopDelayMs < (int)MinLoopDelay) loopDelayMs = MinLoopDelay;
|
||||||
|
if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
|
||||||
|
|
||||||
|
// convert ms -> RTOS ticks (safe conversion)
|
||||||
|
TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
|
||||||
|
|
||||||
|
// compute elapsed ticks since callback start
|
||||||
|
elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
|
||||||
|
|
||||||
|
// compute remaining delay (in ticks)
|
||||||
|
delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
|
||||||
|
|
||||||
|
if (delayTicks == 0) {
|
||||||
|
// Ensure we yield at least one tick to avoid busy-looping
|
||||||
|
vTaskDelay(1);
|
||||||
|
// Check if notified during the yield
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, 0)) break;
|
||||||
|
} else {
|
||||||
|
// Wait for either a notification (termination) or the timeout
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, delayTicks)) {
|
||||||
|
// notified => exit loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_active_flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Animation Loop Template
|
// Animation Loop Template
|
||||||
|
/*
|
||||||
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function<int()> callback) {
|
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function<int()> callback) {
|
||||||
loop_active_flag = true;
|
loop_active_flag = true;
|
||||||
speed = constrain(speed, 0, MaxSpeed);
|
speed = constrain(speed, 0, MaxSpeed);
|
||||||
@ -159,8 +281,72 @@ void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickTyp
|
|||||||
|
|
||||||
loop_active_flag = false;
|
loop_active_flag = false;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function<int()> callback) {
|
||||||
|
loop_active_flag = true;
|
||||||
|
speed = constrain(speed, 0, MaxSpeed);
|
||||||
|
|
||||||
|
// treat durationMs as milliseconds (convert to ticks)
|
||||||
|
const TickType_t durationTicks = pdMS_TO_TICKS(durationMs);
|
||||||
|
|
||||||
|
ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications
|
||||||
|
TickType_t startTicks = xTaskGetTickCount();
|
||||||
|
TickType_t xLastWakeTime;
|
||||||
|
|
||||||
|
const int MAX_LOOP_DELAY_MS = 60 * 1000; // safety cap
|
||||||
|
while (loop_active_flag) {
|
||||||
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
|
// Call animation function
|
||||||
|
int speedIncrease = 0;
|
||||||
|
try {
|
||||||
|
speedIncrease = callback();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
ESP_LOGE("Animation_Loop_Duration", "Callback exception: %s", e.what());
|
||||||
|
break;
|
||||||
|
} catch (...) {
|
||||||
|
ESP_LOGE("Animation_Loop_Duration", "Callback unknown exception");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!loop_active_flag) break;
|
||||||
|
|
||||||
|
// Calculate combined speed with bounds protection
|
||||||
|
int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed);
|
||||||
|
|
||||||
|
// Calculate delay with minimum protection (ms)
|
||||||
|
int loopDelayMs = MaxSpeed - totalSpeed;
|
||||||
|
loopDelayMs = max(loopDelayMs, MinLoopDelay);
|
||||||
|
if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
|
||||||
|
TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
|
||||||
|
|
||||||
|
// Calculate remaining time with overflow protection
|
||||||
|
TickType_t elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
|
||||||
|
TickType_t delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
|
||||||
|
|
||||||
|
// Delay and Check for termination request
|
||||||
|
if (delayTicks == 0) {
|
||||||
|
vTaskDelay(1);
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, 0)) break;
|
||||||
|
} else {
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, delayTicks)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if duration reached and wait for loop_active_flag
|
||||||
|
if (durationTicks > 0) {
|
||||||
|
TickType_t totalElapsed = xTaskGetTickCount() - startTicks;
|
||||||
|
if (totalElapsed >= durationTicks) {
|
||||||
|
break; // Auto-terminate when duration reached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_active_flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Animation Loop Template
|
// Animation Loop Template
|
||||||
|
/*
|
||||||
void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function<int()> callback) {
|
void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function<int()> callback) {
|
||||||
loop_active_flag = true;
|
loop_active_flag = true;
|
||||||
uint32_t loop_cycle_count = 0;
|
uint32_t loop_cycle_count = 0;
|
||||||
@ -211,6 +397,66 @@ void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t
|
|||||||
|
|
||||||
loop_active_flag = false;
|
loop_active_flag = false;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function<int()> callback) {
|
||||||
|
loop_active_flag = true;
|
||||||
|
uint32_t loop_cycle_count = 0;
|
||||||
|
speed = constrain(speed, 0, MaxSpeed);
|
||||||
|
|
||||||
|
ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications
|
||||||
|
TickType_t xLastWakeTime;
|
||||||
|
TickType_t elapsedTicks;
|
||||||
|
TickType_t delayTicks;
|
||||||
|
|
||||||
|
const int MAX_LOOP_DELAY_MS = 60 * 1000; // safety cap
|
||||||
|
for(;;) {
|
||||||
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
|
int speedIncrease = 0;
|
||||||
|
try {
|
||||||
|
speedIncrease = callback(); // Call animation function
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
ESP_LOGE("Animation_Loop_Cycles", "Callback exception: %s", e.what());
|
||||||
|
break;
|
||||||
|
} catch (...) {
|
||||||
|
ESP_LOGE("Animation_Loop_Cycles", "Callback unknown exception");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!loop_active_flag) break;
|
||||||
|
|
||||||
|
// Calculate combined speed with bounds protection
|
||||||
|
int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed);
|
||||||
|
|
||||||
|
// Calculate delay with minimum protection (ms)
|
||||||
|
int loopDelayMs = MaxSpeed - totalSpeed;
|
||||||
|
loopDelayMs = max(loopDelayMs, MinLoopDelay);
|
||||||
|
if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
|
||||||
|
TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
|
||||||
|
|
||||||
|
// Calculate remaining time with overflow protection
|
||||||
|
elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
|
||||||
|
delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
|
||||||
|
|
||||||
|
// Delay and Check for termination request
|
||||||
|
if (delayTicks == 0) {
|
||||||
|
vTaskDelay(1);
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, 0)) break;
|
||||||
|
} else {
|
||||||
|
if (ulTaskNotifyTake(pdTRUE, delayTicks)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if cycles reached and exit
|
||||||
|
if (loop_cycle_count >= loop_cycles) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_cycle_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_active_flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
@ -399,76 +645,6 @@ void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const C
|
|||||||
//#define COMET_FADE_FACTOR COMET_FADE_FACTOR2
|
//#define COMET_FADE_FACTOR COMET_FADE_FACTOR2
|
||||||
#define MAX_COMETS 8 // Maximum number of comets supported
|
#define MAX_COMETS 8 // Maximum number of comets supported
|
||||||
/*
|
/*
|
||||||
void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) {
|
|
||||||
// Validate inputs
|
|
||||||
int numComets = colorPack.size;
|
|
||||||
if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0) return;
|
|
||||||
|
|
||||||
// Calculate comet size
|
|
||||||
int cometSize = (size / (numComets * cometMultiplier)) * COMET_SIZE_FACTOR;
|
|
||||||
if (cometSize < 1) cometSize = 1;
|
|
||||||
|
|
||||||
// Set fade factor
|
|
||||||
uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1;
|
|
||||||
|
|
||||||
// Initialize comet positions to be equally spaced
|
|
||||||
std::unique_ptr<int[]> cometPositions(new (std::nothrow) int[numComets * cometMultiplier]);
|
|
||||||
if (!cometPositions) return;
|
|
||||||
int totalComets = numComets * cometMultiplier;
|
|
||||||
int spacing = size / totalComets;
|
|
||||||
for (int i = 0; i < totalComets; i++) {
|
|
||||||
cometPositions[i] = i * spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation loop
|
|
||||||
bool direction = true; // true = forward, false = backward
|
|
||||||
int loopCounter = 0;
|
|
||||||
try {
|
|
||||||
const int loopTick = 30; // ms per animation tick
|
|
||||||
Animation_Loop(activeFlag, loopTick, [&]() -> int {
|
|
||||||
// Fade all LEDs
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
if(!randomDecay) {
|
|
||||||
leds[i].fadeToBlackBy(fadeFactor);
|
|
||||||
} else if(random(10) > 5){
|
|
||||||
leds[i].fadeToBlackBy(fadeFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move and draw comets
|
|
||||||
for (int i = 0; i < totalComets; i++) {
|
|
||||||
if (direction) {
|
|
||||||
cometPositions[i] = (cometPositions[i] + 1) % size;
|
|
||||||
} else {
|
|
||||||
cometPositions[i] = (cometPositions[i] - 1 + size) % size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw comet with solid color
|
|
||||||
CRGB color = colorPack.col[i % colorPack.size];
|
|
||||||
for (int j = 0; j < cometSize; j++) {
|
|
||||||
int pos = (cometPositions[i] - j + size) % size;
|
|
||||||
leds[pos] += color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loopCounter++;
|
|
||||||
if(loopCounter >= (size * CYCLES_PER_DIRECTION)){
|
|
||||||
direction = !direction;
|
|
||||||
loopCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FastLED.show();
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what());
|
|
||||||
} catch (...) {
|
|
||||||
ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
|
|
||||||
}
|
|
||||||
// No need to delete cometPositions as it is managed by std::unique_ptr
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) {
|
void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) {
|
||||||
// Validate inputs
|
// Validate inputs
|
||||||
int numComets = colorPack.size;
|
int numComets = colorPack.size;
|
||||||
@ -479,8 +655,8 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate comet size
|
// Calculate comet size (use float math and round to avoid integer-division truncation)
|
||||||
int cometSize = (size / totalComets) * COMET_SIZE_FACTOR;
|
int cometSize = (int)(((float)size * (float)COMET_SIZE_FACTOR) / (float)totalComets + 0.5f);
|
||||||
if (cometSize < 1) cometSize = 1;
|
if (cometSize < 1) cometSize = 1;
|
||||||
|
|
||||||
// Set fade factor
|
// Set fade factor
|
||||||
@ -500,9 +676,14 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
|
|||||||
int pos;
|
int pos;
|
||||||
CRGB color;
|
CRGB color;
|
||||||
int speed = minSpeed;
|
int speed = minSpeed;
|
||||||
int loopDuration = (size * CYCLES_PER_DIRECTION);
|
int loopDuration = size * CYCLES_PER_DIRECTION;
|
||||||
|
// Defensive: ensure loopDuration isn't zero to avoid divide-by-zero later
|
||||||
|
if (loopDuration < 1) loopDuration = 1;
|
||||||
int thirdLoop = loopDuration / 3;
|
int thirdLoop = loopDuration / 3;
|
||||||
int twoThirdLoop = (2 * loopDuration) / 3;
|
// Defensive: ensure thirdLoop is at least 1 for progress calculations
|
||||||
|
if (thirdLoop < 1) thirdLoop = 1;
|
||||||
|
// Keep twoThirdLoop consistent with thirdLoop to avoid rounding surprises
|
||||||
|
int twoThirdLoop = 2 * thirdLoop;
|
||||||
try {
|
try {
|
||||||
Animation_Loop_Variable(activeFlag, [&]() -> int {
|
Animation_Loop_Variable(activeFlag, [&]() -> int {
|
||||||
// Fade all LEDs
|
// Fade all LEDs
|
||||||
@ -558,7 +739,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
|
|||||||
|
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
|
|
||||||
return max(MaxSpeed - speed, MinLoopDelay);
|
// Compute delay to return to Animation_Loop_Variable.
|
||||||
|
// Ensure we never return a value less than MinLoopDelay.
|
||||||
|
int computed = MaxSpeed - speed;
|
||||||
|
if (computed < MinLoopDelay) computed = MinLoopDelay;
|
||||||
|
return computed;
|
||||||
});
|
});
|
||||||
|
|
||||||
fill_solid(leds, size, CRGB::Black);
|
fill_solid(leds, size, CRGB::Black);
|
||||||
@ -569,6 +754,131 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
|
|||||||
ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
|
ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) {
|
||||||
|
// Validate inputs
|
||||||
|
int numComets = colorPack.size;
|
||||||
|
int totalComets = numComets * cometMultiplier;
|
||||||
|
|
||||||
|
if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0 || totalComets > MAX_COMETS) {
|
||||||
|
ESP_LOGE("Anim_Comets", "Invalid input parameters or too many comets (max: %d, requested: %d)", MAX_COMETS, totalComets);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize speed inputs to known bounds
|
||||||
|
minSpeed = constrain(minSpeed, 0, MaxSpeed);
|
||||||
|
maxSpeed = constrain(maxSpeed, 0, MaxSpeed);
|
||||||
|
if (minSpeed > maxSpeed) { int t=minSpeed; minSpeed=maxSpeed; maxSpeed=t; }
|
||||||
|
|
||||||
|
// Calculate comet size (use float math and round to avoid integer-division truncation)
|
||||||
|
int cometSize = (int)(((float)size * (float)COMET_SIZE_FACTOR) / (float)totalComets + 0.5f);
|
||||||
|
if (cometSize < 1) cometSize = 1;
|
||||||
|
|
||||||
|
// Set fade factor
|
||||||
|
uint8_t fadeFactor = map(totalComets, 1, MAX_COMETS, COMET_FADE_FACTOR1, COMET_FADE_FACTOR2);
|
||||||
|
fadeFactor = constrain(fadeFactor, COMET_FADE_FACTOR1, COMET_FADE_FACTOR2);
|
||||||
|
|
||||||
|
// Initialize comet positions with fixed array, evenly distributed
|
||||||
|
int cometPositions[MAX_COMETS] = {0};
|
||||||
|
for (int i = 0; i < totalComets; i++) {
|
||||||
|
// Even distribution even when size not divisible by totalComets
|
||||||
|
cometPositions[i] = (i * size) / totalComets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation loop
|
||||||
|
bool direction = true; // true = forward, false = backward
|
||||||
|
int loopCounter = 0;
|
||||||
|
int pos;
|
||||||
|
CRGB color;
|
||||||
|
int speed = minSpeed;
|
||||||
|
|
||||||
|
// Cache size/limits locally for speed
|
||||||
|
const int localSize = size;
|
||||||
|
const int localTotalComets = totalComets;
|
||||||
|
int loopDuration = localSize * CYCLES_PER_DIRECTION;
|
||||||
|
// Defensive: ensure loopDuration isn't zero to avoid divide-by-zero later
|
||||||
|
if (loopDuration < 1) loopDuration = 1;
|
||||||
|
int thirdLoop = loopDuration / 3;
|
||||||
|
if (thirdLoop < 1) thirdLoop = 1;
|
||||||
|
int twoThirdLoop = 2 * thirdLoop;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Animation_Loop_Variable(activeFlag, [&]() -> int {
|
||||||
|
// Fade all LEDs (keep this O(N) operation but keep it tight)
|
||||||
|
for (int i = 0; i < localSize; i++) {
|
||||||
|
if (!randomDecay) {
|
||||||
|
leds[i].fadeToBlackBy(fadeFactor);
|
||||||
|
} else if (getRandomValue(10) > 5) {
|
||||||
|
leds[i].fadeToBlackBy(fadeFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move and draw comets
|
||||||
|
for (int i = 0; i < localTotalComets; i++) {
|
||||||
|
if (direction) {
|
||||||
|
cometPositions[i] = (cometPositions[i] + 1) % localSize;
|
||||||
|
} else {
|
||||||
|
cometPositions[i] = (cometPositions[i] - 1 + localSize) % localSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw comet with solid color (safe modulo)
|
||||||
|
color = colorPack.col[i % colorPack.size];
|
||||||
|
for (int j = 0; j < cometSize; j++) {
|
||||||
|
// Tail follows the direction of movement
|
||||||
|
pos = direction ? (cometPositions[i] - j) : (cometPositions[i] + j);
|
||||||
|
pos = (pos % localSize + localSize) % localSize; // safe modulus
|
||||||
|
leds[pos] += color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speed ramping: 1/3 aggressive ramp up, 1/3 constant, 1/3 aggressive ramp down
|
||||||
|
if (loopCounter < thirdLoop) {
|
||||||
|
// First third: ease-out quartic implemented without pow()
|
||||||
|
float progress = (float)loopCounter / (float)thirdLoop; // 0.0 to 1.0
|
||||||
|
float oneMinus = 1.0f - progress;
|
||||||
|
float eased = 1.0f - (oneMinus * oneMinus * oneMinus * oneMinus); // 1 - (1-t)^4
|
||||||
|
speed = minSpeed + (int)((maxSpeed - minSpeed) * eased);
|
||||||
|
}
|
||||||
|
else if (loopCounter < twoThirdLoop) {
|
||||||
|
// Middle third: constant at maxSpeed
|
||||||
|
speed = maxSpeed;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Last third: ease-in quartic implemented without pow()
|
||||||
|
float progress = (float)(loopCounter - twoThirdLoop) / (float)thirdLoop; // 0.0 to 1.0
|
||||||
|
if (progress < 0.0f) progress = 0.0f; if (progress > 1.0f) progress = 1.0f;
|
||||||
|
float eased = (progress * progress * progress * progress); // t^4
|
||||||
|
speed = maxSpeed - (int)((maxSpeed - minSpeed) * eased);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++loopCounter >= loopDuration) {
|
||||||
|
direction = !direction;
|
||||||
|
loopCounter = 0;
|
||||||
|
speed = minSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
FastLED.show();
|
||||||
|
|
||||||
|
// Compute delay to return to Animation_Loop_Variable.
|
||||||
|
// Ensure we never return a value less than MinLoopDelay.
|
||||||
|
int computed = MaxSpeed - speed;
|
||||||
|
if (computed < MinLoopDelay) computed = MinLoopDelay;
|
||||||
|
return computed;
|
||||||
|
});
|
||||||
|
|
||||||
|
fill_solid(leds, size, CRGB::Black);
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what());
|
||||||
|
} catch (...) {
|
||||||
|
ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: For production/stability consider:
|
||||||
|
// - Replacing volatile<bool> activeFlag with std::atomic<bool> or task-notify mechanism.
|
||||||
|
// - If FastLED.show() blocks too long for very long strips consider chunked updates or an alternate driver.
|
||||||
|
// - If pow() usage was widespread, replace with fixed-point or inline multiplies as done above.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0)
|
void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0)
|
||||||
@ -698,6 +1008,8 @@ void Anim_TimedFill_Flash(bool volatile& activeFlag, CRGB* leds, int size, PWM_O
|
|||||||
if (flashElapsed >= flashTimeout) {
|
if (flashElapsed >= flashTimeout) {
|
||||||
// Flash timeout expired, set PWM to minimum
|
// Flash timeout expired, set PWM to minimum
|
||||||
pwmOut->setOutput(pwmMin);
|
pwmOut->setOutput(pwmMin);
|
||||||
|
// exit the loop
|
||||||
|
activeFlag = false;
|
||||||
ESP_LOGI("Anim_TimedFill_Flash", "Flash timeout expired, PWM set to minimum");
|
ESP_LOGI("Anim_TimedFill_Flash", "Flash timeout expired, PWM set to minimum");
|
||||||
}
|
}
|
||||||
// Continue in infinite loop regardless of timeout status
|
// Continue in infinite loop regardless of timeout status
|
||||||
@ -754,9 +1066,8 @@ void Anim_SolidWhite(bool volatile& activeFlag, CRGB* leds, PWM_Output* pwmOut,
|
|||||||
// No animation, just maintain the solid white state
|
// No animation, just maintain the solid white state
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
if(pwmOut){
|
|
||||||
pwmOut->setOutput(0); // Turn off PWM output
|
if(pwmOut){ pwmOut->setOutput(0); }// Turn off PWM output
|
||||||
}
|
|
||||||
FastLED.setBrightness(origBright); // Restore original brightness
|
FastLED.setBrightness(origBright); // Restore original brightness
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, con
|
|||||||
if (buffer_size < 1024) buffer_size = 1024; // Absolute minimum is 1KB
|
if (buffer_size < 1024) buffer_size = 1024; // Absolute minimum is 1KB
|
||||||
|
|
||||||
downloadBuffer.reset(new uint8_t[buffer_size]);
|
downloadBuffer.reset(new uint8_t[buffer_size]);
|
||||||
|
downloadBufferSize = buffer_size;
|
||||||
|
|
||||||
baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
|
baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
|
||||||
// Ensure baseUrl ends with a single '/'
|
// Ensure baseUrl ends with a single '/'
|
||||||
@ -81,6 +82,7 @@ AppUpdater::ManifestCheckResult AppUpdater::checkManifest() {
|
|||||||
http.end();
|
http.end();
|
||||||
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
|
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(payload.isEmpty()){
|
if(payload.isEmpty()){
|
||||||
ESP_LOGE(TAG, "Failed to fetch manifest after retries");
|
ESP_LOGE(TAG, "Failed to fetch manifest after retries");
|
||||||
return ManifestCheckResult::ERROR_FETCH_FAILED;
|
return ManifestCheckResult::ERROR_FETCH_FAILED;
|
||||||
@ -159,6 +161,7 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const
|
|||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "Local file does not exist: %s", localPath);
|
ESP_LOGI(TAG, "Local file does not exist: %s", localPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(skip){
|
if(skip){
|
||||||
ESP_LOGI(TAG, "File already up to date: %s", localPath);
|
ESP_LOGI(TAG, "File already up to date: %s", localPath);
|
||||||
updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
|
updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
|
||||||
@ -179,9 +182,12 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const
|
|||||||
http.end();
|
http.end();
|
||||||
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
|
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
if (httpCode != HTTP_CODE_OK) {
|
||||||
ESP_LOGE(TAG, "Download failed for %s: HTTP code %d", localPath, httpCode);
|
ESP_LOGE(TAG, "Download failed for %s: HTTP code %d", localPath, httpCode);
|
||||||
updateProgress(UpdateStatus::ERROR, 0, String(String("Download failed: ") + localPath).c_str());
|
char buf[128];
|
||||||
|
snprintf(buf, sizeof(buf), "Download failed: %s", localPath);
|
||||||
|
updateProgress(UpdateStatus::ERROR, 0, buf);
|
||||||
http.end();
|
http.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -217,20 +223,23 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
const int MAX_RETRIES = 2; // Maximum number of retries for MD5 failure
|
const int MAX_RETRIES = 2; // Maximum number of retries for MD5 failure
|
||||||
|
|
||||||
for (int retry = 0; retry <= MAX_RETRIES; retry++) {
|
for (int retry = 0; retry <= MAX_RETRIES; retry++) {
|
||||||
|
HTTPClient retryHttp; // kept alive for the duration of this iteration if used
|
||||||
|
WiFiClient* activeStream = stream;
|
||||||
|
size_t activeContentLength = contentLength;
|
||||||
|
|
||||||
if (retry > 0) {
|
if (retry > 0) {
|
||||||
ESP_LOGW(TAG, "Retrying download of %s (attempt %d/%d)", localPath, retry, MAX_RETRIES);
|
ESP_LOGW(TAG, "Retrying download of %s (attempt %d/%d)", localPath, retry, MAX_RETRIES);
|
||||||
// Need to re-fetch the file for retry - use the REMOTE path, not local path
|
// Need to re-fetch the file for retry - use the REMOTE path, not local path
|
||||||
String url = buildUrl(remotePath);
|
String url = buildUrl(remotePath);
|
||||||
HTTPClient http;
|
retryHttp.begin(url);
|
||||||
http.begin(url);
|
int httpCode = retryHttp.GET();
|
||||||
int httpCode = http.GET();
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
if (httpCode != HTTP_CODE_OK) {
|
||||||
ESP_LOGE(TAG, "Retry download failed: %d", httpCode);
|
ESP_LOGE(TAG, "Retry download failed: %d", httpCode);
|
||||||
http.end();
|
retryHttp.end();
|
||||||
continue; // Try next retry if available
|
continue; // Try next retry if available
|
||||||
}
|
}
|
||||||
stream = http.getStreamPtr();
|
activeStream = retryHttp.getStreamPtr();
|
||||||
contentLength = http.getSize();
|
activeContentLength = retryHttp.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
MD5Builder md5;
|
MD5Builder md5;
|
||||||
@ -259,6 +268,19 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Using temp file path: %s for target: %s", tempPath.c_str(), localPath);
|
ESP_LOGI(TAG, "Using temp file path: %s for target: %s", tempPath.c_str(), localPath);
|
||||||
|
ESP_LOGI(TAG, "verifyAndSaveFile: downloadBufferSize=%u", (unsigned)downloadBufferSize);
|
||||||
|
|
||||||
|
// Check available space if content length is known
|
||||||
|
if (activeContentLength > 0) {
|
||||||
|
size_t freeBytes = LittleFS.totalBytes() - LittleFS.usedBytes();
|
||||||
|
// Leave a small headroom (5%) to avoid filling filesystem completely
|
||||||
|
if (activeContentLength > freeBytes * 95 / 100) {
|
||||||
|
ESP_LOGE(TAG, "Not enough LittleFS space for %s (%u bytes needed, %u bytes free)",
|
||||||
|
localPath, (unsigned)activeContentLength, (unsigned)freeBytes);
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open temporary file for writing (LittleFS will create directories automatically)
|
// Open temporary file for writing (LittleFS will create directories automatically)
|
||||||
File file = fileSystem.open(tempPath.c_str(), FILE_WRITE, true); // true = create if not exists
|
File file = fileSystem.open(tempPath.c_str(), FILE_WRITE, true); // true = create if not exists
|
||||||
@ -267,25 +289,33 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
|
|
||||||
// Try to diagnose the issue
|
// Try to diagnose the issue
|
||||||
ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes",
|
ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes",
|
||||||
LittleFS.usedBytes(), LittleFS.totalBytes());
|
(unsigned)LittleFS.usedBytes(), (unsigned)LittleFS.totalBytes());
|
||||||
|
|
||||||
// Check if we're out of space
|
// Check if we're out of space
|
||||||
if (LittleFS.usedBytes() >= LittleFS.totalBytes() * 0.95) {
|
if (LittleFS.usedBytes() >= LittleFS.totalBytes() * 0.95) {
|
||||||
ESP_LOGE(TAG, "LittleFS nearly full - may not have space for temp file");
|
ESP_LOGE(TAG, "LittleFS nearly full - may not have space for temp file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
|
// Defensive: ensure download buffer exists
|
||||||
|
if (!downloadBuffer || downloadBufferSize == 0) {
|
||||||
|
ESP_LOGE(TAG, "No download buffer available");
|
||||||
|
file.close();
|
||||||
|
fileSystem.remove(tempPath.c_str());
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (contentLength > 0) {
|
if (activeContentLength > 0) {
|
||||||
// Single pass with known content length
|
// Single pass with known content length
|
||||||
while (totalRead < contentLength) {
|
while (totalRead < activeContentLength) {
|
||||||
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
|
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; }
|
||||||
size_t available = stream->available();
|
size_t available = activeStream->available();
|
||||||
if (available) {
|
if (available) {
|
||||||
size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE)));
|
size_t readLen = activeStream->readBytes(downloadBuffer.get(), std::min(available, downloadBufferSize));
|
||||||
|
|
||||||
// Write to temp file and update MD5
|
// Write to temp file and update MD5
|
||||||
if (file.write(downloadBuffer.get(), readLen) != readLen) {
|
if (file.write(downloadBuffer.get(), readLen) != readLen) {
|
||||||
@ -293,20 +323,21 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
fileSystem.remove(tempPath.c_str());
|
fileSystem.remove(tempPath.c_str());
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
md5.add(downloadBuffer.get(), readLen);
|
md5.add(downloadBuffer.get(), readLen);
|
||||||
totalRead += readLen;
|
totalRead += readLen;
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / contentLength , localPath);
|
updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / activeContentLength , localPath);
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Unknown content length: read until stream ends
|
// Unknown content length: read until stream ends
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
|
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; }
|
||||||
size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
|
size_t readLen = activeStream->readBytes(downloadBuffer.get(), downloadBufferSize);
|
||||||
if (readLen == 0) {
|
if (readLen == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -314,6 +345,7 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
ESP_LOGE(TAG, "Failed to write to temporary file");
|
ESP_LOGE(TAG, "Failed to write to temporary file");
|
||||||
file.close();
|
file.close();
|
||||||
fileSystem.remove(tempPath.c_str());
|
fileSystem.remove(tempPath.c_str());
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
md5.add(downloadBuffer.get(), readLen);
|
md5.add(downloadBuffer.get(), readLen);
|
||||||
@ -347,11 +379,12 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
ESP_LOGE(TAG, "MD5 mismatch for %s (attempt %d/%d). Expected: %s, Got: %s",
|
ESP_LOGE(TAG, "MD5 mismatch for %s (attempt %d/%d). Expected: %s, Got: %s",
|
||||||
localPath, retry+1, MAX_RETRIES+1, expectedMd5, calculatedMd5.c_str());
|
localPath, retry+1, MAX_RETRIES+1, expectedMd5, calculatedMd5.c_str());
|
||||||
ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars",
|
ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars",
|
||||||
strlen(expectedMd5), calculatedMd5.length());
|
(int)strlen(expectedMd5), (int)calculatedMd5.length());
|
||||||
fileSystem.remove(tempPath.c_str());
|
fileSystem.remove(tempPath.c_str());
|
||||||
|
|
||||||
if (retry < MAX_RETRIES) {
|
if (retry < MAX_RETRIES) {
|
||||||
// Will retry in next loop iteration
|
// Will retry in next loop iteration
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,12 +425,15 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
ESP_LOGE(TAG, "Failed to rename temporary file for non-critical use: %s -> %s",
|
ESP_LOGE(TAG, "Failed to rename temporary file for non-critical use: %s -> %s",
|
||||||
tempPath.c_str(), localPath);
|
tempPath.c_str(), localPath);
|
||||||
fileSystem.remove(tempPath.c_str());
|
fileSystem.remove(tempPath.c_str());
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
// Return false to indicate verification failure, but the file will still be used
|
// Return false to indicate verification failure, but the file will still be used
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,10 +463,12 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
|
|||||||
if (!fileSystem.rename(tempPath.c_str(), localPath)) {
|
if (!fileSystem.rename(tempPath.c_str(), localPath)) {
|
||||||
ESP_LOGE(TAG, "Failed to rename temporary file: %s -> %s", tempPath.c_str(), localPath);
|
ESP_LOGE(TAG, "Failed to rename temporary file: %s -> %s", tempPath.c_str(), localPath);
|
||||||
fileSystem.remove(tempPath.c_str());
|
fileSystem.remove(tempPath.c_str());
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress(UpdateStatus::VERIFYING, 100, localPath);
|
updateProgress(UpdateStatus::VERIFYING, 100, localPath);
|
||||||
|
if (retry > 0) retryHttp.end();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,7 +488,7 @@ String AppUpdater::getLocalMD5(const char* filePath){
|
|||||||
size_t totalRead = 0;
|
size_t totalRead = 0;
|
||||||
size_t readLen = 0;
|
size_t readLen = 0;
|
||||||
while (totalRead < fileSize) {
|
while (totalRead < fileSize) {
|
||||||
readLen = file.readBytes(reinterpret_cast<char*>(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE)));
|
readLen = file.readBytes(reinterpret_cast<char*>(downloadBuffer.get()), std::min(fileSize - totalRead, downloadBufferSize));
|
||||||
md5Builder.add(downloadBuffer.get(), readLen);
|
md5Builder.add(downloadBuffer.get(), readLen);
|
||||||
totalRead += readLen;
|
totalRead += readLen;
|
||||||
}
|
}
|
||||||
@ -569,6 +607,7 @@ bool AppUpdater::updateApp() {
|
|||||||
if (httpCode != HTTP_CODE_OK) {
|
if (httpCode != HTTP_CODE_OK) {
|
||||||
ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
|
ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
|
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
|
||||||
|
http.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -662,12 +701,12 @@ bool AppUpdater::updateApp() {
|
|||||||
|
|
||||||
while (remaining > 0 && failedReads < MAX_FAILED_READS) {
|
while (remaining > 0 && failedReads < MAX_FAILED_READS) {
|
||||||
// Check for cancellation
|
// Check for cancellation
|
||||||
if(g_UpdateCancelFlag) {
|
if(g_UpdateCancelFlag) {
|
||||||
ESP_LOGE(TAG, "Update cancelled by user");
|
ESP_LOGE(TAG, "Update cancelled by user");
|
||||||
Update.abort();
|
Update.abort();
|
||||||
http.end();
|
http.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check WiFi status every 5 seconds
|
// Check WiFi status every 5 seconds
|
||||||
if (millis() - lastWatchdogKick > 5000) {
|
if (millis() - lastWatchdogKick > 5000) {
|
||||||
@ -735,8 +774,9 @@ bool AppUpdater::updateApp() {
|
|||||||
// Send periodic progress updates to keep client informed
|
// Send periodic progress updates to keep client informed
|
||||||
if (millis() - lastWatchdogKick > 5000) {
|
if (millis() - lastWatchdogKick > 5000) {
|
||||||
int percent = (totalReceived * 100) / firmwareSize;
|
int percent = (totalReceived * 100) / firmwareSize;
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, percent,
|
char buf[64];
|
||||||
String("Firmware: " + String(percent) + "% - waiting for data...").c_str());
|
snprintf(buf, sizeof(buf), "Firmware: %d%% - waiting for data...", percent);
|
||||||
|
updateProgress(UpdateStatus::DOWNLOADING, percent, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(100); // Short delay to prevent CPU hogging
|
delay(100); // Short delay to prevent CPU hogging
|
||||||
@ -782,8 +822,9 @@ bool AppUpdater::updateApp() {
|
|||||||
lastProgressTime = millis();
|
lastProgressTime = millis();
|
||||||
|
|
||||||
// Just update with received byte count since we don't know total
|
// Just update with received byte count since we don't know total
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, 0,
|
char buf2[64];
|
||||||
String("Firmware: " + String(totalReceived / 1024) + "KB received").c_str());
|
snprintf(buf2, sizeof(buf2), "Firmware: %uKB received", (unsigned)(totalReceived / 1024));
|
||||||
|
updateProgress(UpdateStatus::DOWNLOADING, 0, buf2);
|
||||||
} else {
|
} else {
|
||||||
emptyReads++;
|
emptyReads++;
|
||||||
delay(100);
|
delay(100);
|
||||||
@ -878,116 +919,123 @@ void firmwareUpdateTask(void* parameter) {
|
|||||||
esp_task_wdt_init(60, true); // 60 second timeout, panic on timeout
|
esp_task_wdt_init(60, true); // 60 second timeout, panic on timeout
|
||||||
esp_task_wdt_add(NULL); // Add current task to watchdog
|
esp_task_wdt_add(NULL); // Add current task to watchdog
|
||||||
|
|
||||||
try {
|
// Load update.json; proceed only if successful
|
||||||
loadUpdateJson();
|
if (!loadUpdateJson()) {
|
||||||
|
ESP_LOGE(TAG, "Failed to load update.json, aborting update task");
|
||||||
esp_task_wdt_reset(); // Reset watchdog timer after JSON loading
|
// Clean up watchdog and exit task
|
||||||
|
esp_task_wdt_delete(NULL);
|
||||||
// Initialize updater with smart pointer
|
Update_Task_Handle = NULL;
|
||||||
std::unique_ptr<AppUpdater> updater(new AppUpdater(
|
vTaskDelete(NULL);
|
||||||
LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin"));
|
return;
|
||||||
updater->setProgressCallback(updateProgress);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
|
|
||||||
|
|
||||||
// Check and perform updates
|
|
||||||
auto manifestResult = updater->checkManifest();
|
|
||||||
|
|
||||||
if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) {
|
|
||||||
// Handle different error cases
|
|
||||||
std::string errorMsg;
|
|
||||||
switch (manifestResult) {
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED:
|
|
||||||
errorMsg = "Failed to fetch manifest";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE:
|
|
||||||
errorMsg = "Manifest file too large";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED:
|
|
||||||
errorMsg = "Failed to parse manifest";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION:
|
|
||||||
errorMsg = "Manifest missing files section";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION:
|
|
||||||
errorMsg = "Manifest missing version section";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::VERSION_CURRENT:
|
|
||||||
errorMsg = "Current version is up to date";
|
|
||||||
// This is not actually an error
|
|
||||||
ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str());
|
|
||||||
updateProgress(AppUpdater::UpdateStatus::MESSAGE, 0, errorMsg.c_str());
|
|
||||||
// Don't throw, just exit gracefully
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errorMsg = "Unknown manifest check error";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manifestResult != AppUpdater::ManifestCheckResult::VERSION_CURRENT) {
|
|
||||||
ESP_LOGE(TAG, "Manifest check failed: %s", errorMsg.c_str());
|
|
||||||
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, errorMsg.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updater->IsUpdateAvailable()) {
|
|
||||||
bool filesUpdated = true;
|
|
||||||
bool firmwareUpdated = false; // Initialize to false - only set to true if firmware is actually updated
|
|
||||||
|
|
||||||
// Update files based on update mode
|
|
||||||
if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
|
|
||||||
ESP_LOGI(TAG, "Update mode includes files, updating files...");
|
|
||||||
filesUpdated = updater->updateFilesArray();
|
|
||||||
if (!filesUpdated) {
|
|
||||||
ESP_LOGW(TAG, "Some files failed to update");
|
|
||||||
if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY) {
|
|
||||||
ESP_LOGE(TAG, "Files-only update failed");
|
|
||||||
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update files");
|
|
||||||
// Skip to cleanup since this is files-only mode and it failed
|
|
||||||
goto cleanup;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "File update failed, but continuing with firmware update");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Skipping file updates (mode: firmware only)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update firmware based on update mode
|
|
||||||
if (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
|
|
||||||
ESP_LOGI(TAG, "Update mode includes firmware, updating firmware...");
|
|
||||||
firmwareUpdated = updater->updateApp();
|
|
||||||
if (!firmwareUpdated) {
|
|
||||||
ESP_LOGE(TAG, "Failed to update firmware");
|
|
||||||
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update firmware");
|
|
||||||
// Skip to cleanup since firmware update failed
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Skipping firmware update (mode: files only)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if we need to restart
|
|
||||||
bool needsRestart = (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) && firmwareUpdated;
|
|
||||||
|
|
||||||
if (needsRestart) {
|
|
||||||
ESP_LOGI(TAG, "Firmware update successful, restarting...");
|
|
||||||
sendUpdateMessage("Restarting... ", true, 100);
|
|
||||||
vTaskDelay(2000);
|
|
||||||
ESP.restart();
|
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Update completed successfully (no restart required)");
|
|
||||||
updateProgress(AppUpdater::UpdateStatus::COMPLETE, 100, "Update completed successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
ESP_LOGE(TAG, "Update failed with exception: %s", e.what());
|
|
||||||
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, e.what());
|
|
||||||
} catch (...) {
|
|
||||||
ESP_LOGE(TAG, "Update failed with unknown exception");
|
|
||||||
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Unknown error during update");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_task_wdt_reset(); // Reset watchdog timer after JSON loading
|
||||||
|
|
||||||
|
// Initialize updater with smart pointer
|
||||||
|
std::unique_ptr<AppUpdater> updater(new AppUpdater(
|
||||||
|
LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin"));
|
||||||
|
updater->setProgressCallback(updateProgress);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
|
||||||
|
|
||||||
|
// Check and perform updates
|
||||||
|
auto manifestResult = updater->checkManifest();
|
||||||
|
|
||||||
|
if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) {
|
||||||
|
// Handle different error cases
|
||||||
|
std::string errorMsg;
|
||||||
|
switch (manifestResult) {
|
||||||
|
case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED:
|
||||||
|
errorMsg = "Failed to fetch manifest";
|
||||||
|
break;
|
||||||
|
case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE:
|
||||||
|
errorMsg = "Manifest file too large";
|
||||||
|
break;
|
||||||
|
case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED:
|
||||||
|
errorMsg = "Failed to parse manifest";
|
||||||
|
break;
|
||||||
|
case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION:
|
||||||
|
errorMsg = "Manifest missing files section";
|
||||||
|
break;
|
||||||
|
case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION:
|
||||||
|
errorMsg = "Manifest missing version section";
|
||||||
|
break;
|
||||||
|
case AppUpdater::ManifestCheckResult::VERSION_CURRENT:
|
||||||
|
errorMsg = "Current version is up to date";
|
||||||
|
// This is not actually an error
|
||||||
|
ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str());
|
||||||
|
updateProgress(AppUpdater::UpdateStatus::MESSAGE, 0, errorMsg.c_str());
|
||||||
|
// Don't throw, just exit gracefully
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMsg = "Unknown manifest check error";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifestResult != AppUpdater::ManifestCheckResult::VERSION_CURRENT) {
|
||||||
|
ESP_LOGE(TAG, "Manifest check failed: %s", errorMsg.c_str());
|
||||||
|
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, errorMsg.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updater->IsUpdateAvailable()) {
|
||||||
|
bool filesUpdated = true;
|
||||||
|
bool firmwareUpdated = false; // Initialize to false - only set to true if firmware is actually updated
|
||||||
|
|
||||||
|
// Update files based on update mode
|
||||||
|
if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
|
||||||
|
ESP_LOGI(TAG, "Update mode includes files, updating files...");
|
||||||
|
filesUpdated = updater->updateFilesArray();
|
||||||
|
if (!filesUpdated) {
|
||||||
|
ESP_LOGW(TAG, "Some files failed to update");
|
||||||
|
if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY) {
|
||||||
|
ESP_LOGE(TAG, "Files-only update failed");
|
||||||
|
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update files");
|
||||||
|
// Clean up and exit task
|
||||||
|
esp_task_wdt_delete(NULL);
|
||||||
|
Update_Task_Handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "File update failed, but continuing with firmware update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Skipping file updates (mode: firmware only)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update firmware based on update mode
|
||||||
|
if (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
|
||||||
|
ESP_LOGI(TAG, "Update mode includes firmware, updating firmware...");
|
||||||
|
firmwareUpdated = updater->updateApp();
|
||||||
|
if (!firmwareUpdated) {
|
||||||
|
ESP_LOGE(TAG, "Failed to update firmware");
|
||||||
|
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update firmware");
|
||||||
|
// Clean up and exit task
|
||||||
|
esp_task_wdt_delete(NULL);
|
||||||
|
Update_Task_Handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Skipping firmware update (mode: files only)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we need to restart
|
||||||
|
bool needsRestart = (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) && firmwareUpdated;
|
||||||
|
|
||||||
|
if (needsRestart) {
|
||||||
|
ESP_LOGI(TAG, "Firmware update successful, restarting...");
|
||||||
|
sendUpdateMessage("Restarting... ", true, 100);
|
||||||
|
vTaskDelay(2000);
|
||||||
|
ESP.restart();
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Update completed successfully (no restart required)");
|
||||||
|
updateProgress(AppUpdater::UpdateStatus::COMPLETE, 100, "Update completed successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No C++ exceptions used - errors are handled inline and via return codes
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
// Clean up watchdog before exit
|
// Clean up watchdog before exit
|
||||||
esp_task_wdt_delete(NULL);
|
esp_task_wdt_delete(NULL);
|
||||||
@ -1007,7 +1055,12 @@ void startVersionCheckTask() {
|
|||||||
|
|
||||||
void versionCheckTask(void* parameter){
|
void versionCheckTask(void* parameter){
|
||||||
if(updateUrl == ""){
|
if(updateUrl == ""){
|
||||||
loadUpdateJson();
|
if(!loadUpdateJson()){
|
||||||
|
ESP_LOGE(TAG, "versionCheckTask: failed to load update.json");
|
||||||
|
versionCheckTask_Handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
|
AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
|
||||||
|
|
||||||
@ -1025,35 +1078,36 @@ void versionCheckTask(void* parameter){
|
|||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadUpdateJson(void) {
|
bool loadUpdateJson(void) {
|
||||||
try {
|
ESP_LOGD(TAG, "loadUpdateJson function...");
|
||||||
ESP_LOGD(TAG, "loadUpdateJaon function...");
|
if(updateUrl == "") {
|
||||||
if(updateUrl == "") {
|
String updateJsonPath = "/system/update.json";
|
||||||
String updateJsonPath = "/system/update.json";
|
|
||||||
|
|
||||||
// Read and parse update.json
|
// Read and parse update.json
|
||||||
File file = LittleFS.open(updateJsonPath);
|
File file = LittleFS.open(updateJsonPath);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
throw std::runtime_error("Failed to open update.json");
|
ESP_LOGE(TAG, "Failed to open update.json");
|
||||||
}
|
return false;
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
if (error) { throw std::runtime_error("Failed to parse update.json"); }
|
|
||||||
|
|
||||||
// Get update configuration
|
|
||||||
JsonObject jObj = doc.as<JsonObject>();
|
|
||||||
String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
|
|
||||||
String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
|
|
||||||
updateUrl = baseUrl + folderName;
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
|
||||||
ESP_LOGE(TAG, "Update failed: %s", e.what());
|
JsonDocument doc;
|
||||||
|
DeserializationError error = deserializeJson(doc, file);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
ESP_LOGE(TAG, "Failed to parse update.json: %s", error.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get update configuration
|
||||||
|
JsonObject jObj = doc.as<JsonObject>();
|
||||||
|
String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
|
||||||
|
String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
|
||||||
|
updateUrl = baseUrl + folderName;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {
|
void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
#include "ATALights.h"
|
#include "ATALights.h"
|
||||||
#include "BleSettings.h"
|
#include "BleSettings.h"
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
|
#include "my_device.h"
|
||||||
|
#include "global.h" // for get_chip_mac
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
static const char *tag = "BLE_SP110E";
|
static const char *tag = "BLE_SP110E";
|
||||||
|
|
||||||
@ -33,7 +36,6 @@ TaskHandle_t LightStick_Client_Task_Handle = NULL;
|
|||||||
|
|
||||||
//#define UPGRADE_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0"
|
//#define UPGRADE_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0"
|
||||||
//#define UPGRADE_CHARACTERISTIC_UUID "abcdef01-2345-6789-1234-56789abcdef1"
|
//#define UPGRADE_CHARACTERISTIC_UUID "abcdef01-2345-6789-1234-56789abcdef1"
|
||||||
|
|
||||||
//typedef enum {SM16703,TM1804,UCS1903,WS2811,WS2801,SK6812,LPD6803,LPD8806,APA102,APA105,DMX512,TM1914,TM1913,P9813,INK1003,P943S,P9411,P9413,TX1812,TX1813,GS8206,GS8208,SK9822,TM1814,SK6812_RGBW,P9414,PG412,IC_MODEL_COUNT } IC_MODELS;
|
//typedef enum {SM16703,TM1804,UCS1903,WS2811,WS2801,SK6812,LPD6803,LPD8806,APA102,APA105,DMX512,TM1914,TM1913,P9813,INK1003,P943S,P9411,P9413,TX1812,TX1813,GS8206,GS8208,SK9822,TM1814,SK6812_RGBW,P9414,PG412,IC_MODEL_COUNT } IC_MODELS;
|
||||||
//typedef enum {RGB,RBG,GRB,GBR,BRG,BGR,SEQUENCE_COUNT} SEQUENCES;
|
//typedef enum {RGB,RBG,GRB,GBR,BRG,BGR,SEQUENCE_COUNT} SEQUENCES;
|
||||||
|
|
||||||
@ -133,6 +135,78 @@ public:
|
|||||||
process_BLE_SP110E_Command(data, procLen, pCharacteristic);
|
process_BLE_SP110E_Command(data, procLen, pCharacteristic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Respond to read requests by returning the current led_status structure
|
||||||
|
void onRead(NimBLECharacteristic* pCharacteristic) override {
|
||||||
|
if (!pCharacteristic) return;
|
||||||
|
|
||||||
|
USER_SETTINGS userSettings = {};
|
||||||
|
|
||||||
|
userSettings.cmd = 0; // Indicate this is a status response
|
||||||
|
userSettings.limitedMode = sys_settings.limitedMode ? 1 : 0;
|
||||||
|
userSettings.profile[9] = '\0'; // Ensure null-terminated
|
||||||
|
{
|
||||||
|
// sys_settings.profile is an Arduino String; use c_str() to obtain a const char*
|
||||||
|
const char* src = sys_settings.profile.c_str();
|
||||||
|
// copy up to size-1 characters to ensure null-termination
|
||||||
|
size_t maxCopy = sizeof(userSettings.profile) - 1;
|
||||||
|
size_t srcLen = strlen(src);
|
||||||
|
size_t copyLen = srcLen < maxCopy ? srcLen : maxCopy;
|
||||||
|
if (copyLen) memcpy(userSettings.profile, src, copyLen);
|
||||||
|
// null terminate right after copied data
|
||||||
|
userSettings.profile[copyLen] = '\0';
|
||||||
|
// ensure the remaining bytes are zeroed out
|
||||||
|
for (size_t i = copyLen + 1; i < sizeof(userSettings.profile); ++i) userSettings.profile[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
userSettings.temperature = boardTemperature; // Placeholder, implement actual temperature reading if needed
|
||||||
|
userSettings.vIn = PowerVin; // Placeholder, implement actual voltage reading if needed
|
||||||
|
|
||||||
|
// Determine chipset with case‐insensitive substring match
|
||||||
|
std::string chip = std::string(sys_settings.ledStripSettings[0]->chip.c_str());
|
||||||
|
std::string chipLower;
|
||||||
|
chipLower.resize(chip.size());
|
||||||
|
std::transform(chip.begin(), chip.end(), chipLower.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||||
|
if (chipLower.find("WS2812b") != std::string::npos) userSettings.ledChipset1 = 0;
|
||||||
|
else if (chipLower.find("SK6812") != std::string::npos) userSettings.ledChipset1 = 1;
|
||||||
|
else if (chipLower.find("WS2811") != std::string::npos) userSettings.ledChipset1 = 2;
|
||||||
|
else if (chipLower.find("WS2815") != std::string::npos) userSettings.ledChipset1 = 3;
|
||||||
|
else userSettings.ledChipset1 = 0;
|
||||||
|
|
||||||
|
|
||||||
|
strncpy(userSettings.rgbOrder1, sys_settings.ledStripSettings[0]->rgbOrder.c_str(), 4);
|
||||||
|
userSettings.ledCount1 = sys_settings.ledStripSettings[0]->size;
|
||||||
|
userSettings.ledShift1 = sys_settings.ledStripSettings[0]->shift;
|
||||||
|
userSettings.ledBrightness1= sys_settings.ledStripSettings[0]->bright;
|
||||||
|
|
||||||
|
std::string chip2 = std::string(sys_settings.ledStripSettings[1]->chip.c_str());
|
||||||
|
std::string chipLower2;
|
||||||
|
chipLower2.resize(chip2.size());
|
||||||
|
std::transform(chip2.begin(), chip2.end(), chipLower2.begin(),
|
||||||
|
[](unsigned char c){ return std::tolower(c); });
|
||||||
|
if (chipLower2.find("WS2812b") != std::string::npos) userSettings.ledChipset2 = 0;
|
||||||
|
else if (chipLower2.find("SK6812") != std::string::npos) userSettings.ledChipset2 = 1;
|
||||||
|
else if (chipLower2.find("WS2811") != std::string::npos) userSettings.ledChipset2 = 2;
|
||||||
|
else if (chipLower2.find("WS2815") != std::string::npos) userSettings.ledChipset2 = 3;
|
||||||
|
else userSettings.ledChipset2 = 0;
|
||||||
|
|
||||||
|
strncpy(userSettings.rgbOrder2, sys_settings.ledStripSettings[1]->rgbOrder.c_str(), 4);
|
||||||
|
userSettings.ledCount2 = sys_settings.ledStripSettings[1]->size;
|
||||||
|
userSettings.ledShift2 = sys_settings.ledStripSettings[1]->shift;
|
||||||
|
userSettings.ledBrightness2= sys_settings.ledStripSettings[1]->bright;
|
||||||
|
|
||||||
|
userSettings.frontLightMin = sys_settings.rampLightSettings[0].min;
|
||||||
|
userSettings.frontLightMax = sys_settings.rampLightSettings[0].max;
|
||||||
|
userSettings.rearLightMin = sys_settings.rampLightSettings[1].min;
|
||||||
|
userSettings.rearLightMax = sys_settings.rampLightSettings[1].max;
|
||||||
|
userSettings.fanLowerTemp = sys_settings.tSensorSettings.setpoint1;
|
||||||
|
userSettings.fanUpperTemp = sys_settings.tSensorSettings.setpoint2;
|
||||||
|
|
||||||
|
// Provide the full INFO_PACK as the characteristic value so readers get device info
|
||||||
|
pCharacteristic->setValue((uint8_t *)&userSettings, sizeof(USER_SETTINGS));
|
||||||
|
// Do not automatically notify on read (notify is for subscriptions), but keep a log
|
||||||
|
ESP_LOGD(tag, "onRead: returning INFO_PACK (%zu bytes)", sizeof(INFO_PACK));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void logBytes(const uint8_t* data, size_t len) {
|
static void logBytes(const uint8_t* data, size_t len) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
@ -222,20 +296,24 @@ void sendToAllClients(const uint8_t *data, size_t len) {
|
|||||||
|
|
||||||
|
|
||||||
void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacteristic* bleChar) {
|
void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacteristic* bleChar) {
|
||||||
if (!val) {
|
//uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer
|
||||||
ESP_LOGE(tag, "Null command data received");
|
|
||||||
return;
|
if (!val) { ESP_LOGE(tag, "Null command data received"); return;}
|
||||||
|
if (len < 4) { ESP_LOGW(tag, "Command too short: %d bytes, expected at least 4", len); return; }
|
||||||
|
|
||||||
|
//ESP_LOGI(tag, "USER_SETTING size is: %d", sizeof(USER_SETTINGS));
|
||||||
|
|
||||||
|
uint8_t command = 0;
|
||||||
|
if (len == 4){
|
||||||
|
command = val[3];
|
||||||
|
}else if (len == sizeof(USER_SETTINGS)){
|
||||||
|
command = val[0];
|
||||||
|
//ESP_LOGI(tag, "Command received: 0x%02X, length: %d", command, len);
|
||||||
|
//ESP_LOGI(tag, "byte1: %d byte2: %d byte3: %d byte4: %d byte5: %d byte6: %d byte7: %d byte8: %d",
|
||||||
|
// val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len < 4) {
|
ESP_LOGI(tag, "Command received: 0x%02X, data0: %d, data1: %d, data2: %d, length: %d", command, val[0], val[1], val[2], len);
|
||||||
ESP_LOGW(tag, "Command too short: %d bytes, expected at least 4", len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t command = val[3];
|
|
||||||
//ESP_LOGI(tag, "Command received: 0x%02X, length: %d", command, len);
|
|
||||||
|
|
||||||
uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer
|
|
||||||
|
|
||||||
// Handle different commands
|
// Handle different commands
|
||||||
switch (command) {
|
switch (command) {
|
||||||
@ -273,7 +351,7 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
|
|||||||
break;
|
break;
|
||||||
case SET_SPEED:
|
case SET_SPEED:
|
||||||
led_status.speed = val[0];
|
led_status.speed = val[0];
|
||||||
ESP_LOGI(tag, "Mode set to %d", led_status.speed);
|
ESP_LOGI(tag, "Speed set to %d", led_status.speed);
|
||||||
break;
|
break;
|
||||||
case GET_CHECK_DEVICE: // This prepends a checksum
|
case GET_CHECK_DEVICE: // This prepends a checksum
|
||||||
led_status.checksum = calculateChecksum(val);
|
led_status.checksum = calculateChecksum(val);
|
||||||
@ -307,9 +385,42 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
|
|||||||
case SET_DEVICE_NAME:
|
case SET_DEVICE_NAME:
|
||||||
ESP_LOGI(tag, "Set Device Name");
|
ESP_LOGI(tag, "Set Device Name");
|
||||||
break;
|
break;
|
||||||
|
case SET_SHIFT:
|
||||||
|
RGB_Lights_Set_Animation(SHIFT_INDEX, val[0], val[1], val[2]);
|
||||||
|
ESP_LOGI(tag, "Set Shift to %d", val[0]);
|
||||||
|
break;
|
||||||
|
case SET_USER_SETTINGS:{
|
||||||
|
USER_SETTINGS userSettings;
|
||||||
|
|
||||||
|
if (len < sizeof(USER_SETTINGS)) {
|
||||||
|
ESP_LOGW(tag, "SET_USER_SETTINGS command too short: %d bytes, expected at least %d", len, sizeof(USER_SETTINGS));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy(&userSettings, &val[0], sizeof(USER_SETTINGS) );
|
||||||
|
|
||||||
|
// Print out all the received settings for debugging
|
||||||
|
/*
|
||||||
|
ESP_LOGI(tag, "Received User Settings:");
|
||||||
|
ESP_LOGI(tag, " Limited Mode: %d", userSettings.limitedMode);
|
||||||
|
|
||||||
|
ESP_LOGI(tag, " LED Chip Type: %d", userSettings.ledChipset1);
|
||||||
|
ESP_LOGI(tag, " LED Color Order: %s", userSettings.rgbOrder1);
|
||||||
|
ESP_LOGI(tag, " LED Count: %d", userSettings.ledCount1);
|
||||||
|
ESP_LOGI(tag, " LED Shift: %d", userSettings.ledShift1);
|
||||||
|
ESP_LOGI(tag, " LED Brightness: %d", userSettings.ledBrightness1);
|
||||||
|
|
||||||
|
ESP_LOGI(tag, " Fan Lower Temp: %d", userSettings.fanLowerTemp);
|
||||||
|
ESP_LOGI(tag, " Fan Upper Temp: %d", userSettings.fanUpperTemp);
|
||||||
|
*/
|
||||||
|
|
||||||
|
SetAndSaveUserSettings(userSettings);
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ESP_LOGW(tag, "Unknown command: 0x%02X", command);
|
ESP_LOGW(tag, "Unknown command: 0x%02X", command);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +442,7 @@ void Init_BLE_SP110E(NimBLEServer* pServer) {
|
|||||||
// Create FFE1 Characteristic with WRITE and NOTIFY properties
|
// Create FFE1 Characteristic with WRITE and NOTIFY properties
|
||||||
pSP110ECharacteristic = pService->createCharacteristic(
|
pSP110ECharacteristic = pService->createCharacteristic(
|
||||||
BTSP110ECharacteristicUUID.c_str(),
|
BTSP110ECharacteristicUUID.c_str(),
|
||||||
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
|
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register the callback with the characteristic
|
// Register the callback with the characteristic
|
||||||
@ -422,11 +533,8 @@ void BLE_LightStick_Client_Task(void *parameter) {
|
|||||||
// Get the characteristic.
|
// Get the characteristic.
|
||||||
pRemoteCharacteristic = pRemoteService->getCharacteristic(BTStickCharacteristicUUID.c_str());
|
pRemoteCharacteristic = pRemoteService->getCharacteristic(BTStickCharacteristicUUID.c_str());
|
||||||
if (pRemoteCharacteristic != nullptr && pRemoteCharacteristic->canNotify()) {
|
if (pRemoteCharacteristic != nullptr && pRemoteCharacteristic->canNotify()) {
|
||||||
pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic,
|
pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
|
||||||
uint8_t* pData, size_t length, bool isNotify) {
|
ESP_LOGD(tag, "Notification received: %zu bytes", length);
|
||||||
Serial.print("Notification received: ");
|
|
||||||
Serial.write(pData, length);
|
|
||||||
Serial.println();
|
|
||||||
process_BLE_SP110E_Command(pData, length, nullptr);
|
process_BLE_SP110E_Command(pData, length, nullptr);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -483,7 +591,7 @@ class MyAdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {
|
|||||||
NimBLEDevice::getScan()->stop();
|
NimBLEDevice::getScan()->stop();
|
||||||
myDevice = advertisedDevice;
|
myDevice = advertisedDevice;
|
||||||
masterFound = true;
|
masterFound = true;
|
||||||
Serial.println("Device found!");
|
ESP_LOGE(tag, "Device found!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -38,35 +38,87 @@ class ServerCallbacks : public NimBLEServerCallbacks {
|
|||||||
|
|
||||||
class ServerCallbacks : public NimBLEServerCallbacks {
|
class ServerCallbacks : public NimBLEServerCallbacks {
|
||||||
public:
|
public:
|
||||||
void onConnect(NimBLEServer* /*pServer*/) override {
|
ServerCallbacks() : connectedClients(0) {}
|
||||||
ESP_LOGI(tag, "Client connected");
|
|
||||||
ensureAdvertising("onConnect");
|
void onConnect(NimBLEServer* pServer) override {
|
||||||
|
// Use server's connected count to avoid double-handling between overloads
|
||||||
|
int count = 0;
|
||||||
|
if (pServer) count = pServer->getConnectedCount();
|
||||||
|
connectedClients = count;
|
||||||
|
ESP_LOGI(tag, "Client connected (count=%d) [no-desc overload]", connectedClients);
|
||||||
|
if (connectedClients == 1) {
|
||||||
|
NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
|
||||||
|
if (adv && adv->isAdvertising()) {
|
||||||
|
if (adv->stop()) ESP_LOGI(tag, "Advertising stopped after client connected");
|
||||||
|
else ESP_LOGE(tag, "Failed to stop advertising after connect");
|
||||||
|
}
|
||||||
|
} else if (connectedClients > 1) {
|
||||||
|
ESP_LOGW(tag, "Additional client connected while one is active (no-desc)");
|
||||||
|
}
|
||||||
Buzzer_Play_Tune(TUNE_CONNECTED, 1);
|
Buzzer_Play_Tune(TUNE_CONNECTED, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This overload provides connection descriptor details (conn handle) so we can reject extra clients.
|
||||||
|
void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) override {
|
||||||
|
// desc contains the connection handle for this new client
|
||||||
|
int connHandle = desc ? desc->conn_handle : -1;
|
||||||
|
// Use server's connected count to determine how many clients are attached
|
||||||
|
int count = 0;
|
||||||
|
if (pServer) count = pServer->getConnectedCount();
|
||||||
|
connectedClients = count;
|
||||||
|
ESP_LOGI(tag, "Client connected (count=%d) conn_handle=%d", connectedClients, connHandle);
|
||||||
|
|
||||||
|
// If this is the first client, stop advertising so no additional clients can connect
|
||||||
|
if (connectedClients == 1) {
|
||||||
|
NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
|
||||||
|
if (adv && adv->isAdvertising()) {
|
||||||
|
if (adv->stop()) ESP_LOGI(tag, "Advertising stopped after client connected");
|
||||||
|
else ESP_LOGE(tag, "Failed to stop advertising after connect");
|
||||||
|
}
|
||||||
|
} else if (connectedClients > 1) {
|
||||||
|
// Reject this new connection immediately to enforce single-client policy
|
||||||
|
ESP_LOGW(tag, "Rejecting extra client conn_handle=%d (server count=%d)", connHandle, connectedClients);
|
||||||
|
if (pServer) {
|
||||||
|
try {
|
||||||
|
pServer->disconnect(connHandle);
|
||||||
|
ESP_LOGI(tag, "Disconnected extra client conn_handle=%d", connHandle);
|
||||||
|
} catch (...) {
|
||||||
|
ESP_LOGE(tag, "Exception while disconnecting extra client conn_handle=%d", connHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update connectedClients after rejecting (get fresh count if possible)
|
||||||
|
if (pServer) connectedClients = pServer->getConnectedCount();
|
||||||
|
}
|
||||||
|
//Buzzer_Play_Tune(TUNE_CONNECTED, 1);
|
||||||
|
}
|
||||||
|
|
||||||
void onDisconnect(NimBLEServer* /*pServer*/) override {
|
void onDisconnect(NimBLEServer* /*pServer*/) override {
|
||||||
ESP_LOGI(tag, "Client disconnected");
|
if (connectedClients > 0) connectedClients--;
|
||||||
ensureAdvertising("onDisconnect");
|
ESP_LOGI(tag, "Client disconnected (count=%d)", connectedClients);
|
||||||
|
|
||||||
|
// If there are no clients left, restart advertising so a new client can connect
|
||||||
|
if (connectedClients == 0) {
|
||||||
|
NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
|
||||||
|
if (!adv) {
|
||||||
|
ESP_LOGE(tag, "Advertising object unavailable on disconnect");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (adv->isAdvertising()) {
|
||||||
|
ESP_LOGD(tag, "Advertising already running on disconnect");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (adv->start()) {
|
||||||
|
ESP_LOGI(tag, "Advertising restarted after client disconnect");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(tag, "Failed to start advertising after disconnect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Buzzer_Play_Tune(TUNE_DISCONNECTED, 1);
|
Buzzer_Play_Tune(TUNE_DISCONNECTED, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ensureAdvertising(const char* reason) {
|
int connectedClients;
|
||||||
NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
|
|
||||||
if (!adv) {
|
|
||||||
ESP_LOGE(tag, "[%s] Advertising object unavailable", reason);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (adv->isAdvertising()) {
|
|
||||||
ESP_LOGD(tag, "[%s] Advertising already running", reason);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (adv->start()) {
|
|
||||||
ESP_LOGI(tag, "[%s] Advertising (re)started", reason);
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(tag, "[%s] Failed to start advertising", reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -70,8 +70,6 @@ void Load_BLE_Settings(const String &configPath) {
|
|||||||
BTDeviceName += macSuffix[0]; // Add first character
|
BTDeviceName += macSuffix[0]; // Add first character
|
||||||
BTDeviceName += macSuffix[1]; // Add second character
|
BTDeviceName += macSuffix[1]; // Add second character
|
||||||
|
|
||||||
BTDeviceName = "SP110E";
|
|
||||||
|
|
||||||
ESP_LOGI(tag, "Loaded BLE config: name=%s svc=%s char1=%s stick=%s upg_svc=%s upg1=%s upg2=%s",
|
ESP_LOGI(tag, "Loaded BLE config: name=%s svc=%s char1=%s stick=%s upg_svc=%s upg1=%s upg2=%s",
|
||||||
BTDeviceName.c_str(), BTServiceUUID.c_str(), BTSP110ECharacteristicUUID.c_str(),
|
BTDeviceName.c_str(), BTServiceUUID.c_str(), BTSP110ECharacteristicUUID.c_str(),
|
||||||
BTStickCharacteristicUUID.c_str(), BTUpgradeServiceUUID.c_str(),
|
BTStickCharacteristicUUID.c_str(), BTUpgradeServiceUUID.c_str(),
|
||||||
|
|||||||
@ -3,11 +3,7 @@
|
|||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
|
||||||
|
|
||||||
const char* tag = "pwmout";
|
static const char* tag = "pwmout";
|
||||||
|
|
||||||
const float binaryPow[17] = {
|
|
||||||
0, 2, 4, 8, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535
|
|
||||||
};
|
|
||||||
|
|
||||||
PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected){
|
PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected){
|
||||||
this->currDuty = 0;
|
this->currDuty = 0;
|
||||||
@ -15,17 +11,21 @@ PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float
|
|||||||
this->maxDuty = maxDuty;
|
this->maxDuty = maxDuty;
|
||||||
this->visionCorrected = visionCorrected;
|
this->visionCorrected = visionCorrected;
|
||||||
this->freq = freq;
|
this->freq = freq;
|
||||||
|
this->pin = pin;
|
||||||
setResolution(res);
|
setResolution(res);
|
||||||
pinMode(pin, OUTPUT);
|
pinMode(pin, OUTPUT);
|
||||||
|
|
||||||
uint32_t actualFreq = ledcSetup(ch, freq, res);
|
uint32_t actualFreq = ledcSetup(ch, freq, res);
|
||||||
if (actualFreq != freq) {
|
if (actualFreq != freq) {
|
||||||
ESP_LOGE(tag, "pwmOut-> ch:%d ledcSetup failed! Requested freq: %d, Actual freq: %d", ch, freq, actualFreq);
|
ESP_LOGE(tag, "pwmOut-> ch:%d ledcSetup failed! Requested freq: %d, Actual freq: %d", ch, freq, actualFreq);
|
||||||
|
// continue but mark as uninitialized to prevent usage
|
||||||
|
this->initialized = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ledcAttachPin(pin, ch);
|
ledcAttachPin(pin, ch);
|
||||||
setOutput(this->currDuty);
|
setOutput(this->currDuty);
|
||||||
|
this->initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PWM_Output::setMaxDuty(float duty) {
|
void PWM_Output::setMaxDuty(float duty) {
|
||||||
@ -41,6 +41,10 @@ void PWM_Output::setMaxDuty(float duty) {
|
|||||||
|
|
||||||
// Range is 0 to 100%
|
// Range is 0 to 100%
|
||||||
void PWM_Output::setOutput(float duty){
|
void PWM_Output::setOutput(float duty){
|
||||||
|
if(!this->initialized){
|
||||||
|
ESP_LOGW(tag, "setOutput called on uninitialized PWM_Output (ch=%d)", this->ch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Clamp the duty cycle to the range [0.0, maxDuty]
|
// Clamp the duty cycle to the range [0.0, maxDuty]
|
||||||
if (duty < 0.0) {
|
if (duty < 0.0) {
|
||||||
duty = 0.0;
|
duty = 0.0;
|
||||||
@ -52,13 +56,12 @@ void PWM_Output::setOutput(float duty){
|
|||||||
int outDutyVal;
|
int outDutyVal;
|
||||||
if(this->visionCorrected){
|
if(this->visionCorrected){
|
||||||
outDutyVal = linearizeOutput(duty);
|
outDutyVal = linearizeOutput(duty);
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
outDutyVal = static_cast<int>(duty * this->standardFactor);
|
outDutyVal = static_cast<int>(duty * this->standardFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp to valid resolution range [0, 2^res - 1]
|
// Clamp to valid resolution range [0, (1<<res)-1]
|
||||||
int maxVal = static_cast<int>(binaryPow[this->res]);
|
int maxVal = (this->res >= 31) ? INT32_MAX : ((1 << this->res) - 1);
|
||||||
if (outDutyVal < 0) outDutyVal = 0;
|
if (outDutyVal < 0) outDutyVal = 0;
|
||||||
if (outDutyVal > maxVal) outDutyVal = maxVal;
|
if (outDutyVal > maxVal) outDutyVal = maxVal;
|
||||||
|
|
||||||
@ -72,8 +75,10 @@ void PWM_Output::setFreq(uint32_t fq){
|
|||||||
uint32_t newFreq;
|
uint32_t newFreq;
|
||||||
if(this->freq != fq){
|
if(this->freq != fq){
|
||||||
newFreq = ledcChangeFrequency(this->ch, fq, this->res);
|
newFreq = ledcChangeFrequency(this->ch, fq, this->res);
|
||||||
if(newFreq){
|
if(newFreq > 0){
|
||||||
this->freq = fq;
|
this->freq = newFreq;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(tag, "ledcChangeFrequency failed for ch:%d requested:%u", this->ch, fq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,8 +89,9 @@ void PWM_Output::setResolution(uint8_t res){
|
|||||||
if(this->res > 16) this->res = 16;
|
if(this->res > 16) this->res = 16;
|
||||||
|
|
||||||
// Use the clamped resolution when computing factors
|
// Use the clamped resolution when computing factors
|
||||||
this->standardFactor = binaryPow[this->res] * 0.01f;
|
int maxVal = (this->res >= 31) ? INT32_MAX : ((1 << this->res) - 1);
|
||||||
this->visionFactor = binaryPow[this->res] * 0.0001f;
|
this->standardFactor = static_cast<float>(maxVal) * 0.01f;
|
||||||
|
this->visionFactor = static_cast<float>(maxVal) * 0.0001f;
|
||||||
ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor);
|
ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,12 @@ RAMP_LIGHT::RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, float min, floa
|
|||||||
button->attachLongPressStop([](void* context) { static_cast<RAMP_LIGHT*>(context)->longPressStop(); }, this);
|
button->attachLongPressStop([](void* context) { static_cast<RAMP_LIGHT*>(context)->longPressStop(); }, this);
|
||||||
button->attachDuringLongPress([](void* context) { static_cast<RAMP_LIGHT*>(context)->duringLongPress(); }, this);
|
button->attachDuringLongPress([](void* context) { static_cast<RAMP_LIGHT*>(context)->duringLongPress(); }, this);
|
||||||
|
|
||||||
if(min < 0.0) min = 0.0;
|
|
||||||
if(max > 100.0) max = 100.0;
|
if(min < 0.0) this->min = 0.0;
|
||||||
currentValue = min;
|
if(max > 100.0) this->max = 100.0;
|
||||||
|
if(this->max < this->min) this->max = this->min;
|
||||||
|
if(step <= 0.0) this->step = 1.0;
|
||||||
|
currentValue = this->min;
|
||||||
IsOn = false;
|
IsOn = false;
|
||||||
rampState = RampingUp;
|
rampState = RampingUp;
|
||||||
}
|
}
|
||||||
@ -52,11 +55,17 @@ void RAMP_LIGHT::longPressStop(){
|
|||||||
void RAMP_LIGHT::duringLongPress(){
|
void RAMP_LIGHT::duringLongPress(){
|
||||||
if(IsOn){
|
if(IsOn){
|
||||||
if (tickCount > 0 && --tickCount == 0) {
|
if (tickCount > 0 && --tickCount == 0) {
|
||||||
// When ramping down, currentValue may go below min if step is large; constrain ensures bounds.
|
// Adjust currentValue based on ramp direction
|
||||||
// Ensure currentValue stays within [min, max] bounds for safe PWM operation
|
if(rampState == RampingUp){
|
||||||
currentValue = constrain(currentValue, min, max);
|
currentValue += step;
|
||||||
pwmOutput->setOutput(currentValue);
|
} else {
|
||||||
//ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput->currOutVal, pwmOutput->getOutVal());
|
currentValue -= step;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constrain and apply
|
||||||
|
currentValue = constrain(currentValue, this->min, this->max);
|
||||||
|
if(pwmOutput) pwmOutput->setOutput(currentValue);
|
||||||
|
ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput? pwmOutput->currOutVal : -1, pwmOutput? pwmOutput->getOutVal() : -1);
|
||||||
tickCount = TickDelayCount;
|
tickCount = TickDelayCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,23 +23,31 @@ void Init_File_System(void){
|
|||||||
//printAllSystemFiles();
|
//printAllSystemFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){
|
void getAllDirectories(String directoryList[], int &count){
|
||||||
File root = LittleFS.open("/");
|
File root = LittleFS.open("/");
|
||||||
if (!root || !root.isDirectory()){
|
if (!root || !root.isDirectory()){
|
||||||
ESP_LOGE("FileSystem", "Failed to open root directory or root is not a directory");
|
ESP_LOGE("FileSystem", "Failed to open root directory or root is not a directory");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize with root directory
|
||||||
|
if (MAX_DIRECTORIES <= 0) {
|
||||||
|
ESP_LOGW("FileSystem", "MAX_DIRECTORIES is not set or invalid");
|
||||||
|
root.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
directoryList[0] = "/";
|
directoryList[0] = "/";
|
||||||
count = 1;
|
count = 1;
|
||||||
|
|
||||||
File file = root.openNextFile();
|
File file = root.openNextFile();
|
||||||
while (file) {
|
while (file) {
|
||||||
if (file.isDirectory()){
|
if (file.isDirectory()){
|
||||||
if (count < 10){ // Ensure we don't overflow the array
|
if (count < MAX_DIRECTORIES){ // Ensure we don't overflow the caller's array
|
||||||
directoryList[count] = '/' + String(file.name());
|
String entry = String(file.name());
|
||||||
|
if (!entry.startsWith("/")) entry = "/" + entry;
|
||||||
|
directoryList[count] = entry;
|
||||||
count++;
|
count++;
|
||||||
}else{
|
} else {
|
||||||
ESP_LOGW("FileSystem", "Directory list array is full");
|
ESP_LOGW("FileSystem", "Directory list array is full");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ void writeFilesToSerial(void);
|
|||||||
//void getAllDirectoriesRecursive(const String& path, String directoryList[], int& count, int maxSize);
|
//void getAllDirectoriesRecursive(const String& path, String directoryList[], int& count, int maxSize);
|
||||||
|
|
||||||
//void getAllDirectories(String directoryList[], int& count, int maxSize);
|
//void getAllDirectories(String directoryList[], int& count, int maxSize);
|
||||||
void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int& count);
|
void getAllDirectories(String directoryList[], int& count);
|
||||||
|
|
||||||
void printFilesInDirectories(const String directoryList[], int count);
|
void printFilesInDirectories(const String directoryList[], int count);
|
||||||
|
|
||||||
|
|||||||
@ -413,3 +413,8 @@ void print_task_watermarks(void) {
|
|||||||
ESP_LOGE(tag, "Failed to get current task handle");
|
ESP_LOGE(tag, "Failed to get current task handle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float updateLowpass(float currentValue, float newValue, float alpha) {
|
||||||
|
return (alpha * newValue) + ((1 - alpha) * currentValue);
|
||||||
|
}
|
||||||
|
|||||||
44
src/main.cpp
44
src/main.cpp
@ -82,6 +82,9 @@ TimerHandle_t upgradeHeartbeatTimer = NULL;
|
|||||||
TimerHandle_t diagnosticsTimer = NULL;
|
TimerHandle_t diagnosticsTimer = NULL;
|
||||||
#define diagnosticsInterval 60000 // ms
|
#define diagnosticsInterval 60000 // ms
|
||||||
|
|
||||||
|
TimerHandle_t analogInputTimer = NULL;
|
||||||
|
#define analogInputInterval 3000 // ms
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void setupLogLevels(esp_log_level_t logLevel);
|
void setupLogLevels(esp_log_level_t logLevel);
|
||||||
@ -120,6 +123,13 @@ void DiagnosticsCallback(TimerHandle_t xTimer) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnalogInputCallback(TimerHandle_t xTimer) {
|
||||||
|
float v = readBoardInputVoltage();
|
||||||
|
PowerVin = updateLowpass(prevPowerVin, v*1.01, PowerVinAlpha);
|
||||||
|
prevPowerVin = PowerVin;
|
||||||
|
//ESP_LOGI(tag, "Input Voltage = %f", PowerVin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void checkLEDCChannels()
|
void checkLEDCChannels()
|
||||||
{
|
{
|
||||||
@ -167,7 +177,7 @@ void setup()
|
|||||||
printAllSystemFiles();
|
printAllSystemFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
String board_file_path, booth_file_path;
|
String board_file_path;
|
||||||
Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);
|
Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);
|
||||||
|
|
||||||
// Load Board Pins
|
// Load Board Pins
|
||||||
@ -205,15 +215,10 @@ void setup()
|
|||||||
// Initialize Temperature Sensor
|
// Initialize Temperature Sensor
|
||||||
Init_TSensor(72, &sys_settings.tSensorSettings);
|
Init_TSensor(72, &sys_settings.tSensorSettings);
|
||||||
|
|
||||||
|
|
||||||
float val = readBoardInputVoltage();
|
|
||||||
ESP_LOGI(tag, "Input Volage = %f", val);
|
|
||||||
|
|
||||||
// Initialize BLE & Wifi
|
// Initialize BLE & Wifi
|
||||||
// If button 1 is held during boot, enable upgrade mode
|
// If button 1 is held during boot, enable upgrade mode
|
||||||
Load_BLE_Settings("/system/ble.json");
|
Load_BLE_Settings("/system/ble.json");
|
||||||
if (digitalRead(sys_settings.boardPins.btn[0]) == LOW)
|
if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){
|
||||||
{
|
|
||||||
setStatusPin1(true);
|
setStatusPin1(true);
|
||||||
ESP_LOGW(tag, "Upgrade Mode Triggered");
|
ESP_LOGW(tag, "Upgrade Mode Triggered");
|
||||||
ESP_LOGW(tag, "Enabling BLE and Update Service");
|
ESP_LOGW(tag, "Enabling BLE and Update Service");
|
||||||
@ -223,8 +228,7 @@ void setup()
|
|||||||
UpgradeMode = true;
|
UpgradeMode = true;
|
||||||
upgradeHeartbeatTimer = xTimerCreate("UpgradeHeartbeat", pdMS_TO_TICKS(5000), pdTRUE, NULL, UpgradeHeartbeatCallback);
|
upgradeHeartbeatTimer = xTimerCreate("UpgradeHeartbeat", pdMS_TO_TICKS(5000), pdTRUE, NULL, UpgradeHeartbeatCallback);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
ESP_LOGI(tag, "Enabling BLE, No Update Service");
|
ESP_LOGI(tag, "Enabling BLE, No Update Service");
|
||||||
Init_BleServer(true, false); // Dont start the Upgrade service
|
Init_BleServer(true, false); // Dont start the Upgrade service
|
||||||
}
|
}
|
||||||
@ -236,20 +240,18 @@ void setup()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if OLED_ENABLED
|
#if OLED_ENABLED
|
||||||
// Init OLED
|
|
||||||
Init_OLED(sys_settings.oledSettings.width, sys_settings.oledSettings.height, sys_settings.boardPins.oled_mosi, sys_settings.boardPins.oled_sck, sys_settings.boardPins.oled_dc, sys_settings.boardPins.oled_rst, sys_settings.boardPins.oled_cs);
|
Init_OLED(sys_settings.oledSettings.width, sys_settings.oledSettings.height, sys_settings.boardPins.oled_mosi, sys_settings.boardPins.oled_sck, sys_settings.boardPins.oled_dc, sys_settings.boardPins.oled_rst, sys_settings.boardPins.oled_cs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if STRIPS_ENABLED
|
#if STRIPS_ENABLED
|
||||||
Init_RGB_Lights_Task();
|
Init_RGB_Lights_Task(UpgradeMode);
|
||||||
//vTaskDelay(100);
|
|
||||||
//Init_Ramp_Front_Light_Task();
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Create and start software timers
|
// Create and start software timers
|
||||||
buttonScanTimer = xTimerCreate("ButtonScan", pdMS_TO_TICKS(buttonScanInterval), pdTRUE, NULL, ButtonScanCallback);
|
buttonScanTimer = xTimerCreate("ButtonScan", pdMS_TO_TICKS(buttonScanInterval), pdTRUE, NULL, ButtonScanCallback);
|
||||||
temperatureTimer = xTimerCreate("Temperature", pdMS_TO_TICKS(sys_settings.tSensorSettings.intervalMs), pdTRUE, NULL, TemperatureCallback);
|
temperatureTimer = xTimerCreate("Temperature", pdMS_TO_TICKS(sys_settings.tSensorSettings.intervalMs), pdTRUE, NULL, TemperatureCallback);
|
||||||
statusLedTimer = xTimerCreate("StatusLED", pdMS_TO_TICKS(statusLedInterval), pdTRUE, NULL, StatusLedCallback);
|
statusLedTimer = xTimerCreate("StatusLED", pdMS_TO_TICKS(statusLedInterval), pdTRUE, NULL, StatusLedCallback);
|
||||||
|
analogInputTimer = xTimerCreate("AnalogInput", pdMS_TO_TICKS(analogInputInterval), pdTRUE, NULL, AnalogInputCallback);
|
||||||
|
|
||||||
|
|
||||||
#if FREERTOs_DIAGNOSTICS
|
#if FREERTOs_DIAGNOSTICS
|
||||||
@ -257,10 +259,11 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Start the timers
|
// Start the timers
|
||||||
if (buttonScanTimer) xTimerStart(buttonScanTimer, 25);
|
//if (buttonScanTimer) xTimerStart(buttonScanTimer, 100);
|
||||||
if (temperatureTimer && sys_settings.tSensorSettings.enabled) xTimerStart(temperatureTimer, 100);
|
if (temperatureTimer && sys_settings.tSensorSettings.enabled) xTimerStart(temperatureTimer, 100);
|
||||||
if (statusLedTimer) xTimerStart(statusLedTimer, 0);
|
if (statusLedTimer) xTimerStart(statusLedTimer, 0);
|
||||||
if (upgradeHeartbeatTimer && UpgradeMode) xTimerStart(upgradeHeartbeatTimer, upgradeHeartbeatInterval);
|
if (upgradeHeartbeatTimer && UpgradeMode) xTimerStart(upgradeHeartbeatTimer, upgradeHeartbeatInterval);
|
||||||
|
if (analogInputTimer) xTimerStart(analogInputTimer, 0);
|
||||||
|
|
||||||
#if FREERTOS_DIAGNOSTICS
|
#if FREERTOS_DIAGNOSTICS
|
||||||
if (diagnosticsTimer) xTimerStart(diagnosticsTimer, diagnosticsInterval);
|
if (diagnosticsTimer) xTimerStart(diagnosticsTimer, diagnosticsInterval);
|
||||||
@ -316,8 +319,19 @@ void loop()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (boardButtons[i] != NULL) {
|
||||||
|
boardButtons[i]->tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelay(buttonScanInterval);
|
||||||
|
*/
|
||||||
|
|
||||||
// Small delay to prevent busy-waiting
|
// Small delay to prevent busy-waiting
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
//vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
|
||||||
|
vTaskDelay(portMAX_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupLogLevels(esp_log_level_t logLevel)
|
void setupLogLevels(esp_log_level_t logLevel)
|
||||||
|
|||||||
211
src/my_board.cpp
211
src/my_board.cpp
@ -4,124 +4,143 @@
|
|||||||
#include <FS.h>
|
#include <FS.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
|
|
||||||
#include "global.h"
|
|
||||||
#include "JsonConstrain.h"
|
#include "JsonConstrain.h"
|
||||||
|
#include "global.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
|
||||||
static const char* tag = "board";
|
static const char *tag = "board";
|
||||||
|
|
||||||
BOARD_PINS* thisBoardPins;
|
BOARD_PINS *thisBoardPins = nullptr;
|
||||||
|
|
||||||
// Basic validator for ESP32-S3 GPIOs; rejects common reserved/USB/strap pins
|
// Basic validator for ESP32-S3 GPIOs; rejects common reserved/USB/strap pins
|
||||||
static bool isValidGpio(int pin) {
|
static bool isValidGpio(int pin) {
|
||||||
if (pin < 0 || pin > 48) return false;
|
if (pin < 0 || pin > 48)
|
||||||
switch (pin) {
|
return false;
|
||||||
|
switch (pin) {
|
||||||
case 19: // USB D-
|
case 19: // USB D-
|
||||||
case 20: // USB D+
|
case 20: // USB D+
|
||||||
case 45: // strapping
|
case 45: // strapping
|
||||||
case 46: // strapping
|
case 46: // strapping
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Load_Board_Pins(BOARD_PINS& boardPins, const String& path){
|
bool Load_Board_Pins(BOARD_PINS &boardPins, const String &path) {
|
||||||
// Default initialize to -1 to avoid stale values on partial loads
|
// Default initialize to -1 to avoid stale values on partial loads
|
||||||
memset(&boardPins, -1, sizeof(boardPins));
|
memset(&boardPins, -1, sizeof(boardPins));
|
||||||
thisBoardPins = &boardPins;
|
thisBoardPins = &boardPins;
|
||||||
File file = LittleFS.open(path);
|
File file = LittleFS.open(path);
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
ESP_LOGE(tag, "Error opening %s...", path.c_str());
|
ESP_LOGE(tag, "Error opening %s...", path.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
DeserializationError error = deserializeJson(doc, file);
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
ESP_LOGE(tag, "%s deserialize error!..", path.c_str());
|
ESP_LOGE(tag, "%s deserialize error: %s", path.c_str(), error.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject boardJson = doc.as<JsonObject>();
|
JsonObject boardJson = doc.as<JsonObject>();
|
||||||
boardPins.rgb1 = jsonConstrain<int>(tag, boardJson, "rgb1", -1, 48, -1);
|
boardPins.rgb1 = jsonConstrain<int>(tag, boardJson, "rgb1", -1, 48, -1);
|
||||||
boardPins.rgb2 = jsonConstrain<int>(tag, boardJson, "rgb2", -1, 48, -1);
|
boardPins.rgb2 = jsonConstrain<int>(tag, boardJson, "rgb2", -1, 48, -1);
|
||||||
boardPins.btn[0] = jsonConstrain<int>(tag, boardJson, "btn1", -1, 48, -1);
|
boardPins.btn[0] = jsonConstrain<int>(tag, boardJson, "btn1", -1, 48, -1);
|
||||||
boardPins.btn[1] = jsonConstrain<int>(tag, boardJson, "btn2", -1, 48, -1);
|
boardPins.btn[1] = jsonConstrain<int>(tag, boardJson, "btn2", -1, 48, -1);
|
||||||
boardPins.btn[2] = jsonConstrain<int>(tag, boardJson, "btn3", -1, 48, -1);
|
boardPins.btn[2] = jsonConstrain<int>(tag, boardJson, "btn3", -1, 48, -1);
|
||||||
boardPins.buzzer = jsonConstrain<int>(tag, boardJson, "buzzer", -1, 48, -1);
|
boardPins.buzzer = jsonConstrain<int>(tag, boardJson, "buzzer", -1, 48, -1);
|
||||||
boardPins.touch[0] = jsonConstrain<int>(tag, boardJson, "touch1", -1, 48, -1);
|
boardPins.touch[0] = jsonConstrain<int>(tag, boardJson, "touch1", -1, 48, -1);
|
||||||
boardPins.touch[1] = jsonConstrain<int>(tag, boardJson, "touch2", -1, 48, -1);
|
boardPins.touch[1] = jsonConstrain<int>(tag, boardJson, "touch2", -1, 48, -1);
|
||||||
boardPins.touch[2] = jsonConstrain<int>(tag, boardJson, "touch3", -1, 48, -1);
|
boardPins.touch[2] = jsonConstrain<int>(tag, boardJson, "touch3", -1, 48, -1);
|
||||||
boardPins.touch[3] = jsonConstrain<int>(tag, boardJson, "touch4", -1, 48, -1);
|
boardPins.touch[3] = jsonConstrain<int>(tag, boardJson, "touch4", -1, 48, -1);
|
||||||
boardPins.touch[4] = jsonConstrain<int>(tag, boardJson, "touch5", -1, 48, -1);
|
boardPins.touch[4] = jsonConstrain<int>(tag, boardJson, "touch5", -1, 48, -1);
|
||||||
boardPins.shield = jsonConstrain<int>(tag, boardJson, "shield", -1, 48, -1);
|
boardPins.shield = jsonConstrain<int>(tag, boardJson, "shield", -1, 48, -1);
|
||||||
boardPins.relay[0] = jsonConstrain<int>(tag, boardJson, "relay1", -1, 48, -1);
|
boardPins.relay[0] = jsonConstrain<int>(tag, boardJson, "relay1", -1, 48, -1);
|
||||||
boardPins.relay[1] = jsonConstrain<int>(tag, boardJson, "relay2", -1, 48, -1);
|
boardPins.relay[1] = jsonConstrain<int>(tag, boardJson, "relay2", -1, 48, -1);
|
||||||
boardPins.relay[2] = jsonConstrain<int>(tag, boardJson, "relay3", -1, 48, -1);
|
boardPins.relay[2] = jsonConstrain<int>(tag, boardJson, "relay3", -1, 48, -1);
|
||||||
boardPins.relay[3] = jsonConstrain<int>(tag, boardJson, "relay4", -1, 48, -1);
|
boardPins.relay[3] = jsonConstrain<int>(tag, boardJson, "relay4", -1, 48, -1);
|
||||||
boardPins.stat[0] = jsonConstrain<int>(tag, boardJson, "stat1", -1, 48, -1);
|
boardPins.stat[0] = jsonConstrain<int>(tag, boardJson, "stat1", -1, 48, -1);
|
||||||
boardPins.stat[1] = jsonConstrain<int>(tag, boardJson, "stat2", -1, 48, -1);
|
boardPins.stat[1] = jsonConstrain<int>(tag, boardJson, "stat2", -1, 48, -1);
|
||||||
boardPins.adc1 = jsonConstrain<int>(tag, boardJson, "adc1", -1, 48, -1);
|
boardPins.adc1 = jsonConstrain<int>(tag, boardJson, "adc1", -1, 48, -1);
|
||||||
boardPins.oled_dc = jsonConstrain<int>(tag, boardJson, "oled_dc", -1, 48, -1);
|
boardPins.oled_dc = jsonConstrain<int>(tag, boardJson, "oled_dc", -1, 48, -1);
|
||||||
boardPins.oled_rst = jsonConstrain<int>(tag, boardJson, "oled_rst", -1, 48, -1);
|
boardPins.oled_rst = jsonConstrain<int>(tag, boardJson, "oled_rst", -1, 48, -1);
|
||||||
boardPins.oled_mosi = jsonConstrain<int>(tag, boardJson, "oled_mosi", -1, 48, -1);
|
boardPins.oled_mosi = jsonConstrain<int>(tag, boardJson, "oled_mosi", -1, 48, -1);
|
||||||
boardPins.oled_sck = jsonConstrain<int>(tag, boardJson, "oled_sck", -1, 48, -1);
|
boardPins.oled_sck = jsonConstrain<int>(tag, boardJson, "oled_sck", -1, 48, -1);
|
||||||
boardPins.oled_cs = jsonConstrain<int>(tag, boardJson, "oled_cs", -1, 48, -1);
|
boardPins.oled_cs = jsonConstrain<int>(tag, boardJson, "oled_cs", -1, 48, -1);
|
||||||
boardPins.ext[0] = jsonConstrain<int>(tag, boardJson, "ext1", -1, 48, -1);
|
boardPins.ext[0] = jsonConstrain<int>(tag, boardJson, "ext1", -1, 48, -1);
|
||||||
boardPins.ext[1] = jsonConstrain<int>(tag, boardJson, "ext2", -1, 48, -1);
|
boardPins.ext[1] = jsonConstrain<int>(tag, boardJson, "ext2", -1, 48, -1);
|
||||||
boardPins.rf433tx = jsonConstrain<int>(tag, boardJson, "rf433tx", -1, 48, -1);
|
boardPins.rf433tx = jsonConstrain<int>(tag, boardJson, "rf433tx", -1, 48, -1);
|
||||||
boardPins.rf433rx = jsonConstrain<int>(tag, boardJson, "rf433rx", -1, 48, -1);
|
boardPins.rf433rx = jsonConstrain<int>(tag, boardJson, "rf433rx", -1, 48, -1);
|
||||||
|
|
||||||
// Validate pins against reserved GPIOs
|
// Validate pins against reserved GPIOs
|
||||||
auto clampPin = [](int v){ return isValidGpio(v) ? v : -1; };
|
auto clampPin = [](int v) { return isValidGpio(v) ? v : -1; };
|
||||||
boardPins.rgb1 = clampPin(boardPins.rgb1);
|
boardPins.rgb1 = clampPin(boardPins.rgb1);
|
||||||
boardPins.rgb2 = clampPin(boardPins.rgb2);
|
boardPins.rgb2 = clampPin(boardPins.rgb2);
|
||||||
for (int i=0;i<3;i++) boardPins.btn[i] = clampPin(boardPins.btn[i]);
|
for (int i = 0; i < 3; i++)
|
||||||
boardPins.buzzer = clampPin(boardPins.buzzer);
|
boardPins.btn[i] = clampPin(boardPins.btn[i]);
|
||||||
for (int i=0;i<5;i++) boardPins.touch[i] = clampPin(boardPins.touch[i]);
|
boardPins.buzzer = clampPin(boardPins.buzzer);
|
||||||
boardPins.shield = clampPin(boardPins.shield);
|
for (int i = 0; i < 5; i++)
|
||||||
for (int i=0;i<4;i++) boardPins.relay[i] = clampPin(boardPins.relay[i]);
|
boardPins.touch[i] = clampPin(boardPins.touch[i]);
|
||||||
for (int i=0;i<2;i++) boardPins.stat[i] = clampPin(boardPins.stat[i]);
|
boardPins.shield = clampPin(boardPins.shield);
|
||||||
boardPins.adc1 = clampPin(boardPins.adc1);
|
for (int i = 0; i < 4; i++)
|
||||||
boardPins.oled_dc = clampPin(boardPins.oled_dc);
|
boardPins.relay[i] = clampPin(boardPins.relay[i]);
|
||||||
boardPins.oled_rst = clampPin(boardPins.oled_rst);
|
for (int i = 0; i < 2; i++)
|
||||||
boardPins.oled_mosi = clampPin(boardPins.oled_mosi);
|
boardPins.stat[i] = clampPin(boardPins.stat[i]);
|
||||||
boardPins.oled_sck = clampPin(boardPins.oled_sck);
|
boardPins.adc1 = clampPin(boardPins.adc1);
|
||||||
boardPins.oled_cs = clampPin(boardPins.oled_cs);
|
boardPins.oled_dc = clampPin(boardPins.oled_dc);
|
||||||
for (int i=0;i<2;i++) boardPins.ext[i] = clampPin(boardPins.ext[i]);
|
boardPins.oled_rst = clampPin(boardPins.oled_rst);
|
||||||
boardPins.rf433tx = clampPin(boardPins.rf433tx);
|
boardPins.oled_mosi = clampPin(boardPins.oled_mosi);
|
||||||
boardPins.rf433rx = clampPin(boardPins.rf433rx);
|
boardPins.oled_sck = clampPin(boardPins.oled_sck);
|
||||||
|
boardPins.oled_cs = clampPin(boardPins.oled_cs);
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
boardPins.ext[i] = clampPin(boardPins.ext[i]);
|
||||||
|
boardPins.rf433tx = clampPin(boardPins.rf433tx);
|
||||||
|
boardPins.rf433rx = clampPin(boardPins.rf433rx);
|
||||||
|
|
||||||
ESP_LOGI(tag, "loaded Pins from %s", path.c_str());
|
ESP_LOGI(tag, "loaded Pins from %s", path.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Init_Board_Basic(BOARD_PINS &boardPins) {
|
||||||
|
|
||||||
void Init_Board_Basic(BOARD_PINS& boardPins)
|
if (boardPins.stat[0] >= 0) {
|
||||||
{
|
pinMode(boardPins.stat[0], OUTPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.stat[1] >= 0) {
|
||||||
|
pinMode(boardPins.stat[1], OUTPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.btn[0] >= 0) {
|
||||||
|
pinMode(boardPins.btn[0], INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
if (boardPins.btn[1] >= 0) {
|
||||||
|
pinMode(boardPins.btn[1], INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
if (boardPins.btn[2] >= 0) {
|
||||||
|
pinMode(boardPins.btn[2], INPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.rgb1 >= 0) {
|
||||||
|
pinMode(boardPins.rgb1, OUTPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.rgb2 >= 0) {
|
||||||
|
pinMode(boardPins.rgb2, OUTPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.relay[0] >= 0) {
|
||||||
|
pinMode(boardPins.relay[0], OUTPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.relay[1] >= 0) {
|
||||||
|
pinMode(boardPins.relay[1], OUTPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.relay[2] >= 0) {
|
||||||
|
pinMode(boardPins.relay[2], OUTPUT);
|
||||||
|
}
|
||||||
|
if (boardPins.relay[3] >= 0) {
|
||||||
|
pinMode(boardPins.relay[3], OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
if(boardPins.stat[0] >= 0){ pinMode(boardPins.stat[0], OUTPUT); }
|
ESP_LOGI(tag, "Board pins initialized...");
|
||||||
if(boardPins.stat[1] >= 0){ pinMode(boardPins.stat[1], OUTPUT); }
|
|
||||||
|
|
||||||
if(boardPins.btn[0] >= 0){ pinMode(boardPins.btn[0], INPUT_PULLUP); }
|
|
||||||
if(boardPins.btn[1] >= 0){ pinMode(boardPins.btn[1], INPUT_PULLUP); }
|
|
||||||
if(boardPins.btn[2] >= 0){ pinMode(boardPins.btn[2], INPUT); }
|
|
||||||
|
|
||||||
if(boardPins.rgb1 >= 0){ pinMode(boardPins.rgb1, OUTPUT); }
|
|
||||||
if(boardPins.rgb2 >= 0){ pinMode(boardPins.rgb2, OUTPUT); }
|
|
||||||
|
|
||||||
if(boardPins.relay[0] >= 0){ pinMode(boardPins.relay[0], OUTPUT); }
|
|
||||||
if(boardPins.relay[1] >= 0){ pinMode(boardPins.relay[1], OUTPUT); }
|
|
||||||
if(boardPins.relay[2] >= 0){ pinMode(boardPins.relay[2], OUTPUT); }
|
|
||||||
if(boardPins.relay[3] >= 0){ pinMode(boardPins.relay[3], OUTPUT); }
|
|
||||||
|
|
||||||
ESP_LOGI(tag, "Board pins initialized...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,10 @@
|
|||||||
#include "AppUpgrade.h"
|
#include "AppUpgrade.h"
|
||||||
|
|
||||||
static const char* tag = "button";
|
static const char* tag = "button";
|
||||||
OneButton *boardButtons[3];
|
OneButton *boardButtons[3] = { nullptr, nullptr, nullptr };
|
||||||
|
|
||||||
void Init_ButtonEvents(int8_t (&pin)[3]){
|
void Init_ButtonEvents(int8_t (&pin)[3]){
|
||||||
|
// Initialize buttons if pins are valid and not already initialized
|
||||||
|
|
||||||
if (pin[0] >= 0) {
|
if (pin[0] >= 0) {
|
||||||
if (boardButtons[0] == nullptr) {
|
if (boardButtons[0] == nullptr) {
|
||||||
boardButtons[0] = new OneButton(pin[0], true, true);
|
boardButtons[0] = new OneButton(pin[0], true, true);
|
||||||
|
|||||||
@ -17,7 +17,7 @@ static const char* tag = "buzzer";
|
|||||||
// Define static constexpr member from RtttlPlayer class
|
// Define static constexpr member from RtttlPlayer class
|
||||||
constexpr uint16_t RtttlPlayer::LUT4[12];
|
constexpr uint16_t RtttlPlayer::LUT4[12];
|
||||||
|
|
||||||
RtttlPlayer *player;
|
RtttlPlayer *player = nullptr;
|
||||||
BUZZ_TUNE buzzTune[TUNE_MAX_COUNT];
|
BUZZ_TUNE buzzTune[TUNE_MAX_COUNT];
|
||||||
int8_t buzzPin;
|
int8_t buzzPin;
|
||||||
int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer
|
int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer
|
||||||
@ -105,7 +105,7 @@ void Buzzer_Load_Tunes(const char* tunesPath){
|
|||||||
buzzTune[tuneIndex].cycles = jsonConstrain<int>(tag, obj, "cycles", 1, 100, 1);
|
buzzTune[tuneIndex].cycles = jsonConstrain<int>(tag, obj, "cycles", 1, 100, 1);
|
||||||
buzzTune[tuneIndex].pause = jsonConstrain<int>(tag, obj, "pause", 0, 100, 0);
|
buzzTune[tuneIndex].pause = jsonConstrain<int>(tag, obj, "pause", 0, 100, 0);
|
||||||
buzzTune[tuneIndex].melody = jsonConstrainString(tag, obj, "tune", DEFAULT_MELODY);
|
buzzTune[tuneIndex].melody = jsonConstrainString(tag, obj, "tune", DEFAULT_MELODY);
|
||||||
ESP_LOGI(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...",
|
ESP_LOGD(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...",
|
||||||
tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause,
|
tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause,
|
||||||
buzzTune[tuneIndex].melody.c_str());
|
buzzTune[tuneIndex].melody.c_str());
|
||||||
tuneIndex++;
|
tuneIndex++;
|
||||||
|
|||||||
@ -1,79 +1,94 @@
|
|||||||
#include "my_device.h"
|
#include "my_device.h"
|
||||||
#include "JsonConstrain.h"
|
#include "JsonConstrain.h"
|
||||||
#include "global.h"
|
|
||||||
#include "system.h"
|
|
||||||
#include "my_buttons.h"
|
|
||||||
#include "PWM_Output.h"
|
#include "PWM_Output.h"
|
||||||
#include "Ramp_Lights.h"
|
#include "Ramp_Lights.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include <FS.h>
|
#include "global.h"
|
||||||
#include <LittleFS.h>
|
#include "my_buttons.h"
|
||||||
|
#include "system.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <esp_system.h>
|
#include <FS.h>
|
||||||
#include <esp_log.h>
|
#include <LittleFS.h>
|
||||||
#include <esp_adc_cal.h>
|
#include <OneButton.h>
|
||||||
#include <driver/adc.h>
|
#include <driver/adc.h>
|
||||||
#include <driver/ledc.h>
|
#include <driver/ledc.h>
|
||||||
#include <OneButton.h>
|
#include <esp_adc_cal.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_system.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
static const char *tag = "my_device";
|
static const char *tag = "my_device";
|
||||||
|
|
||||||
SYS_SETTINGS sys_settings;
|
SYS_SETTINGS sys_settings;
|
||||||
PWM_Output *pwmOutputs[4];
|
PWM_Output *pwmOutputs[4] = { nullptr, nullptr, nullptr, nullptr };
|
||||||
RAMP_LIGHT *rampLight1;
|
RAMP_LIGHT *rampLight1 = nullptr;
|
||||||
RAMP_LIGHT *rampLight2;
|
RAMP_LIGHT *rampLight2 = nullptr;
|
||||||
|
|
||||||
|
String booth_file_path;
|
||||||
|
|
||||||
|
float PowerVinAlpha = 0.5; // Low-pass filter alpha
|
||||||
|
float prevPowerVin = 0.0;
|
||||||
|
float PowerVin = 0.0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TODO Restore original setOutput code..
|
// TODO Restore original setOutput code..
|
||||||
#define RELAY_RES 10
|
#define RELAY_RES 10
|
||||||
void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4])
|
void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]) {
|
||||||
{
|
// Delete any existing objects to avoid leaks on re-init
|
||||||
// Initialize all pointers to nullptr first
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
pwmOutputs[i] = nullptr;
|
if (pwmOutputs[i]) {
|
||||||
|
delete pwmOutputs[i];
|
||||||
|
pwmOutputs[i] = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++) {
|
||||||
{
|
|
||||||
int chIndex = findUnusedLedcChannel();
|
int chIndex = findUnusedLedcChannel();
|
||||||
if (chIndex < 0)
|
if (chIndex < 0) {
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i);
|
ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, pwmSettings[i].max, false);
|
pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq,
|
||||||
pwmOutputs[i]->setOutput(pwmSettings[i].def);
|
pwmSettings[i].max, false);
|
||||||
|
if (pwmOutputs[i]) {
|
||||||
|
pwmOutputs[i]->setOutput(pwmSettings[i].def);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(tag, "Allocation failed for PWM Output%d", i);
|
||||||
|
}
|
||||||
// pwmOutputs[i]->setOutput(5.0);
|
// pwmOutputs[i]->setOutput(5.0);
|
||||||
ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq, chIndex);
|
ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq,
|
||||||
|
chIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]) {
|
||||||
void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4])
|
if (settings[0].enabled) {
|
||||||
{
|
if (rampLight1) { delete rampLight1; rampLight1 = nullptr; }
|
||||||
if (settings[0].enabled)
|
rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], settings[0].min, settings[0].max, settings[0].step);
|
||||||
{
|
if (rampLight1)
|
||||||
rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], settings[0].min, settings[0].max, settings[0].step);
|
ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d, min=%f, max=%f, step=%f", 1, settings[0].btnIndex, settings[0].pwmOutIndex, settings[0].min, settings[0].max, settings[0].step);
|
||||||
ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 1, settings[0].btnIndex, settings[0].pwmOutIndex);
|
else
|
||||||
|
ESP_LOGE(tag, "Failed to allocate RampLight1");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings[1].enabled)
|
if (settings[1].enabled) {
|
||||||
{
|
if (rampLight2) { delete rampLight2; rampLight2 = nullptr; }
|
||||||
rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], settings[1].min, settings[1].max, settings[1].step);
|
rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], settings[1].min, settings[1].max, settings[1].step);
|
||||||
ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 2, settings[1].btnIndex, settings[1].pwmOutIndex);
|
if (rampLight2)
|
||||||
|
ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d, min=%f, max=%f, step=%f", 2, settings[1].btnIndex, settings[1].pwmOutIndex, settings[1].min, settings[1].max, settings[1].step);
|
||||||
|
else
|
||||||
|
ESP_LOGE(tag, "Failed to allocate RampLight2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Get the files that should be used to setup the system
|
// Get the files that should be used to setup the system
|
||||||
void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, String &boothPath)
|
void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, String &boothPath) {
|
||||||
{
|
|
||||||
File file = LittleFS.open(sysPath);
|
File file = LittleFS.open(sysPath);
|
||||||
|
|
||||||
if (!file)
|
if (!file) {
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "Error opening %s...", sysPath);
|
ESP_LOGE(tag, "Error opening %s...", sysPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -82,23 +97,21 @@ void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, Stri
|
|||||||
DeserializationError error = deserializeJson(doc, file);
|
DeserializationError error = deserializeJson(doc, file);
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (error)
|
if (error) {
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "%s deserialize error!..", sysPath);
|
ESP_LOGE(tag, "%s deserialize error!..", sysPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get hardware version string
|
// get hardware version string
|
||||||
boardPath = jsonConstrainString(tag, doc.as<JsonObject>(), "boardfile", "/cfg/boards/board15.json");
|
boardPath =
|
||||||
boothPath = jsonConstrainString(tag, doc.as<JsonObject>(), "configfile", "/cfg/booths/custom.json");
|
jsonConstrainString(tag, doc.as<JsonObject>(), "boardfile", "/cfg/boards/board15.json");
|
||||||
|
boothPath =
|
||||||
|
jsonConstrainString(tag, doc.as<JsonObject>(), "configfile", "/cfg/booths/custom.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath) {
|
||||||
void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
|
|
||||||
{
|
|
||||||
File file = LittleFS.open(boothPath);
|
File file = LittleFS.open(boothPath);
|
||||||
if (!file)
|
if (!file) {
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "Error opening %s...", boothPath.c_str());
|
ESP_LOGE(tag, "Error opening %s...", boothPath.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -107,139 +120,150 @@ void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
|
|||||||
DeserializationError error = deserializeJson(doc, file);
|
DeserializationError error = deserializeJson(doc, file);
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (error)
|
if (error) {
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str());
|
ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sys_settings.profile = jsonConstrainString(tag, doc.as<JsonObject>(), "profile", "custom");
|
||||||
|
|
||||||
// ********** Mode ***********
|
// ********** Mode ***********
|
||||||
String modeStr = jsonConstrainString(tag, doc.as<JsonObject>(), "mode", "booth");
|
String modeStr = jsonConstrainString(tag, doc.as<JsonObject>(), "mode", "booth");
|
||||||
if (modeStr == "roamer")
|
if (modeStr == "roamer") {
|
||||||
{
|
|
||||||
sys_settings.mode = BOOTH_MODE_ROAMER;
|
sys_settings.mode = BOOTH_MODE_ROAMER;
|
||||||
}
|
} else if (modeStr == "stik") {
|
||||||
else if (modeStr == "stik")
|
|
||||||
{
|
|
||||||
sys_settings.mode = BOOTH_MODE_STIK;
|
sys_settings.mode = BOOTH_MODE_STIK;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
sys_settings.mode = BOOTH_MODE_NONE;
|
sys_settings.mode = BOOTH_MODE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********** PWM Out ***********
|
// ********** PWM Out ***********
|
||||||
JsonArray pwmJsonArray = doc["pwmout"];
|
JsonArray pwmJsonArray = doc["pwmout"];
|
||||||
if (!pwmJsonArray.isNull())
|
if (!pwmJsonArray.isNull()) {
|
||||||
{
|
|
||||||
int pwmIndex = 0;
|
int pwmIndex = 0;
|
||||||
for (JsonObject obj : pwmJsonArray)
|
for (JsonObject obj : pwmJsonArray) {
|
||||||
{
|
if (pwmIndex >=
|
||||||
if (pwmIndex >= sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0]))
|
sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0]))
|
||||||
break;
|
break;
|
||||||
sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
|
sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
|
||||||
sys_settings.pwmOutSettings[pwmIndex].freq = jsonConstrain<int>(tag, obj, "freq", 100, 5000, 250);
|
sys_settings.pwmOutSettings[pwmIndex].freq =
|
||||||
sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
|
jsonConstrain<int>(tag, obj, "freq", 100, 5000, 250);
|
||||||
sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
|
sys_settings.pwmOutSettings[pwmIndex].min =
|
||||||
sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain<float>(tag, obj, "default", 0.0, 100.0, 0.0);
|
jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
|
||||||
|
sys_settings.pwmOutSettings[pwmIndex].max =
|
||||||
|
jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
|
||||||
|
sys_settings.pwmOutSettings[pwmIndex].def =
|
||||||
|
jsonConstrain<float>(tag, obj, "default", 0.0, 100.0, 0.0);
|
||||||
sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
|
sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
|
||||||
// sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true);
|
// sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true);
|
||||||
pwmIndex++;
|
pwmIndex++;
|
||||||
}
|
}
|
||||||
ESP_LOGI(tag, "Loaded PWmOutput settings...");
|
ESP_LOGI(tag, "Loaded PWmOutput settings...");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath);
|
ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********** Ramp Lights ***********
|
// ********** Ramp Lights ***********
|
||||||
JsonArray rampJsonArray = doc["ramp-lights"];
|
JsonArray rampJsonArray = doc["ramp-lights"];
|
||||||
if (!rampJsonArray.isNull())
|
if (!rampJsonArray.isNull()) {
|
||||||
{
|
|
||||||
int rampIndex = 0;
|
int rampIndex = 0;
|
||||||
for (JsonObject obj : rampJsonArray)
|
for (JsonObject obj : rampJsonArray) {
|
||||||
{
|
if (rampIndex >=
|
||||||
if (rampIndex >= sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0]))
|
sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0]))
|
||||||
break;
|
break;
|
||||||
sys_settings.rampLightSettings[rampIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
|
sys_settings.rampLightSettings[rampIndex].enabled =
|
||||||
sys_settings.rampLightSettings[rampIndex].vision = jsonConstrainBool(tag, obj, "vision", true);
|
jsonConstrainBool(tag, obj, "en", true);
|
||||||
sys_settings.rampLightSettings[rampIndex].pwmOutIndex = jsonConstrain<int>(tag, obj, "relay-index", 0, 1, 0);
|
sys_settings.rampLightSettings[rampIndex].vision =
|
||||||
sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain<int>(tag, obj, "button-index", 0, 1, 0);
|
jsonConstrainBool(tag, obj, "vision", true);
|
||||||
sys_settings.rampLightSettings[rampIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
|
sys_settings.rampLightSettings[rampIndex].pwmOutIndex =
|
||||||
sys_settings.rampLightSettings[rampIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
|
jsonConstrain<int>(tag, obj, "relay-index", 0, 1, 0);
|
||||||
sys_settings.rampLightSettings[rampIndex].step = jsonConstrain<float>(tag, obj, "step", 0.1, 10.0, 1.5);
|
sys_settings.rampLightSettings[rampIndex].btnIndex =
|
||||||
|
jsonConstrain<int>(tag, obj, "button-index", 0, 1, 0);
|
||||||
|
sys_settings.rampLightSettings[rampIndex].min =
|
||||||
|
jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
|
||||||
|
sys_settings.rampLightSettings[rampIndex].max =
|
||||||
|
jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
|
||||||
|
sys_settings.rampLightSettings[rampIndex].step =
|
||||||
|
jsonConstrain<float>(tag, obj, "step", 0.1, 10.0, 1.5);
|
||||||
rampIndex++;
|
rampIndex++;
|
||||||
}
|
}
|
||||||
ESP_LOGI(tag, "Loaded Ramp Lights settings...");
|
ESP_LOGI(tag, "Loaded Ramp Lights settings...");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath);
|
ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********** Fan ***********
|
// ********** Fan ***********
|
||||||
JsonObject sensorJson = doc["t-sensor"];
|
JsonObject sensorJson = doc["t-sensor"];
|
||||||
if (!sensorJson.isNull())
|
if (!sensorJson.isNull()) {
|
||||||
{
|
|
||||||
sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true);
|
sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true);
|
||||||
sys_settings.tSensorSettings.pwmIndex = jsonConstrain<int>(tag, sensorJson, "relay", 0, 3, 3);
|
sys_settings.tSensorSettings.pwmIndex =
|
||||||
sys_settings.tSensorSettings.setpoint1 = jsonConstrain<float>(tag, sensorJson, "sp1", 50.0, 100.0, 80.0);
|
jsonConstrain<int>(tag, sensorJson, "relay", 0, 3, 3);
|
||||||
sys_settings.tSensorSettings.setpoint2 = jsonConstrain<float>(tag, sensorJson, "sp2", 60.0, 110.0, 90.0);
|
sys_settings.tSensorSettings.setpoint1 =
|
||||||
sys_settings.tSensorSettings.fanPower1 = jsonConstrain<float>(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
|
jsonConstrain<float>(tag, sensorJson, "sp1", 50.0, 100.0, 80.0);
|
||||||
sys_settings.tSensorSettings.fanPower2 = jsonConstrain<float>(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0);
|
sys_settings.tSensorSettings.setpoint2 =
|
||||||
sys_settings.tSensorSettings.hyst = jsonConstrain<float>(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
|
jsonConstrain<float>(tag, sensorJson, "sp2", 60.0, 110.0, 90.0);
|
||||||
sys_settings.tSensorSettings.intervalMs = jsonConstrain<int>(tag, sensorJson, "interval", 1000, 30000, 5000);
|
sys_settings.tSensorSettings.fanPower1 =
|
||||||
|
jsonConstrain<float>(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
|
||||||
|
sys_settings.tSensorSettings.fanPower2 =
|
||||||
|
jsonConstrain<float>(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0);
|
||||||
|
sys_settings.tSensorSettings.hyst =
|
||||||
|
jsonConstrain<float>(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
|
||||||
|
sys_settings.tSensorSettings.intervalMs =
|
||||||
|
jsonConstrain<int>(tag, sensorJson, "interval", 1000, 30000, 5000);
|
||||||
ESP_LOGI(tag, "Loaded TSensor settings...");
|
ESP_LOGI(tag, "Loaded TSensor settings...");
|
||||||
ESP_LOGI(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1, sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
|
ESP_LOGI(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1,
|
||||||
}
|
sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath);
|
ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********** RGB Strips ***********
|
// ********** RGB Strips ***********
|
||||||
JsonArray stripsJsonArray = doc["strips"];
|
JsonArray stripsJsonArray = doc["strips"];
|
||||||
if (!stripsJsonArray.isNull())
|
if (!stripsJsonArray.isNull()) {
|
||||||
{
|
|
||||||
int stripIndex = 0;
|
int stripIndex = 0;
|
||||||
for (JsonObject obj : stripsJsonArray)
|
for (JsonObject obj : stripsJsonArray) {
|
||||||
{
|
|
||||||
if (stripIndex >= 2)
|
if (stripIndex >= 2)
|
||||||
break;
|
break;
|
||||||
sys_settings.ledStripSettings[stripIndex]->enabled = jsonConstrainBool(tag, obj, "en", true);
|
if (sys_settings.ledStripSettings[stripIndex]) {
|
||||||
sys_settings.ledStripSettings[stripIndex]->size = jsonConstrain<int>(tag, obj, "size", 1, 250, 25);
|
sys_settings.ledStripSettings[stripIndex]->enabled =
|
||||||
sys_settings.ledStripSettings[stripIndex]->chip = jsonConstrainString(tag, obj, "chip", "WS2812B");
|
jsonConstrainBool(tag, obj, "en", true);
|
||||||
sys_settings.ledStripSettings[stripIndex]->rgbOrder = jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
|
sys_settings.ledStripSettings[stripIndex]->size =
|
||||||
sys_settings.ledStripSettings[stripIndex]->shift = jsonConstrain<int>(tag, obj, "shift", -250, 250, 0);
|
jsonConstrain<int>(tag, obj, "size", 1, 250, 25);
|
||||||
sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain<int>(tag, obj, "offset", -250, 250, 0);
|
sys_settings.ledStripSettings[stripIndex]->chip =
|
||||||
sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain<int>(tag, obj, "bright", 5, 255, 200);
|
jsonConstrainString(tag, obj, "chip", "WS2812B");
|
||||||
sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
|
sys_settings.ledStripSettings[stripIndex]->rgbOrder =
|
||||||
sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
|
jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
|
||||||
sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain<int>(tag, obj, "core", 0, 1, 0);
|
sys_settings.ledStripSettings[stripIndex]->shift =
|
||||||
|
jsonConstrain<int>(tag, obj, "shift", -250, 250, 0);
|
||||||
|
sys_settings.ledStripSettings[stripIndex]->offset =
|
||||||
|
jsonConstrain<int>(tag, obj, "offset", -250, 250, 0);
|
||||||
|
sys_settings.ledStripSettings[stripIndex]->bright =
|
||||||
|
jsonConstrain<int>(tag, obj, "bright", 5, 255, 200);
|
||||||
|
sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
|
||||||
|
sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
|
||||||
|
sys_settings.ledStripSettings[stripIndex]->core =
|
||||||
|
jsonConstrain<int>(tag, obj, "core", 0, 1, 0);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(tag, "ledStripSettings[%d] is null, skipping config", stripIndex);
|
||||||
|
}
|
||||||
stripIndex++;
|
stripIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1;
|
sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1;
|
||||||
sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2;
|
sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2;
|
||||||
ESP_LOGI(tag, "Loaded LED Strip settings...");
|
ESP_LOGI(tag, "Loaded LED Strip settings...");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "Error!, %s key: strips not found..");
|
ESP_LOGE(tag, "Error!, %s key: strips not found..");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********** BLE ***********
|
// ********** BLE ***********
|
||||||
JsonObject bleJson = doc["ble"];
|
JsonObject bleJson = doc["ble"];
|
||||||
if (!bleJson.isNull())
|
if (!bleJson.isNull()) {
|
||||||
{
|
|
||||||
sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true);
|
sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true);
|
||||||
sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS");
|
sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS");
|
||||||
|
|
||||||
ESP_LOGI(tag, "Loaded BLE settings...");
|
ESP_LOGI(tag, "Loaded BLE settings...");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath);
|
ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,14 +279,11 @@ void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Init_ADC(void) {
|
||||||
void Init_ADC(void)
|
|
||||||
{
|
|
||||||
// Configure ADC
|
// Configure ADC
|
||||||
analogReadResolution(12); // 12-bit ADC
|
analogReadResolution(12); // 12-bit ADC
|
||||||
analogSetAttenuation(ADC_11db);
|
analogSetAttenuation(ADC_11db);
|
||||||
if (sys_settings.boardPins.adc1 >= 0)
|
if (sys_settings.boardPins.adc1 >= 0) {
|
||||||
{
|
|
||||||
analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db);
|
analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,35 +292,26 @@ void Init_ADC(void)
|
|||||||
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars);
|
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars);
|
||||||
|
|
||||||
// Check calibration success
|
// Check calibration success
|
||||||
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK)
|
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
|
||||||
{
|
|
||||||
ESP_LOGI(tag, "ADC calibration: Using Two Point values from eFuse");
|
ESP_LOGI(tag, "ADC calibration: Using Two Point values from eFuse");
|
||||||
}
|
} else if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
|
||||||
else if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse");
|
ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGW(tag, "ADC calibration: Using default reference voltage");
|
ESP_LOGW(tag, "ADC calibration: Using default reference voltage");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float readBoardInputVoltage(void) {
|
||||||
float readBoardInputVoltage(void)
|
|
||||||
{
|
|
||||||
const int SAMPLES = 64;
|
const int SAMPLES = 64;
|
||||||
uint32_t reading = 0;
|
uint32_t reading = 0;
|
||||||
|
|
||||||
if (sys_settings.boardPins.adc1 < 0)
|
if (sys_settings.boardPins.adc1 < 0) {
|
||||||
{
|
|
||||||
ESP_LOGE(tag, "ADC Pin not valid");
|
ESP_LOGE(tag, "ADC Pin not valid");
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple readings for averaging
|
// Multiple readings for averaging
|
||||||
for (int i = 0; i < SAMPLES; i++)
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
{
|
|
||||||
reading += analogRead(sys_settings.boardPins.adc1);
|
reading += analogRead(sys_settings.boardPins.adc1);
|
||||||
delayMicroseconds(50); // Small delay between samples
|
delayMicroseconds(50); // Small delay between samples
|
||||||
}
|
}
|
||||||
@ -311,8 +323,15 @@ float readBoardInputVoltage(void)
|
|||||||
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
|
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
|
||||||
voltage_mv = esp_adc_cal_raw_to_voltage(reading, &adc_chars);
|
voltage_mv = esp_adc_cal_raw_to_voltage(reading, &adc_chars);
|
||||||
|
|
||||||
// Scale to 12V range
|
// Voltage divider: R1 = 10k (series to input), R2 = 470 (to ground). Measurement is across R2.
|
||||||
float voltage = (voltage_mv / 1000.0f) * (10470.0f / 470.0f);
|
// Vin = Vout * (R1 + R2) / R2
|
||||||
|
const float R1 = 10000.0f;
|
||||||
|
const float R2 = 470.0f;
|
||||||
|
const float dividerFactor = (R1 + R2) / R2;
|
||||||
|
|
||||||
return voltage;
|
// Convert mV to V and apply divider factor
|
||||||
|
float vout = voltage_mv / 1000.0f;
|
||||||
|
float vin = vout * dividerFactor;
|
||||||
|
|
||||||
|
return vin;
|
||||||
}
|
}
|
||||||
121
src/my_oled.cpp
121
src/my_oled.cpp
@ -1,24 +1,25 @@
|
|||||||
#include "my_oled.h"
|
#include "my_oled.h"
|
||||||
#include <SPI.h>
|
|
||||||
#include <Adafruit_GFX.h>
|
#include <Adafruit_GFX.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
static const char* tag = "oled";
|
static const char *tag = "oled";
|
||||||
Adafruit_SSD1306 *oled;
|
Adafruit_SSD1306 *oled = nullptr;
|
||||||
|
|
||||||
void Init_OLED(uint8_t width, uint8_t height, uint8_t mosiPin, uint8_t sckPin, uint8_t dcPin, uint8_t rstPin, uint8_t csPin)
|
void Init_OLED(uint8_t width, uint8_t height, uint8_t mosiPin, uint8_t sckPin, uint8_t dcPin,
|
||||||
{
|
uint8_t rstPin, uint8_t csPin) {
|
||||||
oled = new Adafruit_SSD1306(width, height, mosiPin, sckPin, dcPin, rstPin, csPin);
|
oled = new Adafruit_SSD1306(width, height, mosiPin, sckPin, dcPin, rstPin, csPin);
|
||||||
|
|
||||||
if(!oled->begin(SSD1306_SWITCHCAPVCC)) {
|
if (!oled->begin(SSD1306_SWITCHCAPVCC)) {
|
||||||
ESP_LOGE(tag, "SSD1306 allocation failed");
|
ESP_LOGE(tag, "SSD1306 allocation failed");
|
||||||
for(;;); // Don't proceed, loop forever
|
for (;;)
|
||||||
}
|
; // Don't proceed, loop forever
|
||||||
|
}
|
||||||
|
|
||||||
oled_ShowInfo();
|
oled_ShowInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void oled_ShowInfo(void){
|
void oled_ShowInfo(void) {
|
||||||
//if(sysProps.oledEnabled) {
|
// if(sysProps.oledEnabled) {
|
||||||
oled->display();
|
oled->display();
|
||||||
vTaskDelay(2000); // Pause for 2 seconds
|
vTaskDelay(2000); // Pause for 2 seconds
|
||||||
|
|
||||||
@ -37,89 +38,89 @@ void oled_ShowInfo(void){
|
|||||||
// drawing operations and then update the screen all at once by calling
|
// drawing operations and then update the screen all at once by calling
|
||||||
// display.display(). These examples demonstrate both approaches...
|
// display.display(). These examples demonstrate both approaches...
|
||||||
|
|
||||||
//testdrawline(*oled); // Draw many lines
|
// testdrawline(*oled); // Draw many lines
|
||||||
|
|
||||||
//testdrawrect(*oled); // Draw rectangles (outlines)
|
// testdrawrect(*oled); // Draw rectangles (outlines)
|
||||||
|
|
||||||
testdrawline(oled); // Draw many lines
|
testdrawline(oled); // Draw many lines
|
||||||
|
|
||||||
testdrawrect(oled); // Draw rectangles (outlines)
|
testdrawrect(oled); // Draw rectangles (outlines)
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
void testdrawline(Adafruit_SSD1306* display) {
|
void testdrawline(Adafruit_SSD1306 *display) {
|
||||||
//if(sysProps.oledEnabled) {
|
// if(sysProps.oledEnabled) {
|
||||||
int16_t i;
|
int16_t i;
|
||||||
|
|
||||||
display->clearDisplay(); // Clear display buffer
|
display->clearDisplay(); // Clear display buffer
|
||||||
|
|
||||||
for(i=0; i<display->width(); i+=4) {
|
for (i = 0; i < display->width(); i += 4) {
|
||||||
display->drawLine(0, 0, i, display->height()-1, SSD1306_WHITE);
|
display->drawLine(0, 0, i, display->height() - 1, SSD1306_WHITE);
|
||||||
display->display(); // Update screen with each newly-drawn line
|
display->display(); // Update screen with each newly-drawn line
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
for(i=0; i<display->height(); i+=4) {
|
for (i = 0; i < display->height(); i += 4) {
|
||||||
display->drawLine(0, 0, display->width()-1, i, SSD1306_WHITE);
|
display->drawLine(0, 0, display->width() - 1, i, SSD1306_WHITE);
|
||||||
display->display();
|
display->display();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
vTaskDelay(250);
|
vTaskDelay(250);
|
||||||
|
|
||||||
display->clearDisplay();
|
display->clearDisplay();
|
||||||
|
|
||||||
for(i=0; i<display->width(); i+=4) {
|
for (i = 0; i < display->width(); i += 4) {
|
||||||
display->drawLine(0, display->height()-1, i, 0, SSD1306_WHITE);
|
display->drawLine(0, display->height() - 1, i, 0, SSD1306_WHITE);
|
||||||
display->display();
|
display->display();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
for(i=display->height()-1; i>=0; i-=4) {
|
for (i = display->height() - 1; i >= 0; i -= 4) {
|
||||||
display->drawLine(0, display->height()-1, display->width()-1, i, SSD1306_WHITE);
|
display->drawLine(0, display->height() - 1, display->width() - 1, i, SSD1306_WHITE);
|
||||||
display->display();
|
display->display();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
vTaskDelay(250);
|
vTaskDelay(250);
|
||||||
|
|
||||||
display->clearDisplay();
|
display->clearDisplay();
|
||||||
|
|
||||||
for(i=display->width()-1; i>=0; i-=4) {
|
for (i = display->width() - 1; i >= 0; i -= 4) {
|
||||||
display->drawLine(display->width()-1, display->height()-1, i, 0, SSD1306_WHITE);
|
display->drawLine(display->width() - 1, display->height() - 1, i, 0, SSD1306_WHITE);
|
||||||
display->display();
|
display->display();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
for(i=display->height()-1; i>=0; i-=4) {
|
for (i = display->height() - 1; i >= 0; i -= 4) {
|
||||||
display->drawLine(display->width()-1, display->height()-1, 0, i, SSD1306_WHITE);
|
display->drawLine(display->width() - 1, display->height() - 1, 0, i, SSD1306_WHITE);
|
||||||
display->display();
|
display->display();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
vTaskDelay(250);
|
vTaskDelay(250);
|
||||||
|
|
||||||
display->clearDisplay();
|
display->clearDisplay();
|
||||||
|
|
||||||
for(i=0; i<display->height(); i+=4) {
|
for (i = 0; i < display->height(); i += 4) {
|
||||||
display->drawLine(display->width()-1, 0, 0, i, SSD1306_WHITE);
|
display->drawLine(display->width() - 1, 0, 0, i, SSD1306_WHITE);
|
||||||
display->display();
|
display->display();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
for(i=0; i<display->width(); i+=4) {
|
for (i = 0; i < display->width(); i += 4) {
|
||||||
display->drawLine(display->width()-1, 0, i, display->height()-1, SSD1306_WHITE);
|
display->drawLine(display->width() - 1, 0, i, display->height() - 1, SSD1306_WHITE);
|
||||||
display->display();
|
display->display();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(2000); // Pause for 2 seconds
|
vTaskDelay(2000); // Pause for 2 seconds
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
void testdrawrect(Adafruit_SSD1306* display) {
|
void testdrawrect(Adafruit_SSD1306 *display) {
|
||||||
//if(sysProps.oledEnabled) {
|
// if(sysProps.oledEnabled) {
|
||||||
display->clearDisplay();
|
display->clearDisplay();
|
||||||
|
|
||||||
for(int16_t i=0; i<display->height()/2; i+=2) {
|
for (int16_t i = 0; i < display->height() / 2; i += 2) {
|
||||||
display->drawRect(i, i, display->width()-2*i, display->height()-2*i, SSD1306_WHITE);
|
display->drawRect(i, i, display->width() - 2 * i, display->height() - 2 * i, SSD1306_WHITE);
|
||||||
display->display(); // Update screen with each newly-drawn rectangle
|
display->display(); // Update screen with each newly-drawn rectangle
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(2000);
|
vTaskDelay(2000);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
@ -21,7 +21,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
|
|||||||
// Initialize the temperature sensor once with the provided I2C address
|
// Initialize the temperature sensor once with the provided I2C address
|
||||||
if (tSensor == nullptr) {
|
if (tSensor == nullptr) {
|
||||||
tSensor = new TI_TMP102_Compatible(addr);
|
tSensor = new TI_TMP102_Compatible(addr);
|
||||||
ESP_LOGI(tag, "TSensor initialized at I2C addr 0x%02X", addr);
|
ESP_LOGD(tag, "TSensor initialized at I2C addr 0x%02X", addr);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(tag, "TSensor already initialized; ignoring re-init request (addr 0x%02X)", addr);
|
ESP_LOGW(tag, "TSensor already initialized; ignoring re-init request (addr 0x%02X)", addr);
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
|
|||||||
// Fan is off - check if we should turn it on
|
// Fan is off - check if we should turn it on
|
||||||
if (temperature >= sp1) {
|
if (temperature >= sp1) {
|
||||||
fanIsOn = true;
|
fanIsOn = true;
|
||||||
ESP_LOGI(tag, "Fan turning ON - temp %.2f >= setpoint1 %.2f", temperature, sp1);
|
ESP_LOGD(tag, "Fan turning ON - temp %.2f >= setpoint1 %.2f", temperature, sp1);
|
||||||
} else {
|
} else {
|
||||||
newDuty = 0.0f; // Stay off
|
newDuty = 0.0f; // Stay off
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
|
|||||||
if (temperature < (sp1 - hyst)) {
|
if (temperature < (sp1 - hyst)) {
|
||||||
fanIsOn = false;
|
fanIsOn = false;
|
||||||
newDuty = 0.0f;
|
newDuty = 0.0f;
|
||||||
ESP_LOGI(tag, "Fan turning OFF - temp %.2f < (setpoint1 - hyst) %.2f", temperature, sp1 - hyst);
|
ESP_LOGD(tag, "Fan turning OFF - temp %.2f < (setpoint1 - hyst) %.2f", temperature, sp1 - hyst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
|
|||||||
float tempRatio = (temperature - sp1) / tempRange;
|
float tempRatio = (temperature - sp1) / tempRange;
|
||||||
newDuty = fp1 + (tempRatio * powerRange);
|
newDuty = fp1 + (tempRatio * powerRange);
|
||||||
|
|
||||||
ESP_LOGV(tag, "Linear scaling: temp=%.2f, ratio=%.3f, duty=%.2f", temperature, tempRatio, newDuty);
|
ESP_LOGD(tag, "Linear scaling: temp=%.2f, ratio=%.3f, duty=%.2f", temperature, tempRatio, newDuty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure duty is within bounds
|
// Ensure duty is within bounds
|
||||||
@ -115,7 +115,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
|
|||||||
// Apply new duty cycle if changed (with small tolerance to avoid constant updates)
|
// Apply new duty cycle if changed (with small tolerance to avoid constant updates)
|
||||||
if (fabs(currentDuty - newDuty) > 0.1f) {
|
if (fabs(currentDuty - newDuty) > 0.1f) {
|
||||||
pwmOut->setOutput(newDuty);
|
pwmOut->setOutput(newDuty);
|
||||||
ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f%% (on=%s)", temperature, newDuty, fanIsOn ? "true" : "false");
|
ESP_LOGD(tag, "Board T: %.2f F, Fan -> %.2f%% (on=%s)", temperature, newDuty, fanIsOn ? "true" : "false");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1261
src/my_wifi.cpp
1261
src/my_wifi.cpp
File diff suppressed because it is too large
Load Diff
@ -1,673 +0,0 @@
|
|||||||
#include "AppUpgrade.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include <MD5Builder.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include <memory>
|
|
||||||
#include "global.h"
|
|
||||||
#include "JsonConstrain.h"
|
|
||||||
#include "BLE_UpdateService.h"
|
|
||||||
#include <HTTPClient.h>
|
|
||||||
#include <Update.h>
|
|
||||||
|
|
||||||
static const char* TAG = "AppUpdater";
|
|
||||||
TaskHandle_t Update_Task_Handle = NULL;
|
|
||||||
TaskHandle_t versionCheckTask_Handle = NULL;
|
|
||||||
|
|
||||||
// Queue handle for firmware update messages
|
|
||||||
//QueueHandle_t updateMsgQueue = NULL;
|
|
||||||
|
|
||||||
String updateUrl = "";
|
|
||||||
|
|
||||||
Version otaVersion;
|
|
||||||
|
|
||||||
|
|
||||||
AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName, const char* appBin)
|
|
||||||
: localVersion(localVersion), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE])
|
|
||||||
{
|
|
||||||
baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
|
|
||||||
// Ensure baseUrl ends with a single '/'
|
|
||||||
if(!baseUrl.endsWith("/")) baseUrl += "/";
|
|
||||||
ESP_LOGI(TAG, "AppUpdater initialized (local v%s) baseUrl=%s", localVersion.toString().c_str(), baseUrl.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppUpdater::setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)) {
|
|
||||||
progressCb = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppUpdater::updateProgress(UpdateStatus newStatus, int percentage, const char* message) {
|
|
||||||
status = newStatus;
|
|
||||||
if (progressCb) {
|
|
||||||
progressCb(status, percentage, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::checkManifest() {
|
|
||||||
String url = buildUrl(manifestName);
|
|
||||||
ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str());
|
|
||||||
|
|
||||||
// Start the HTTP client and Send GET request for manifest
|
|
||||||
HTTPClient http;
|
|
||||||
http.begin(url);
|
|
||||||
int httpCode = http.GET();
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
|
||||||
ESP_LOGE(TAG, "HTTP GET failed, error: %d", httpCode);
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the response
|
|
||||||
String payload = http.getString();
|
|
||||||
http.end();
|
|
||||||
|
|
||||||
// Parse JSON
|
|
||||||
DeserializationError error = deserializeJson(jsonManifest, payload);
|
|
||||||
ESP_LOGD(TAG, "Manifest deserialized");
|
|
||||||
if (error) {
|
|
||||||
ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for files section
|
|
||||||
jsonFilesArray = jsonManifest["files"];
|
|
||||||
if (jsonFilesArray.isNull()) {
|
|
||||||
ESP_LOGE(TAG, "No files section in manifest");
|
|
||||||
return false;
|
|
||||||
}else{
|
|
||||||
ESP_LOGD(TAG, "%d Files found", jsonFilesArray.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for version section
|
|
||||||
JsonObject jsonVersion = jsonManifest["version"];
|
|
||||||
ESP_LOGD(TAG, "Version section found");
|
|
||||||
if (jsonVersion.isNull()) {
|
|
||||||
ESP_LOGE(TAG, "No version section in manifest");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the remote version
|
|
||||||
byte major = jsonVersion["major"] | 0;
|
|
||||||
byte minor = jsonVersion["minor"] | 0;
|
|
||||||
byte patch = jsonVersion["patch"] | 0;
|
|
||||||
otaVersion = {major, minor, patch};
|
|
||||||
|
|
||||||
//Version localVersion;
|
|
||||||
//::sscanf(localVersion, "%d.%d.%d", &localVersion.major, &localVersion.minor, &localVersion.patch);
|
|
||||||
|
|
||||||
// Check if an update is available
|
|
||||||
updateAvailable = false;
|
|
||||||
// Only mark update available if remote is strictly newer than local
|
|
||||||
if (otaVersion <= localVersion) {
|
|
||||||
ESP_LOGI(TAG, "No updates available");
|
|
||||||
return false;
|
|
||||||
}else{
|
|
||||||
updateAvailable = true;
|
|
||||||
ESP_LOGD(TAG, "Update available");
|
|
||||||
}
|
|
||||||
|
|
||||||
//ESP_LOGD(TAG, "Manifest content: %s", payload.c_str());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const char* expectedMd5) {
|
|
||||||
//updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
|
|
||||||
|
|
||||||
// Construct full URL
|
|
||||||
String url = buildUrl(remotePath);
|
|
||||||
ESP_LOGD(TAG, "Downloading: %s -> %s", url.c_str(), localPath);
|
|
||||||
|
|
||||||
String localMd5 = getLocalMD5(localPath);
|
|
||||||
|
|
||||||
if (localMd5.equals(expectedMd5)) {
|
|
||||||
ESP_LOGI(TAG, "File already up to date: %s", localPath);
|
|
||||||
updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the download
|
|
||||||
HTTPClient http;
|
|
||||||
http.begin(url);
|
|
||||||
int httpCode = http.GET();
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
|
||||||
ESP_LOGE(TAG, "Download failed: %d", httpCode);
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Download failed");
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the stream and content length
|
|
||||||
WiFiClient* stream = http.getStreamPtr();
|
|
||||||
size_t contentLength = http.getSize();
|
|
||||||
|
|
||||||
// Verify and save the file
|
|
||||||
bool success = verifyAndSaveFile(stream, contentLength, localPath, expectedMd5);
|
|
||||||
http.end();
|
|
||||||
if(!success){
|
|
||||||
updateProgress( UpdateStatus::ERROR, 0, "MD5 verification failed");
|
|
||||||
}else{
|
|
||||||
updateProgress( UpdateStatus::FILE_SAVED, 100, localPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* expectedMd5)
|
|
||||||
{
|
|
||||||
MD5Builder md5;
|
|
||||||
md5.begin();
|
|
||||||
size_t totalRead = 0;
|
|
||||||
|
|
||||||
// Create temporary filename
|
|
||||||
String tempPath = String(localPath) + ".tmp";
|
|
||||||
|
|
||||||
// Open temporary file for writing
|
|
||||||
File file = fileSystem.open(tempPath.c_str(), FILE_WRITE);
|
|
||||||
if (!file) {
|
|
||||||
ESP_LOGE(TAG, "Failed to open temporary file for writing");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
|
|
||||||
|
|
||||||
if (contentLength > 0) {
|
|
||||||
// Single pass with known content length
|
|
||||||
while (totalRead < contentLength) {
|
|
||||||
size_t available = stream->available();
|
|
||||||
if (available) {
|
|
||||||
size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE)));
|
|
||||||
|
|
||||||
// Write to temp file and update MD5
|
|
||||||
if (file.write(downloadBuffer.get(), readLen) != readLen) {
|
|
||||||
ESP_LOGE(TAG, "Failed to write to temporary file");
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
md5.add(downloadBuffer.get(), readLen);
|
|
||||||
totalRead += readLen;
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 90) / contentLength , localPath);
|
|
||||||
}
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown content length: read until stream ends
|
|
||||||
for (;;) {
|
|
||||||
size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
|
|
||||||
if (readLen == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (file.write(downloadBuffer.get(), readLen) != readLen) {
|
|
||||||
ESP_LOGE(TAG, "Failed to write to temporary file");
|
|
||||||
file.close();
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
md5.add(downloadBuffer.get(), readLen);
|
|
||||||
totalRead += readLen;
|
|
||||||
// Progress unknown; emit periodic heartbeats at 0%
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
md5.calculate();
|
|
||||||
String calculatedMd5 = md5.toString();
|
|
||||||
|
|
||||||
// Verify MD5 hash
|
|
||||||
if (!calculatedMd5.equals(expectedMd5)) {
|
|
||||||
//ESP_LOGE(TAG, "MD5 mismatch for %s", localPath);
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace original file with verified temp file
|
|
||||||
if (fileSystem.exists(localPath)) {
|
|
||||||
fileSystem.remove(localPath);
|
|
||||||
}
|
|
||||||
if (!fileSystem.rename(tempPath.c_str(), localPath)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to rename temporary file");
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String AppUpdater::getLocalMD5(const char* filePath){
|
|
||||||
File file = fileSystem.open(filePath, "r");
|
|
||||||
if(!file){
|
|
||||||
ESP_LOGE(TAG, "Error opening %s...", filePath);
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
MD5Builder md5Builder;
|
|
||||||
md5Builder.begin();
|
|
||||||
size_t fileSize = file.size();
|
|
||||||
size_t totalRead = 0;
|
|
||||||
size_t readLen = 0;
|
|
||||||
while (totalRead < fileSize) {
|
|
||||||
readLen = file.readBytes(reinterpret_cast<char*>(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE)));
|
|
||||||
md5Builder.add(downloadBuffer.get(), readLen);
|
|
||||||
totalRead += readLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
md5Builder.calculate();
|
|
||||||
file.close();
|
|
||||||
return md5Builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::updateFilesArray() {
|
|
||||||
int successCount = 0;
|
|
||||||
int totalFiles = jsonFilesArray.size();
|
|
||||||
ESP_LOGI(TAG, "Found %d files in manifest", totalFiles);
|
|
||||||
|
|
||||||
// Iterate over each file entry in the manifest
|
|
||||||
for (JsonObject file : jsonFilesArray) {
|
|
||||||
const char* remotePath = file["remote"];
|
|
||||||
const char* localPath = file["local"];
|
|
||||||
const char* expectedMd5 = file["md5"];
|
|
||||||
|
|
||||||
// Skip invalid entries
|
|
||||||
if (!remotePath || !localPath || !expectedMd5) {
|
|
||||||
ESP_LOGE(TAG, "Invalid file entry in manifest");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to update the file
|
|
||||||
if (updateFile(remotePath, localPath, expectedMd5)) {
|
|
||||||
successCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Manifest update complete: %d/%d files updated", successCount, totalFiles);
|
|
||||||
return successCount == totalFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::updateApp() {
|
|
||||||
updateProgress(UpdateStatus::MESSAGE, 0, "Starting firmware update");
|
|
||||||
|
|
||||||
// Check for firmware section in manifest
|
|
||||||
if (!jsonManifest["firmware"].is<JsonObject>() || !jsonManifest["firmware"]["md5"].is<const char*>()) {
|
|
||||||
ESP_LOGE(TAG, "Invalid firmware section in manifest");
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Invalid firmware section in manifest");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the firmware MD5 hash and URL
|
|
||||||
const char* expectedMd5 = jsonManifest["firmware"]["md5"];
|
|
||||||
String firmwareUrl = buildUrl(appName);
|
|
||||||
|
|
||||||
// Download the firmware
|
|
||||||
HTTPClient http;
|
|
||||||
http.begin(firmwareUrl);
|
|
||||||
int httpCode = http.GET();
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
|
||||||
ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check available space
|
|
||||||
size_t firmwareSize = http.getSize();
|
|
||||||
if (!Update.begin(firmwareSize > 0 ? firmwareSize : UPDATE_SIZE_UNKNOWN)) {
|
|
||||||
ESP_LOGE(TAG, "Firmware: Not enough space for update");
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Not enough space for update");
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up MD5 checking
|
|
||||||
MD5Builder md5;
|
|
||||||
md5.begin();
|
|
||||||
|
|
||||||
// Download and verify firmware
|
|
||||||
WiFiClient* stream = http.getStreamPtr();
|
|
||||||
if (firmwareSize > 0) {
|
|
||||||
size_t remaining = firmwareSize;
|
|
||||||
while (remaining > 0) {
|
|
||||||
size_t chunk = std::min(remaining, size_t(BUFFER_SIZE));
|
|
||||||
size_t read = stream->readBytes(downloadBuffer.get(), chunk);
|
|
||||||
|
|
||||||
// Check for timeout
|
|
||||||
if (read == 0) {
|
|
||||||
ESP_LOGE(TAG, "Read timeout");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update MD5 and write firmware
|
|
||||||
md5.add(downloadBuffer.get(), read);
|
|
||||||
if (Update.write(downloadBuffer.get(), read) != read) {
|
|
||||||
ESP_LOGE(TAG, "Write failed");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining -= read;
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown size: stream until end
|
|
||||||
for (;;) {
|
|
||||||
size_t read = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
|
|
||||||
if (read == 0) break;
|
|
||||||
md5.add(downloadBuffer.get(), read);
|
|
||||||
if (Update.write(downloadBuffer.get(), read) != read) {
|
|
||||||
ESP_LOGE(TAG, "Write failed");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, 0, "firmware");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify MD5
|
|
||||||
md5.calculate();
|
|
||||||
String calculatedMd5 = md5.toString();
|
|
||||||
if (!calculatedMd5.equals(expectedMd5)) {
|
|
||||||
ESP_LOGE(TAG, "MD5 mismatch. Expected: %s, Got: %s", expectedMd5, calculatedMd5.c_str());
|
|
||||||
updateProgress(UpdateStatus::MD5_FAILED, 0, "Firmware: MD5 mismatch");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish update
|
|
||||||
if (!Update.end()) {
|
|
||||||
ESP_LOGE(TAG, "Update end failed");
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Update failed");
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
http.end();
|
|
||||||
updateProgress(UpdateStatus::COMPLETE, 0, "Firmware: Complete");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::IsUpdateAvailable(){
|
|
||||||
return updateAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
String AppUpdater::buildUrl(const char* path) const {
|
|
||||||
if(!path || !*path) return baseUrl; // just base
|
|
||||||
String p(path);
|
|
||||||
// If already absolute URL, pass through
|
|
||||||
if(p.startsWith("http://") || p.startsWith("https://")) return p;
|
|
||||||
// Strip leading slashes to avoid double
|
|
||||||
while(p.startsWith("/")) p.remove(0,1);
|
|
||||||
// Ensure baseUrl has single trailing slash
|
|
||||||
String b = baseUrl;
|
|
||||||
if(!b.endsWith("/")) b += "/";
|
|
||||||
return b + p;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AsyncEventSource* eventProgress = nullptr;
|
|
||||||
void startFirmwareUpdateTask(AsyncEventSource* evProg) {
|
|
||||||
eventProgress = evProg;
|
|
||||||
if(Update_Task_Handle) {
|
|
||||||
ESP_LOGW(TAG, "Firmware update task already running");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xTaskCreate(firmwareUpdateTask, "FirmwareUpdate", 1024*8, NULL, 1, &Update_Task_Handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void firmwareUpdateTask(void* parameter) {
|
|
||||||
static const char* TAG = "UpdateTask";
|
|
||||||
AppUpdater* updater = nullptr;
|
|
||||||
|
|
||||||
try {
|
|
||||||
loadUpdateJson();
|
|
||||||
|
|
||||||
// Initialize updater
|
|
||||||
updater = new AppUpdater(LittleFS, localVersion, updateUrl.c_str(), "update.json", "firmware.bin");
|
|
||||||
updater->setProgressCallback(updateProgress);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
|
|
||||||
|
|
||||||
// Check and perform updates
|
|
||||||
if (!updater->checkManifest()) { throw std::runtime_error("Failed to check manifest"); }
|
|
||||||
|
|
||||||
if (updater->IsUpdateAvailable()) {
|
|
||||||
ESP_LOGI(TAG, "Update available, updating files...");
|
|
||||||
|
|
||||||
if (!updater->updateFilesArray()) {
|
|
||||||
throw std::runtime_error("Failed to update files");
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Updating firmware...");
|
|
||||||
if (!updater->updateApp()) {
|
|
||||||
throw std::runtime_error("Failed to update firmware");
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "Update successful, restarting...");
|
|
||||||
|
|
||||||
sendUpdateMessage("Restarting ", true, 100);
|
|
||||||
vTaskDelay(2000);
|
|
||||||
|
|
||||||
ESP.restart();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
ESP_LOGE(TAG, "Update failed: %s", e.what());
|
|
||||||
}
|
|
||||||
end:
|
|
||||||
delete updater;
|
|
||||||
Update_Task_Handle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void startVersionCheckTask() {
|
|
||||||
if(versionCheckTask_Handle != NULL) {
|
|
||||||
ESP_LOGW(TAG, "Version Check Tak already running");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xTaskCreate(versionCheckTask, "VersionCheckTask", 1024*8, NULL, 1, &versionCheckTask_Handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void versionCheckTask(void* parameter){
|
|
||||||
|
|
||||||
if(updateUrl == ""){
|
|
||||||
loadUpdateJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(checkManifest(otaVersion) == false){
|
|
||||||
ESP_LOGE(TAG, "Error checking manifest");
|
|
||||||
}
|
|
||||||
|
|
||||||
versionCheckTask_Handle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadUpdateJson(void) {
|
|
||||||
try {
|
|
||||||
ESP_LOGD(TAG, "loadUpdateJaon function...");
|
|
||||||
if(updateUrl == "") {
|
|
||||||
String updateJsonPath = "/system/update.json";
|
|
||||||
|
|
||||||
// Read and parse update.json
|
|
||||||
File file = LittleFS.open(updateJsonPath);
|
|
||||||
if (!file) {
|
|
||||||
throw std::runtime_error("Failed to open update.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
if (error) { throw std::runtime_error("Failed to parse update.json"); }
|
|
||||||
|
|
||||||
// Get update configuration
|
|
||||||
JsonObject jObj = doc.as<JsonObject>();
|
|
||||||
String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
|
|
||||||
String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
|
|
||||||
updateUrl = baseUrl + folderName;
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
ESP_LOGE(TAG, "Update failed: %s", e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {
|
|
||||||
|
|
||||||
char buffer[128];
|
|
||||||
const char* msg;
|
|
||||||
bool isComplete = false;
|
|
||||||
|
|
||||||
const char* safeMsg = message ? message : "";
|
|
||||||
switch (newStatus) {
|
|
||||||
case AppUpdater::UpdateStatus::IDLE:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Update idle");
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::MESSAGE:
|
|
||||||
msg = message ? message : "";
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::DOWNLOADING:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", safeMsg, percentage);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::VERIFYING:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", safeMsg, percentage);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::FILE_SKIPPED:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: Skipping file update, already up to date", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::FILE_SAVED:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: File Saved", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::MD5_FAILED:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::COMPLETE:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Firmware Update Complete!!!");
|
|
||||||
msg = buffer;
|
|
||||||
isComplete = true;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::ERROR:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Error!: %s", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Unknown update status: %d", (int)newStatus);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "%s", msg);
|
|
||||||
sendUpdateMessage(msg, isComplete, percentage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendUpdateMessage(const char* message, bool complete, int progress = -1) {
|
|
||||||
if(eventProgress && eventProgress->count() > 0) {
|
|
||||||
JsonDocument jsonDoc;
|
|
||||||
jsonDoc["message"] = message;
|
|
||||||
jsonDoc["complete"] = complete;
|
|
||||||
jsonDoc["progress"] = progress;
|
|
||||||
String strMessage;
|
|
||||||
serializeJson(jsonDoc, strMessage);
|
|
||||||
eventProgress->send(strMessage.c_str(), "update", millis());
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
ESP_LOGW(TAG, "No clients connected to event source");
|
|
||||||
}
|
|
||||||
|
|
||||||
bleUpgrade_send_message(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool checkManifest(Version& remoteVersion) {
|
|
||||||
const char* TAG = "manifestCheck";
|
|
||||||
String url = updateUrl + "update.json";
|
|
||||||
ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str());
|
|
||||||
|
|
||||||
// Start the HTTP client and send GET request for manifest
|
|
||||||
HTTPClient http;
|
|
||||||
http.begin(url);
|
|
||||||
int httpCode = http.GET();
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
|
||||||
ESP_LOGE(TAG, "HTTP GET failed, error: %d", httpCode);
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the response
|
|
||||||
String payload = http.getString();
|
|
||||||
http.end();
|
|
||||||
|
|
||||||
//ESP_LOGD(TAG, "%s", payload.c_str());
|
|
||||||
|
|
||||||
// Parse JSON
|
|
||||||
JsonDocument jsonManifest;
|
|
||||||
DeserializationError error = deserializeJson(jsonManifest, payload);
|
|
||||||
if (error) {
|
|
||||||
ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for version section
|
|
||||||
JsonObject jsonVersion = jsonManifest["version"];
|
|
||||||
|
|
||||||
if (jsonVersion.isNull()) {
|
|
||||||
ESP_LOGE(TAG, "No version section in manifest");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the remote version
|
|
||||||
byte major = jsonVersion["major"] | 0;
|
|
||||||
byte minor = jsonVersion["minor"] | 0;
|
|
||||||
byte patch = jsonVersion["patch"] | 0;
|
|
||||||
remoteVersion = {major, minor, patch};
|
|
||||||
ESP_LOGI(TAG, "Remote version: %s", remoteVersion.toString().c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
|
|
||||||
// Initialize WiFi connection first
|
|
||||||
// ... WiFi connection code ...
|
|
||||||
|
|
||||||
// Initialize filesystem
|
|
||||||
if(!LittleFS.begin()) {
|
|
||||||
Serial.println("LittleFS Mount Failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create updater instance with:
|
|
||||||
// - Current version: "1.0.0"
|
|
||||||
// - Update server URL: "https://my-update-server.com/"
|
|
||||||
// - Filesystem: LittleFS
|
|
||||||
AppUpdater updater("1.0.0", "https://storage.googleapis.com/boothifier/latest/", LittleFS);
|
|
||||||
|
|
||||||
// Set progress callback
|
|
||||||
updater.setProgressCallback([](int progress) {
|
|
||||||
Serial.printf("Update progress: %d%%\n", progress);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check and update firmware
|
|
||||||
if (updater.checkAndUpdate()) {
|
|
||||||
Serial.println("Update successful! Rebooting...");
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update specific files from manifest
|
|
||||||
int updatedFiles = updater.updateFilesFromManifest("test_update.json");
|
|
||||||
Serial.printf("Updated %d files\n", updatedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@ -1,716 +0,0 @@
|
|||||||
#include "AppUpgrade.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include <MD5Builder.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include <memory>
|
|
||||||
#include "global.h"
|
|
||||||
#include "JsonConstrain.h"
|
|
||||||
#include "BLE_UpdateService.h"
|
|
||||||
#include <HTTPClient.h>
|
|
||||||
#include <Update.h>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
static const char* TAG = "AppUpdater";
|
|
||||||
TaskHandle_t Update_Task_Handle = NULL;
|
|
||||||
TaskHandle_t versionCheckTask_Handle = NULL;
|
|
||||||
volatile bool g_UpdateCancelFlag = false; // cancellation flag
|
|
||||||
String updateUrl = "";
|
|
||||||
Version otaVersion;
|
|
||||||
|
|
||||||
|
|
||||||
AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName, const char* appBin)
|
|
||||||
: localVersion(localVersion), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE])
|
|
||||||
{
|
|
||||||
baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
|
|
||||||
// Ensure baseUrl ends with a single '/'
|
|
||||||
if(!baseUrl.endsWith("/")) baseUrl += "/";
|
|
||||||
ESP_LOGI(TAG, "AppUpdater initialized (local v%s) baseUrl=%s", localVersion.toString().c_str(), baseUrl.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppUpdater::setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)) {
|
|
||||||
progressCb = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppUpdater::updateProgress(UpdateStatus newStatus, int percentage, const char* message) {
|
|
||||||
status = newStatus;
|
|
||||||
if (progressCb) {
|
|
||||||
progressCb(status, percentage, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppUpdater::ManifestCheckResult AppUpdater::checkManifest() {
|
|
||||||
String url = buildUrl(manifestName);
|
|
||||||
ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str());
|
|
||||||
|
|
||||||
String payload;
|
|
||||||
for(int attempt=0; attempt<HTTP_RETRY_COUNT; ++attempt){
|
|
||||||
if(g_UpdateCancelFlag) return ManifestCheckResult::ERROR_FETCH_FAILED;
|
|
||||||
HTTPClient http;
|
|
||||||
http.begin(url);
|
|
||||||
int httpCode = http.GET();
|
|
||||||
if (httpCode == HTTP_CODE_OK) {
|
|
||||||
payload = http.getString();
|
|
||||||
http.end();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGW(TAG, "Manifest GET failed (attempt %d/%d): %d", attempt+1, HTTP_RETRY_COUNT, httpCode);
|
|
||||||
http.end();
|
|
||||||
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
|
|
||||||
}
|
|
||||||
if(payload.isEmpty()){
|
|
||||||
ESP_LOGE(TAG, "Failed to fetch manifest after retries");
|
|
||||||
return ManifestCheckResult::ERROR_FETCH_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(payload.length() > MAX_MANIFEST_SIZE){
|
|
||||||
ESP_LOGE(TAG, "Manifest too large (%u bytes)", (unsigned)payload.length());
|
|
||||||
return ManifestCheckResult::ERROR_TOO_LARGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse JSON
|
|
||||||
DeserializationError error = deserializeJson(jsonManifest, payload);
|
|
||||||
ESP_LOGD(TAG, "Manifest deserialized");
|
|
||||||
if (error) {
|
|
||||||
ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str());
|
|
||||||
return ManifestCheckResult::ERROR_PARSE_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for files section
|
|
||||||
jsonFilesArray = jsonManifest["files"];
|
|
||||||
if (jsonFilesArray.isNull()) {
|
|
||||||
ESP_LOGE(TAG, "No files section in manifest");
|
|
||||||
return ManifestCheckResult::ERROR_NO_FILES_SECTION;
|
|
||||||
}else{
|
|
||||||
ESP_LOGD(TAG, "%d Files found", jsonFilesArray.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for version section
|
|
||||||
JsonObject jsonVersion = jsonManifest["version"];
|
|
||||||
ESP_LOGD(TAG, "Version section found");
|
|
||||||
if (jsonVersion.isNull()) {
|
|
||||||
ESP_LOGE(TAG, "No version section in manifest");
|
|
||||||
return ManifestCheckResult::ERROR_NO_VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the remote version
|
|
||||||
byte major = jsonVersion["major"] | 0;
|
|
||||||
byte minor = jsonVersion["minor"] | 0;
|
|
||||||
byte patch = jsonVersion["patch"] | 0;
|
|
||||||
otaVersion = {major, minor, patch};
|
|
||||||
|
|
||||||
//Version localVersion;
|
|
||||||
//::sscanf(localVersion, "%d.%d.%d", &localVersion.major, &localVersion.minor, &localVersion.patch);
|
|
||||||
|
|
||||||
// Check if an update is available
|
|
||||||
updateAvailable = false;
|
|
||||||
// Only mark update available if remote is strictly newer than local
|
|
||||||
if (otaVersion <= localVersion) {
|
|
||||||
ESP_LOGI(TAG, "No updates available: remote=%s, local=%s",
|
|
||||||
otaVersion.toString().c_str(), localVersion.toString().c_str());
|
|
||||||
return ManifestCheckResult::VERSION_CURRENT;
|
|
||||||
}else{
|
|
||||||
updateAvailable = true;
|
|
||||||
ESP_LOGI(TAG, "Update available: remote=%s, local=%s",
|
|
||||||
otaVersion.toString().c_str(), localVersion.toString().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
//ESP_LOGD(TAG, "Manifest content: %s", payload.c_str());
|
|
||||||
|
|
||||||
return ManifestCheckResult::UPDATE_AVAILABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const char* expectedMd5) {
|
|
||||||
//updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
|
|
||||||
|
|
||||||
// Construct full URL
|
|
||||||
String url = buildUrl(remotePath);
|
|
||||||
ESP_LOGD(TAG, "Downloading: %s -> %s", url.c_str(), localPath);
|
|
||||||
|
|
||||||
// Quick skip: if exists and size & MD5 match
|
|
||||||
bool skip = false;
|
|
||||||
if(fileSystem.exists(localPath)){
|
|
||||||
String localMd5 = getLocalMD5(localPath);
|
|
||||||
if(localMd5.equals(expectedMd5)) skip = true;
|
|
||||||
}
|
|
||||||
if(skip){
|
|
||||||
ESP_LOGI(TAG, "File already up to date: %s", localPath);
|
|
||||||
updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the download
|
|
||||||
HTTPClient http;
|
|
||||||
int httpCode = -1;
|
|
||||||
for(int attempt=0; attempt<HTTP_RETRY_COUNT; ++attempt){
|
|
||||||
if(g_UpdateCancelFlag) return false;
|
|
||||||
http.begin(url);
|
|
||||||
httpCode = http.GET();
|
|
||||||
if(httpCode == HTTP_CODE_OK) break;
|
|
||||||
ESP_LOGW(TAG, "File GET failed (attempt %d/%d): %d", attempt+1, HTTP_RETRY_COUNT, httpCode);
|
|
||||||
http.end();
|
|
||||||
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
|
|
||||||
}
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
|
||||||
ESP_LOGE(TAG, "Download failed: %d", httpCode);
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Download failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the stream and content length
|
|
||||||
WiFiClient* stream = http.getStreamPtr();
|
|
||||||
size_t contentLength = http.getSize();
|
|
||||||
|
|
||||||
// Verify and save the file
|
|
||||||
bool success = verifyAndSaveFile(stream, contentLength, localPath, expectedMd5);
|
|
||||||
http.end();
|
|
||||||
if(!success){
|
|
||||||
String errMsg = String(localPath) + " MD5 failed";
|
|
||||||
updateProgress( UpdateStatus::ERROR, 0, errMsg.c_str() );
|
|
||||||
}else{
|
|
||||||
updateProgress( UpdateStatus::FILE_SAVED, 100, localPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* expectedMd5)
|
|
||||||
{
|
|
||||||
MD5Builder md5;
|
|
||||||
md5.begin();
|
|
||||||
size_t totalRead = 0;
|
|
||||||
|
|
||||||
// Create temporary filename
|
|
||||||
String tempPath = String(localPath) + ".tmp";
|
|
||||||
|
|
||||||
// Open temporary file for writing
|
|
||||||
File file = fileSystem.open(tempPath.c_str(), FILE_WRITE);
|
|
||||||
if (!file) {
|
|
||||||
ESP_LOGE(TAG, "Failed to open temporary file for writing");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
|
|
||||||
|
|
||||||
if (contentLength > 0) {
|
|
||||||
// Single pass with known content length
|
|
||||||
while (totalRead < contentLength) {
|
|
||||||
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
|
|
||||||
size_t available = stream->available();
|
|
||||||
if (available) {
|
|
||||||
size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE)));
|
|
||||||
|
|
||||||
// Write to temp file and update MD5
|
|
||||||
if (file.write(downloadBuffer.get(), readLen) != readLen) {
|
|
||||||
ESP_LOGE(TAG, "Failed to write to temporary file");
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
md5.add(downloadBuffer.get(), readLen);
|
|
||||||
totalRead += readLen;
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / contentLength , localPath);
|
|
||||||
}
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown content length: read until stream ends
|
|
||||||
for (;;) {
|
|
||||||
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
|
|
||||||
size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
|
|
||||||
if (readLen == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (file.write(downloadBuffer.get(), readLen) != readLen) {
|
|
||||||
ESP_LOGE(TAG, "Failed to write to temporary file");
|
|
||||||
file.close();
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
md5.add(downloadBuffer.get(), readLen);
|
|
||||||
totalRead += readLen;
|
|
||||||
// Progress unknown; emit periodic heartbeats at 0%
|
|
||||||
// For unknown size, send heartbeats every ~16KB
|
|
||||||
if((totalRead & 0x3FFF) == 0){
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
|
|
||||||
}
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
md5.calculate();
|
|
||||||
String calculatedMd5 = md5.toString();
|
|
||||||
|
|
||||||
// Verify MD5 hash
|
|
||||||
updateProgress(UpdateStatus::VERIFYING, 90, localPath);
|
|
||||||
if (!calculatedMd5.equals(expectedMd5)) {
|
|
||||||
//ESP_LOGE(TAG, "MD5 mismatch for %s", localPath);
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(UpdateStatus::VERIFYING, 95, localPath);
|
|
||||||
|
|
||||||
// Replace original file with verified temp file
|
|
||||||
if (fileSystem.exists(localPath)) {
|
|
||||||
fileSystem.remove(localPath);
|
|
||||||
}
|
|
||||||
if (!fileSystem.rename(tempPath.c_str(), localPath)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to rename temporary file");
|
|
||||||
fileSystem.remove(tempPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(UpdateStatus::VERIFYING, 100, localPath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String AppUpdater::getLocalMD5(const char* filePath){
|
|
||||||
File file = fileSystem.open(filePath, "r");
|
|
||||||
if(!file){
|
|
||||||
ESP_LOGE(TAG, "Error opening %s...", filePath);
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
MD5Builder md5Builder;
|
|
||||||
md5Builder.begin();
|
|
||||||
size_t fileSize = file.size();
|
|
||||||
size_t totalRead = 0;
|
|
||||||
size_t readLen = 0;
|
|
||||||
while (totalRead < fileSize) {
|
|
||||||
readLen = file.readBytes(reinterpret_cast<char*>(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE)));
|
|
||||||
md5Builder.add(downloadBuffer.get(), readLen);
|
|
||||||
totalRead += readLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
md5Builder.calculate();
|
|
||||||
file.close();
|
|
||||||
return md5Builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::updateFilesArray() {
|
|
||||||
int successCount = 0;
|
|
||||||
int totalFiles = jsonFilesArray.size();
|
|
||||||
ESP_LOGI(TAG, "Found %d files in manifest", totalFiles);
|
|
||||||
|
|
||||||
// Iterate over each file entry in the manifest
|
|
||||||
for (JsonObject file : jsonFilesArray) {
|
|
||||||
const char* remotePath = file["path"];
|
|
||||||
const char* localPath = remotePath;
|
|
||||||
// If path begins with "data/" or "/data/" strip only the "data" portion, retaining the leading slash
|
|
||||||
if (localPath) {
|
|
||||||
if (strncmp(localPath, "data/", 5) == 0) {
|
|
||||||
localPath += 4; // points to '/'
|
|
||||||
} else if (strncmp(localPath, "/data/", 6) == 0) {
|
|
||||||
localPath += 5; // points to '/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const char* expectedMd5 = file["md5"];
|
|
||||||
|
|
||||||
// Skip invalid entries
|
|
||||||
if (!remotePath || !localPath || !expectedMd5) {
|
|
||||||
ESP_LOGE(TAG, "Invalid file entry in manifest");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to update the file
|
|
||||||
if (updateFile(remotePath, localPath, expectedMd5)) {
|
|
||||||
successCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Manifest update complete: %d/%d files updated", successCount, totalFiles);
|
|
||||||
return successCount == totalFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::updateApp() {
|
|
||||||
updateProgress(UpdateStatus::MESSAGE, 0, "Starting firmware update");
|
|
||||||
|
|
||||||
// Check for firmware section in manifest
|
|
||||||
if (!jsonManifest["firmware"].is<JsonObject>() || !jsonManifest["firmware"]["md5"].is<const char*>()) {
|
|
||||||
ESP_LOGE(TAG, "Invalid firmware section in manifest");
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Invalid firmware section in manifest");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the firmware MD5 hash and URL
|
|
||||||
const char* expectedMd5 = jsonManifest["firmware"]["md5"];
|
|
||||||
String firmwareUrl = buildUrl(appName);
|
|
||||||
|
|
||||||
// Download the firmware
|
|
||||||
HTTPClient http;
|
|
||||||
int httpCode = -1;
|
|
||||||
for(int attempt=0; attempt<HTTP_RETRY_COUNT; ++attempt){
|
|
||||||
if(g_UpdateCancelFlag) return false;
|
|
||||||
http.begin(firmwareUrl);
|
|
||||||
httpCode = http.GET();
|
|
||||||
if(httpCode == HTTP_CODE_OK) break;
|
|
||||||
ESP_LOGW(TAG, "Firmware GET failed (attempt %d/%d): %d", attempt+1, HTTP_RETRY_COUNT, httpCode);
|
|
||||||
http.end();
|
|
||||||
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
|
|
||||||
}
|
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
|
||||||
ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check available space
|
|
||||||
size_t firmwareSize = http.getSize();
|
|
||||||
if (!Update.begin(firmwareSize > 0 ? firmwareSize : UPDATE_SIZE_UNKNOWN)) {
|
|
||||||
ESP_LOGE(TAG, "Firmware: Not enough space for update");
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Not enough space for update");
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up MD5 checking
|
|
||||||
MD5Builder md5;
|
|
||||||
md5.begin();
|
|
||||||
|
|
||||||
// Download and verify firmware
|
|
||||||
WiFiClient* stream = http.getStreamPtr();
|
|
||||||
if (firmwareSize > 0) {
|
|
||||||
size_t remaining = firmwareSize;
|
|
||||||
while (remaining > 0) {
|
|
||||||
if(g_UpdateCancelFlag){ Update.abort(); http.end(); return false; }
|
|
||||||
size_t chunk = std::min(remaining, size_t(BUFFER_SIZE));
|
|
||||||
size_t read = stream->readBytes(downloadBuffer.get(), chunk);
|
|
||||||
|
|
||||||
// Check for timeout
|
|
||||||
if (read == 0) {
|
|
||||||
ESP_LOGE(TAG, "Read timeout");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update MD5 and write firmware
|
|
||||||
md5.add(downloadBuffer.get(), read);
|
|
||||||
if (Update.write(downloadBuffer.get(), read) != read) {
|
|
||||||
ESP_LOGE(TAG, "Write failed");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining -= read;
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown size: stream until end
|
|
||||||
for (;;) {
|
|
||||||
if(g_UpdateCancelFlag){ Update.abort(); http.end(); return false; }
|
|
||||||
size_t read = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
|
|
||||||
if (read == 0) break;
|
|
||||||
md5.add(downloadBuffer.get(), read);
|
|
||||||
if (Update.write(downloadBuffer.get(), read) != read) {
|
|
||||||
ESP_LOGE(TAG, "Write failed");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
updateProgress(UpdateStatus::DOWNLOADING, 0, "firmware");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify MD5
|
|
||||||
md5.calculate();
|
|
||||||
String calculatedMd5 = md5.toString();
|
|
||||||
updateProgress(UpdateStatus::VERIFYING, 95, "firmware");
|
|
||||||
if (!calculatedMd5.equals(expectedMd5)) {
|
|
||||||
ESP_LOGE(TAG, "MD5 mismatch. Expected: %s, Got: %s", expectedMd5, calculatedMd5.c_str());
|
|
||||||
updateProgress(UpdateStatus::MD5_FAILED, 0, "Firmware: MD5 mismatch");
|
|
||||||
Update.abort();
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish update
|
|
||||||
if (!Update.end()) {
|
|
||||||
ESP_LOGE(TAG, "Update end failed");
|
|
||||||
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Update failed");
|
|
||||||
http.end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
http.end();
|
|
||||||
updateProgress(UpdateStatus::COMPLETE, 100, "Firmware: Complete");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppUpdater::IsUpdateAvailable(){
|
|
||||||
return updateAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
String AppUpdater::buildUrl(const char* path) const {
|
|
||||||
if(!path || !*path) return baseUrl; // just base
|
|
||||||
String p(path);
|
|
||||||
// If already absolute URL, pass through
|
|
||||||
if(p.startsWith("http://") || p.startsWith("https://")) return p;
|
|
||||||
// Strip leading slashes to avoid double
|
|
||||||
while(p.startsWith("/")) p.remove(0,1);
|
|
||||||
// Ensure baseUrl has single trailing slash
|
|
||||||
String b = baseUrl;
|
|
||||||
if(!b.endsWith("/")) b += "/";
|
|
||||||
return b + p;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AsyncEventSource* eventProgress = nullptr;
|
|
||||||
void startFirmwareUpdateTask(AsyncEventSource* evProg) {
|
|
||||||
eventProgress = evProg;
|
|
||||||
if(Update_Task_Handle) {
|
|
||||||
ESP_LOGW(TAG, "Firmware update task already running");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xTaskCreate(firmwareUpdateTask, "FirmwareUpdate", 1024*8, NULL, 1, &Update_Task_Handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void firmwareUpdateTask(void* parameter) {
|
|
||||||
static const char* TAG = "UpdateTask";
|
|
||||||
AppUpdater* updater = nullptr;
|
|
||||||
|
|
||||||
try {
|
|
||||||
loadUpdateJson();
|
|
||||||
|
|
||||||
// Initialize updater
|
|
||||||
updater = new AppUpdater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
|
|
||||||
updater->setProgressCallback(updateProgress);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
|
|
||||||
|
|
||||||
// Check and perform updates
|
|
||||||
auto manifestResult = updater->checkManifest();
|
|
||||||
|
|
||||||
if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) {
|
|
||||||
// Handle different error cases
|
|
||||||
std::string errorMsg;
|
|
||||||
switch (manifestResult) {
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED:
|
|
||||||
errorMsg = "Failed to fetch manifest";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE:
|
|
||||||
errorMsg = "Manifest file too large";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED:
|
|
||||||
errorMsg = "Failed to parse manifest";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION:
|
|
||||||
errorMsg = "Manifest missing files section";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION:
|
|
||||||
errorMsg = "Manifest missing version section";
|
|
||||||
break;
|
|
||||||
case AppUpdater::ManifestCheckResult::VERSION_CURRENT:
|
|
||||||
errorMsg = "Current version is up to date";
|
|
||||||
// This is not actually an error
|
|
||||||
ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str());
|
|
||||||
throw std::runtime_error(errorMsg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errorMsg = "Unknown manifest check error";
|
|
||||||
}
|
|
||||||
throw std::runtime_error(errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updater->IsUpdateAvailable()) {
|
|
||||||
ESP_LOGI(TAG, "Update available, updating files...");
|
|
||||||
|
|
||||||
if (!updater->updateFilesArray()) {
|
|
||||||
throw std::runtime_error("Failed to update files");
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Updating firmware...");
|
|
||||||
if (!updater->updateApp()) {
|
|
||||||
throw std::runtime_error("Failed to update firmware");
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "Update successful, restarting...");
|
|
||||||
|
|
||||||
sendUpdateMessage("Restarting ", true, 100);
|
|
||||||
vTaskDelay(2000);
|
|
||||||
|
|
||||||
ESP.restart();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
ESP_LOGE(TAG, "Update failed: %s", e.what());
|
|
||||||
}
|
|
||||||
delete updater;
|
|
||||||
Update_Task_Handle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void startVersionCheckTask() {
|
|
||||||
if(versionCheckTask_Handle != NULL) {
|
|
||||||
ESP_LOGW(TAG, "Version Check Tak already running");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xTaskCreate(versionCheckTask, "VersionCheckTask", 1024*8, NULL, 1, &versionCheckTask_Handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void versionCheckTask(void* parameter){
|
|
||||||
if(updateUrl == ""){
|
|
||||||
loadUpdateJson();
|
|
||||||
}
|
|
||||||
AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
|
|
||||||
|
|
||||||
auto manifestResult = updater.checkManifest();
|
|
||||||
|
|
||||||
if (manifestResult == AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE ||
|
|
||||||
manifestResult == AppUpdater::ManifestCheckResult::VERSION_CURRENT) {
|
|
||||||
otaVersion = updater.otaVersion; // capture remote
|
|
||||||
ESP_LOGI(TAG, "Version check: remote=%s", otaVersion.toString().c_str());
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "Version check: manifest check failed with code %d", static_cast<int>(manifestResult));
|
|
||||||
}
|
|
||||||
|
|
||||||
versionCheckTask_Handle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadUpdateJson(void) {
|
|
||||||
try {
|
|
||||||
ESP_LOGD(TAG, "loadUpdateJaon function...");
|
|
||||||
if(updateUrl == "") {
|
|
||||||
String updateJsonPath = "/system/update.json";
|
|
||||||
|
|
||||||
// Read and parse update.json
|
|
||||||
File file = LittleFS.open(updateJsonPath);
|
|
||||||
if (!file) {
|
|
||||||
throw std::runtime_error("Failed to open update.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
if (error) { throw std::runtime_error("Failed to parse update.json"); }
|
|
||||||
|
|
||||||
// Get update configuration
|
|
||||||
JsonObject jObj = doc.as<JsonObject>();
|
|
||||||
String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
|
|
||||||
String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
|
|
||||||
updateUrl = baseUrl + folderName;
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
ESP_LOGE(TAG, "Update failed: %s", e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {
|
|
||||||
|
|
||||||
char buffer[128];
|
|
||||||
const char* msg;
|
|
||||||
bool isComplete = false;
|
|
||||||
|
|
||||||
const char* safeMsg = message ? message : "";
|
|
||||||
switch (newStatus) {
|
|
||||||
case AppUpdater::UpdateStatus::IDLE:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Update idle");
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::MESSAGE:
|
|
||||||
msg = message ? message : "";
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::DOWNLOADING:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", safeMsg, percentage);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::VERIFYING:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", safeMsg, percentage);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::FILE_SKIPPED:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: File Skipped, up to date", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::FILE_SAVED:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: File Saved", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::MD5_FAILED:
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::COMPLETE:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Firmware Update Complete!!!");
|
|
||||||
msg = buffer;
|
|
||||||
isComplete = true;
|
|
||||||
break;
|
|
||||||
case AppUpdater::UpdateStatus::ERROR:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Error!: %s", safeMsg);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
snprintf(buffer, sizeof(buffer), "Unknown update status: %d", (int)newStatus);
|
|
||||||
msg = buffer;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "%s", msg);
|
|
||||||
sendUpdateMessage(msg, isComplete, percentage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendUpdateMessage(const char* message, bool complete, int progress = -1) {
|
|
||||||
|
|
||||||
if(eventProgress && eventProgress->count() > 0) {
|
|
||||||
// This is for the web client and not the BLE client
|
|
||||||
JsonDocument jsonDoc;
|
|
||||||
jsonDoc["message"] = message;
|
|
||||||
jsonDoc["complete"] = complete;
|
|
||||||
jsonDoc["progress"] = progress;
|
|
||||||
String strMessage;
|
|
||||||
serializeJson(jsonDoc, strMessage);
|
|
||||||
eventProgress->send(strMessage.c_str(), "update", millis());
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
ESP_LOGW(TAG, "No clients connected to event source");
|
|
||||||
}
|
|
||||||
|
|
||||||
bleUpgrade_send_message(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// (Removed duplicate global checkManifest; AppUpdater::checkManifest used instead)
|
|
||||||
|
|
||||||
/*
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
|
|
||||||
// Initialize WiFi connection first
|
|
||||||
// ... WiFi connection code ...
|
|
||||||
|
|
||||||
// Initialize filesystem
|
|
||||||
if(!LittleFS.begin()) {
|
|
||||||
Serial.println("LittleFS Mount Failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create updater instance with:
|
|
||||||
// - Current version: "1.0.0"
|
|
||||||
// - Update server URL: "https://my-update-server.com/"
|
|
||||||
// - Filesystem: LittleFS
|
|
||||||
AppUpdater updater("1.0.0", "https://storage.googleapis.com/boothifier/latest/", LittleFS);
|
|
||||||
|
|
||||||
// Set progress callback
|
|
||||||
updater.setProgressCallback([](int progress) {
|
|
||||||
Serial.printf("Update progress: %d%%\n", progress);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check and update firmware
|
|
||||||
if (updater.checkAndUpdate()) {
|
|
||||||
Serial.println("Update successful! Rebooting...");
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update specific files from manifest
|
|
||||||
int updatedFiles = updater.updateFilesFromManifest("test_update.json");
|
|
||||||
Serial.printf("Updated %d files\n", updatedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@ -1,178 +0,0 @@
|
|||||||
#include "ATALights.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "Animations.h"
|
|
||||||
#include <functional>
|
|
||||||
#include <NeoPixelBus.h>
|
|
||||||
|
|
||||||
|
|
||||||
static const char* tag = "strips";
|
|
||||||
|
|
||||||
// Define constants for maximum LEDs
|
|
||||||
#define MAX_LEDS 300 // Adjust based on your LED requirements
|
|
||||||
|
|
||||||
// Create pointers for NeoPixelBus objects
|
|
||||||
void* strip1 = nullptr;
|
|
||||||
void* strip2 = nullptr;
|
|
||||||
|
|
||||||
TaskHandle_t Animation_Task_Handle;
|
|
||||||
|
|
||||||
// Runtime configuration variables
|
|
||||||
int numLeds1 = 0, numLeds2 = 0; // Number of LEDs for each strip
|
|
||||||
int dataPin1 = -1, dataPin2 = -1; // Data pins for each strip
|
|
||||||
String chipType1, chipType2; // Chip types (e.g., WS2812, SK6812, TM1814)
|
|
||||||
String colorOrder1, colorOrder2; // Color orders (e.g., GRB, RGB, BGR)
|
|
||||||
|
|
||||||
|
|
||||||
void Init_Lights_Task(void){
|
|
||||||
|
|
||||||
xTaskCreatePinnedToCore(Lights_Control_Task, "LumaMaster_Task", 1024*6, NULL, 1, &Animation_Task_Handle, CONFIG_ARDUINO_RUNNING_CORE);
|
|
||||||
ESP_LOGI(tag, "Lights Task Created...");
|
|
||||||
|
|
||||||
|
|
||||||
// Example runtime configuration for two strips
|
|
||||||
dataPin1 = 5; // Pin for Strip 1
|
|
||||||
numLeds1 = 150; // Number of LEDs on Strip 1
|
|
||||||
chipType1 = "WS2812"; // Chip type for Strip 1
|
|
||||||
colorOrder1 = "GRB"; // Color order for Strip 1
|
|
||||||
|
|
||||||
dataPin2 = 18; // Pin for Strip 2
|
|
||||||
numLeds2 = 100; // Number of LEDs on Strip 2
|
|
||||||
chipType2 = "WS2812"; // Chip type for Strip 2
|
|
||||||
colorOrder2 = "RGB"; // Color order for Strip 2
|
|
||||||
|
|
||||||
// Dynamically initialize the strips
|
|
||||||
strip1 = initializeStrip(dataPin1, numLeds1, chipType1, colorOrder1);
|
|
||||||
strip2 = initializeStrip(dataPin2, numLeds2, chipType2, colorOrder2);
|
|
||||||
|
|
||||||
|
|
||||||
// Start the strips if initialized
|
|
||||||
if (strip1) static_cast<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip1)->Begin();
|
|
||||||
if (strip2) static_cast<NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>*>(strip2)->Begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Animation_Loop_Exit(void){
|
|
||||||
if( Animation_Task_Handle ){
|
|
||||||
xTaskNotifyGive( Animation_Task_Handle );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void LightsON(void){
|
|
||||||
FastLED.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void LightsOff(void){
|
|
||||||
FastLED.clear();
|
|
||||||
FastLED.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
inline void setPixel1(int pixelIndex, const CRGB col) {
|
|
||||||
register uint16_t x = pixelIndex + ledSettings[0].shift;
|
|
||||||
// If strip.effSize is power of 2, use faster bit masking
|
|
||||||
if ((ledSettings[0].effSize & (ledSettings[0].effSize - 1)) == 0) {
|
|
||||||
x = (x < 0) ? ((x + ledSettings[0].effSize) & (ledSettings[0].effSize - 1)) : (x & (ledSettings[0].effSize - 1));
|
|
||||||
leds1[(x + ledSettings[0].offset) & (ledSettings[0].effSize - 1)] = col;
|
|
||||||
} else {
|
|
||||||
// For non-power-of-2 sizes, still need modulo
|
|
||||||
x = (x < 0) ? ((x + ledSettings[0].effSize) % ledSettings[0].effSize) : (x % ledSettings[0].effSize);
|
|
||||||
leds1[(x + ledSettings[0].offset) % ledSettings[0].effSize] = col;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
inline void setPixel2(int pixelIndex, const CRGB col) {
|
|
||||||
register uint16_t x = pixelIndex + ledSettings[1].shift;
|
|
||||||
// If strip.effSize is power of 2, use faster bit masking
|
|
||||||
if ((ledSettings[1].effSize & (ledSettings[1].effSize - 1)) == 0) {
|
|
||||||
x = (x < 0) ? ((x + ledSettings[1].effSize) & (ledSettings[1].effSize - 1)) : (x & (ledSettings[1].effSize - 1));
|
|
||||||
leds2[(x + ledSettings[1].offset) & (ledSettings[1].effSize - 1)] = col;
|
|
||||||
} else {
|
|
||||||
// For non-power-of-2 sizes, still need modulo
|
|
||||||
x = (x < 0) ? ((x + ledSettings[1].effSize) % ledSettings[1].effSize) : (x % ledSettings[1].effSize);
|
|
||||||
leds2[(x + ledSettings[1].offset) % ledSettings[1].effSize] = col;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Lights_Control_Task_Resume(void){
|
|
||||||
vTaskResume(Animation_Task_Handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lights_Control_Task(void *parameters){
|
|
||||||
|
|
||||||
ESP_LOGD(tag, "Lights Control Task Entered...");
|
|
||||||
vTaskSuspend(NULL);
|
|
||||||
ESP_LOGD(tag, "Lights Control Task Resumed...");
|
|
||||||
vTaskDelay(2000);
|
|
||||||
fill_solid(leds1, ledSettings[0].size-1, CRGB::Blue);
|
|
||||||
FastLED.show();
|
|
||||||
vTaskDelay(5000);
|
|
||||||
|
|
||||||
while(true){
|
|
||||||
Animation_Loop(2000, [&]() {
|
|
||||||
ESP_LOGD(tag, "Looping....");
|
|
||||||
|
|
||||||
// Example animation: Alternate colors between strips
|
|
||||||
setStripColor<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>>(strip1, numLeds1, RgbColor(255, 0, 0)); // Red for Strip 1
|
|
||||||
setStripColor<NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>>(strip2, numLeds2, RgbColor(0, 255, 0)); // Green for Strip 2
|
|
||||||
delay(1000);
|
|
||||||
|
|
||||||
setStripColor<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>>(strip1, numLeds1, RgbColor(0, 0, 255)); // Blue for Strip 1
|
|
||||||
setStripColor<NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>>(strip2, numLeds2, RgbColor(255, 255, 0)); // Yellow for Strip 2
|
|
||||||
delay(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
uint8_t animMode = 1;
|
|
||||||
switch(animMode){
|
|
||||||
case 0:
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Init_FastLED_Strip(CRGB* leds, uint8_t pin, int size, EOrder rgbOrder, const String& chipType) {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to initialize a strip dynamically (Non-SPI chipsets only)
|
|
||||||
void* initializeStrip(int dataPin, int numLeds, const String& chipType, const String& colorOrder) {
|
|
||||||
if (chipType == "WS2812" || chipType == "SK6812") {
|
|
||||||
if (colorOrder == "GRB") {
|
|
||||||
return new NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>(numLeds, dataPin);
|
|
||||||
} else if (colorOrder == "RGB") {
|
|
||||||
return new NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>(numLeds, dataPin);
|
|
||||||
} else if (colorOrder == "BGR") {
|
|
||||||
return new NeoPixelBus<NeoBgrFeature, Neo800KbpsMethod>(numLeds, dataPin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Serial.println("Unsupported chipset or color order!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to set all LEDs of a strip to a specific color
|
|
||||||
template <typename T>
|
|
||||||
void setStripColor(void* strip, int numLeds, RgbColor color) {
|
|
||||||
if (strip) {
|
|
||||||
T* actualStrip = static_cast<T*>(strip);
|
|
||||||
for (int i = 0; i < numLeds; i++) {
|
|
||||||
actualStrip->SetPixelColor(i, color);
|
|
||||||
}
|
|
||||||
actualStrip->Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <NimBLEDevice.h>
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
#include "BLE-FlashStick-Service.h"
|
|
||||||
#include "WiFi.h"
|
|
||||||
#include "my_wifi.h"
|
|
||||||
#include "global.h"
|
|
||||||
#include "AppUpgrade.h"
|
|
||||||
#include "AppVersion.h"
|
|
||||||
|
|
||||||
static const char *tag = "BLE_FlashStickService";
|
|
||||||
|
|
||||||
#define UPGRADE_SERVICE_UUID "abcdef03-2345-6789-1234-56789abcdef0"
|
|
||||||
#define UPGRADE_CHARACTERISTIC1_UUID "abcdef03-2345-6789-1234-56789abcdef1"
|
|
||||||
|
|
||||||
NimBLEService *pUpgradeService = nullptr;
|
|
||||||
NimBLECharacteristic *pUpgradeCharacteristic1 = nullptr;
|
|
||||||
|
|
||||||
enum WIFI_STAT : byte { WIFI_DISCONNECTED=0, WIFI_BAD_CREDS=1, WIFI_NO_AP=2, WIFI_CONNECTED=3 };
|
|
||||||
|
|
||||||
struct FLASHSTICK_PACKET {
|
|
||||||
bool reistered = false;
|
|
||||||
char msg[16] = "Hello...";
|
|
||||||
}flashstickPacket;
|
|
||||||
|
|
||||||
|
|
||||||
// Class for handling server events
|
|
||||||
class ServerCallbacks : public NimBLEServerCallbacks {
|
|
||||||
void onConnect(NimBLEServer* pServer) override {
|
|
||||||
ESP_LOGI(tag, "Flash-Stick connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDisconnect(NimBLEServer* pServer) override {
|
|
||||||
ESP_LOGI(tag, "Flash-Stick disconnected");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Class for handling characteristic events
|
|
||||||
class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
|
|
||||||
|
|
||||||
void onWrite(NimBLECharacteristic *pCharacteristic) override {
|
|
||||||
std::string value = pCharacteristic->getValue();
|
|
||||||
ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str());
|
|
||||||
|
|
||||||
if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials
|
|
||||||
JsonDocument doc;
|
|
||||||
deserializeJson(doc, value.substr(13));
|
|
||||||
JsonObject wifiJson = doc.as<JsonObject>();
|
|
||||||
String ssid = wifiJson["ssid"].as<String>();
|
|
||||||
String pass = wifiJson["pass"].as<String>();
|
|
||||||
ESP_LOGI(tag, "Wifi Credentials: %s, %s", ssid.c_str(), pass.c_str());
|
|
||||||
|
|
||||||
bool status = StartWifiConnectTask(ssid, pass);
|
|
||||||
if(status == true){
|
|
||||||
updatePacket.wifiStatus = WIFI_DISCONNECTED;
|
|
||||||
updatePacket.wifiOnline = false;
|
|
||||||
updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
|
|
||||||
}else{
|
|
||||||
ESP_LOGI(tag, "Failed to start WiFi connection task");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value.compare("version-check") == 0) { // Check if new version is available
|
|
||||||
ESP_LOGI(tag, "Version check command received: newVersion=%d.%d.%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch());
|
|
||||||
if(updatePacket.newVersion[0] == 0){
|
|
||||||
startVersionCheckTask(); // start the task and done
|
|
||||||
}else{
|
|
||||||
ESP_LOGI(tag, "Version already checked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value.compare("upgrade-start") == 0) { // Start OTA update
|
|
||||||
ESP_LOGI(tag, "Start OTA update command received");
|
|
||||||
startFirmwareUpdateTask(nullptr); // start the task
|
|
||||||
}
|
|
||||||
else if (value.compare("rename-device") == 0) { // Start renaming device
|
|
||||||
ESP_LOGI(tag, "Start renane device command received");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ESP_LOGW(tag, "Unknown command received: %s", value.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onRead(NimBLECharacteristic *pCharacteristic) override {
|
|
||||||
updatePacket.wifiOnline = InternetAvailable;
|
|
||||||
if(WiFi.status() == WL_CONNECTED){
|
|
||||||
updatePacket.wifiStatus = WIFI_CONNECTED;
|
|
||||||
if(updatePacket.wifiIP[0] == 0){
|
|
||||||
updatePacket.wifiIP[0] = WiFi.localIP()[0];
|
|
||||||
updatePacket.wifiIP[1] = WiFi.localIP()[1];
|
|
||||||
updatePacket.wifiIP[2] = WiFi.localIP()[2];
|
|
||||||
updatePacket.wifiIP[3] = WiFi.localIP()[3];
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
updatePacket.wifiStatus = WIFI_DISCONNECTED;
|
|
||||||
if(updatePacket.wifiIP[0] > 0){
|
|
||||||
updatePacket.wifiIP[0] = 0;
|
|
||||||
updatePacket.wifiIP[1] = 0;
|
|
||||||
updatePacket.wifiIP[2] = 0;
|
|
||||||
updatePacket.wifiIP[3] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//update version
|
|
||||||
if(otaVersion.major() != 0){
|
|
||||||
ESP_LOGI(tag, "Updated new version: major=%d, minor=%d, patch=%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch());
|
|
||||||
updatePacket.newVersion[0] = otaVersion.major();
|
|
||||||
updatePacket.newVersion[1] = otaVersion.minor();
|
|
||||||
updatePacket.newVersion[2] = otaVersion.patch();
|
|
||||||
}
|
|
||||||
|
|
||||||
pCharacteristic->setValue(reinterpret_cast<uint8_t*>(&updatePacket), sizeof(updatePacket));
|
|
||||||
ESP_LOGI(tag, "Upgrade Char read");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void bleUpgrade_send_message(String s){
|
|
||||||
if(pUpgradeCharacteristic2){
|
|
||||||
if (s != nullptr) {
|
|
||||||
pUpgradeCharacteristic2->setValue(s);
|
|
||||||
pUpgradeCharacteristic2->notify();
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(tag, "Null string passed to bleUpgrade_send_message");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Init_UpgradeBLEService(NimBLEServer *pServer){
|
|
||||||
|
|
||||||
// Create Upgrade BLE Service
|
|
||||||
pUpgradeService= pServer->createService( UPGRADE_SERVICE_UUID );
|
|
||||||
|
|
||||||
pUpgradeCharacteristic1 = pUpgradeService->createCharacteristic(
|
|
||||||
UPGRADE_CHARACTERISTIC1_UUID,
|
|
||||||
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register the callback with the characteristic
|
|
||||||
pUpgradeCharacteristic1->setCallbacks(new UpgradeChar_Callbacks());
|
|
||||||
ESP_LOGI(tag, "Upgrade callback registered!");
|
|
||||||
|
|
||||||
|
|
||||||
pUpgradeCharacteristic2 = pUpgradeService->createCharacteristic(
|
|
||||||
UPGRADE_CHARACTERISTIC2_UUID,
|
|
||||||
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register the callback with the characteristic
|
|
||||||
pUpgradeCharacteristic2->setCallbacks(new UpgradeChar_Callbacks());
|
|
||||||
ESP_LOGI(tag, "Upgrade callback registered!");
|
|
||||||
|
|
||||||
pUpgradeService->start();
|
|
||||||
|
|
||||||
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
|
|
||||||
pAdvertising->addServiceUUID( UPGRADE_SERVICE_UUID ); // Advertise service UUID
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,240 +0,0 @@
|
|||||||
#include "BTSerial.h"
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <FS.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include <NimBLEDevice.h>
|
|
||||||
#include <string>
|
|
||||||
#include "command_processor.h"
|
|
||||||
#include "led_strip.h"
|
|
||||||
#include "global.h"
|
|
||||||
#include "JsonConstrain.h"
|
|
||||||
#include "my_buzzer.h"
|
|
||||||
#include "common/led_animation.h"
|
|
||||||
|
|
||||||
|
|
||||||
static const char* tag = "ble";
|
|
||||||
TaskHandle_t BTSerial_Task_Handle;
|
|
||||||
bool BTDeviceConnected = false;
|
|
||||||
|
|
||||||
BLEServer *pServer = NULL;
|
|
||||||
BLECharacteristic * pTxCharacteristic;
|
|
||||||
BLECharacteristic * pRxCharacteristic;
|
|
||||||
bool oldDeviceConnected = false;
|
|
||||||
uint8_t txValue = 0;
|
|
||||||
|
|
||||||
// See the following for generating UUIDs:
|
|
||||||
// https://www.uuidgenerator.net/
|
|
||||||
#define SERVICE_UUID_DEF "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
|
|
||||||
#define CHARACTERISTIC_UUID_RX_DEF "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
|
|
||||||
#define CHARACTERISTIC_UUID_TX_DEF "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
|
|
||||||
|
|
||||||
String BLEDeviceName;
|
|
||||||
String BLEKey;
|
|
||||||
String SERVICE_UUID;
|
|
||||||
String CHARACTERISTIC_UUID_RX;
|
|
||||||
String CHARACTERISTIC_UUID_TX;
|
|
||||||
|
|
||||||
#define replyActive true
|
|
||||||
|
|
||||||
|
|
||||||
void Init_BTSerial(void)
|
|
||||||
{
|
|
||||||
File file = LittleFS.open("/cfg/ble.json");
|
|
||||||
if(!file){
|
|
||||||
ESP_LOGE(tag, "Error opening ble.json...");
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
file.close();
|
|
||||||
if(error){ ESP_LOGE(tag, "ble.json deserialize error!.."); return;}
|
|
||||||
|
|
||||||
JsonObject bleJson = doc.as<JsonObject>();
|
|
||||||
|
|
||||||
// if(jsonConstrainBool(bleJson, "en", false)){
|
|
||||||
SERVICE_UUID = jsonConstrainString(tag, bleJson, "service-uuid", SERVICE_UUID_DEF);
|
|
||||||
ESP_LOGD(tag, "SERVICE_UUID: %s", SERVICE_UUID.c_str());
|
|
||||||
|
|
||||||
CHARACTERISTIC_UUID_RX = jsonConstrainString(tag, bleJson, "char-uuid-rx", CHARACTERISTIC_UUID_RX_DEF);
|
|
||||||
ESP_LOGD(tag, "Char UUID RX: %s", CHARACTERISTIC_UUID_RX.c_str());
|
|
||||||
|
|
||||||
CHARACTERISTIC_UUID_TX = jsonConstrainString(tag, bleJson, "char-uuid-tx", CHARACTERISTIC_UUID_TX_DEF);
|
|
||||||
ESP_LOGD(tag, "Char UUID TX: %s", CHARACTERISTIC_UUID_TX.c_str());
|
|
||||||
|
|
||||||
BLEDeviceName = jsonConstrainString(tag, bleJson, "device-name", "ATA_COMM");
|
|
||||||
String hexStr = String(chipInfo.macByte[0], HEX);
|
|
||||||
hexStr.toUpperCase();
|
|
||||||
BLEDeviceName += '_';
|
|
||||||
BLEDeviceName += hexStr;
|
|
||||||
|
|
||||||
BLEKey = jsonConstrainString(tag, bleJson, "key", "123456");
|
|
||||||
int core = jsonConstrain<int>(tag, bleJson, "core", 0, 1, 0);
|
|
||||||
ESP_LOGD(tag, "BLE SSID: %s, key: %s, core: %d", BLEDeviceName.c_str(),BLEKey.c_str(), core);
|
|
||||||
|
|
||||||
xTaskCreatePinnedToCore(BTSerial_Task, "BTSerial_Task", 12000, NULL, 1, &BTSerial_Task_Handle, core);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** None of these are required as they will be handled by the library with defaults. **
|
|
||||||
** Remove as you see fit for your needs */
|
|
||||||
class MyServerCallbacks: public BLEServerCallbacks {
|
|
||||||
void onConnect(BLEServer* pServer) {
|
|
||||||
BTDeviceConnected = true;
|
|
||||||
//BLEDevice::startAdvertising();//adding this line allows for multiple simultaneous BLE connections
|
|
||||||
};
|
|
||||||
/*
|
|
||||||
void onConnect(BLEServer* pServer, BLEClient* pClient) {
|
|
||||||
BTDeviceConnected = true;
|
|
||||||
BLEAddress connectedAddress = pClient->getPeerAddress();
|
|
||||||
Log.traceln("Client connected: %s", connectedAddress.toString().c_str());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
void onDisconnect(BLEServer* pServer) {
|
|
||||||
BTDeviceConnected = false;
|
|
||||||
}
|
|
||||||
/***************** New - Security handled here ********************
|
|
||||||
****** Note: these are the same return values as defaults ********/
|
|
||||||
uint32_t onPassKeyRequest(){
|
|
||||||
ESP_LOGD(tag, "Server PassKeyRequest");
|
|
||||||
return 123456;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool onConfirmPIN(uint32_t pass_key){
|
|
||||||
ESP_LOGD(tag, "The passkey YES/NO number: %d", pass_key);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAuthenticationComplete(ble_gap_conn_desc desc){
|
|
||||||
ESP_LOGD(tag, "Starting BLE work!");
|
|
||||||
}
|
|
||||||
/*******************************************************************/
|
|
||||||
};
|
|
||||||
|
|
||||||
#define MAX_PACKET_PARAMS 8
|
|
||||||
int packet_data[MAX_PACKET_PARAMS];
|
|
||||||
int paramCount = 0;
|
|
||||||
class MyCallbacks: public BLECharacteristicCallbacks
|
|
||||||
{
|
|
||||||
void onWrite(BLECharacteristic *pCharacteristic)
|
|
||||||
{
|
|
||||||
std::string rxValue = pCharacteristic->getValue();
|
|
||||||
|
|
||||||
ESP_LOGD(tag, "raw: %s", rxValue.c_str());
|
|
||||||
if(!rxValue.empty() && ((rxValue[0] == '$') || rxValue[0] == '%')){
|
|
||||||
extractCommand(&rxValue[0], packet_data, ¶mCount); // delimited command
|
|
||||||
|
|
||||||
// Call Animation Index Update
|
|
||||||
if(packet_data[0] == 100){
|
|
||||||
int cntDown = 0;
|
|
||||||
if(packet_data[2]){
|
|
||||||
cntDown = packet_data[2];
|
|
||||||
}
|
|
||||||
animProps.event[packet_data[1]].countDown = cntDown;
|
|
||||||
animProps.event[packet_data[1]].type = EV_NORMAL;
|
|
||||||
PostNewEvent(animProps.event[packet_data[1]]);
|
|
||||||
}else{
|
|
||||||
// TODO: Process other Bluetooth commands
|
|
||||||
}
|
|
||||||
|
|
||||||
if(replyActive){
|
|
||||||
rxValue[0] = '%';
|
|
||||||
pTxCharacteristic->setValue(rxValue);
|
|
||||||
pTxCharacteristic->notify();
|
|
||||||
}
|
|
||||||
// print params
|
|
||||||
ESP_LOGD(tag, "packet: %c%d, %d", rxValue[0], packet_data[0], packet_data[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void extractCommand(char* packet, int* data, int* count)
|
|
||||||
{
|
|
||||||
int base = 10;
|
|
||||||
//if(*packet == '#'){
|
|
||||||
// base = 16;
|
|
||||||
//}
|
|
||||||
packet++;
|
|
||||||
|
|
||||||
char* token = strtok(packet, ",\n");
|
|
||||||
int8_t index = 0;
|
|
||||||
|
|
||||||
while (token != NULL && index < MAX_PACKET_PARAMS) {
|
|
||||||
data[index] = strtol(token, NULL, base);
|
|
||||||
index++;
|
|
||||||
token = strtok(NULL, ",\n");
|
|
||||||
}
|
|
||||||
*count = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BTSerial_Task(void *parameters){
|
|
||||||
vTaskDelay(1000);
|
|
||||||
// Extend watchdog timer
|
|
||||||
esp_task_wdt_init(5, false);
|
|
||||||
|
|
||||||
// Create the BLE Device.
|
|
||||||
BLEDevice::init(BLEDeviceName.c_str());
|
|
||||||
|
|
||||||
// Create the BLE Server
|
|
||||||
pServer = BLEDevice::createServer();
|
|
||||||
pServer->setCallbacks(new MyServerCallbacks());
|
|
||||||
|
|
||||||
// Create the BLE Service
|
|
||||||
BLEService *pService = pServer->createService((const char*)SERVICE_UUID.c_str());
|
|
||||||
|
|
||||||
// Create a BLE Characteristic
|
|
||||||
pTxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_TX.c_str(), NIMBLE_PROPERTY::NOTIFY );
|
|
||||||
/***************************************************
|
|
||||||
NOTE: DO NOT create a 2902 descriptor, it will be created auto.. if notifications
|
|
||||||
or indications are enabled on a characteristic.
|
|
||||||
|
|
||||||
pCharacteristic->addDescriptor(new BLE2902());
|
|
||||||
****************************************************/
|
|
||||||
pRxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_RX.c_str(), NIMBLE_PROPERTY::WRITE );
|
|
||||||
pRxCharacteristic->setCallbacks(new MyCallbacks());
|
|
||||||
// Start the service
|
|
||||||
pService->start();
|
|
||||||
|
|
||||||
// Start advertising
|
|
||||||
vTaskDelay(100);
|
|
||||||
pServer->getAdvertising()->start();
|
|
||||||
ESP_LOGV(tag, "Waiting for a client...");
|
|
||||||
ANIMATION_EVENT newEvent;
|
|
||||||
for(;;){
|
|
||||||
//if (BTDeviceConnected) {
|
|
||||||
//pTxCharacteristic->setValue(&txValue, 1);
|
|
||||||
//pTxCharacteristic->notify();
|
|
||||||
//txValue++;
|
|
||||||
//vTaskDelay(100); // bluetooth stack will go into congestion, if too many packets are sent
|
|
||||||
//}
|
|
||||||
|
|
||||||
// disconnecting
|
|
||||||
if (!BTDeviceConnected && oldDeviceConnected) {
|
|
||||||
vTaskDelay(750); // give the bluetooth stack the chance to get things ready
|
|
||||||
pServer->startAdvertising(); // restart advertising
|
|
||||||
oldDeviceConnected = BTDeviceConnected;
|
|
||||||
newEvent.animIndex = POP_BLE_DISC;
|
|
||||||
newEvent.type = EV_INJECT;
|
|
||||||
PostNewEvent(newEvent);
|
|
||||||
ESP_LOGI(tag, "Client disconnected...");
|
|
||||||
ESP_LOGI(tag, "Advertising again...");
|
|
||||||
Buzzer_Play_Tune(TUNE_BLE_DISCONNECTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// connecting
|
|
||||||
if (BTDeviceConnected && !oldDeviceConnected) {
|
|
||||||
// do stuff here on connecting
|
|
||||||
oldDeviceConnected = BTDeviceConnected;
|
|
||||||
newEvent.animIndex = POP_BLE_CONN;
|
|
||||||
newEvent.type = EV_INJECT;
|
|
||||||
PostNewEvent(newEvent);
|
|
||||||
ESP_LOGI(tag, "Client connected...");
|
|
||||||
Buzzer_Play_Tune(TUNE_BLE_CONNECTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
#ifndef _BTSERIAL_H
|
|
||||||
#define _BTSERIAL_H
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
|
|
||||||
extern bool BTDeviceConnected;
|
|
||||||
extern String BLEDeviceName;
|
|
||||||
|
|
||||||
void Init_BTSerial(void);
|
|
||||||
|
|
||||||
void BTSerial_Task(void *parameters);
|
|
||||||
|
|
||||||
void extractCommand(char* packet, int* data, int* count);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<script src="event-box.js"></script>
|
|
||||||
<script src="hue-select.js"></script>
|
|
||||||
<title>Event Container</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
padding: 0;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 700px;
|
|
||||||
min-width: 300px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
input[type="checkbox"] {
|
|
||||||
transform: scale(2.5);
|
|
||||||
}
|
|
||||||
input[type="checkbox"].css-checkbox + label.css-label {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
.checkbox-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 1px;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<br>
|
|
||||||
<div>
|
|
||||||
|
|
||||||
<event-box id="event1"></event-box>
|
|
||||||
<!--<hue-select id="hueItem"></hue-select>-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.onload = function() { onLoad(); };
|
|
||||||
|
|
||||||
const event1 = document.getElementById('event1');
|
|
||||||
const hueItem = document.getElementById('hueItem');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function onLoad(){
|
|
||||||
//debugger;
|
|
||||||
hueItem.setHue(145);
|
|
||||||
/*
|
|
||||||
event1.setTitle("Event 0");
|
|
||||||
debugger;
|
|
||||||
//event1.setHueValue(101);
|
|
||||||
|
|
||||||
|
|
||||||
//event1.setParam1Name(`Param1`);
|
|
||||||
event1.setParam2Name("Cooling");
|
|
||||||
event1.setCheck1Name("CHECK1");
|
|
||||||
event1.setCheck2Name(`Check2`);
|
|
||||||
|
|
||||||
event1.setHueValue(140);
|
|
||||||
event1.setSpeedSettings("SPEED: ");
|
|
||||||
event1.addOptionToList(0, "Animation0");
|
|
||||||
event1.addOptionToList(1, "Animation1");
|
|
||||||
event1.addOptionToList(2, "Animation2");
|
|
||||||
event1.setAnimationIndex(1);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,164 +0,0 @@
|
|||||||
import os
|
|
||||||
import shutil
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
|
|
||||||
def copy_folder_to_destination(src_path, dest_path, skip_dirs=None, skip_files=None):
|
|
||||||
# Ensure the destination directory exists
|
|
||||||
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
|
||||||
|
|
||||||
# Remove existing folder if present
|
|
||||||
if os.path.exists(dest_path):
|
|
||||||
shutil.rmtree(dest_path)
|
|
||||||
|
|
||||||
# Create destination directory
|
|
||||||
os.makedirs(dest_path)
|
|
||||||
|
|
||||||
# Walk through source directory
|
|
||||||
for root, dirs, files in os.walk(src_path):
|
|
||||||
# Remove directories to skip from dirs list
|
|
||||||
if skip_dirs:
|
|
||||||
dirs[:] = [d for d in dirs if d not in skip_dirs]
|
|
||||||
|
|
||||||
# Calculate relative path
|
|
||||||
rel_path = os.path.relpath(root, src_path)
|
|
||||||
dest_dir = os.path.join(dest_path, rel_path)
|
|
||||||
|
|
||||||
# Create corresponding destination directory
|
|
||||||
os.makedirs(dest_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Copy files that aren't in skip_files
|
|
||||||
for file in files:
|
|
||||||
if skip_files and file in skip_files:
|
|
||||||
continue
|
|
||||||
src_file = os.path.join(root, file)
|
|
||||||
dest_file = os.path.join(dest_dir, file)
|
|
||||||
shutil.copy2(src_file, dest_file)
|
|
||||||
|
|
||||||
def calculate_md5(file_path):
|
|
||||||
hash_md5 = hashlib.md5()
|
|
||||||
with open(file_path, "rb") as f:
|
|
||||||
for chunk in iter(lambda: f.read(4096), b""):
|
|
||||||
hash_md5.update(chunk)
|
|
||||||
return hash_md5.hexdigest()
|
|
||||||
|
|
||||||
def get_file_size(file_path):
|
|
||||||
return os.path.getsize(file_path)
|
|
||||||
|
|
||||||
def update_json_file(json_array, folder_path):
|
|
||||||
# Create new data array for files
|
|
||||||
file_array = []
|
|
||||||
|
|
||||||
# Walk through the copied folder and collect file details
|
|
||||||
for root, _, files in os.walk(folder_path):
|
|
||||||
for file in files:
|
|
||||||
file_path = os.path.join(root, file)
|
|
||||||
relative_path = os.path.relpath(file_path, folder_path)
|
|
||||||
|
|
||||||
# Replace backslashes with forward slashes
|
|
||||||
relative_path = relative_path.replace('\\', '/')
|
|
||||||
|
|
||||||
file_entry = {
|
|
||||||
"remote": os.path.join("data/", relative_path),
|
|
||||||
"local": os.path.join("/", relative_path),
|
|
||||||
"md5": calculate_md5(file_path),
|
|
||||||
"size": get_file_size(file_path)
|
|
||||||
}
|
|
||||||
file_array.append(file_entry)
|
|
||||||
|
|
||||||
# Replace the contents of the input json_array with new data
|
|
||||||
json_array.clear()
|
|
||||||
json_array.extend(file_array)
|
|
||||||
|
|
||||||
def update_files(src_path, dest_path, skip_dirs=None, skip_files=None):
|
|
||||||
# Check if the source folder exists
|
|
||||||
if not os.path.isdir(src_path) or not os.path.isdir(dest_path):
|
|
||||||
print("Invalid folder path!")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Copy all data contents
|
|
||||||
copy_folder_to_destination(src_path, dest_path, skip_dirs, skip_files)
|
|
||||||
print("Folder copied successfully.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
here_path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
# Path of the folder to copy (you can modify this)
|
|
||||||
src_folder_name = "data"
|
|
||||||
src_path = os.path.join(project_path, src_folder_name)
|
|
||||||
#print(f"source path: {src_path}")
|
|
||||||
|
|
||||||
# Path of the destination folder
|
|
||||||
dest_folder_name = "firmware_update\\latest\\data"
|
|
||||||
dest_path = os.path.join(project_path, dest_folder_name)
|
|
||||||
#print(f"destination path: {dest_path}")
|
|
||||||
|
|
||||||
# Path of the firmware binary file
|
|
||||||
bin_name = ".pio\\build\\esp32s3dev\\firmware.bin"
|
|
||||||
|
|
||||||
# Skip these directories
|
|
||||||
skip_dirs = ["boards", "booths"]
|
|
||||||
|
|
||||||
# Skip these files
|
|
||||||
skip_files = ["wifi.json", "system.json", "luma-stiks.json", ]
|
|
||||||
|
|
||||||
|
|
||||||
update_files = input("Do you want to update the files? (y/n): ")
|
|
||||||
|
|
||||||
# *********************** Copy Data Files ***********************
|
|
||||||
if update_files.lower() == "y":
|
|
||||||
update_files(src_path, dest_path, skip_dirs, skip_files)
|
|
||||||
|
|
||||||
|
|
||||||
# *********************** Copy Binary file ***********************
|
|
||||||
update_firmware = input("Do you want to update the firmware? (y/n): ")
|
|
||||||
if update_firmware.lower() == "y":
|
|
||||||
# Copy firmware.bin to the destination
|
|
||||||
bin_path = os.path.join(project_path, bin_name)
|
|
||||||
shutil.copy(bin_path, here_path)
|
|
||||||
#print(f"firmware path: {bin_path}")
|
|
||||||
print("firmware.bin copied successfully.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# *********************** Process update.json ***********************
|
|
||||||
# Update the JSON file
|
|
||||||
json_path = os.path.join(here_path, "update.json")
|
|
||||||
print(f"json path: {json_path}")
|
|
||||||
|
|
||||||
# Read existing JSON
|
|
||||||
with open(json_path, "r") as f:
|
|
||||||
try:
|
|
||||||
json_doc = json.load(f)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print("Invalid JSON file!")
|
|
||||||
return
|
|
||||||
|
|
||||||
# process the files array
|
|
||||||
#if update_files.lower() == "y":
|
|
||||||
json_files_array = json_doc["files"]
|
|
||||||
update_json_file(json_files_array, dest_path)
|
|
||||||
#print(f"Folder {os.path.basename(src_path)} processed successfully.")
|
|
||||||
|
|
||||||
|
|
||||||
# *********************** Process firmwware.bin in update.json ***********************
|
|
||||||
# process the firmware
|
|
||||||
if update_firmware.lower() == "y":
|
|
||||||
json_firmware = json_doc["firmware"]
|
|
||||||
firmware_path = os.path.join(here_path, "firmware.bin")
|
|
||||||
json_firmware["md5"] = calculate_md5(firmware_path)
|
|
||||||
json_firmware["size"] = get_file_size(firmware_path)
|
|
||||||
|
|
||||||
|
|
||||||
# Write updated JSON
|
|
||||||
with open(json_path, "w") as f:
|
|
||||||
json.dump(json_doc, f, indent=4)
|
|
||||||
|
|
||||||
print("Update JSON files created successfully.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
#include <NeoPixelBus.h>
|
|
||||||
|
|
||||||
// Define constants for maximum LEDs
|
|
||||||
#define MAX_LEDS 300 // Adjust based on your LED requirements
|
|
||||||
|
|
||||||
// Wrapper class for dynamic LED strips
|
|
||||||
class DynamicLedStrip {
|
|
||||||
private:
|
|
||||||
void* strip; // Pointer to the NeoPixelBus object
|
|
||||||
int numLeds; // Number of LEDs
|
|
||||||
String colorOrder; // Color order (for reference)
|
|
||||||
|
|
||||||
public:
|
|
||||||
DynamicLedStrip() : strip(nullptr), numLeds(0), colorOrder("") {}
|
|
||||||
|
|
||||||
// Initialize the LED strip
|
|
||||||
void initialize(int dataPin, int numLeds, const String& chipType, const String& colorOrder) {
|
|
||||||
this->numLeds = numLeds;
|
|
||||||
this->colorOrder = colorOrder;
|
|
||||||
|
|
||||||
if (chipType == "WS2812" || chipType == "SK6812") {
|
|
||||||
if (colorOrder == "GRB") {
|
|
||||||
strip = new NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>(numLeds, dataPin);
|
|
||||||
} else if (colorOrder == "RGB") {
|
|
||||||
strip = new NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>(numLeds, dataPin);
|
|
||||||
} else if (colorOrder == "BGR") {
|
|
||||||
strip = new NeoPixelBus<NeoBgrFeature, Neo800KbpsMethod>(numLeds, dataPin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strip) {
|
|
||||||
static_cast<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip)->Begin();
|
|
||||||
} else {
|
|
||||||
Serial.println("Unsupported chipset or color order!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all LEDs to a specific color
|
|
||||||
void setColor(const RgbColor& color) {
|
|
||||||
if (strip) {
|
|
||||||
for (int i = 0; i < numLeds; i++) {
|
|
||||||
static_cast<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip)->SetPixelColor(i, color);
|
|
||||||
}
|
|
||||||
static_cast<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip)->Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,337 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Upload firmware, manifest, and data assets to a MinIO (S3-compatible) bucket.
|
|
||||||
|
|
||||||
Features preserved from original GCS script:
|
|
||||||
- Optional backup (copies existing objects under destination prefix to timestamped folder under backups/)
|
|
||||||
- Upload firmware.bin, update.json, and recursively mirror a data directory
|
|
||||||
- Cache-Control set to disable caching on clients
|
|
||||||
|
|
||||||
Switches from google.cloud.storage to boto3 (S3 API) for MinIO compatibility.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
try:
|
|
||||||
import boto3
|
|
||||||
from botocore.exceptions import ClientError
|
|
||||||
from botocore.config import Config
|
|
||||||
except ImportError:
|
|
||||||
print("ERROR: boto3 is required. Install with: pip install boto3")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# CONFIGURATION CONSTANTS (edit as needed or supply via environment variables)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
CREATE_BACKUP = False
|
|
||||||
UPLOAD_FIRMWARE = False
|
|
||||||
UPLOAD_MANIFEST = True
|
|
||||||
UPLOAD_DATA = False
|
|
||||||
|
|
||||||
# Bucket / endpoint configuration
|
|
||||||
BUCKET_NAME = os.getenv('MINIO_BUCKET', 'boothifier')
|
|
||||||
DESTINATION_DIR = os.getenv('MINIO_DEST_PREFIX', 'latest') # prefix inside bucket
|
|
||||||
BACKUPS_DIR = os.getenv('MINIO_BACKUPS_PREFIX', 'backups')
|
|
||||||
|
|
||||||
LOCAL_ROOT_PATH = Path(__file__).parent.resolve()
|
|
||||||
|
|
||||||
# Optional service account style JSON key (generated by MinIO Console). Expected fields:
|
|
||||||
# {"url":"https://minio.example.com/api/v1/service-account-credentials","accessKey":"...","secretKey":"...","api":"s3v4","path":"auto"}
|
|
||||||
MINIO_KEY_FILE = LOCAL_ROOT_PATH / 'minio-boothifier-key.json'
|
|
||||||
|
|
||||||
# Defaults before loading file / env
|
|
||||||
_json_access = None
|
|
||||||
_json_secret = None
|
|
||||||
_json_url = None
|
|
||||||
|
|
||||||
def _load_json_key():
|
|
||||||
global _json_access, _json_secret, _json_url
|
|
||||||
try:
|
|
||||||
if MINIO_KEY_FILE.is_file():
|
|
||||||
with open(MINIO_KEY_FILE, 'r', encoding='utf-8') as fh:
|
|
||||||
data = json.load(fh)
|
|
||||||
_json_access = data.get('accessKey') or None
|
|
||||||
_json_secret = data.get('secretKey') or None
|
|
||||||
_json_url = data.get('url') or None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"WARN: Failed to load MinIO key file '{MINIO_KEY_FILE.name}': {e}")
|
|
||||||
|
|
||||||
_load_json_key()
|
|
||||||
|
|
||||||
def _derive_endpoint(url_value: str) -> str:
|
|
||||||
if not url_value:
|
|
||||||
return 'https://s3-minio.boothwizard.com'
|
|
||||||
# Remove known API suffix if present (/api/...)
|
|
||||||
# e.g. https://s3-minio.boothwizard.com/api/v1/service-account-credentials -> https://s3-minio.boothwizard.com
|
|
||||||
parts = url_value.split('/api/')
|
|
||||||
return parts[0] if parts else url_value
|
|
||||||
|
|
||||||
# MinIO credentials with precedence: ENV > JSON file > fallback
|
|
||||||
MINIO_ENDPOINT = os.getenv('MINIO_ENDPOINT') or _derive_endpoint(_json_url)
|
|
||||||
MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') or _json_access or 'CHANGE_ME_ACCESS'
|
|
||||||
MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY') or _json_secret or 'CHANGE_ME_SECRET'
|
|
||||||
MINIO_REGION = os.getenv('MINIO_REGION', 'us-east-1') # MinIO ignores but boto3 wants some value
|
|
||||||
|
|
||||||
# Addressing / SSL options
|
|
||||||
MINIO_ADDRESSING = os.getenv('MINIO_ADDRESSING_STYLE', 'path').lower() # 'path' or 'virtual'
|
|
||||||
MINIO_VERIFY_SSL = os.getenv('MINIO_TLS_VERIFY', '1') not in ('0','false','no')
|
|
||||||
MINIO_DEBUG = os.getenv('MINIO_DEBUG', '0') in ('1','true','yes')
|
|
||||||
MINIO_ALLOW_VARIANTS = os.getenv('MINIO_ALLOW_ENDPOINT_VARIANTS', '0') in ('1','true','yes') # normally false with nginx redirect
|
|
||||||
|
|
||||||
LOCAL_FIRMWARE_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'firmware.bin')
|
|
||||||
LOCAL_MANIFEST_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'update.json')
|
|
||||||
LOCAL_DATA_DIRECTORY = str(LOCAL_ROOT_PATH / 'latest' / 'data')
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# HELPERS
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
def s3_client():
|
|
||||||
"""Create an S3 client pointed at MinIO endpoint, forcing path-style unless overridden, with short timeouts."""
|
|
||||||
addressing = 'path' if MINIO_ADDRESSING not in ('virtual','auto') else 'virtual'
|
|
||||||
cfg = Config(
|
|
||||||
s3={'addressing_style': addressing},
|
|
||||||
signature_version='s3v4',
|
|
||||||
connect_timeout=3,
|
|
||||||
read_timeout=5,
|
|
||||||
retries={'max_attempts': 2}
|
|
||||||
)
|
|
||||||
if MINIO_DEBUG:
|
|
||||||
masked_key = (MINIO_ACCESS_KEY[:3] + '...' + MINIO_ACCESS_KEY[-3:]) if MINIO_ACCESS_KEY else 'None'
|
|
||||||
print(f"[DEBUG] Creating client: endpoint={MINIO_ENDPOINT} addressing={addressing} verifySSL={MINIO_VERIFY_SSL} region={MINIO_REGION} accessKey={masked_key}")
|
|
||||||
return boto3.client(
|
|
||||||
's3',
|
|
||||||
endpoint_url=MINIO_ENDPOINT,
|
|
||||||
aws_access_key_id=MINIO_ACCESS_KEY,
|
|
||||||
aws_secret_access_key=MINIO_SECRET_KEY,
|
|
||||||
region_name=MINIO_REGION,
|
|
||||||
verify=MINIO_VERIFY_SSL,
|
|
||||||
config=cfg,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _endpoint_variants(base: str):
|
|
||||||
"""Return endpoint variants only if explicitly allowed; otherwise just the base (nginx handles forwarding)."""
|
|
||||||
if not MINIO_ALLOW_VARIANTS:
|
|
||||||
return [base]
|
|
||||||
# Fallback to previous expanded logic if variants are enabled
|
|
||||||
try:
|
|
||||||
variants = []
|
|
||||||
if not base:
|
|
||||||
return variants
|
|
||||||
base = base.rstrip('/')
|
|
||||||
proto_sep = '://'
|
|
||||||
if proto_sep in base:
|
|
||||||
scheme, rest = base.split(proto_sep,1)
|
|
||||||
else:
|
|
||||||
scheme, rest = 'https', base
|
|
||||||
host_port = rest
|
|
||||||
if ':' in host_port:
|
|
||||||
host, port = host_port.split(':',1)
|
|
||||||
else:
|
|
||||||
host, port = host_port, ''
|
|
||||||
variants.append(f"{scheme}://{host_port}")
|
|
||||||
common_ports = ['9000','443','80']
|
|
||||||
for p in common_ports:
|
|
||||||
if port != p:
|
|
||||||
variants.append(f"{scheme}://{host}:{p}")
|
|
||||||
alt_scheme = 'http' if scheme == 'https' else 'https'
|
|
||||||
variants.append(f"{alt_scheme}://{host_port}")
|
|
||||||
for p in common_ports:
|
|
||||||
if port != p:
|
|
||||||
variants.append(f"{alt_scheme}://{host}:{p}")
|
|
||||||
seen = set()
|
|
||||||
uniq = []
|
|
||||||
for v in variants:
|
|
||||||
if v not in seen:
|
|
||||||
uniq.append(v)
|
|
||||||
seen.add(v)
|
|
||||||
return uniq
|
|
||||||
except Exception:
|
|
||||||
return [base]
|
|
||||||
|
|
||||||
def create_validated_client():
|
|
||||||
"""Validate (or create) client using only provided endpoint unless variants enabled."""
|
|
||||||
global MINIO_ENDPOINT
|
|
||||||
primary = MINIO_ENDPOINT
|
|
||||||
variants = _endpoint_variants(primary) or [primary]
|
|
||||||
errors = []
|
|
||||||
probe_bucket = BUCKET_NAME # we will head the target bucket directly
|
|
||||||
for candidate in variants:
|
|
||||||
saved = MINIO_ENDPOINT
|
|
||||||
MINIO_ENDPOINT = candidate
|
|
||||||
if MINIO_DEBUG:
|
|
||||||
print(f"[DEBUG] Probing endpoint candidate: {candidate}")
|
|
||||||
try:
|
|
||||||
c = s3_client()
|
|
||||||
try:
|
|
||||||
c.head_bucket(Bucket=probe_bucket)
|
|
||||||
if MINIO_DEBUG:
|
|
||||||
print(f"[DEBUG] head_bucket succeeded on {candidate} for '{probe_bucket}'.")
|
|
||||||
return c
|
|
||||||
except ClientError as e:
|
|
||||||
msg = str(e)
|
|
||||||
# Acceptable if bucket not found (we can create later)
|
|
||||||
if any(code in msg for code in ('404', 'NoSuchBucket', 'NotFound')):
|
|
||||||
if MINIO_DEBUG:
|
|
||||||
print(f"[DEBUG] Bucket not found on {candidate} (expected if first deploy). Using this endpoint.")
|
|
||||||
return c
|
|
||||||
if 'API Requests must be made to API port' in msg:
|
|
||||||
errors.append(f"{candidate}: wrong port (console endpoint)")
|
|
||||||
else:
|
|
||||||
errors.append(f"{candidate}: {msg}")
|
|
||||||
MINIO_ENDPOINT = saved
|
|
||||||
except Exception as ex:
|
|
||||||
errors.append(f"{candidate}: {ex}")
|
|
||||||
MINIO_ENDPOINT = saved
|
|
||||||
continue
|
|
||||||
print("ERROR: Could not validate any endpoint candidate.")
|
|
||||||
for e in errors:
|
|
||||||
print(' - ' + e)
|
|
||||||
print("Provide correct API endpoint (e.g. https://host:9000) via MINIO_ENDPOINT env var.")
|
|
||||||
sys.exit(3)
|
|
||||||
|
|
||||||
def list_objects(client, prefix: str):
|
|
||||||
"""Generator yielding object keys under a prefix (non-recursive listing with pagination)."""
|
|
||||||
kwargs = {'Bucket': BUCKET_NAME, 'Prefix': prefix}
|
|
||||||
while True:
|
|
||||||
resp = client.list_objects_v2(**kwargs)
|
|
||||||
for obj in resp.get('Contents', []):
|
|
||||||
yield obj['Key']
|
|
||||||
if not resp.get('IsTruncated'):
|
|
||||||
break
|
|
||||||
kwargs['ContinuationToken'] = resp['NextContinuationToken']
|
|
||||||
|
|
||||||
def normalize_prefix(p: str) -> str:
|
|
||||||
p = p.strip('/')
|
|
||||||
return p
|
|
||||||
|
|
||||||
def join_key(*parts: str) -> str:
|
|
||||||
parts_clean = [p.strip('/') for p in parts if p is not None and p != '']
|
|
||||||
return '/'.join(parts_clean)
|
|
||||||
|
|
||||||
def backup_existing_files(client, destination_prefix: str, backups_prefix: str, backup_folder: str):
|
|
||||||
if not destination_prefix:
|
|
||||||
prefix = ''
|
|
||||||
else:
|
|
||||||
prefix = destination_prefix + '/'
|
|
||||||
print(f"Scanning existing objects under '{prefix}' for backup...")
|
|
||||||
for key in list_objects(client, prefix):
|
|
||||||
if backups_prefix and key.startswith(backups_prefix + '/'): # Skip prior backups
|
|
||||||
continue
|
|
||||||
# relative path within destination
|
|
||||||
relative = key[len(prefix):] if prefix and key.startswith(prefix) else key
|
|
||||||
backup_key = join_key(backups_prefix, backup_folder, relative)
|
|
||||||
print(f"Backup copy: {key} -> {backup_key}")
|
|
||||||
client.copy_object(
|
|
||||||
Bucket=BUCKET_NAME,
|
|
||||||
CopySource={'Bucket': BUCKET_NAME, 'Key': key},
|
|
||||||
Key=backup_key,
|
|
||||||
MetadataDirective='COPY'
|
|
||||||
)
|
|
||||||
|
|
||||||
def upload_file(client, local_path: str, key: str, cache_control: str = 'private, max-age=0, no-transform'):
|
|
||||||
if not os.path.isfile(local_path):
|
|
||||||
print(f"WARN: File missing, skipping: {local_path}")
|
|
||||||
return
|
|
||||||
print(f"Upload: {local_path} -> s3://{BUCKET_NAME}/{key}")
|
|
||||||
extra_args = { 'CacheControl': cache_control }
|
|
||||||
client.upload_file(local_path, BUCKET_NAME, key, ExtraArgs=extra_args)
|
|
||||||
|
|
||||||
def upload_directory(client, local_directory: str, destination_prefix: str):
|
|
||||||
if not os.path.isdir(local_directory):
|
|
||||||
print(f"WARN: Data directory missing: {local_directory}")
|
|
||||||
return
|
|
||||||
for root, _, files in os.walk(local_directory):
|
|
||||||
for fname in files:
|
|
||||||
full = os.path.join(root, fname)
|
|
||||||
rel = os.path.relpath(full, local_directory)
|
|
||||||
key = join_key(destination_prefix, rel)
|
|
||||||
upload_file(client, full, key)
|
|
||||||
|
|
||||||
def ensure_bucket(client):
|
|
||||||
"""Ensure bucket exists; provide diagnostics if HeadBucket returns 400/other errors."""
|
|
||||||
try:
|
|
||||||
client.head_bucket(Bucket=BUCKET_NAME)
|
|
||||||
if MINIO_DEBUG:
|
|
||||||
print(f"[DEBUG] Bucket '{BUCKET_NAME}' exists.")
|
|
||||||
return
|
|
||||||
except ClientError as e:
|
|
||||||
code = e.response.get('Error', {}).get('Code')
|
|
||||||
status = e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
|
|
||||||
print(f"HeadBucket failed (code={code}, status={status}).")
|
|
||||||
|
|
||||||
# List buckets for diagnostics
|
|
||||||
try:
|
|
||||||
resp = client.list_buckets()
|
|
||||||
bucket_names = [b['Name'] for b in resp.get('Buckets', [])]
|
|
||||||
print(f"Available buckets: {bucket_names or 'None'}")
|
|
||||||
except Exception as le:
|
|
||||||
print(f"WARN: list_buckets failed: {le}")
|
|
||||||
|
|
||||||
if code in ('404', 'NoSuchBucket', 'NotFound'):
|
|
||||||
print(f"Bucket '{BUCKET_NAME}' not found. Attempting to create...")
|
|
||||||
try:
|
|
||||||
client.create_bucket(Bucket=BUCKET_NAME)
|
|
||||||
print(f"Created bucket '{BUCKET_NAME}'.")
|
|
||||||
return
|
|
||||||
except ClientError as ce:
|
|
||||||
print(f"ERROR: Cannot create bucket: {ce}")
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if status == 400:
|
|
||||||
print("HINTS: \n - Verify endpoint URL (MINIO_ENDPOINT).\n - Ensure no trailing slash in endpoint.\n - Check that TLS verify matches server cert (set MINIO_TLS_VERIFY=0 to test).\n - Confirm bucket name is correct and DNS compatible.\n - Credentials may lack permission: verify access key policies.")
|
|
||||||
|
|
||||||
# Retry once forcing path style if not already
|
|
||||||
if MINIO_ADDRESSING != 'path':
|
|
||||||
print("Retrying with path-style addressing...")
|
|
||||||
os.environ['MINIO_ADDRESSING_STYLE'] = 'path'
|
|
||||||
new_client = s3_client()
|
|
||||||
try:
|
|
||||||
new_client.head_bucket(Bucket=BUCKET_NAME)
|
|
||||||
print("Second attempt succeeded with path-style addressing.")
|
|
||||||
return
|
|
||||||
except ClientError as e2:
|
|
||||||
print(f"Second HeadBucket attempt failed: {e2}")
|
|
||||||
print(f"ERROR: head_bucket ultimately failed: {e}")
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# MAIN
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
def main():
|
|
||||||
dest_prefix = normalize_prefix(DESTINATION_DIR)
|
|
||||||
backups_prefix = normalize_prefix(BACKUPS_DIR) if BACKUPS_DIR else ''
|
|
||||||
client = create_validated_client()
|
|
||||||
if MINIO_DEBUG:
|
|
||||||
print("[DEBUG] Starting ensure_bucket phase...")
|
|
||||||
ensure_bucket(client)
|
|
||||||
|
|
||||||
if CREATE_BACKUP:
|
|
||||||
ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
||||||
backup_folder = f"backup_{ts}"
|
|
||||||
print(f"Creating backup under '{backups_prefix}/{backup_folder}' from prefix '{dest_prefix}'")
|
|
||||||
backup_existing_files(client, dest_prefix, backups_prefix, backup_folder)
|
|
||||||
|
|
||||||
# Firmware
|
|
||||||
if UPLOAD_FIRMWARE:
|
|
||||||
firmware_key = join_key(dest_prefix, 'firmware.bin') if dest_prefix else 'firmware.bin'
|
|
||||||
upload_file(client, LOCAL_FIRMWARE_PATH, firmware_key)
|
|
||||||
|
|
||||||
# Manifest
|
|
||||||
if UPLOAD_MANIFEST:
|
|
||||||
manifest_key = join_key(dest_prefix, 'update.json') if dest_prefix else 'update.json'
|
|
||||||
upload_file(client, LOCAL_MANIFEST_PATH, manifest_key)
|
|
||||||
|
|
||||||
# Data directory
|
|
||||||
if UPLOAD_DATA:
|
|
||||||
data_prefix = join_key(dest_prefix, 'data') if dest_prefix else 'data'
|
|
||||||
upload_directory(client, LOCAL_DATA_DIRECTORY, data_prefix)
|
|
||||||
|
|
||||||
print("All uploads complete.")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -1,623 +0,0 @@
|
|||||||
{{NAVBAR}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>App Control Configuration</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href="global-style.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
#table{
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
#first_td_th {
|
|
||||||
width:325px;
|
|
||||||
}
|
|
||||||
#v2_td_th {
|
|
||||||
width:200px;
|
|
||||||
}
|
|
||||||
#color-picker{
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
width: 35px;
|
|
||||||
height: 35px;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
#select-anim {
|
|
||||||
width:175px;
|
|
||||||
}
|
|
||||||
input{
|
|
||||||
cursor:pointer;
|
|
||||||
}
|
|
||||||
#slide-density{
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
#slide-speed{
|
|
||||||
accent-color: rgb(181, 53, 53);
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
input::-webkit-slider-runnable-track {
|
|
||||||
width: 450px;
|
|
||||||
/*background: linear-gradient(to right, red, orange, yellow, lime, green, Turquoise, Cyan, Blue, Violet, Magenta, Crimson, red);*/
|
|
||||||
border: none;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>App Control Configuration</h1>
|
|
||||||
<!--<form action="/upload" method="POST" enctype="multipart/form-data"> -->
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Saved Animation Profiles</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="first_td_th">
|
|
||||||
<label for="selSavedAnimProfiles">Profiles:</label>
|
|
||||||
<select name= "selSavedAnimProfiles" id="selSaveddAnimProfiles" style="width:150px">
|
|
||||||
<option>(1)</option>
|
|
||||||
<option>(2)</option>
|
|
||||||
<option>(3)</option>
|
|
||||||
<option>(4)</option>
|
|
||||||
<option>(5)</option>
|
|
||||||
<option>(6)</option>
|
|
||||||
<option>(7)</option>
|
|
||||||
<option>(8)</option>
|
|
||||||
</select>
|
|
||||||
  
|
|
||||||
<label for="profileName">Name:</label>
|
|
||||||
<input type="text" name="inputProfileName" id="profileName">
|
|
||||||
   
|
|
||||||
<button id="saveProfile" onclick="sendProfilesToServer()">Save Profile</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<tr><td>
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
</td></tr>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendLights">Countdown Animation ( White Fill )</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="first_td_th">
|
|
||||||
<label for="constlight">Light Min: (0-100)</label> <br>
|
|
||||||
<input type="range" name="constLightMin" id="constlight" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="constlight">Light Max:</label> <br>
|
|
||||||
<input type="range" name="constLightMax" id="constlight" min="0" max="100" value="90" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<!-- <button>Try</button> -->
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td>
|
|
||||||
<div id="spacer-10"></div>
|
|
||||||
</td></tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td id="first_td_th">
|
|
||||||
<label for="Holdtime">Hold time(ms):</label><br>
|
|
||||||
<input type="number" name="holdTime" id="holdtime" min="0" max="5000" value="500">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="Ramptime">Ramp down(ms):</label><br>
|
|
||||||
<input type="number" name="rampTime" id="Ramptime" min="0" max="5000" value="500">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim0">Event 1</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim0" id="select-anim0">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim0" id="color-picker" > 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim0" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density0" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed0" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(0)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim1">Event 2</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim1" id="select-anim1">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim1" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim1" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density1" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed1" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(1)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim2">Event 3</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim2" id="select-anim2">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim2" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim2" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density2" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed2" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(2)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim3">Event 4</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim3" id="select-anim3">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim3" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim3" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density3" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed3" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(3)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim4">Event 5</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim4" id="select-anim4">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim4" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim4" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density4" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed4" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(4)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim5">Event 6</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim5" id="select-anim5">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim5" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim5" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density5" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed5" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(5)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim6">Event 7</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim6" id="select-anim6">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim6" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim6" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density6" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed6" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(6)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendAnim7">Event 8</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim7" id="select-anim7">
|
|
||||||
<option>Animation 1</option>
|
|
||||||
<option>Animation 2</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Main:</label><br>
|
|
||||||
<input type="color" name="main-color-anim7" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Base:</label><br>
|
|
||||||
<input type="color" name="base-color-anim7" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-density">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density7" id="slide-density" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="slide-speed">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed7" id="slide-speed" min="0" max="100" value="25" step="1">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="sendPlayAnim(7)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
<!--</form> -->
|
|
||||||
<script>
|
|
||||||
|
|
||||||
//const websocket = new WebSocket(`ws://${window.location.hostname}/wsdata`);
|
|
||||||
//const websocket = new WebSocket({{WEBSOCKET_ADDR}});
|
|
||||||
|
|
||||||
// Initialize Names
|
|
||||||
var animProf = [8];
|
|
||||||
var profilesJson;
|
|
||||||
const inputProfileName = document.getElementsByName('inputProfileName')[0];
|
|
||||||
const selSavedAnimProfiles = document.getElementsByName('selSavedAnimProfiles')[0];
|
|
||||||
const constLightMin = document.getElementsByName('constLightMin')[0];
|
|
||||||
const constLightMax = document.getElementsByName('constLightMax')[0];
|
|
||||||
const holdTime = document.getElementsByName('holdTime')[0];
|
|
||||||
const rampTime = document.getElementsByName('rampTime')[0];
|
|
||||||
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
animProf[i] = {
|
|
||||||
selAnim: document.getElementsByName('sel-anim' + i)[0],
|
|
||||||
colMain: document.getElementsByName('main-color-anim' + i)[0],
|
|
||||||
colBase: document.getElementsByName('base-color-anim' + i)[0],
|
|
||||||
density: document.getElementsByName('slide-density' + i)[0],
|
|
||||||
speed: document.getElementsByName('slide-speed' + i)[0]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// When new profile is selected
|
|
||||||
selSaveddAnimProfiles.addEventListener('change', function(event) {
|
|
||||||
const selectedValue = event.target.selectedIndex;
|
|
||||||
console.log('Selected value:', selectedValue);
|
|
||||||
setProfile(selectedValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
// update the animation data
|
|
||||||
function setProfile(index){
|
|
||||||
// update profiles
|
|
||||||
inputProfileName.value = profilesJson.profiles[index].name;
|
|
||||||
//selSavedAnimProfiles.selectedIndex = index;
|
|
||||||
constLightMin.value = profilesJson.countdown.min;
|
|
||||||
constLightMax.value = profilesJson.countdown.max;
|
|
||||||
holdTime.value = profilesJson.countdown.hold;
|
|
||||||
rampTime.value = profilesJson.countdown.ramp;
|
|
||||||
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
animProf[i].selAnim.selectedIndex = profilesJson.profiles[index].anims[i].anim;
|
|
||||||
animProf[i].colMain.value = profilesJson.profiles[index].anims[i].colmain;
|
|
||||||
animProf[i].colBase.value = profilesJson.profiles[index].anims[i].colbase;
|
|
||||||
animProf[i].density.value = profilesJson.profiles[index].anims[i].density;
|
|
||||||
animProf[i].speed.value = profilesJson.profiles[index].anims[i].speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
// Send test animation for viewing
|
|
||||||
function sendPlayAnim(animIndex){
|
|
||||||
const type = 'try-anim';
|
|
||||||
console.log("try index= " + animIndex);
|
|
||||||
profileIndex = selSavedAnimProfiles.selectedIndex;
|
|
||||||
|
|
||||||
const playAnim = {
|
|
||||||
anim: animProf[animIndex].selAnim.selectedIndex,
|
|
||||||
colmain: animProf[animIndex].colMain.value,
|
|
||||||
colbase: animProf[animIndex].colBase.value,
|
|
||||||
density: animProf[animIndex].density.value,
|
|
||||||
speed: animProf[animIndex].speed.value
|
|
||||||
};
|
|
||||||
|
|
||||||
const payload = { type:"try-anim", data: JSON.stringify(playAnim) }
|
|
||||||
console.log(payload);
|
|
||||||
websocket.send( JSON.stringify(payload) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send complete json to server
|
|
||||||
function sendProfilesToServer(event) {
|
|
||||||
//event.preventDefault();
|
|
||||||
|
|
||||||
var index = selSavedAnimProfiles.selectedIndex;
|
|
||||||
profilesJson.profiles[index].name = inputProfileName.value;
|
|
||||||
profilesJson.countdown = {
|
|
||||||
min: constLightMin.value,
|
|
||||||
max: constLightMax.value,
|
|
||||||
hold: holdTime.value,
|
|
||||||
ramp: rampTime.value
|
|
||||||
};
|
|
||||||
|
|
||||||
for(let i = 0; i < 8; i++){
|
|
||||||
profilesJson.profiles[index].anims[i] = {
|
|
||||||
anim: animProf[i].selAnim.selectedIndex,
|
|
||||||
colmain: animProf[i].colMain.value,
|
|
||||||
colbase: animProf[i].colBase.value,
|
|
||||||
density: animProf[i].density.value,
|
|
||||||
speed: animProf[i].speed.value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = { type:"save-profiles", data: JSON.stringify(profilesJson) }
|
|
||||||
//console.log(profilesJson);
|
|
||||||
websocket.send( JSON.stringify(payload) );
|
|
||||||
};
|
|
||||||
|
|
||||||
// Receive profiles json
|
|
||||||
websocket.onmessage = (event) => {
|
|
||||||
try{
|
|
||||||
console.log('Received data:', event.data);
|
|
||||||
profilesJson = JSON.parse(event.data);
|
|
||||||
index = profilesJson.index;
|
|
||||||
|
|
||||||
inputProfileName.value = profilesJson.profiles[index].name;
|
|
||||||
//Upate profile list
|
|
||||||
for(let i = 0; i < 8; i++){
|
|
||||||
opText = selSavedAnimProfiles.options[i].textContent + ' - ' + profilesJson.profiles[i].name;
|
|
||||||
selSavedAnimProfiles.options[i].textContent = opText;
|
|
||||||
}
|
|
||||||
setProfile(index);
|
|
||||||
}
|
|
||||||
catch(error){
|
|
||||||
console.error('Error parsing received JSON data:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get profiles json
|
|
||||||
websocket.onopen = () => {
|
|
||||||
const type = 'get-profiles';
|
|
||||||
const data = {};
|
|
||||||
|
|
||||||
const payload = { type: type, data: data }
|
|
||||||
websocket.send(JSON.stringify(payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
websocket.onerror = (error) => {
|
|
||||||
console.error('WebSocket error:', error);
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleColorChange(event) {
|
|
||||||
const colorPicker = event.target;
|
|
||||||
const colorValue = colorPicker.value;
|
|
||||||
|
|
||||||
// Extract the hue and lightness values from the color value
|
|
||||||
const hue = colorValue.substring(1, 4);
|
|
||||||
const lightness = colorValue.substring(5, 7);
|
|
||||||
|
|
||||||
// Set the saturation value to a fixed value (e.g., 100%)
|
|
||||||
const lockedColorValue = `hsl(${hue}, 100%, ${lightness}%)`;
|
|
||||||
colorPicker.value = lockedColorValue;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// save profilesJson
|
|
||||||
function postProfiles(){
|
|
||||||
fetch('/post', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'type': 'profiles'
|
|
||||||
} ,
|
|
||||||
body: JSON.stringify(jsonProfiles)
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
// Handle successful response
|
|
||||||
console.log('Request successful');
|
|
||||||
} else {
|
|
||||||
throw new Error('Request failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Handle any errors that occurred during the request
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// send anim to test out
|
|
||||||
function postPlayAnim(animIndex){
|
|
||||||
fetch('/post', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'type': 'play-anim'
|
|
||||||
} ,
|
|
||||||
body: JSON.stringify(jsonProfiles.profiles[0])
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
// Handle successful response
|
|
||||||
console.log('Request successful');
|
|
||||||
} else {
|
|
||||||
throw new Error('Request failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Handle any errors that occurred during the request
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get profiles
|
|
||||||
function getProfiles(){
|
|
||||||
fetch('/get',{
|
|
||||||
method: 'GET',
|
|
||||||
headers:{'type':'profiles'}
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
const index = response.headers.get('index');
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
} else {
|
|
||||||
throw new Error('Request failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
profilesJsaon = data;
|
|
||||||
console.log(data);
|
|
||||||
setProfile(index);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,508 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>ATA Firmware Update</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-ble {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-wifi {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-internet {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adds space above the WiFi Connect button */
|
|
||||||
.btn-container.wifi {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 130px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover:not(:disabled) {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 90%;
|
|
||||||
max-width: 300px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, textarea {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>ATA Firmware Update</h1>
|
|
||||||
|
|
||||||
<!-- Status Indicators -->
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-ble"></span>
|
|
||||||
<label id="status-ble-connection">Device: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-wifi"></span>
|
|
||||||
<label id="status-wifi-client">Wifi Client: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-internet"></span>
|
|
||||||
<label id="status-internet">Internet: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<label id="status-current-version">Curr Version: ...</label>
|
|
||||||
</div>
|
|
||||||
<div class="status-container">
|
|
||||||
<label id="status-new-version">New Version: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Buttons -->
|
|
||||||
<div class="btn-container">
|
|
||||||
<button id="bleConnectBtn">Connect</button>
|
|
||||||
<button id="checkStatusBtn" disabled>Check Status</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Log Area -->
|
|
||||||
<textarea id="logArea" readonly></textarea>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<button id="checkVersionBtn" disabled>Check Version</button>
|
|
||||||
<button id="startUpgradeBtn" disabled>Start Update</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Wi-Fi Input Fields -->
|
|
||||||
<div class="input-container">
|
|
||||||
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
|
|
||||||
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
|
|
||||||
<div style="display: flex; align-items: center; gap: 5px;">
|
|
||||||
<input type="checkbox" id="showPassword" style="width: auto;">
|
|
||||||
<label for="showPassword">Show Password</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Added margin-top above this button -->
|
|
||||||
<div class="btn-container wifi">
|
|
||||||
<button id="wifiConnectBtn" disabled>Connect Wifi</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Constants
|
|
||||||
const BLE_SERVER_NAME = "ATALIGHTS"; // Replace with your server name
|
|
||||||
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0"; // Replace with your service UUID
|
|
||||||
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
|
|
||||||
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
|
|
||||||
|
|
||||||
let bleDevice = null;
|
|
||||||
let bleCharacteristic1 = null;
|
|
||||||
let bleCharacteristic2 = null;
|
|
||||||
let bleConnected = false;
|
|
||||||
|
|
||||||
const WIFI_STAT = { WIFI_DISCONNECTED:0, WIFI_BAD_CREDS:1, WIFI_NO_AP:2, WIFI_CONNECTED:3 };
|
|
||||||
|
|
||||||
let updatePacket = {
|
|
||||||
wifiConnected: false,
|
|
||||||
wifiOnline: false,
|
|
||||||
wifiIP: [0, 0, 0, 0],
|
|
||||||
currVersion: [0, 0, 0],
|
|
||||||
newVersion: [0, 0, 0]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Log messages to the textarea
|
|
||||||
function logMessage(message) {
|
|
||||||
const logArea = document.getElementById('logArea');
|
|
||||||
logArea.value += message + '\n';
|
|
||||||
logArea.scrollTop = logArea.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to scan for BLE devices
|
|
||||||
async function scanForDevices() {
|
|
||||||
logMessage('Scanning for BLE devices...');
|
|
||||||
try {
|
|
||||||
const device = await navigator.bluetooth.requestDevice({
|
|
||||||
acceptAllDevices: true,
|
|
||||||
optionalServices: [BLE_SERVICE_UUID]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (device) {
|
|
||||||
logMessage(`Found device: ${device.name || "Unnamed"} (ID: ${device.id})`);
|
|
||||||
} else {
|
|
||||||
logMessage('No devices found.');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logMessage(`Scan failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to connect to the BLE server
|
|
||||||
async function connectToBle() {
|
|
||||||
try {
|
|
||||||
bleDevice = await navigator.bluetooth.requestDevice({
|
|
||||||
filters: [{ name: BLE_SERVER_NAME }],
|
|
||||||
optionalServices: [BLE_SERVICE_UUID]
|
|
||||||
});
|
|
||||||
|
|
||||||
//logMessage(`Connecting to ${bleDevice.name}`);
|
|
||||||
const server = await bleDevice.gatt.connect();
|
|
||||||
//await server.setPreferredMtu(247); // Request larger MTU size
|
|
||||||
|
|
||||||
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
|
|
||||||
|
|
||||||
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
|
|
||||||
|
|
||||||
// Subscribe to notifications
|
|
||||||
//await bleCharacteristic1.startNotifications();
|
|
||||||
|
|
||||||
// Add event listener for incoming notifications
|
|
||||||
//bleCharacteristic1.addEventListener('characteristicvaluechanged', handleChar1Notifications);
|
|
||||||
|
|
||||||
|
|
||||||
//logMessage('Getting characteristic...');
|
|
||||||
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
|
|
||||||
|
|
||||||
// Subscribe to notifications
|
|
||||||
await bleCharacteristic2.startNotifications();
|
|
||||||
|
|
||||||
// Add event listener for incoming notifications
|
|
||||||
bleCharacteristic2.addEventListener('characteristicvaluechanged', (event) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const decodedValue = decoder.decode(value);
|
|
||||||
logMessage('--> ' + decodedValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
bleConnected = true;
|
|
||||||
document.getElementById('bleConnectBtn').disabled = true;
|
|
||||||
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
|
|
||||||
document.getElementById('status-ble-connection').textContent = 'Device: Connected';
|
|
||||||
document.getElementById('wifiConnectBtn').disabled = false;
|
|
||||||
document.getElementById('checkStatusBtn').disabled = false;
|
|
||||||
logMessage(`Connected to ${bleDevice.name}`);
|
|
||||||
|
|
||||||
await readPacket();
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
if (error.message.includes("cancelled")) {
|
|
||||||
logMessage("Connection cancelled by user.");
|
|
||||||
} else {
|
|
||||||
logMessage(`Connection failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendPacket(packetMsg) {
|
|
||||||
if (!bleCharacteristic1) {
|
|
||||||
console.log("Cannot send packet: Not connected to BLE server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxRetries = 3;
|
|
||||||
const retryDelay = 1000; // 1 second
|
|
||||||
let attempt = 0;
|
|
||||||
|
|
||||||
while (attempt < maxRetries) {
|
|
||||||
try {
|
|
||||||
//logMessage(`Sending request: ${packetMsg} (Attempt ${attempt + 1})`);
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
await bleCharacteristic1.writeValueWithResponse(encoder.encode(packetMsg));
|
|
||||||
//console.log("Request sent successfully");
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to send packet: ${error.message}`);
|
|
||||||
attempt++;
|
|
||||||
if (attempt < maxRetries) {
|
|
||||||
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
|
|
||||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
||||||
} else {
|
|
||||||
console.error("Max retries reached. Failed to send request.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readPacket() {
|
|
||||||
if (!bleCharacteristic1) {
|
|
||||||
console.log("Cannot read packet: Not connected to BLE server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxRetries = 3;
|
|
||||||
const retryDelay = 1000; // 1 second
|
|
||||||
let attempt = 0;
|
|
||||||
|
|
||||||
while (attempt < maxRetries) {
|
|
||||||
try {
|
|
||||||
const value = await bleCharacteristic1.readValue();
|
|
||||||
const data = new Uint8Array(value.buffer);
|
|
||||||
if (data.length === 12) {
|
|
||||||
updatePacket.wifiConnected = data[0] !== 0;
|
|
||||||
updatePacket.wifiOnline = data[1] !== 0;
|
|
||||||
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
|
|
||||||
updatePacket.currVersion = [data[6], data[7], data[8]];
|
|
||||||
updatePacket.newVersion = [data[9], data[10], data[11]];
|
|
||||||
|
|
||||||
//processUpdatePacket(updatePacket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("Invalid packet length");
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to read packet: ${error.message}`);
|
|
||||||
attempt++;
|
|
||||||
if (attempt < maxRetries) {
|
|
||||||
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
|
|
||||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
||||||
} else {
|
|
||||||
console.error("Max retries reached. Failed to read packet.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process update packet
|
|
||||||
function processUpdatePacket(packet) {
|
|
||||||
// Process the packet data
|
|
||||||
//console.log("Processing update packet:", packet);
|
|
||||||
if(packet.wifiConnected === true) {
|
|
||||||
if(packet.wifiConnected && packet.wifiIP[0] > 0) {
|
|
||||||
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected (' + packet.wifiIP.join('.') + ')';
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected';
|
|
||||||
}
|
|
||||||
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'green';
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-wifi-client').textContent = 'Wifi Client: ...';
|
|
||||||
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'gray';
|
|
||||||
}
|
|
||||||
|
|
||||||
if(packet.wifiOnline === true) {
|
|
||||||
document.getElementById('status-internet').textContent = 'Online';
|
|
||||||
document.querySelector('.status-indicator-internet').style.backgroundColor = 'green';
|
|
||||||
document.getElementById('checkVersionBtn').disabled = false;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-internet').textContent = 'Offline';
|
|
||||||
document.querySelector('.status-indicator-internet').style.backgroundColor = 'gray';
|
|
||||||
document.getElementById('checkVersionBtn').disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.currVersion[0] > 0) {
|
|
||||||
document.getElementById('status-current-version').textContent = 'Curr Version: ' + packet.currVersion.join('.');
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-current-version').textContent = 'Curr Version: ...';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.newVersion[0] > 0) {
|
|
||||||
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
|
|
||||||
document.getElementById('checkVersionBtn').disabled = true;
|
|
||||||
|
|
||||||
if(packet.wifiOnline && packet.newVersion[0] > packet.currVersion[0] ||
|
|
||||||
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] > packet.currVersion[1]) ||
|
|
||||||
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] === packet.currVersion[1] && packet.newVersion[2] > packet.currVersion[2])) {
|
|
||||||
|
|
||||||
//enable start upgrade button
|
|
||||||
logMessage("New Version Available:");
|
|
||||||
document.getElementById('startUpgradeBtn').disabled = false;
|
|
||||||
} else {
|
|
||||||
//disable start upgrade button
|
|
||||||
document.getElementById('startUpgradeBtn').disabled = true;
|
|
||||||
logMessage("New Version: Not Available");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-new-version').textContent = 'New Version: ...';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//BLE_Characteristic.addEventListener('characteristicvaluechanged', handleNotifications);
|
|
||||||
function handleChar1Notifications(event) {
|
|
||||||
const data = new Uint8Array(event.data);
|
|
||||||
|
|
||||||
if (data.length !== 12) { // 1 byte for id, 4 bytes for booleans, 4 bytes for wifiIP, 3 bytes for currVersion, 3 bytes for newVersion
|
|
||||||
console.log("Invalid packet length");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update existing updatePacket object instead of creating new one
|
|
||||||
updatePacket.wifiConnected = data[0] !== 0;
|
|
||||||
updatePacket.internetAvailable = data[1] !== 0;
|
|
||||||
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
|
|
||||||
updatePacket.currVersion = [data[6], data[7], data[8]];
|
|
||||||
updatePacket.newVersion = [data[9], data[10], data[11]];
|
|
||||||
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('showPassword').addEventListener('change', function() {
|
|
||||||
const passwordInput = document.getElementById('wifipassword');
|
|
||||||
passwordInput.type = this.checked ? 'text' : 'password';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Event listeners for buttons
|
|
||||||
document.getElementById('bleConnectBtn').addEventListener('click', connectToBle);
|
|
||||||
document.getElementById('checkStatusBtn').addEventListener('click', async () => {
|
|
||||||
if (bleCharacteristic1) {
|
|
||||||
await readPacket();
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
} else {
|
|
||||||
logMessage('BLE device not connected.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.getElementById('checkVersionBtn').addEventListener('click', async () => {
|
|
||||||
await sendPacket('version-check');
|
|
||||||
// loop and monitor the the updatePacket.newVersion
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
success = false;
|
|
||||||
for (let i = 0; i < 20; i++) {
|
|
||||||
await readPacket();
|
|
||||||
if (updatePacket.newVersion[0] > 0) {
|
|
||||||
success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
}
|
|
||||||
if(success) {
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
logMessage("New Version: Available");
|
|
||||||
} else {
|
|
||||||
logMessage("New Version: Not Available");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
document.getElementById('wifiConnectBtn').addEventListener('click', async () => {
|
|
||||||
const ssid = document.getElementById('wifissid').value;
|
|
||||||
const password = document.getElementById('wifipassword').value;
|
|
||||||
if (ssid && password) {
|
|
||||||
// Send credentials to the device
|
|
||||||
jsonString = ' {"ssid":"' + ssid + '","pass":"' + password + '"} ';
|
|
||||||
await sendPacket('wifi-connect' + jsonString);
|
|
||||||
|
|
||||||
await readPacket();
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
} else {
|
|
||||||
alert('Please enter both SSID and password.');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
document.getElementById('startUpgradeBtn').addEventListener('click', async () => {
|
|
||||||
try {
|
|
||||||
await sendPacket('upgrade-start');
|
|
||||||
logMessage("Upgrade Starting... Please wait.");
|
|
||||||
} catch (error) {
|
|
||||||
logMessage(`Error starting upgrade: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initial call to process the update packet with defaults
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,547 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>ATA Firmware Update</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-ble {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-wifi {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-internet {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adds space above the WiFi Connect button */
|
|
||||||
.btn-container.wifi {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 130px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover:not(:disabled) {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 90%;
|
|
||||||
max-width: 300px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, textarea {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>ATA Firmware Update</h1>
|
|
||||||
|
|
||||||
<!-- Status Indicators -->
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-ble"></span>
|
|
||||||
<label id="status-ble-connection">BLE Status: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-wifi"></span>
|
|
||||||
<label id="status-wifi-client">Wifi Client: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-internet"></span>
|
|
||||||
<label id="status-internet">Internet: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<label id="status-current-version">Curr Version: ...</label>
|
|
||||||
</div>
|
|
||||||
<div class="status-container">
|
|
||||||
<label id="status-new-version">New Version: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<label id="ble-device-name">Device Name:</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Buttons -->
|
|
||||||
<div class="btn-container">
|
|
||||||
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
|
|
||||||
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Log Area -->
|
|
||||||
<textarea id="logArea" readonly></textarea>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
|
|
||||||
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Wi-Fi Input Fields -->
|
|
||||||
<div class="input-container">
|
|
||||||
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
|
|
||||||
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
|
|
||||||
<div style="display: flex; align-items: center; gap: 5px;">
|
|
||||||
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
|
|
||||||
<label for="showPassword">Show Password</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Added margin-top above this button -->
|
|
||||||
<div class="btn-container wifi">
|
|
||||||
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function(){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
const BLE_SERVER_NAME = "ATALIGHTS"; // Replace with your server name
|
|
||||||
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0"; // Replace with your service UUID
|
|
||||||
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
|
|
||||||
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
|
|
||||||
|
|
||||||
let bleDevice = null;
|
|
||||||
let bleCharacteristic1 = null;
|
|
||||||
let bleCharacteristic2 = null;
|
|
||||||
let bleConnected = false;
|
|
||||||
|
|
||||||
const WIFI_STAT = { WIFI_DISCONNECTED:0, WIFI_BAD_CREDS:1, WIFI_NO_AP:2, WIFI_CONNECTED:3 };
|
|
||||||
|
|
||||||
const updatePacket = {
|
|
||||||
wifiConnected: false,
|
|
||||||
wifiOnline: false,
|
|
||||||
wifiIP: [0, 0, 0, 0],
|
|
||||||
currVersion: [0, 0, 0],
|
|
||||||
newVersion: [0, 0, 0]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to automatically start the connection process when the page loads
|
|
||||||
function autoStart() {
|
|
||||||
document.getElementById('input-DeviceName').value = BLE_SERVER_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call autoStart when the page loads
|
|
||||||
window.addEventListener('DOMContentLoaded', autoStart);
|
|
||||||
|
|
||||||
function compareVersions(a, b){
|
|
||||||
for(let i=0;i<3;i++){ if(a[i] > b[i]) return 1; if(a[i] < b[i]) return -1; }
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log messages to the textarea
|
|
||||||
function logMessage(message) {
|
|
||||||
const logArea = document.getElementById('logArea');
|
|
||||||
logArea.value += message + '\n';
|
|
||||||
logArea.scrollTop = logArea.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to scan for BLE devices
|
|
||||||
async function scanForDevices() {
|
|
||||||
logMessage('Scanning for BLE devices...');
|
|
||||||
try {
|
|
||||||
const device = await navigator.bluetooth.requestDevice({
|
|
||||||
acceptAllDevices: true,
|
|
||||||
optionalServices: [BLE_SERVICE_UUID]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (device) {
|
|
||||||
logMessage(`Found device: ${device.name || "Unnamed"} (ID: ${device.id})`);
|
|
||||||
} else {
|
|
||||||
logMessage('No devices found.');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logMessage(`Scan failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to connect to the BLE server
|
|
||||||
async function connectToBle() {
|
|
||||||
if(!navigator.bluetooth){
|
|
||||||
logMessage('Web Bluetooth not supported in this browser.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
bleDevice = await navigator.bluetooth.requestDevice({
|
|
||||||
filters: [{ name: document.getElementById('input-DeviceName').value }],
|
|
||||||
optionalServices: [BLE_SERVICE_UUID]
|
|
||||||
});
|
|
||||||
|
|
||||||
//logMessage(`Connecting to ${bleDevice.name}`);
|
|
||||||
const server = await bleDevice.gatt.connect();
|
|
||||||
//await server.setPreferredMtu(247); // Request larger MTU size
|
|
||||||
|
|
||||||
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
|
|
||||||
|
|
||||||
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
|
|
||||||
|
|
||||||
// Subscribe to notifications
|
|
||||||
//await bleCharacteristic1.startNotifications();
|
|
||||||
|
|
||||||
// Add event listener for incoming notifications
|
|
||||||
//bleCharacteristic1.addEventListener('characteristicvaluechanged', handleChar1Notifications);
|
|
||||||
|
|
||||||
|
|
||||||
//logMessage('Getting characteristic...');
|
|
||||||
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
|
|
||||||
|
|
||||||
// Subscribe to notifications
|
|
||||||
await bleCharacteristic2.startNotifications();
|
|
||||||
|
|
||||||
// Add event listener for incoming notifications
|
|
||||||
bleCharacteristic2.addEventListener('characteristicvaluechanged', (event) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const decodedValue = decoder.decode(value);
|
|
||||||
logMessage('--> ' + decodedValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
bleConnected = true;
|
|
||||||
// Auto-reconnect / state reset handler
|
|
||||||
bleDevice.addEventListener('gattserverdisconnected', handleDisconnect);
|
|
||||||
document.getElementById('bleConnectBtn').disabled = true;
|
|
||||||
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
|
|
||||||
document.getElementById('status-ble-connection').textContent ="BLE Status: Connected";
|
|
||||||
document.getElementById('wifiConnectBtn').disabled = false;
|
|
||||||
document.getElementById('checkStatusBtn').disabled = false;
|
|
||||||
logMessage(`Connected to ${bleDevice.name}`);
|
|
||||||
|
|
||||||
await readPacket();
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
if (error.message.includes("cancelled")) {
|
|
||||||
logMessage("Connection cancelled by user.");
|
|
||||||
} else {
|
|
||||||
logMessage(`Connection failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function wifiConnect() {
|
|
||||||
const ssid = document.getElementById('wifissid').value;
|
|
||||||
const password = document.getElementById('wifipassword').value;
|
|
||||||
if (ssid && password) {
|
|
||||||
// Send credentials to the device (retain original spacing format expected by firmware)
|
|
||||||
const jsonString = ' {"ssid":"' + ssid.trim() + '","pass":"' + password + '"} ';
|
|
||||||
await sendPacket('wifi-connect' + jsonString);
|
|
||||||
|
|
||||||
await readPacket();
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
} else {
|
|
||||||
alert('Please enter both SSID and password.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkStatus() {
|
|
||||||
if (bleCharacteristic1) {
|
|
||||||
await readPacket();
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
} else {
|
|
||||||
logMessage('BLE device not connected.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkVersion() {
|
|
||||||
await sendPacket('version-check');
|
|
||||||
// loop and monitor the the updatePacket.newVersion
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
let success = false;
|
|
||||||
for (let i = 0; i < 20; i++) {
|
|
||||||
await readPacket();
|
|
||||||
if (updatePacket.newVersion[0] > 0) {
|
|
||||||
success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
}
|
|
||||||
if(success) {
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
logMessage("New Version: Available");
|
|
||||||
} else {
|
|
||||||
logMessage("New Version: Not Available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startUpgrade() {
|
|
||||||
try {
|
|
||||||
await sendPacket('upgrade-start');
|
|
||||||
logMessage("Upgrade Starting... Please wait.");
|
|
||||||
} catch (error) {
|
|
||||||
logMessage(`Error starting upgrade: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDisconnect(){
|
|
||||||
bleConnected = false;
|
|
||||||
document.querySelector('.status-indicator-ble').style.backgroundColor = 'gray';
|
|
||||||
document.getElementById('status-ble-connection').textContent = 'BLE Status: Disconnected';
|
|
||||||
document.getElementById('bleConnectBtn').disabled = false;
|
|
||||||
document.getElementById('checkStatusBtn').disabled = true;
|
|
||||||
document.getElementById('wifiConnectBtn').disabled = true;
|
|
||||||
logMessage('BLE disconnected');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendPacket(packetMsg) {
|
|
||||||
if (!bleCharacteristic1) {
|
|
||||||
console.log("Cannot send packet: Not connected to BLE server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxRetries = 3;
|
|
||||||
const retryDelay = 1000; // 1 second
|
|
||||||
let attempt = 0;
|
|
||||||
|
|
||||||
while (attempt < maxRetries) {
|
|
||||||
try {
|
|
||||||
//logMessage(`Sending request: ${packetMsg} (Attempt ${attempt + 1})`);
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
await bleCharacteristic1.writeValueWithResponse(encoder.encode(packetMsg));
|
|
||||||
//console.log("Request sent successfully");
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to send packet: ${error.message}`);
|
|
||||||
attempt++;
|
|
||||||
if (attempt < maxRetries) {
|
|
||||||
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
|
|
||||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
||||||
} else {
|
|
||||||
console.error("Max retries reached. Failed to send request.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readPacket() {
|
|
||||||
if (!bleCharacteristic1) {
|
|
||||||
console.log("Cannot read packet: Not connected to BLE server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxRetries = 3;
|
|
||||||
const retryDelay = 1000; // 1 second
|
|
||||||
let attempt = 0;
|
|
||||||
|
|
||||||
while (attempt < maxRetries) {
|
|
||||||
try {
|
|
||||||
const value = await bleCharacteristic1.readValue();
|
|
||||||
const data = new Uint8Array(value.buffer);
|
|
||||||
if (data.length === 12) {
|
|
||||||
updatePacket.wifiConnected = data[0] !== 0;
|
|
||||||
updatePacket.wifiOnline = data[1] !== 0;
|
|
||||||
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
|
|
||||||
updatePacket.currVersion = [data[6], data[7], data[8]];
|
|
||||||
updatePacket.newVersion = [data[9], data[10], data[11]];
|
|
||||||
|
|
||||||
//processUpdatePacket(updatePacket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("Invalid packet length");
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to read packet: ${error.message}`);
|
|
||||||
attempt++;
|
|
||||||
if (attempt < maxRetries) {
|
|
||||||
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
|
|
||||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
||||||
} else {
|
|
||||||
console.error("Max retries reached. Failed to read packet.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process update packet
|
|
||||||
function processUpdatePacket(packet) {
|
|
||||||
// Process the packet data
|
|
||||||
//console.log("Processing update packet:", packet);
|
|
||||||
if(packet.wifiConnected === true) {
|
|
||||||
if(packet.wifiConnected && packet.wifiIP[0] > 0) {
|
|
||||||
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected (' + packet.wifiIP.join('.') + ')';
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected';
|
|
||||||
}
|
|
||||||
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'green';
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-wifi-client').textContent = 'Wifi Client: ...';
|
|
||||||
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'gray';
|
|
||||||
}
|
|
||||||
|
|
||||||
if(packet.wifiOnline === true) {
|
|
||||||
document.getElementById('status-internet').textContent = 'Online';
|
|
||||||
document.querySelector('.status-indicator-internet').style.backgroundColor = 'green';
|
|
||||||
document.getElementById('checkVersionBtn').disabled = false;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-internet').textContent = 'Offline';
|
|
||||||
document.querySelector('.status-indicator-internet').style.backgroundColor = 'gray';
|
|
||||||
document.getElementById('checkVersionBtn').disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.currVersion[0] > 0) {
|
|
||||||
document.getElementById('status-current-version').textContent = 'Curr Version: ' + packet.currVersion.join('.');
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-current-version').textContent = 'Curr Version: ...';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.newVersion[0] > 0) {
|
|
||||||
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
|
|
||||||
document.getElementById('checkVersionBtn').disabled = true;
|
|
||||||
|
|
||||||
|
|
||||||
logMessage("Latest Update is: " + packet.newVersion.join('.'));
|
|
||||||
if(packet.wifiOnline && compareVersions(packet.newVersion, packet.currVersion) > 0) {
|
|
||||||
//enable start upgrade button
|
|
||||||
logMessage("New Version: Available");
|
|
||||||
document.getElementById('startUpgradeBtn').disabled = false;
|
|
||||||
} else {
|
|
||||||
//disable start upgrade button
|
|
||||||
document.getElementById('startUpgradeBtn').disabled = true;
|
|
||||||
logMessage("New Version: Not Available");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById('status-new-version').textContent = 'New Version: ...';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//BLE_Characteristic.addEventListener('characteristicvaluechanged', handleNotifications);
|
|
||||||
function handleChar1Notifications(event) {
|
|
||||||
const data = new Uint8Array(event.data);
|
|
||||||
|
|
||||||
if (data.length !== 12) { // 1 byte for id, 4 bytes for booleans, 4 bytes for wifiIP, 3 bytes for currVersion, 3 bytes for newVersion
|
|
||||||
console.log("Invalid packet length");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update existing updatePacket object instead of creating new one
|
|
||||||
updatePacket.wifiConnected = data[0] !== 0;
|
|
||||||
updatePacket.wifiOnline = data[1] !== 0;
|
|
||||||
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
|
|
||||||
updatePacket.currVersion = [data[6], data[7], data[8]];
|
|
||||||
updatePacket.newVersion = [data[9], data[10], data[11]];
|
|
||||||
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listeners for buttons
|
|
||||||
function togglePasswordVisibility() {
|
|
||||||
const passwordInput = document.getElementById('wifipassword');
|
|
||||||
passwordInput.type = this.checked ? 'text' : 'password';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial call to process the update packet with defaults
|
|
||||||
processUpdatePacket(updatePacket);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
{{NAVBAR}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>BLE Config</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link href="global-style.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>App Control - Bluetooth Configuration</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Color Picker</title>
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="pageTitle" ></div>
|
|
||||||
<div class="secTitle"> <h1 class="secTitleFont">Countdown Animation</h1> </div>
|
|
||||||
<div class="secCntrls" height: 200px>
|
|
||||||
<label for="dropList">Animation: </label>
|
|
||||||
<select class="dropList">
|
|
||||||
<option>Custom 1</option>
|
|
||||||
<option>Custom 2</option>
|
|
||||||
<option>Custom 3</option>
|
|
||||||
<option>Custom 4</option>
|
|
||||||
<option>Rainbow</option>
|
|
||||||
<option>Chasing</option>
|
|
||||||
<option>Meteors</option>
|
|
||||||
<option>Fire (red)</option>
|
|
||||||
</select>
|
|
||||||
<label for="color-picker">Color: </label>
|
|
||||||
<input type="color" id="color-picker">
|
|
||||||
</div>
|
|
||||||
<div class="secTitle"> <h1 class="secTitleFont"> Selection Screen</h1> </div>
|
|
||||||
<div class="secCntrls">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
#include "command_processor.h"
|
|
||||||
|
|
||||||
int boothState[16];
|
|
||||||
|
|
||||||
void process_command(int* data, int paramCount)
|
|
||||||
{
|
|
||||||
bool reply = false;
|
|
||||||
|
|
||||||
switch(*data){
|
|
||||||
case COMM_NULL:
|
|
||||||
break;
|
|
||||||
case COMM_REBOOT:
|
|
||||||
break;
|
|
||||||
case COMM_DIN:
|
|
||||||
break;
|
|
||||||
case COMM_DOUT:
|
|
||||||
break;
|
|
||||||
case COMM_RELAY:
|
|
||||||
break;
|
|
||||||
case COMM_AOUT:
|
|
||||||
break;
|
|
||||||
case COMM_AIN:
|
|
||||||
break;
|
|
||||||
case COMM_ECHO:
|
|
||||||
break;
|
|
||||||
case COMM_LED_STATUS:
|
|
||||||
break;
|
|
||||||
case COMM_GET_TEMP:
|
|
||||||
break;
|
|
||||||
case COMM_PLAY:
|
|
||||||
break;
|
|
||||||
case COMM_RF:
|
|
||||||
break;
|
|
||||||
case COMM_NEST:
|
|
||||||
break;
|
|
||||||
case COMM_BOOTH_STATE:
|
|
||||||
break;
|
|
||||||
case COMM_STRIP1:
|
|
||||||
|
|
||||||
break;
|
|
||||||
case COMM_STRIP2:
|
|
||||||
break;
|
|
||||||
case COMM_OLED:
|
|
||||||
break;
|
|
||||||
default:;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(reply){
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
int AppStates[16];
|
|
||||||
|
|
||||||
int getEvent(int index, int& ev){
|
|
||||||
return ev[index];
|
|
||||||
|
|
||||||
}
|
|
||||||
enum BoothStates = { }
|
|
||||||
void RunAnimation(int stateIndex){
|
|
||||||
switch (AppStates[stateIndex]){
|
|
||||||
case 0:
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
#ifndef _COMMAND_PROCESSOR_H
|
|
||||||
#define _COMMAND_PROCESSOR_H
|
|
||||||
|
|
||||||
extern int boothState[16];
|
|
||||||
|
|
||||||
void process_command(int* data, int paramCount);
|
|
||||||
|
|
||||||
enum COMM_FUNC{
|
|
||||||
COMM_NULL = 0,
|
|
||||||
COMM_REBOOT = 1,
|
|
||||||
COMM_DIN = 2,
|
|
||||||
COMM_DOUT = 3,
|
|
||||||
COMM_RELAY = 4,
|
|
||||||
COMM_AOUT = 5,
|
|
||||||
COMM_AIN = 6,
|
|
||||||
COMM_ECHO = 7,
|
|
||||||
COMM_LED_STATUS = 8,
|
|
||||||
COMM_GET_TEMP = 9,
|
|
||||||
COMM_PLAY = 10,
|
|
||||||
COMM_RF = 11,
|
|
||||||
|
|
||||||
COMM_NEST = 30,
|
|
||||||
COMM_BOOTH_STATE = 50,
|
|
||||||
COMM_STRIP1 = 75,
|
|
||||||
COMM_STRIP2 = 100,
|
|
||||||
COMM_OLED = 125
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
|
|
||||||
<title>Event Container</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 40vh;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
.outer-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 600px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 5px 20px 5px 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
width:90%;
|
|
||||||
/*margin:0 auto;*/
|
|
||||||
}
|
|
||||||
.select-container {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.row > * {
|
|
||||||
flex: 0 0 calc(50% - 20px); /* Adjusted width for better fit */
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
select, input[type="range"], input[type="color"] {
|
|
||||||
width:90%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
select{
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
input[type="color"] {
|
|
||||||
height: 30px;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
.row:last-child {
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.checkbox-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left;
|
|
||||||
}
|
|
||||||
.try-button-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: right;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.center-text {
|
|
||||||
text-align: center;
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="outer-container">
|
|
||||||
<div class="container">
|
|
||||||
<legend class="center-text">Event0</legend><br>
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<label for="animation-list" id="list-label">Event:</label>
|
|
||||||
<select id="animation-list">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="color-picker">Color:</label>
|
|
||||||
<input type="color" id="color-picker">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<label for="speed" id="speed-label">Speed:</label>
|
|
||||||
<input type="range" id="speed" min="1" max="10">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="range1">Col. Range:</label>
|
|
||||||
<input type="range" id="range1" min="0" max="100">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<label for="param1" id="param1-label">Param1:</label>
|
|
||||||
<input type="range" id="param1" min="0" max="50">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="param2" id="param2-label">Param2:</label>
|
|
||||||
<input type="range" id="param2" min="0" max="100">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="checkbox-container">
|
|
||||||
<input type="checkbox" id="check1">Mix</input>
|
|
||||||
<input type="checkbox" id="check1">Fwd</input>
|
|
||||||
</div>
|
|
||||||
<div class="try-button-container">
|
|
||||||
<button id="try-button">Try</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
#ifndef _FIRMWARE_HTML_H
|
|
||||||
#define _FIRMWARE_HTML_H
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
const char firmware_html_page[] PROGMEM = R"rawliteral(
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Firmware Update</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<style>
|
|
||||||
h1{
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
padding: 0;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 700px;
|
|
||||||
min-width: 300px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.outer-container {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 4px 20px 4px 20px
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 5px 20px 10px 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
width:90%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.center-text {
|
|
||||||
text-align: center;
|
|
||||||
font-size: medium;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
.row > * {
|
|
||||||
flex: 1;
|
|
||||||
width: calc(50% - 10px);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.row > *:last-child {
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
progress{
|
|
||||||
flex: 1;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
#lblprogress{
|
|
||||||
flex: 0;
|
|
||||||
}
|
|
||||||
.progress-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
button{
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-end;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1 name="h1element">Firmware Update</h1>
|
|
||||||
<div class="outer-container">
|
|
||||||
<div class="container">
|
|
||||||
<legend class="center-text">Local Update</legend>
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<input type="file" id="update-file" name="update-file">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="submit-update-local" onclick="uploadFile()">Update!</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="progress-container">
|
|
||||||
<label id="lblprogress" for="firm-progress">Progress:</label>
|
|
||||||
<progress id="firm-progress" value="0" max="100"></progress>
|
|
||||||
<label id="lbl-firm-progress" for="firm-progress">- - - -</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<label>File name: "ata_fw_booth_xxx.bin" or "ata_fs_booth_xxx.bin"</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const fileInput = document.getElementById('update-file');
|
|
||||||
const progressBar = document.getElementById('firm-progress');
|
|
||||||
const progressLabel = document.getElementById('lbl-firm-progress');
|
|
||||||
const submitButton = document.getElementById('submit-update-local');
|
|
||||||
|
|
||||||
//submitButton.addEventListener('click', uploadFile);
|
|
||||||
|
|
||||||
function uploadFile() {
|
|
||||||
event.preventDefault(); // Prevent the default form submission
|
|
||||||
|
|
||||||
const file = fileInput.files[0];
|
|
||||||
const url = '/update'; // Replace with the actual upload URL
|
|
||||||
|
|
||||||
// File Checks
|
|
||||||
//*********************************************************
|
|
||||||
// Check file extension
|
|
||||||
if (!file.name.toLowerCase().endsWith('.bin')) {
|
|
||||||
alert('Please select a file with the ".bin" extension.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check filename prefix
|
|
||||||
if (!file.name.toLowerCase().startsWith('fwata') && !file.name.toLowerCase().startsWith('lfsata')) {
|
|
||||||
alert('Please select a file with a filename starting with "fwata" or "lfsata".');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check file size
|
|
||||||
const maxSizeBytes = 2.7 * 1024 * 1024; // 2.75Mb in bytes
|
|
||||||
if (file.size > maxSizeBytes) {
|
|
||||||
alert('Please select a file with a size not exceeding 2.7Mb.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//*********************************************************
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file-size', file.size); // Include the file size as a parameter
|
|
||||||
formData.append('update-file', file);
|
|
||||||
let s = "file-size: " + file.size;
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.upload.addEventListener('progress', (event) => {
|
|
||||||
if (event.lengthComputable) {
|
|
||||||
const progressPercent = Math.round((event.loaded * 100) / event.total);
|
|
||||||
progressBar.value = progressPercent;
|
|
||||||
progressLabel.innerHTML =progressBar.value + "%";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function () {
|
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
||||||
console.log("received status: %d", xhr.status);
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
// Upload completed successfully
|
|
||||||
alert('Upload completed!');
|
|
||||||
progressLabel.innerHTML = "Completed!";
|
|
||||||
} else if (xhr.status === 500) {
|
|
||||||
// Request was aborted (server-side)
|
|
||||||
alert('Upload aborted by the server.');
|
|
||||||
progressLabel.innerHTML = "Aborted!";
|
|
||||||
} else {
|
|
||||||
// Handle other error cases
|
|
||||||
alert('An error occurred during the upload.');
|
|
||||||
progressLabel.innerHTML = "Error!";
|
|
||||||
}
|
|
||||||
submitButton.disabled = false;
|
|
||||||
progressBar.value = 0;
|
|
||||||
progressLabel.innerHTML = "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.open('POST', url, true);
|
|
||||||
xhr.send(formData);
|
|
||||||
submitButton.disabled = true;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)rawliteral";
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
#ifndef CERT_H
|
|
||||||
|
|
||||||
#define CERT_H
|
|
||||||
|
|
||||||
const char * github_rootCACertificate = \
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n"
|
|
||||||
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
|
|
||||||
"d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n"
|
|
||||||
"ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n"
|
|
||||||
"MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n"
|
|
||||||
"LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n"
|
|
||||||
"RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n"
|
|
||||||
"+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n"
|
|
||||||
"PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n"
|
|
||||||
"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n"
|
|
||||||
"Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n"
|
|
||||||
"hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n"
|
|
||||||
"EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n"
|
|
||||||
"MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n"
|
|
||||||
"FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n"
|
|
||||||
"nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n"
|
|
||||||
"eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n"
|
|
||||||
"hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n"
|
|
||||||
"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n"
|
|
||||||
"vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n"
|
|
||||||
"+OkuE6N36B9K\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
.container{
|
|
||||||
color:blue;
|
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
grid-template-columns: 50% 1fr 1fr;
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<head>
|
|
||||||
<link rel="stylesheet" href="gridstyles.css">
|
|
||||||
<title> CSS Grid</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="flow"> Test text 1</div>
|
|
||||||
<div class="flow"> Test text 1</div>
|
|
||||||
<div class="flow"> Test text 1</div>
|
|
||||||
<div class="flow"> Test text 1</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
1
temporary/Temp/hue-select-min.js
vendored
1
temporary/Temp/hue-select-min.js
vendored
@ -1 +0,0 @@
|
|||||||
class HueSelect extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.shadowRoot.innerHTML='\n\t\t<style>\n\t\t .color-option {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tcursor: pointer;\n\t\t \t}\n\t\t \t.color-patch {\n\t\t\t\twidth: 30px;\n\t\t\t\theight: 30px;\n\t\t\t\tmargin-top: 18px;\n\t\t\t\tmargin-right: 10px;\n\t\t\t\tborder: 1px solid #000;\n\t\t \t}\n\t\t \t.hue-label {\n\t\t\t\tmargin-top: 18px;\n\t\t\t\twidth: 90px;\n\t\t\t\ttext-align: left;\n\t \t\t}\n\t\t\t.label-container {\n display: flex;\n align-items: center;\n }\n\t\t\t.dropdown {\n\t\t\t\tposition: relative;\n\t\t\t\tdisplay: inline-block;\n\t\t\t}\n\t\t \t.dropdown-content {\n\t\t\t\tdisplay: none;\n\t\t\t\tposition: absolute;\n\t\t\t\tbackground-color: #f9f9f9;\n\t\t\t\tmin-width: 180px;\n\t\t\t\tbox-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);\n\t\t\t\tz-index: 1;\n\t\t\t\tmax-height: 300px;\n\t\t\t\toverflow-y: auto;\n\t\t \t}\n\t\t\t.dropdown-content .color-option {\n\t\t\t\tpadding: 8px 12px;\n\t\t\t}\n\t\t \t.dropdown:hover .dropdown-content {\n\t\t\t\tdisplay: block;\n\t\t \t}\n\t\t</style>\n\t\t<div class="dropdown">\n\t\t \t<div class="color-option" id="selectedColor">\n\t\t\t\t<span class="color-patch" style="background-color: hsl(0, 100%, 50%)"></span>\n\t\t\t\t<div class="label-container">\n\t\t\t\t\t<label class="hue-label" id="hue-label">0</label>\n\t\t\t\t</div>\n\t\t\t</div> \n\t\t \t<div class="dropdown-content" id="colorPicker">\n\t\t\t\t\x3c!-- The options will be added dynamically using JavaScript --\x3e\n\t\t \t</div>\n\t\t</div>\n\t ',this.currentHue=0,this.colorList,this.hueLabel=this.shadowRoot.getElementById("hue-label");this.shadowRoot.getElementById("selectedColor").querySelector(".color-patch").addEventListener("mouseenter",(()=>{this.shadowRoot.querySelector(".dropdown-content").style.display="block"})),window.addEventListener("click",(t=>{const e=this.shadowRoot.querySelector(".dropdown-content");t.target.closest(".dropdown")||(e.style.display="none")})),this.createColorOptions(),this.setHue(0)}generateColors(){const t=[];for(let e=0;e<=360;e+=10)360==e&&(e=359),t.push(e);return t.push(-1),t.push(-2),t}createColorOption(t){const e=document.createElement("div");e.classList.add("color-option");const o=document.createElement("span");o.classList.add("color-patch");const n=document.createElement("span");n.classList.add("color-text"),n.textContent=t;const s=document.createElement("span");if(s.classList.add("rgb-hex"),-2===t)o.style.backgroundColor="rgb(0,0,0)",s.innerHTML=" #000000";else if(-1===t)o.style.backgroundColor="rgb(255,255,255)",s.innerHTML=" #FFFFFF";else{o.style.backgroundColor=`hsl(${t}, 100%, 50%)`;const e=this.hslToRgb(t,100,50).toUpperCase();s.innerHTML=`<span> ${e}</span>`}return e.appendChild(o),e.appendChild(n),e.appendChild(s),e.addEventListener("click",(()=>this.handleColorSelection(t))),e}createColorOptions(){const t=this.shadowRoot.querySelector(".dropdown-content");this.colorList=this.generateColors(),this.colorList.forEach((e=>{const o=this.createColorOption(e);t.appendChild(o)}))}hslToRgb(t,e,o){let n,s,l;if(t/=360,o/=100,0===(e/=100))n=s=l=o;else{const r=(t,e,o)=>(o<0&&(o+=1),o>1&&(o-=1),o<1/6?t+6*(e-t)*o:o<.5?e:o<2/3?t+(e-t)*(2/3-o)*6:t),i=o<.5?o*(1+e):o+e-o*e,d=2*o-i;n=r(d,i,t+1/3),s=r(d,i,t),l=r(d,i,t-1/3)}const r=t=>{const e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e};return`#${r(n)}${r(s)}${r(l)}`}handleColorSelection(t){this.currentHue=t;const e=this.shadowRoot.getElementById("selectedColor").querySelector(".color-patch"),o=this.getRGBfromHue(t);e.style.backgroundColor=o,console.log(e.style.backgroundColor),this.setHueLabel(t),this.hideDropdown(),this.dispatchEvent(new CustomEvent("change",{detail:{hue:t,rgb:o}}))}getSelectedHue(){return this.currentHue}getSelectedRGB(){const t=this.shadowRoot.getElementById("selectedColor").textContent.trim();return parseFloat(t)}getRGBfromHue(t){return-1==t?"#FFFFFF":-2==t?"#000000":this.hslToRgb(t,100,50)}getSelectedRgb(){const t=this.getSelectedHue();return getRGBfromHue(t)}setHue(t){let e=t;-1===t||-2===t?e=t:(e=10*Math.round(t/10),e>=360&&(e=359),e<0&&(e=0)),this.currentHue=e,this.colorList.forEach((t=>{t===e&&this.handleColorSelection(e)})),this.hideDropdown()}hideDropdown(){this.shadowRoot.querySelector(".dropdown-content").style.display="none"}setHueLabel(t){this.hueLabel.textContent="Hue: "+t}}customElements.define("hue-select",HueSelect);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,205 +0,0 @@
|
|||||||
#ifndef _LED_ANIMATION_H
|
|
||||||
#define _LED_ANIMATION_H
|
|
||||||
|
|
||||||
#include "common/LEDStrip.h"
|
|
||||||
#include "led_strip.h"
|
|
||||||
#include "my_board.h"
|
|
||||||
#include <functional>
|
|
||||||
#include "common/HSVTable.h"
|
|
||||||
|
|
||||||
#define HUE_MAX 767
|
|
||||||
|
|
||||||
enum EXIT_TYPE { EXIT_NORMAL, EXIT_FROM_NOTIFY, EXIT_FINISHED, EXIT_TIMEOUT };
|
|
||||||
|
|
||||||
|
|
||||||
#define HUE_CALC_METHOD 0
|
|
||||||
// TODO Fix hue range... 0-359 or 0-360;
|
|
||||||
#if HUE_CALC_METHOD == 0
|
|
||||||
#define HUEtoRGB(x) trueHSV(x)
|
|
||||||
#elif HUE_CALC_METHOD == 1
|
|
||||||
#define HUEtoRGB(x) sineHSV(x)
|
|
||||||
#elif HUE_CALC_METHOD == 2
|
|
||||||
#define HUEtoRGB(x) pwrHSV(x)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define RGBtoHUE(x) RGBToHSV(x)
|
|
||||||
|
|
||||||
#define INFINITE_LOOP 0
|
|
||||||
|
|
||||||
|
|
||||||
enum DIR_TYPE { DT_REV, DT_FWD, DT_BOTH };
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int RampUpTime;
|
|
||||||
int msRampDownTime;
|
|
||||||
int msHoldTime;
|
|
||||||
int msFreq;
|
|
||||||
float minDuty;
|
|
||||||
float maxDuty;
|
|
||||||
int interval;
|
|
||||||
uint8_t pwr;
|
|
||||||
}params_whiteFill;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int hue;
|
|
||||||
int cooling;
|
|
||||||
int sparking;
|
|
||||||
int interval;
|
|
||||||
uint8_t pwr;
|
|
||||||
}params_fire_hue;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
rgbpixel_t col1;
|
|
||||||
rgbpixel_t col2;
|
|
||||||
uint8_t range; /* hue range 0-127*/
|
|
||||||
int density;
|
|
||||||
uint8_t step; /* range step inteval*/
|
|
||||||
int interval;
|
|
||||||
uint8_t pwr;
|
|
||||||
}params_rain;
|
|
||||||
|
|
||||||
#define ANIMTESTMODE_TIMEOUT (20 * 1000) // 20 secs
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int EventTestCountdown = 0;
|
|
||||||
int EventsIndex; // upto 8 events based on anim-events.json
|
|
||||||
int EventsCount; // upto 8 events based on anim-events.json
|
|
||||||
int PopupAnimIndex;
|
|
||||||
bool busy;
|
|
||||||
bool repeat;
|
|
||||||
int countStatus;
|
|
||||||
}ANIM_STATUS;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool busy;
|
|
||||||
int dslrCountStatus;
|
|
||||||
}WHITE_FILL_STATUS;
|
|
||||||
|
|
||||||
extern ANIM_STATUS animStatus;
|
|
||||||
extern WHITE_FILL_STATUS whiteStatus;
|
|
||||||
|
|
||||||
float calculateSteps(int msFreq, float LEDCount, int msFillTime);
|
|
||||||
void GetOptimizedStepInterval(float &steps, int &interval, int LEDCount, int msFillTime, int startInterval, float tolerance);
|
|
||||||
|
|
||||||
float GetNextPalletHue(void);
|
|
||||||
void Init_Hue_Range_Pallet(float hue1, float hue2, int colSteps);
|
|
||||||
|
|
||||||
float GetHueRange(float hue1, float hue2);
|
|
||||||
float GetHueRangeWeb(float hue1, float hue2);
|
|
||||||
|
|
||||||
int CalcEventInterval(float speed, int min, int max);
|
|
||||||
|
|
||||||
/******************************** ANIMATION LOOP HELPER *********************************
|
|
||||||
* All Animations should use this lopp helper.
|
|
||||||
* Logic for monitoring exit notifications
|
|
||||||
* Timeout in mSec can be set to exit
|
|
||||||
**************************************************************************************/
|
|
||||||
EXIT_TYPE Animation_Loop(ANIM_STATUS &stateMon, int msFreq, TickType_t duration, std::function<int()> callback=NULL);
|
|
||||||
|
|
||||||
// min:0-100, max:0-100
|
|
||||||
//int Const_White_Fill(WHITE_FILL_STATUS &status, int msRampUpTime, int msHoldTime, int msRampDownTime, int msFreq, float minDuty, float maxDuty);
|
|
||||||
int Const_White_Fill(WHITE_FILL_STATUS &status, FRONT_LIGHT light , float delayFactor, int countDown, int msFreq);
|
|
||||||
//int Const_White_Fill_HelioType(ANIM_STATUS &stateMon, float startPoint, int msRampUpTime, int msRampDownTime, int msHoldTime, int msFreq, float minDuty, float maxDuty);
|
|
||||||
|
|
||||||
EXIT_TYPE Animation_White_Fill_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
|
|
||||||
EXIT_TYPE Animation_Linear_Brighten(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
EXIT_TYPE Animation_Tick_Fill(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
|
|
||||||
|
|
||||||
/******************************** COLORED SNAKES *********************************
|
|
||||||
* cycles=0=infinite
|
|
||||||
* (check1) "mix"=false, all appearing sectors will be the same color until the next iteration
|
|
||||||
* "mix"=true, each meteor color will be different
|
|
||||||
* (check2) "roate", add rotation to animation
|
|
||||||
* (hue range) hue range (0-360)
|
|
||||||
* (param1) sectors
|
|
||||||
* (param2) colorCount, number of color divisions
|
|
||||||
**************************************************************************************/
|
|
||||||
EXIT_TYPE Animation_Snakes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0);
|
|
||||||
void drawSnakeMix(LEDSTRIP& strip, int sectors, float hue1, float hue2, int colorCount);
|
|
||||||
|
|
||||||
|
|
||||||
/******************************** COLORED COMETS *********************************
|
|
||||||
*(check1) "mix"=false, all appearing comets will be the same color until the next iteration
|
|
||||||
* (check2) "mix"=true, each comets color will be different
|
|
||||||
* (param1) hue range (0-360)
|
|
||||||
* (param2) colorCount, number of color divisions
|
|
||||||
**************************************************************************************/
|
|
||||||
EXIT_TYPE Animation_Comets(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0);
|
|
||||||
|
|
||||||
void fadeToBlackBy(rgbpixel_t& pixel, uint8_t fadeAmount);
|
|
||||||
inline uint8_t nscale8(uint8_t i, uint8_t scale);
|
|
||||||
|
|
||||||
/******************************** COLORED SECTORS *********************************
|
|
||||||
* (check1) "mix"=false, all appearing sectors will be the same color until the next iteration
|
|
||||||
* (check2) "mix"=true, each meteor color will be different
|
|
||||||
* (param1) hue range (0-360)
|
|
||||||
* (param2) colorCount, number of color divisions
|
|
||||||
**************************************************************************************/
|
|
||||||
EXIT_TYPE Animation_Sectors(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0);
|
|
||||||
|
|
||||||
|
|
||||||
/******************************** COLORED DASHES *********************************
|
|
||||||
* (check1) "mix"=false, all appearing dashes will be the same color until the next iteration
|
|
||||||
* (check2) "mix"=true, each dash color will be different
|
|
||||||
* (param1) hue range (0-360)
|
|
||||||
* (param2) colorCount, number of color divisions
|
|
||||||
**************************************************************************************/
|
|
||||||
EXIT_TYPE Animation_Dashes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType=DT_BOTH, int cycles=0);
|
|
||||||
|
|
||||||
|
|
||||||
/******************************** HUE SPECTRUM *********************************
|
|
||||||
* (check1) "mix"=false, all appearing dashes will be the same color until the next iteration
|
|
||||||
* (check2) "mix"=true, each dash color will be different
|
|
||||||
* (param1) hue range (0-360)
|
|
||||||
* (param2) colorCount, number of color divisions
|
|
||||||
**************************************************************************************/
|
|
||||||
EXIT_TYPE Animation_Hue_Spectrum(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration);
|
|
||||||
EXIT_TYPE Animation_Hue_Spectrum_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration);
|
|
||||||
void Fill_Hue_Spectrum(LEDSTRIP& strip, float hue1, float hue2);
|
|
||||||
void Fill_Hue_Spectrum_Mirrored(LEDSTRIP& strip, float hue1, float hue2);
|
|
||||||
|
|
||||||
|
|
||||||
/************************ RAINBOW *********************/
|
|
||||||
EXIT_TYPE Animation_Rainbow(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration);
|
|
||||||
|
|
||||||
/************************ PULSE COLOR *********************/
|
|
||||||
EXIT_TYPE Animation_Pulse_Color_Cycling(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
|
|
||||||
|
|
||||||
/**************** FIRE ****************/
|
|
||||||
enum FIRE_COLOR { RED_FIRE, GREEN_FIRE, BLUE_FIRE};
|
|
||||||
//void Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event, FIRE_COLOR fire, TickType_t duration);
|
|
||||||
EXIT_TYPE Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
void LEDStrip_FireInit(LEDSTRIP& strip);
|
|
||||||
void Fire_Update( LEDSTRIP& strip, FIRE_COLOR fire, int Cooling, int Sparking);
|
|
||||||
rgbpixel_t GetPixelHeatColor ( FIRE_COLOR fire, uint8_t temperature);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/************************ *********************/
|
|
||||||
void Animation_Splash(LEDSTRIP& strip);
|
|
||||||
void Animation_SparkleColor(LEDSTRIP& strip, int msFreq);
|
|
||||||
void Animation_SparkleHue(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
|
|
||||||
EXIT_TYPE Animation_Serial_Test2(LEDSTRIP& strip, int msFreq, const char* s1, const char* s2);
|
|
||||||
// msTransTime(Duration), msFreq(update interval)
|
|
||||||
void Animation_TransTo_Color(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msTransTime, int msFreq, rgbpixel_t col);
|
|
||||||
|
|
||||||
|
|
||||||
void drawSectorsHueSingle(LEDSTRIP& strip, rgbpixel_t col, float hue1, float hue2, int sectors) ;
|
|
||||||
void drawSectorsHueMix(LEDSTRIP& strip, float hue1, float hue2, int sectors, int colorCount);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/************************ TEST ANIMATIONS *********************/
|
|
||||||
EXIT_TYPE Animation_SetPixel(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
EXIT_TYPE Animation_Clear(LEDSTRIP& strip, ANIMATION_EVENT& event);
|
|
||||||
|
|
||||||
|
|
||||||
rgbpixel_t GetColorFromHue(int hue);
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,564 +0,0 @@
|
|||||||
#include "led_strip.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
#include <FS.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include "common/led_animation.h"
|
|
||||||
#include "common/LEDStrip.h"
|
|
||||||
#include "global.h"
|
|
||||||
#include "common/neo_colors.h"
|
|
||||||
#include "common/HSVTable.h"
|
|
||||||
#include "JsonConstrain.h"
|
|
||||||
#include "my_board.h"
|
|
||||||
#include "luma_master.h"
|
|
||||||
#include "common/luma-stiks.h"
|
|
||||||
|
|
||||||
#define STRIP1_PIN RGBLED1_Pin
|
|
||||||
#define STRIP2_PIN RGBLED2_Pin
|
|
||||||
|
|
||||||
#define POWERUP_TIME 3000
|
|
||||||
|
|
||||||
static const char* tag = "led_strip";
|
|
||||||
|
|
||||||
ANIMATION_PROPS animProps;
|
|
||||||
|
|
||||||
LEDSTRIP *strip1;
|
|
||||||
LEDSTRIP *strip2;
|
|
||||||
TaskHandle_t Strip1_Task_Handle;
|
|
||||||
TaskHandle_t Strip2_Task_Handle;
|
|
||||||
TaskHandle_t FrontLight_Task_Handle;
|
|
||||||
|
|
||||||
QueueHandle_t eventQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) );
|
|
||||||
QueueHandle_t whiteQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) );
|
|
||||||
|
|
||||||
ANIMATION_EVENT lastNormalEventMsg;
|
|
||||||
ANIMATION_EVENT eventMsg;
|
|
||||||
|
|
||||||
LUMA_PACKET TempLumaPacket;
|
|
||||||
|
|
||||||
int CurrentRunningEventIndex = -1;
|
|
||||||
|
|
||||||
|
|
||||||
void Init_LED_Devices(BOARD_PINS boardPins)
|
|
||||||
{
|
|
||||||
ESP_LOGD(tag, "Strips Initialization entered...");
|
|
||||||
File file = LittleFS.open("/cfg/led-devices.json");
|
|
||||||
|
|
||||||
if(!file){
|
|
||||||
ESP_LOGE(tag,"Error opening led-devices.json!");
|
|
||||||
file.close();
|
|
||||||
}else{
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
file.close();
|
|
||||||
if(error){ ESP_LOGE(tag, "led-devices.json deserialize error!.."); return;}
|
|
||||||
|
|
||||||
// ********** STRIP1 ********************************
|
|
||||||
JsonObject stripJson = doc["strip1"];
|
|
||||||
if(jsonConstrainBool(tag, stripJson, "en", false)){
|
|
||||||
int port = jsonConstrain<int>(tag, stripJson, "i2s-ch", 0, 1, 0);
|
|
||||||
int size = jsonConstrain<int>(tag, stripJson, "size", 1, 200, 10);
|
|
||||||
//int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED1_Pin);
|
|
||||||
LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() );
|
|
||||||
int shift = jsonConstrain<int>(tag, stripJson, "shift", -size, size, 0);
|
|
||||||
int offset = jsonConstrain<int>(tag, stripJson, "offset", -size, size, 0);
|
|
||||||
int core = jsonConstrain<int>(tag, stripJson, "core", 0, 1, 0);
|
|
||||||
int powerDiv = jsonConstrain<int>(tag, stripJson, "power-div", 0, 3, 0);
|
|
||||||
|
|
||||||
strip1 = new LEDSTRIP(port, size, boardPins.rgb1, order, shift, offset);
|
|
||||||
strip1->setPowerDiv(powerDiv);
|
|
||||||
ESP_LOGI(tag,"Strip1 initialized.");
|
|
||||||
ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset);
|
|
||||||
ESP_LOGD(tag," Strip1 Task Creating...Core: %d", core);
|
|
||||||
xTaskCreatePinnedToCore(Strip1_Control_Task, "LED_Strip1_Task", 1024*10, NULL, 1, &Strip1_Task_Handle, core);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ********** STRIP2 ********************************
|
|
||||||
stripJson.clear();
|
|
||||||
stripJson = doc["strip2"];
|
|
||||||
if(jsonConstrainBool(tag, stripJson, "en", false)){
|
|
||||||
int port = jsonConstrain<int>(tag, stripJson, "i2s-ch", 0, 1, 0);
|
|
||||||
int size = jsonConstrain<int>(tag, stripJson, "size", 1, 200, 10);
|
|
||||||
//int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED2_Pin);
|
|
||||||
LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() );
|
|
||||||
int shift = jsonConstrain<int>(tag, stripJson, "shift", -size, size, 0);
|
|
||||||
int offset = jsonConstrain<int>(tag, stripJson, "offset", -size, size, 0);
|
|
||||||
int core2 = jsonConstrain<int>(tag, stripJson, "core", 0, 1, 0);
|
|
||||||
int powerDiv = jsonConstrain<int>(tag, stripJson, "power-div", 0, 3, 0);
|
|
||||||
|
|
||||||
strip2 = new LEDSTRIP(port, size, boardPins.rgb2, order, shift, offset);
|
|
||||||
strip2->setPowerDiv(powerDiv);
|
|
||||||
ESP_LOGI(tag,"Strip2 initialized.");
|
|
||||||
ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset);
|
|
||||||
ESP_LOGD(tag,"Strip2 Task Creating...Core: %d", core2);
|
|
||||||
xTaskCreatePinnedToCore(Strip2_Control_Task, "LED_Strip2_Task", 1024*8, NULL, 1, &Strip2_Task_Handle, core2);
|
|
||||||
}else{
|
|
||||||
ESP_LOGE(tag,"Strip2 disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject flightJson = doc["front-light"];
|
|
||||||
animProps.frontLight.enabled = jsonConstrainBool(tag, flightJson, "en", false);
|
|
||||||
if(animProps.frontLight.enabled){
|
|
||||||
animProps.frontLight.relayIndex = jsonConstrain<int>(tag, flightJson, "relay", 0, 3, 0);
|
|
||||||
animProps.frontLight.core = jsonConstrain<int>(tag, flightJson, "core", 0, 1, 1);
|
|
||||||
animProps.frontLight.style = (FRONT_LIGHT_STYLE)(flightJson, "style", 0, 1, 0);
|
|
||||||
ESP_LOGI(tag, "Front Light initialized..");
|
|
||||||
Init_FrontLight();
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject rlightJson = doc["rear-light"];
|
|
||||||
animProps.rearLight.enabled = jsonConstrainBool(tag, rlightJson, "en", false);
|
|
||||||
if(animProps.rearLight.enabled){
|
|
||||||
animProps.rearLight.relayIndex = jsonConstrain<int>(tag, rlightJson, "relay", 0, 3, 1);
|
|
||||||
animProps.rearLight.buttonIndex = jsonConstrain<int>(tag, rlightJson, "button", 0, 2, 2);
|
|
||||||
animProps.rearLight.rampTime = jsonConstrain<int>(tag, rlightJson, "ramp", 1000, 20000, 3000);
|
|
||||||
animProps.rearLight.rampSteps = jsonConstrain<int>(tag, rlightJson, "steps", 2, 20, 8);
|
|
||||||
animProps.rearLight.min = jsonConstrain<float>(tag, rlightJson, "min", 0.0f, 50.0f, 0.0f);
|
|
||||||
animProps.rearLight.max = jsonConstrain<float>(tag, rlightJson, "max", animProps.rearLight.min, 100.0f, 100.0f);
|
|
||||||
ESP_LOGD(tag, "relay: %d, btn: %d, ramp: %d, steps: %d, min: %.2f, max: %.2f", animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps, animProps.rearLight.min, animProps.rearLight.max);
|
|
||||||
|
|
||||||
Initialize_Rear_Control(animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps , animProps.rearLight.min, animProps.rearLight.max);
|
|
||||||
ESP_LOGI(tag, "Rear Light initialized..");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Init_FrontLight(void)
|
|
||||||
{
|
|
||||||
xTaskCreatePinnedToCore(FrontLight_Task, "FrontLight_Task", 5000, NULL, 1, &FrontLight_Task_Handle, animProps.frontLight.core);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Init_RearLight(void)
|
|
||||||
{
|
|
||||||
//if(jsonOb.isNull()){ Serial.println(" rear_light json is Null.."); return; }
|
|
||||||
}
|
|
||||||
|
|
||||||
void Test_Animations(void){
|
|
||||||
ANIMATION_EVENT event;
|
|
||||||
|
|
||||||
strip1->fill({0,0,0}, 0, strip1->effSize);
|
|
||||||
strip1->show(true);
|
|
||||||
vTaskDelay(100);
|
|
||||||
|
|
||||||
event.hue = RGBtoHUE(col_blue);
|
|
||||||
event.hueRange = 180;
|
|
||||||
event.param1 = 11;
|
|
||||||
event.speed = 70;
|
|
||||||
|
|
||||||
LEDStrip_FireInit(*strip1);
|
|
||||||
|
|
||||||
while(1){
|
|
||||||
event.hue = 0;
|
|
||||||
event.hueRange = 359;
|
|
||||||
event.param1 = 22;
|
|
||||||
event.speed = 70;
|
|
||||||
//Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2);
|
|
||||||
event.param1 = 30;
|
|
||||||
//Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2);
|
|
||||||
event.param1 = 40;
|
|
||||||
//Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2);
|
|
||||||
event.param1 = 50;
|
|
||||||
Animation_Sectors(*strip1, event, DT_BOTH, 2);
|
|
||||||
|
|
||||||
event.param1 = 50;
|
|
||||||
Animation_Dashes(*strip1, event, DT_BOTH, 3);
|
|
||||||
|
|
||||||
event.param1 = 50;
|
|
||||||
Animation_Dashes(*strip1, event, DT_BOTH, 3);
|
|
||||||
/*
|
|
||||||
Animation_Fire(*strip1, event, RED_FIRE, 5000);
|
|
||||||
Animation_Fire(*strip1, event, GREEN_FIRE, 5000);
|
|
||||||
Animation_Fire(*strip1, event, BLUE_FIRE, 5000);
|
|
||||||
|
|
||||||
event.col1 = col_blue;
|
|
||||||
event.col2 = {255,136,0};
|
|
||||||
event.density = 11;
|
|
||||||
event.speed = 100;
|
|
||||||
Animation_Snakes(*strip1, event, false, DT_FWD, 2, 1);
|
|
||||||
|
|
||||||
event.col1 = HUEtoRGB(359);
|
|
||||||
event.col2 = HUEtoRGB(0);
|
|
||||||
event.density = 22;
|
|
||||||
event.speed = 70;
|
|
||||||
Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4);
|
|
||||||
|
|
||||||
event.col1 = HUEtoRGB(359);
|
|
||||||
event.col2 = HUEtoRGB(0);
|
|
||||||
event.density = 56;
|
|
||||||
event.speed = 50;
|
|
||||||
Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Strip1_Control_Task(void *parameters)
|
|
||||||
{
|
|
||||||
vTaskDelay(1000); // small start delay
|
|
||||||
ESP_LOGD(tag, "Strip1 Task Entered....");
|
|
||||||
//Test_Animations();
|
|
||||||
|
|
||||||
LEDStrip_FireInit(*strip1); // must be initialized before calling fire animation
|
|
||||||
|
|
||||||
// Set default event 1 animation base on profile values
|
|
||||||
ANIMATION_EVENT ev; // empty event
|
|
||||||
ev.animIndex = 0;
|
|
||||||
ev.countDown = 6000;
|
|
||||||
ev.param2 = 75;
|
|
||||||
ev.type = EV_NON_INJECT;
|
|
||||||
PostNewEvent(ev); // Start Animation (White Fill)
|
|
||||||
|
|
||||||
animProps.event[1].type = EV_NORMAL;
|
|
||||||
PostNewEvent(animProps.event[1]); // default attraction animation
|
|
||||||
|
|
||||||
while(true){
|
|
||||||
xQueueReceive( eventQueue, &eventMsg, portMAX_DELAY );
|
|
||||||
ESP_LOGD(tag, "Queue waiting: %d", uxQueueMessagesWaiting(eventQueue));
|
|
||||||
|
|
||||||
if(eventMsg.type == EV_NORMAL ) { // EV_NORMAL
|
|
||||||
ESP_LOGD(tag, "Running event: type: EV_NORMAL, event: %d, anim: %d", eventMsg.selfIndex, eventMsg.animIndex);
|
|
||||||
lastNormalEventMsg = eventMsg; //save last Normal EventMsg
|
|
||||||
if( eventMsg.selfIndex >= 0 ) {
|
|
||||||
CurrentRunningEventIndex = eventMsg.selfIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Luma_PeerCount() > 0){
|
|
||||||
TempLumaPacket.type = LPT_ANIM;
|
|
||||||
memcpy(&TempLumaPacket.data, &eventMsg, sizeof(ANIMATION_EVENT));
|
|
||||||
Luma_Master_Broadcast(TempLumaPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( eventMsg.selfIndex == 0 ){
|
|
||||||
RunWhitefillAnimation( eventMsg );
|
|
||||||
}else{
|
|
||||||
RunAnimation( eventMsg );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( eventMsg.type == EV_INJECT ){ // insert and return to previous animation
|
|
||||||
ESP_LOGD(tag, "EV_INJECT");
|
|
||||||
xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event
|
|
||||||
RunPopUpAnimations( eventMsg );
|
|
||||||
}
|
|
||||||
else if( eventMsg.type == EV_NON_INJECT ){
|
|
||||||
ESP_LOGD(tag, "EV_NON_INJECT");
|
|
||||||
RunPopUpAnimations( eventMsg );
|
|
||||||
}
|
|
||||||
else if( eventMsg.type == EV_TEST ){
|
|
||||||
ESP_LOGD(tag, "EV_TEST");
|
|
||||||
animStatus.EventTestCountdown = EVENT_TESTMODE_TIMEOUT; // start timeout countdown
|
|
||||||
// Should not re-post last event from here in case repeated tests are started.
|
|
||||||
// that would cause a backlog of the same previous event.
|
|
||||||
if( eventMsg.selfIndex == 0 ){
|
|
||||||
RunWhitefillAnimation( eventMsg);
|
|
||||||
}else{
|
|
||||||
RunAnimation( eventMsg );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PostLastNormalEvent(){
|
|
||||||
xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event
|
|
||||||
ESP_LOGD(tag, "RePosted Last Normal Event Index: %d", lastNormalEventMsg.selfIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to cycle to the next event primarity when usinging buttons
|
|
||||||
int IncrementEventIndex(){
|
|
||||||
int x = (CurrentRunningEventIndex + 1) % animStatus.EventsCount;
|
|
||||||
ESP_LOGD(tag, "Increment Event Index from %d to %d", CurrentRunningEventIndex, x);
|
|
||||||
|
|
||||||
animProps.event[x].type = EV_NORMAL;
|
|
||||||
PostNewEvent(animProps.event[x]);
|
|
||||||
return animStatus.EventsIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
//EVENT_MSG newEvent;
|
|
||||||
void PostNewEvent( ANIMATION_EVENT &animEvent) {
|
|
||||||
//newEvent.type = type;
|
|
||||||
//newEvent.event = animEvent;
|
|
||||||
xQueueSend(eventQueue, &animEvent, 100); // queue new event
|
|
||||||
|
|
||||||
// if(type == EV_NORMAL){
|
|
||||||
// If whitefill index ( could be restarted )
|
|
||||||
// if(animEvent.animIndex == ANIM_WHITEFILL_INDEX){
|
|
||||||
//if( countDown == 0 ) { animStatus.countDown = 1000; } // TODO Countdown calc maybe wrong...double check
|
|
||||||
//if( animStatus.busy ) { animStatus.repeat = true; }
|
|
||||||
// if( FrontLight_Task_Handle ) { xTaskNotifyGive( FrontLight_Task_Handle ); }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if( Strip1_Task_Handle ){ xTaskNotifyGive( Strip1_Task_Handle ); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void Strip2_Control_Task(void *parameters)
|
|
||||||
{
|
|
||||||
vTaskDelay(100);
|
|
||||||
ESP_LOGD(tag, "Strip2 Task Entered....\n");
|
|
||||||
|
|
||||||
for(;;){
|
|
||||||
vTaskDelay(10000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FrontLight_Task(void *parameters){
|
|
||||||
animStatus.busy = false;
|
|
||||||
ANIMATION_EVENT whiteMsg;
|
|
||||||
for(;;){
|
|
||||||
xQueueReceive( whiteQueue, &whiteMsg, portMAX_DELAY );
|
|
||||||
|
|
||||||
int msFillTime = 0;
|
|
||||||
if(whiteMsg.countDown > 0){ // use countdown if available
|
|
||||||
msFillTime = whiteMsg.countDown;
|
|
||||||
}else{
|
|
||||||
msFillTime = map(whiteMsg.param1, 0, 100, 0, 10000);
|
|
||||||
if(msFillTime < 1000){ msFillTime = 1000; }
|
|
||||||
}
|
|
||||||
float delayFactor = (float)map(whiteMsg.param2, 0, 100, 0, 100) / 100.0;
|
|
||||||
Const_White_Fill(whiteStatus, animProps.frontLight, delayFactor, msFillTime, 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void load_animation_profile(void){
|
|
||||||
File file = LittleFS.open("/cfg/anim-profile-common.json");
|
|
||||||
|
|
||||||
if(!file){
|
|
||||||
ESP_LOGE(tag, "Error opening anim-profile-common.json...");
|
|
||||||
file.close();
|
|
||||||
}else{
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
file.close();
|
|
||||||
if(error){ ESP_LOGE(tag, "anim-profile-common.json deserialize error!.."); return;}
|
|
||||||
|
|
||||||
JsonObject frontJson = doc["countdown"];
|
|
||||||
animProps.frontLight.minDuty = jsonConstrain<float>(tag, frontJson, "min", 0.0f, 99.0f, 20.0f);
|
|
||||||
animProps.frontLight.maxDuty = jsonConstrain<float>(tag, frontJson, "max", 1.0f, 100.0f, 99.0f);
|
|
||||||
animProps.frontLight.holdTime = jsonConstrain<int>(tag, frontJson, "hold", 100, 5000, 1000);
|
|
||||||
animProps.frontLight.rampTime = jsonConstrain<int>(tag, frontJson, "ramp", 100, 5000, 500);
|
|
||||||
|
|
||||||
animProps.profileIndex = jsonConstrain<int>(tag, doc.as<JsonObject>(), "profile-index", 0, 7, 0);
|
|
||||||
|
|
||||||
ESP_LOGI(tag, "anim-profile-common.json settings loaded.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void load_animation_profileByIndex(int index){
|
|
||||||
|
|
||||||
//for(int index = 1; index <= 8; index++){
|
|
||||||
String strFileName = "/cfg/anim-profile" + String(index + 1) + ".json";
|
|
||||||
File file = LittleFS.open(strFileName);
|
|
||||||
|
|
||||||
if(!file){
|
|
||||||
ESP_LOGE(tag, "%s", "Error opening " + strFileName);
|
|
||||||
file.close();
|
|
||||||
}else{
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
file.close();
|
|
||||||
if(error){ ESP_LOGE(tag, "%s deserialize error!..", strFileName.c_str()); return;}
|
|
||||||
|
|
||||||
JsonObject profJson = doc.as<JsonObject>();
|
|
||||||
animProps.profileName = jsonConstrainString(tag, profJson, "name", "").c_str();
|
|
||||||
ESP_LOGD(tag, "Profile Index: %d, Name: %s", index, animProps.profileName);
|
|
||||||
|
|
||||||
//
|
|
||||||
JsonArray eventsJson = profJson["events"];
|
|
||||||
for(int i = 0; i < animStatus.EventsCount; i++){
|
|
||||||
animProps.event[i].selfIndex = i; // Self Index
|
|
||||||
|
|
||||||
if(i == 0){
|
|
||||||
animProps.event[i].animIndex = jsonConstrain<int>(tag, eventsJson[i], "anim", 0, animProps.whitefillsCount-1, 0);
|
|
||||||
}else{
|
|
||||||
animProps.event[i].animIndex = jsonConstrain<int>(tag, eventsJson[i], "anim", 0, animProps.animationsCount-1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
animProps.event[i].hue = jsonConstrain<int>(tag, eventsJson[i], "hue", -2, 360, 0);
|
|
||||||
animProps.event[i].hueRange = jsonConstrain<int>(tag, eventsJson[i], "hue-range", 0, 360, 1);
|
|
||||||
animProps.event[i].speed = jsonConstrain<int>(tag, eventsJson[i], "speed", 0, 100, 50);
|
|
||||||
animProps.event[i].param1 = jsonConstrain<int>(tag, eventsJson[i], "param1", 0, 100, 50);
|
|
||||||
animProps.event[i].param2 = jsonConstrain<int>(tag, eventsJson[i], "param2", 0, 100, 50);
|
|
||||||
animProps.event[i].check1 = jsonConstrainBool(tag, eventsJson[i], "check1", false);
|
|
||||||
animProps.event[i].check2 = jsonConstrainBool(tag, eventsJson[i], "check2", false);
|
|
||||||
animProps.event[i].check3 = jsonConstrainBool(tag, eventsJson[i], "check3", false);
|
|
||||||
animProps.event[i].check4 = jsonConstrainBool(tag, eventsJson[i], "check4", false);
|
|
||||||
|
|
||||||
ESP_LOGD(tag, " self: %d, anim: %d, param1: %d, speed: %d", animProps.event[i].selfIndex, animProps.event[i].animIndex, animProps.event[i].param1, animProps.event[i].speed);
|
|
||||||
}
|
|
||||||
ESP_LOGI(tag, "%s settings loaded.", strFileName.c_str());
|
|
||||||
}
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RunWhitefillAnimation(ANIMATION_EVENT &event){
|
|
||||||
//Start Constant Light Task if available
|
|
||||||
if( FrontLight_Task_Handle ){
|
|
||||||
xQueueSend(whiteQueue, &event, 100); // queue new event
|
|
||||||
xTaskNotifyGive( FrontLight_Task_Handle );
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
switch(event.animIndex){
|
|
||||||
case 0: // Bottom/Up Fill
|
|
||||||
ESP_LOGD(tag, " Running WhiteFill: %d , bottom/up", event.animIndex);
|
|
||||||
ret = Animation_White_Fill_Mirrored(*strip1, event);
|
|
||||||
break;
|
|
||||||
case 1: // Snake Fill
|
|
||||||
ESP_LOGD(tag, " Running WhiteFill: %d , Snake fill", event.animIndex);
|
|
||||||
ret = Animation_White_Fill_Mirrored(*strip1, event);
|
|
||||||
break;
|
|
||||||
case 2: // Tick Fill
|
|
||||||
ESP_LOGD(tag, " Running WhiteFill: %d , Tick fill", event.animIndex);
|
|
||||||
ret = Animation_White_Fill_Mirrored(*strip1, event);
|
|
||||||
break;
|
|
||||||
case 3: // Smooth Brighten
|
|
||||||
ESP_LOGD(tag, " Running WhiteFill: %d , Smooth Brighten", event.animIndex);
|
|
||||||
ret = Animation_White_Fill_Mirrored(*strip1, event);
|
|
||||||
break;
|
|
||||||
case 4: // Cycle All
|
|
||||||
ESP_LOGD(tag, " Running WhiteFill: %d , Cycle All", event.animIndex);
|
|
||||||
ret = Animation_White_Fill_Mirrored(*strip1, event);
|
|
||||||
break;
|
|
||||||
default: // Random Select
|
|
||||||
ESP_LOGD(tag, " Running WhiteFill: default , bottom/up");
|
|
||||||
ret = Animation_White_Fill_Mirrored(*strip1, event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if exited loop naturally... not from a notification so keep waiting
|
|
||||||
if(ret == EXIT_FINISHED || ret == EXIT_TIMEOUT){
|
|
||||||
ESP_LOGD(tag, "Whitefill normal exit... waiting");
|
|
||||||
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RunAnimation(ANIMATION_EVENT &event){
|
|
||||||
if(event.animIndex < 80){
|
|
||||||
switch(event.animIndex){ // AnimationEventIndex (0-X)
|
|
||||||
case 0: // Rainbow
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Rainbow", event.animIndex);
|
|
||||||
Animation_Rainbow(*strip1, event, INFINITE_LOOP);
|
|
||||||
break;
|
|
||||||
case 1: // Hue Spectrum Mirrored
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Hue Spectrum Mirrored", event.animIndex);
|
|
||||||
Animation_Hue_Spectrum_Mirrored(*strip1, event, INFINITE_LOOP);
|
|
||||||
break;
|
|
||||||
case 2: // Color Pulse Cycle
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Color Pulse Cycle", event.animIndex);
|
|
||||||
Animation_Pulse_Color_Cycling(*strip1, event);
|
|
||||||
break;
|
|
||||||
case 3: // Comets Mixed Colors
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Comets", event.animIndex);
|
|
||||||
Animation_Comets(*strip1, event, DT_BOTH, INFINITE_LOOP);
|
|
||||||
break;
|
|
||||||
case 4: // Dashes Single and Mixed Colors
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Dashes", event.animIndex);
|
|
||||||
Animation_Dashes(*strip1, event, DT_BOTH, INFINITE_LOOP);
|
|
||||||
break;
|
|
||||||
case 5: // Snakes Single and Mixed Colors
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Snakes", event.animIndex);
|
|
||||||
Animation_Snakes(*strip1, event, DT_BOTH, INFINITE_LOOP);
|
|
||||||
break;
|
|
||||||
case 6: // Sectors Mixed Colors (No Sigle Colors because pointless)
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Sectors", event.animIndex);
|
|
||||||
Animation_Sectors(*strip1, event, DT_BOTH, INFINITE_LOOP);
|
|
||||||
break;
|
|
||||||
case 7: // Fire (Red)
|
|
||||||
ESP_LOGD(tag, " Running Anim: %d , Fire", event.animIndex);
|
|
||||||
Animation_Fire(*strip1, event);
|
|
||||||
break;
|
|
||||||
case 8: //
|
|
||||||
break;
|
|
||||||
case 9: // Color Strobing
|
|
||||||
break;
|
|
||||||
case 10: // Random Color Twinkle
|
|
||||||
break;
|
|
||||||
case 11: // Stacking
|
|
||||||
break;
|
|
||||||
case 12: // Rain
|
|
||||||
break;
|
|
||||||
case 13: // Stacking
|
|
||||||
break;
|
|
||||||
case 14: // RED/WHITE/BLU Flag (density selects color pallet) (sector/comets,dashes, snakes)
|
|
||||||
break;
|
|
||||||
case 15:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}else{ // Non Looping Functions
|
|
||||||
switch(event.animIndex){ // AnimationEventIndex (0-X)
|
|
||||||
case 80: // clear strip
|
|
||||||
strip1->fill(col_black, 0, strip1->effSize);
|
|
||||||
strip1->show(true);
|
|
||||||
ESP_LOGD(tag, "Animation Strip1 Clear");
|
|
||||||
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
|
|
||||||
break;
|
|
||||||
case 81: // set pixel
|
|
||||||
strip1->setPixel(event.param1, GetColorFromHue(event.hue));
|
|
||||||
strip1->show(true);
|
|
||||||
ESP_LOGD(tag, "Animation Set Pixel: %d", event.param1);
|
|
||||||
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
|
|
||||||
break;
|
|
||||||
case 82: //
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// These animations should be quick and not infinite
|
|
||||||
void RunPopUpAnimations(ANIMATION_EVENT &event){
|
|
||||||
|
|
||||||
switch(event.animIndex){ // AnimationEventIndex (0-X)
|
|
||||||
case 0: // Start/Boot/Power Up Sequence
|
|
||||||
ANIMATION_EVENT ev;
|
|
||||||
ev.animIndex = 0;
|
|
||||||
ev.hue = -1; //{200, 200, 200}; //col_white;
|
|
||||||
ev.hueRange = 1; //col_black;
|
|
||||||
ev.countDown = 6000; // 6 seconds
|
|
||||||
ev.param2 = 75;
|
|
||||||
if( FrontLight_Task_Handle ){
|
|
||||||
xQueueSend(whiteQueue, &ev, 100); // queue new event
|
|
||||||
xTaskNotifyGive( FrontLight_Task_Handle );
|
|
||||||
}
|
|
||||||
Animation_White_Fill_Mirrored(*strip1, ev);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 1: // BLE Connected
|
|
||||||
break;
|
|
||||||
case 2: // BLE Disconnected
|
|
||||||
break;
|
|
||||||
case 3: // WiFi Connected
|
|
||||||
break;
|
|
||||||
case 4: // WiFi Disconnected
|
|
||||||
break;
|
|
||||||
case 5: // Luma Stick Connected
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// json color to rgbPixel conversion
|
|
||||||
// Unused now
|
|
||||||
rgbpixel_t hexToRGB(const char* hexColor) {
|
|
||||||
long hexValue = strtol(hexColor + 1, nullptr, 16); // Skip the '#' character
|
|
||||||
rgbpixel_t pix;
|
|
||||||
pix.red = (hexValue >> 16) & 0xFF;
|
|
||||||
pix.grn = (hexValue >> 8) & 0xFF;
|
|
||||||
pix.blu = hexValue & 0xFF;
|
|
||||||
return pix;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
#ifndef _LED_STRIP_H
|
|
||||||
#define _LED_STRIP_H
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "common/LEDStrip.h"
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include "my_board.h"
|
|
||||||
|
|
||||||
extern LEDSTRIP *strip1;
|
|
||||||
extern LEDSTRIP *strip2;
|
|
||||||
extern TaskHandle_t Strip1_Task_Handle;
|
|
||||||
extern TaskHandle_t Strip2_Task_Handle;
|
|
||||||
extern TaskHandle_t FrontLight_Task_Handle;
|
|
||||||
|
|
||||||
#define ANIM_WHITEFILL_INDEX 0
|
|
||||||
#define ANIM_DEFAULT_INDEX 1
|
|
||||||
#define ANIM_EVENT_SIZE 12
|
|
||||||
#define PROFILES_SIZE 8
|
|
||||||
|
|
||||||
#define MIN_LED_UPDATE_INTERVAL 13
|
|
||||||
|
|
||||||
enum OtherANIMS { OA_SET_PIXEL=100, OA_CLEAR, };
|
|
||||||
|
|
||||||
enum FRONT_LIGHT_STYLE { FRONT_LIKE_LUMIA, FRONT_LIKE_ROAMER };
|
|
||||||
|
|
||||||
enum EVENT_TYPE { EV_NORMAL, EV_INJECT, EV_NON_INJECT, EV_TEST,};
|
|
||||||
|
|
||||||
// Single web page event (upto 8)
|
|
||||||
typedef struct{
|
|
||||||
EVENT_TYPE type;
|
|
||||||
int selfIndex;
|
|
||||||
int animIndex;
|
|
||||||
int hue;
|
|
||||||
int hueRange;
|
|
||||||
int speed;
|
|
||||||
int param1;
|
|
||||||
int param2;
|
|
||||||
bool check1;
|
|
||||||
bool check2;
|
|
||||||
bool check3;
|
|
||||||
bool check4;
|
|
||||||
int countDown;
|
|
||||||
}ANIMATION_EVENT;
|
|
||||||
|
|
||||||
typedef struct{
|
|
||||||
bool enabled;
|
|
||||||
uint8_t relayIndex;
|
|
||||||
uint8_t core;
|
|
||||||
int holdTime;
|
|
||||||
int rampTime;
|
|
||||||
float minDuty;
|
|
||||||
float maxDuty;
|
|
||||||
FRONT_LIGHT_STYLE style;
|
|
||||||
//float delayFactor;
|
|
||||||
}FRONT_LIGHT;
|
|
||||||
|
|
||||||
typedef struct{
|
|
||||||
bool enabled;
|
|
||||||
uint8_t relayIndex;
|
|
||||||
uint8_t buttonIndex;
|
|
||||||
uint8_t core;
|
|
||||||
int rampTime;
|
|
||||||
uint8_t rampSteps;
|
|
||||||
float min;
|
|
||||||
float max;
|
|
||||||
}REAR_LIGHT;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
EVENT_TYPE type;
|
|
||||||
ANIMATION_EVENT event;
|
|
||||||
}EVENT_MSG;
|
|
||||||
|
|
||||||
// Properties from webpage
|
|
||||||
typedef struct {
|
|
||||||
int profileIndex;
|
|
||||||
const char* profileName;
|
|
||||||
int whitefillsCount;
|
|
||||||
int animationsCount;
|
|
||||||
FRONT_LIGHT frontLight;
|
|
||||||
REAR_LIGHT rearLight;
|
|
||||||
ANIMATION_EVENT event[ANIM_EVENT_SIZE];
|
|
||||||
}ANIMATION_PROPS;
|
|
||||||
|
|
||||||
extern ANIMATION_PROPS animProps;
|
|
||||||
|
|
||||||
enum POPUP_ANIM { POP_STATUP, POP_BLE_CONN, POP_BLE_DISC, POP_WIFI_CONN, POP_WIFI_DISC, POP_STICK_CONN, POP_STICK_DISC };
|
|
||||||
|
|
||||||
void RunWhitefillAnimation(ANIMATION_EVENT &event);
|
|
||||||
|
|
||||||
void RunAnimation(ANIMATION_EVENT &event);
|
|
||||||
|
|
||||||
void RunPopUpAnimations(ANIMATION_EVENT &event);
|
|
||||||
|
|
||||||
//void SetEventIndex(int index, int countdown=0, bool test=false);
|
|
||||||
|
|
||||||
//void PostNewEvent( int countDown, EVENT_TYPE type, ANIMATION_EVENT &animEvent);
|
|
||||||
void PostNewEvent( ANIMATION_EVENT &animEvent);
|
|
||||||
|
|
||||||
int IncrementEventIndex(void);
|
|
||||||
|
|
||||||
void PostLastNormalEvent(void);
|
|
||||||
|
|
||||||
void load_animation_profile(void); // cfg/anim-profiles.json
|
|
||||||
void load_animation_profileByIndex(int); // cfg/anim-profileX.json
|
|
||||||
|
|
||||||
void Init_LED_Devices(BOARD_PINS); // cfg/led-devices.json
|
|
||||||
|
|
||||||
rgbpixel_t hexToRGB(const char* hexColor);
|
|
||||||
|
|
||||||
//void Init_LEDStrip2(void);
|
|
||||||
|
|
||||||
void Init_FrontLight(void);
|
|
||||||
|
|
||||||
//void Init_RearLight(void);
|
|
||||||
|
|
||||||
void Strip1_Control_Task(void *parameters);
|
|
||||||
|
|
||||||
void Strip2_Control_Task(void *parameters);
|
|
||||||
|
|
||||||
void FrontLight_Task(void *parameters);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,755 +0,0 @@
|
|||||||
{{NAVBAR}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>App Control Configuration</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href="global-style.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
#table{
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
#first_td_th {
|
|
||||||
width:300px;
|
|
||||||
}
|
|
||||||
#v2_td_th {
|
|
||||||
width:180px;
|
|
||||||
}
|
|
||||||
#color-picker{
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
width: 35px;
|
|
||||||
height: 35px;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
#select-anim {
|
|
||||||
width:175px;
|
|
||||||
}
|
|
||||||
select{
|
|
||||||
width:150px;
|
|
||||||
}
|
|
||||||
#slide-density{
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
#slide-speed{
|
|
||||||
accent-color: rgb(181, 53, 53);
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
input::-webkit-slider-runnable-track {
|
|
||||||
width: 450px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1 name="h1element">App Control Configuration</h1>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Saved Animation Profiles</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="first_td_th">
|
|
||||||
<label for="selSavedAnimProfiles">Profiles:</label>
|
|
||||||
<select name= "selSavedAnimProfiles" id="selSaveddAnimProfiles" style="width:150px" onchange="OnSavedAnimProfilesChanged(this)">
|
|
||||||
<option>(1)</option>
|
|
||||||
<option>(2)</option>
|
|
||||||
<option>(3)</option>
|
|
||||||
<option>(4)</option>
|
|
||||||
<option>(5)</option>
|
|
||||||
<option>(6)</option>
|
|
||||||
<option>(7)</option>
|
|
||||||
<option>(8)</option>
|
|
||||||
</select>
|
|
||||||
  
|
|
||||||
<label for="profileName">Rename:</label>
|
|
||||||
<input type="text" name="inputProfileName" id="profileName" style="width:140px">
|
|
||||||
   
|
|
||||||
<button id="saveProfile" onclick="SaveProfilesToServer()">Save</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<div ></div>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<tr><td>
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
</td></tr>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend id="legendLights">Countdown ( Constant Light )</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="first_td_th">
|
|
||||||
<label id="constLightMin-label" for="constlightMin">Light Min:</label> <br>
|
|
||||||
<input type="range" name="constLightMin" id="constlightMin" min="0" max="99" value="25" step="1" onchange="updateLabel('Light Min: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="constLightMax-label" for="constlightMax">Light Max:</label> <br>
|
|
||||||
<input type="range" name="constLightMax" id="constlightMax" min="1" max="100" value="90" step="1" onchange="updateLabel('Light Max: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<!-- <button>Try</button> -->
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td>
|
|
||||||
<div id="spacer-10"></div>
|
|
||||||
</td></tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td id="first_td_th">
|
|
||||||
<label for="Holdtime">Hold time(ms):</label><br>
|
|
||||||
<input type="number" name="holdTime" id="holdtime" min="0" max="5000" value="500">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="Ramptime">Ramp dn(ms):</label><br>
|
|
||||||
<input type="number" name="rampTime" id="Ramptime" min="0" max="5000" value="500">
|
|
||||||
</td>
|
|
||||||
<tr><td>
|
|
||||||
<div id="spacer-10"></div>
|
|
||||||
</td></tr>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset0">
|
|
||||||
<legend name="legendAnim0">Event0</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">White Fill Animation: </label> <br>
|
|
||||||
<select name="sel-anim0" id="select-anim0"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim0" id="color-picker" > 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim0" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density0-label" for="slide-density0">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density0" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed0-label" for="slide-speed0">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed0" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(0)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset1">
|
|
||||||
<legend name="legendAnim1">Event1</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim1" id="select-anim1"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim1" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim1" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density1-label" for="slide-density1">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density1" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed1-label" for="slide-speed1">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed1" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(1)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset2">
|
|
||||||
<legend name="legendAnim2">Event2</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim2" id="select-anim2"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim2" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim2" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density2-label" for="slide-density2">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density2" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed2-label" for="slide-speed2">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed2" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(2)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset3">
|
|
||||||
<legend name="legendAnim3">Event3</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim3" id="select-anim3"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim3" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim3" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density3-label" for="slide-density3">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density3" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed3-label" for="slide-speed3">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed3" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(3)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset4">
|
|
||||||
<legend name="legendAnim4">Event4</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim4" id="select-anim4"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim4" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim4" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density4-label" for="slide-density4">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density4" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed4-label" for="slide-speed4">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed4" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(4)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset5">
|
|
||||||
<legend name="legendAnim5">Event5</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim5" id="select-anim5"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim5" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim5" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density5-label" for="slide-density5">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density5" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed5-label" for="slide-speed5">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed5" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(5)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset6">
|
|
||||||
<legend name="legendAnim6">Event6</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim6" id="select-anim6"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim6" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim6" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density6-label" for="slide-density6">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density6" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed6-label" for="slide-speed6">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed6" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(6)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset7">
|
|
||||||
<legend name="legendAnim7">Event7</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim7" id="select-anim7"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim7" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim7" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density7-label" for="slide-density7">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density7" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed7-label" for="slide-speed7">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed7" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(7)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset8">
|
|
||||||
<legend name="legendAnim8">Event8</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim8" id="select-anim8"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim8" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim8" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density8-label" for="slide-density8">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density8" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed8-label" for="slide-speed8">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed8" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(8)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset9">
|
|
||||||
<legend name="legendAnim9">Event9</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim9" id="select-anim9"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim9" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim9" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density9-label" for="slide-density9">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density9" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed9-label" for="slide-speed9">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed9" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(9)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset10">
|
|
||||||
<legend name="legendAnim10">Event10</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim10" id="select-anim10"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim10" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim10" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density10-label" for="slide-density10">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density10" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed10-label" for="slide-speed10">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed10" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(10)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="spacer-20"></div>
|
|
||||||
|
|
||||||
<fieldset name="event-fieldset11">
|
|
||||||
<legend name="legendAnim11">Event11</legend>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td id="v2_td_th">
|
|
||||||
<label for="sel-count-anim">Animation: </label> <br>
|
|
||||||
<select name="sel-anim11" id="select-anim11"></select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col1:</label><br>
|
|
||||||
<input type="color" name="col1-color-anim11" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label for="color-picker">Col2:</label><br>
|
|
||||||
<input type="color" name="col2-color-anim11" id="color-picker"> 
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-density11-label" for="slide-density11">Density:</label> <br>
|
|
||||||
<input type="range" name="slide-density11" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<label id="slide-speed11-label" for="slide-speed11">Speed:</label> <br>
|
|
||||||
<input type="range" name="slide-speed11" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onclick="postPlayAnim(11)">Try</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.onload = function() { getProfilesAndEvents(); };
|
|
||||||
|
|
||||||
// Initialize Names
|
|
||||||
const EVENTCOUNT = 12;
|
|
||||||
const PROFILECOUNT = 8;
|
|
||||||
var animEvent = [EVENTCOUNT];
|
|
||||||
var profilesJson;
|
|
||||||
var eventsJson;
|
|
||||||
var animListJson;
|
|
||||||
const inputProfileName = document.getElementsByName('inputProfileName')[0];
|
|
||||||
const selSavedAnimProfiles = document.getElementsByName('selSavedAnimProfiles')[0];
|
|
||||||
const constLightMin = document.getElementsByName('constLightMin')[0];
|
|
||||||
const constLightMax = document.getElementsByName('constLightMax')[0];
|
|
||||||
const holdTime = document.getElementsByName('holdTime')[0];
|
|
||||||
const rampTime = document.getElementsByName('rampTime')[0];
|
|
||||||
var eventFieldset = [EVENTCOUNT];
|
|
||||||
var eventLegend = [EVENTCOUNT];
|
|
||||||
var lastAnimProfileIndex = 0;
|
|
||||||
var h1element = document.getElementsByName("h1element")[0];
|
|
||||||
|
|
||||||
// set event names by app type and if they are visible
|
|
||||||
for (let i = 0; i < EVENTCOUNT; i++) {
|
|
||||||
eventFieldset[i] = document.getElementsByName("event-fieldset" + i)[0];
|
|
||||||
eventLegend[i] = document.getElementsByName("legendAnim" + i)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < EVENTCOUNT; i++) {
|
|
||||||
animEvent[i] = {
|
|
||||||
anim: document.getElementsByName('sel-anim' + i)[0],
|
|
||||||
col1: document.getElementsByName('col1-color-anim' + i)[0],
|
|
||||||
col2: document.getElementsByName('col2-color-anim' + i)[0],
|
|
||||||
density: document.getElementsByName('slide-density' + i)[0],
|
|
||||||
speed: document.getElementsByName('slide-speed' + i)[0]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the form with animation data when profile list item selected
|
|
||||||
function setProfile(index){
|
|
||||||
var changeEvent = new Event("change");
|
|
||||||
|
|
||||||
// update profiles
|
|
||||||
inputProfileName.value = "";
|
|
||||||
constLightMin.value = profilesJson.countdown.min;
|
|
||||||
constLightMin.dispatchEvent(changeEvent);
|
|
||||||
constLightMax.value = profilesJson.countdown.max;
|
|
||||||
constLightMax.dispatchEvent(changeEvent);
|
|
||||||
holdTime.value = profilesJson.countdown.hold;
|
|
||||||
rampTime.value = profilesJson.countdown.ramp;
|
|
||||||
|
|
||||||
// Update Events
|
|
||||||
for (let i = 0; i < EVENTCOUNT; i++) {
|
|
||||||
animEvent[i].anim.selectedIndex = profilesJson.profiles[index].events[i].anim;
|
|
||||||
animEvent[i].col1.value = profilesJson.profiles[index].events[i].col1;
|
|
||||||
animEvent[i].col2.value = profilesJson.profiles[index].events[i].col2;
|
|
||||||
animEvent[i].density.value = profilesJson.profiles[index].events[i].density;
|
|
||||||
animEvent[i].density.dispatchEvent(changeEvent);
|
|
||||||
animEvent[i].speed.value = profilesJson.profiles[index].events[i].speed;
|
|
||||||
animEvent[i].speed.dispatchEvent(changeEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When new profile is selected
|
|
||||||
function OnSavedAnimProfilesChanged( event ){
|
|
||||||
console.log('Anim Profile Selected index:', event.selectedIndex);
|
|
||||||
console.log('Anim Profile Updated index:', lastAnimProfileIndex);
|
|
||||||
updateProfilesJson(lastAnimProfileIndex); // save current setting in ram but on in server yet
|
|
||||||
setProfile(event.selectedIndex);
|
|
||||||
lastAnimProfileIndex = event.selectedIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update the profilesJson obj with updated values
|
|
||||||
function updateProfilesJson(index){
|
|
||||||
|
|
||||||
//let index = selSavedAnimProfiles.selectedIndex;
|
|
||||||
if(inputProfileName.value != ""){
|
|
||||||
profilesJson.profiles[index].name = inputProfileName.value;
|
|
||||||
selSavedAnimProfiles.options[index].text = '(' + (index + 1) + ') ' + inputProfileName.value;
|
|
||||||
selSavedAnimProfiles.options[index].value = inputProfileName.value;
|
|
||||||
}
|
|
||||||
profilesJson.countdown.min = constLightMin.value;
|
|
||||||
profilesJson.countdown.max = constLightMax.value;
|
|
||||||
profilesJson.countdown.hold = holdTime.value;
|
|
||||||
profilesJson.countdown.ramp = rampTime.value;
|
|
||||||
|
|
||||||
// Update Events
|
|
||||||
for (let i = 0; i < EVENTCOUNT; i++) {
|
|
||||||
profilesJson.profiles[index].events[i].anim = animEvent[i].anim.selectedIndex;
|
|
||||||
profilesJson.profiles[index].events[i].col1 = animEvent[i].col1.value;
|
|
||||||
profilesJson.profiles[index].events[i].col2 = animEvent[i].col2.value;
|
|
||||||
profilesJson.profiles[index].events[i].density = animEvent[i].density.value;
|
|
||||||
profilesJson.profiles[index].events[i].speed = animEvent[i].speed.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// save profilesJson
|
|
||||||
function SaveProfilesToServer(){
|
|
||||||
// update profilesJson obj with current settings before posting
|
|
||||||
updateProfilesJson(selSavedAnimProfiles.selectedIndex);
|
|
||||||
profilesJson["profile-index"] = selSavedAnimProfiles.selectedIndex; // Set at active profile
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('type', 'anim-profiles');
|
|
||||||
const url = '/post?' + params.toString();
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'text/plain' } ,
|
|
||||||
body: JSON.stringify(profilesJson) // convert to string
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) { console.log('Request successful');}
|
|
||||||
else { throw new Error('Request failed');}
|
|
||||||
})
|
|
||||||
.catch(error => { console.error(error);});
|
|
||||||
}
|
|
||||||
|
|
||||||
// send anim event to test out
|
|
||||||
function postPlayAnim(eventIndex){
|
|
||||||
let tempAnimProps = {};
|
|
||||||
tempAnimProps.event = eventIndex;
|
|
||||||
tempAnimProps.anim = animEvent[eventIndex].anim.selectedIndex;
|
|
||||||
tempAnimProps.col1 = animEvent[eventIndex].col1.value
|
|
||||||
tempAnimProps.col2 = animEvent[eventIndex].col2.value
|
|
||||||
tempAnimProps.density = animEvent[eventIndex].density.value;
|
|
||||||
tempAnimProps.speed = animEvent[eventIndex].speed.value;
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('type', 'play-anim');
|
|
||||||
const url = '/post?' + params.toString();
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'text/plain'} ,
|
|
||||||
body: JSON.stringify(tempAnimProps)
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) { throw new Error('Request failed'); }
|
|
||||||
})
|
|
||||||
.catch(error => { console.error(error); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get profiles
|
|
||||||
function getProfilesAndEvents(){
|
|
||||||
try{
|
|
||||||
fetch_json_file('anim-profiles').then(result =>{
|
|
||||||
profilesJson = result.data;
|
|
||||||
//console.log({profilesJson});
|
|
||||||
|
|
||||||
fetch_json_file('app-events').then(result =>{
|
|
||||||
eventsJson = result.data.apps[result.data.index]
|
|
||||||
//console.log({evetnsJson});
|
|
||||||
|
|
||||||
fetch_json_file('anim-list').then(result =>{
|
|
||||||
animListJson = result.data;
|
|
||||||
//console.log({animListJson});
|
|
||||||
|
|
||||||
FillWhitefilList(animListJson.whitefills);
|
|
||||||
FillAnimationsList(animListJson.animations);
|
|
||||||
FillProfilesList(profilesJson);
|
|
||||||
selSavedAnimProfiles.selectedIndex = profilesJson['profile-index'];
|
|
||||||
lastAnimProfileIndex = selSavedAnimProfiles.selectedIndex;
|
|
||||||
setProfile(selSavedAnimProfiles.selectedIndex);
|
|
||||||
NameAndHideEvents();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}catch (error){
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetch_json_file(fileName){
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('type', fileName);
|
|
||||||
const url = '/get?' + params.toString();
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
method: 'GET'
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) { return response.json(); }
|
|
||||||
else { throw new Error('fetching ' + fileName + ' failed'); }
|
|
||||||
})
|
|
||||||
.then(data => { return{data:data}; })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename legends and or hide unused events property boxes
|
|
||||||
function NameAndHideEvents(){
|
|
||||||
h1element.textContent = eventsJson.name;
|
|
||||||
for(let i = 0; i < EVENTCOUNT; i++){
|
|
||||||
eventLegend[i].textContent = eventsJson.events[i];
|
|
||||||
if(eventsJson.events[i] === ''){
|
|
||||||
eventFieldset[i].style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill Profiles List
|
|
||||||
function FillProfilesList(profJson){
|
|
||||||
for(let i = 0; i < PROFILECOUNT; i++){
|
|
||||||
selSavedAnimProfiles.options[i].text = '(' + (i + 1) + ') ' + profJson.profiles[i].name;
|
|
||||||
selSavedAnimProfiles.options[i].value = profJson.profiles[i].name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function FillWhitefilList(whiteJson){
|
|
||||||
for(let x = 0; x < whiteJson.length; x++){
|
|
||||||
var op = document.createElement("option");
|
|
||||||
op.text = whiteJson[x];
|
|
||||||
animEvent[0].anim.appendChild(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill Animations drop down lists
|
|
||||||
function FillAnimationsList(animJson){
|
|
||||||
for(let x = 0; x < animJson.length; x++){
|
|
||||||
if(animJson[x] == ""){break;}// stop if blank
|
|
||||||
for(let i = 1; i < EVENTCOUNT; i++){
|
|
||||||
var op = document.createElement("option");
|
|
||||||
op.text = animJson[x];
|
|
||||||
op.value = animJson[x];
|
|
||||||
animEvent[i].anim.appendChild(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLabel(labelText, slider) {
|
|
||||||
try{
|
|
||||||
let label = document.getElementById(slider.name + "-label");
|
|
||||||
label.innerHTML = labelText + slider.value;
|
|
||||||
}catch(e){
|
|
||||||
debugger;
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>System Log</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>System Log</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
#include "luma_master.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include <FS.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include "JsonConstrain.h"
|
|
||||||
#include <esp_now.h>
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include "global.h"
|
|
||||||
|
|
||||||
static const char* tag = "lumaM";
|
|
||||||
uint8_t broadcastAddress[] = {0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF};
|
|
||||||
uint8_t availableStiks = 0;
|
|
||||||
LUMA_NODE lumaStation[LUMA_NODE_MAX_COUNT];
|
|
||||||
bool LUMASTIKS_READY = false;
|
|
||||||
|
|
||||||
TaskHandle_t LumaMaster_Task_Handle;
|
|
||||||
|
|
||||||
bool SendRegistrationPacket = false;
|
|
||||||
LUMA_PACKET RegistrationPacket;
|
|
||||||
esp_now_peer_info_t TempNode;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
QueueHandle_t lumaQueue = xQueueCreate( 4, sizeof( LUMA_PACKET ) );
|
|
||||||
|
|
||||||
void Init_Luma_Master(void){
|
|
||||||
|
|
||||||
if (esp_now_init() != ESP_OK) {
|
|
||||||
ESP_LOGD(tag, "Error initializing ESP-NOW\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
xTaskCreatePinnedToCore(LumaMaster_Task, "LumaMaster_Task", 1024*6, NULL, 1, &LumaMaster_Task_Handle, CONFIG_ARDUINO_RUNNING_CORE);
|
|
||||||
ESP_LOGV(tag, "Initialized LumaMaster task created...");
|
|
||||||
|
|
||||||
esp_now_register_recv_cb(Luma_Master_Data_Received);
|
|
||||||
esp_now_register_send_cb(Luma_Master_Data_Sent);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LumaMaster_Task(void *parameters){
|
|
||||||
|
|
||||||
while(1){
|
|
||||||
vTaskSuspend(NULL);
|
|
||||||
|
|
||||||
if(SendRegistrationPacket){
|
|
||||||
Luma_Send_Packet(TempNode.peer_addr, RegistrationPacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
LUMA_PACKET lumaPacket;
|
|
||||||
|
|
||||||
while(1){
|
|
||||||
xQueueReceive( lumaQueue, &lumaPacket, portMAX_DELAY );
|
|
||||||
|
|
||||||
switch(lumaPacket.type){
|
|
||||||
case LPT_RAW:
|
|
||||||
break;
|
|
||||||
case LPT_REG:
|
|
||||||
Luma_Send_Packet(TempNode.peer_addr, lumaPacket);
|
|
||||||
break;
|
|
||||||
case LPT_ANIM:
|
|
||||||
Luma_Master_Broadcast(lumaPacket);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Luma_Master_Data_Sent(const uint8_t *mac_addr, esp_now_send_status_t status){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Luma_Master_Data_Received(const uint8_t *mac, const uint8_t *data, int len){
|
|
||||||
|
|
||||||
LUMA_PACKET *packet = (LUMA_PACKET*)data;
|
|
||||||
|
|
||||||
printf("** Data Received **\n\n");
|
|
||||||
printf("Received from MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
||||||
printf("Length: %d byte(s)\n", len);
|
|
||||||
printf("Data: %d\n\n", data[0]);
|
|
||||||
|
|
||||||
switch(packet->type){
|
|
||||||
case LPT_RAW:
|
|
||||||
break;
|
|
||||||
case LPT_REG:
|
|
||||||
LUMA_REGISTER_PACKET *reg = (LUMA_REGISTER_PACKET*)&packet->data;
|
|
||||||
ESP_LOGD(tag, "Node SSID: %s", reg->ssid);
|
|
||||||
|
|
||||||
// Register
|
|
||||||
memcpy(TempNode.peer_addr, mac, 6);
|
|
||||||
TempNode.channel = WiFi.channel();
|
|
||||||
Luma_Add_Peer(TempNode);
|
|
||||||
|
|
||||||
// Notify Peer
|
|
||||||
RegistrationPacket.type = LPT_REG;
|
|
||||||
xQueueSend( lumaQueue, &RegistrationPacket, 100 );
|
|
||||||
break;
|
|
||||||
//default:
|
|
||||||
// break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Luma_Master_Broadcast(LUMA_PACKET packet){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#ifndef _LUMA_MASTER_H
|
|
||||||
#define _LUMA_MASTER_H
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <esp_now.h>
|
|
||||||
#include "common/luma-stiks.h"
|
|
||||||
|
|
||||||
|
|
||||||
extern bool LUMASTIKS_READY;
|
|
||||||
|
|
||||||
#define LUMA_NODE_MAX_COUNT 16
|
|
||||||
//extern LUMA_NODE lumaStation[];
|
|
||||||
|
|
||||||
void LumaMaster_Task(void *parameters);
|
|
||||||
|
|
||||||
void Init_Luma_Master(void);
|
|
||||||
|
|
||||||
void Luma_Master_Data_Received(const uint8_t *mac, const uint8_t *data, int len);
|
|
||||||
|
|
||||||
void Luma_Master_Data_Sent(const uint8_t *mac_addr, esp_now_send_status_t status);
|
|
||||||
|
|
||||||
void Luma_Master_Broadcast(LUMA_PACKET packet);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,66 +0,0 @@
|
|||||||
#ifndef _MY_WIFI_H
|
|
||||||
#define _MY_WIFI_H
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
extern int RebootSystem;
|
|
||||||
|
|
||||||
void Wifi_Start_WebServer(void);
|
|
||||||
void Init_Wifi_Task(void);
|
|
||||||
void Wifi_Start_MDNS(void);
|
|
||||||
void Wifi_Task(void *parameters);
|
|
||||||
String getSoftAPMacAddress(void);
|
|
||||||
//void onWiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
|
||||||
void onWiFiEvent(WiFiEvent_t event);
|
|
||||||
void Wifi_Request_Task(void *parameters);
|
|
||||||
void Setup_WebServer_Handlers(AsyncWebServer* serv);
|
|
||||||
|
|
||||||
void Wifi_Read_Credentials(const JsonObject &credJson);
|
|
||||||
void Wifi_Save_Credentials(String newSSID, String newPass);
|
|
||||||
bool isInternetConnected(void);
|
|
||||||
void Wifi_Client_Loop(void);
|
|
||||||
|
|
||||||
// Handlers
|
|
||||||
void handlePOST_RegStick(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
void handleGET_DSLRBooth(AsyncWebServerRequest *request);
|
|
||||||
void handlePOST_Upload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
|
||||||
void handlePOST_Update(AsyncWebServerRequest *request);
|
|
||||||
void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
|
||||||
void updateFirmwareProgress(size_t progress, size_t total);
|
|
||||||
|
|
||||||
void handleGET_SendFile(AsyncWebServerRequest *request);
|
|
||||||
void onFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
|
||||||
|
|
||||||
void handleGET_Get(AsyncWebServerRequest *request);
|
|
||||||
void handlePOST_Post(AsyncWebServerRequest *request);
|
|
||||||
void postFileUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final);
|
|
||||||
void postBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
|
|
||||||
|
|
||||||
|
|
||||||
void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&));
|
|
||||||
String htmlProcessor(const String& var);
|
|
||||||
String HomeHtmlProcessor(const String& var);
|
|
||||||
//const char* htmlProcessor(const char* var);
|
|
||||||
String listDir(fs::FS &fs, const char *dirname, bool isSubdirectory);
|
|
||||||
|
|
||||||
const char* getFileExtension(const char* filename);
|
|
||||||
const char* getFileType(const char* ext);
|
|
||||||
const char* convertFileSize(const size_t bytes);
|
|
||||||
char* readFile(fs::FS &fs, const char* path);
|
|
||||||
void writeFile(fs::FS &fs, const char * path, const char * message);
|
|
||||||
|
|
||||||
String varReplace(const String& input, String (*callback)(const String&));
|
|
||||||
//const char* varReplace(const char* input, const char* (*callback)(const char*));
|
|
||||||
|
|
||||||
void CreateSysSummmaryPacket(JsonDocument &doc);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
#ifndef NEO_COLORS_H
|
|
||||||
#define NEO_COLORS_H
|
|
||||||
|
|
||||||
#include <NeoPixelBus.h>
|
|
||||||
|
|
||||||
#define USE_CORRECTED_COLORS 0
|
|
||||||
|
|
||||||
const RgbColor col_black(0, 0, 0);
|
|
||||||
const RgbColor col_white(255, 255, 255);
|
|
||||||
const RgbColor col_red(255, 0, 0);
|
|
||||||
const RgbColor col_green(0, 255, 0);
|
|
||||||
const RgbColor col_blue(0, 0, 255);
|
|
||||||
|
|
||||||
#if USE_CORRECTED_COLORS == 0
|
|
||||||
|
|
||||||
const RgbColor col_orange(255, 165, 0);
|
|
||||||
const RgbColor col_yellow(255, 255, 0);
|
|
||||||
const RgbColor col_cyan(0, 255, 255);
|
|
||||||
const RgbColor col_magenta(255, 0, 255);
|
|
||||||
const RgbColor col_purple(128, 0, 128);
|
|
||||||
const RgbColor col_pink(255, 192, 203);
|
|
||||||
const RgbColor col_teal(0, 128, 128);
|
|
||||||
const RgbColor col_lime(0, 255, 0);
|
|
||||||
const RgbColor col_indigo(75, 0, 130);
|
|
||||||
const RgbColor col_maroon(128, 0, 0);
|
|
||||||
const RgbColor col_navy(0, 0, 128);
|
|
||||||
const RgbColor col_olive(128, 128, 0);
|
|
||||||
const RgbColor col_beige(245, 245, 220);
|
|
||||||
const RgbColor col_brown(165, 42, 42);
|
|
||||||
const RgbColor col_coral(255, 127, 80);
|
|
||||||
const RgbColor col_gold(255, 215, 0);
|
|
||||||
const RgbColor col_gray(128, 128, 128);
|
|
||||||
const RgbColor col_ivory(255, 255, 240);
|
|
||||||
const RgbColor col_khaki(240, 230, 140);
|
|
||||||
const RgbColor col_lavender(230, 230, 250);
|
|
||||||
const RgbColor col_peach(255, 218, 185);
|
|
||||||
const RgbColor col_periwinkle(204, 204, 255);
|
|
||||||
const RgbColor col_salmon(250, 128, 114);
|
|
||||||
const RgbColor col_sienna(160, 82, 45);
|
|
||||||
const RgbColor col_silver(192, 192, 192);
|
|
||||||
const RgbColor col_tan(210, 180, 140);
|
|
||||||
const RgbColor col_turquoise(64, 224, 208);
|
|
||||||
const RgbColor col_violet(238, 130, 238);
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
const RgbColor col_orange(255, 128, 0);
|
|
||||||
const RgbColor col_yellow(255, 255, 0);
|
|
||||||
const RgbColor col_cyan(0, 255, 255);
|
|
||||||
const RgbColor col_magenta(255, 0, 255);
|
|
||||||
const RgbColor col_purple(170, 0, 255);
|
|
||||||
const RgbColor col_pink(255, 170, 255);
|
|
||||||
const RgbColor col_teal(0, 128, 128);
|
|
||||||
const RgbColor col_lime(128, 255, 0);
|
|
||||||
const RgbColor col_indigo(85, 0, 255);
|
|
||||||
const RgbColor col_maroon(128, 0, 0);
|
|
||||||
const RgbColor col_navy(0, 0, 128);
|
|
||||||
const RgbColor col_olive(128, 128, 0);
|
|
||||||
const RgbColor col_beige(255, 230, 204);
|
|
||||||
const RgbColor col_brown(153, 51, 0);
|
|
||||||
const RgbColor col_coral(255, 102, 102);
|
|
||||||
const RgbColor col_gold(204, 153, 0);
|
|
||||||
const RgbColor col_gray(128, 128, 128);
|
|
||||||
const RgbColor col_ivory(255, 255, 204);
|
|
||||||
const RgbColor col_khaki(204, 204, 0);
|
|
||||||
const RgbColor col_lavender(204, 153, 255);
|
|
||||||
const RgbColor col_peach(255, 204, 153);
|
|
||||||
const RgbColor col_periwinkle(153, 153, 255);
|
|
||||||
const RgbColor col_salmon(255, 153, 102);
|
|
||||||
const RgbColor col_sienna(153, 76, 0);
|
|
||||||
const RgbColor col_silver(204, 204, 204);
|
|
||||||
const RgbColor col_tan(204, 153, 102);
|
|
||||||
const RgbColor col_turquoise(0, 204, 204);
|
|
||||||
const RgbColor col_violet(204, 0, 255);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
#include "otaupdate.h"
|
|
||||||
#include "LittleFS.h"
|
|
||||||
#include <HTTPClient.h>
|
|
||||||
#include <Update.h>
|
|
||||||
#include "githubCert.h"
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
|
|
||||||
String binFile = "manifest.json";
|
|
||||||
String rootUrl = "https://raw.githubusercontent.com/ataindustries/atacontrolboardV1/main/";
|
|
||||||
String github_token = "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";
|
|
||||||
|
|
||||||
const float FirmwareVer = 1.0;
|
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
int otaupdate_check(void)
|
|
||||||
{/*
|
|
||||||
String payload;
|
|
||||||
int httpCode;
|
|
||||||
WiFiClientSecure * client = new WiFiClientSecure;
|
|
||||||
HTTPClient https;
|
|
||||||
|
|
||||||
String manifest_url = rootUrl + binFile + "?token=" + github_token;
|
|
||||||
https.begin(manifest_url);
|
|
||||||
httpCode = https.GET();
|
|
||||||
|
|
||||||
if (httpCode == HTTP_CODE_OK) // if version received
|
|
||||||
{
|
|
||||||
String payload = https.getString();
|
|
||||||
JsonDocument doc;
|
|
||||||
deserializeJson(doc, payload);
|
|
||||||
|
|
||||||
float firmver = doc["firmver"].as<float>();
|
|
||||||
const char* firmfile = doc["firmfile"];
|
|
||||||
const char* fsfile = doc["fsfile"];
|
|
||||||
|
|
||||||
Serial.printf("From manifest: %.1f, %s, $s\n\r", firmver, firmfile, fsfile);
|
|
||||||
|
|
||||||
if(firmver > FirmwareVer){
|
|
||||||
Serial.println("New firmware available!");
|
|
||||||
return 1;
|
|
||||||
}else{
|
|
||||||
Serial.println("firmware is upto date!");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void otaupdate_perform(void)
|
|
||||||
{
|
|
||||||
|
|
||||||
// setup events
|
|
||||||
ota_events();
|
|
||||||
|
|
||||||
|
|
||||||
// Pause unnecessary tasks
|
|
||||||
|
|
||||||
//check for update
|
|
||||||
}
|
|
||||||
|
|
||||||
void ota_events(void)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
fota->setUpdateEndCb( [](int partition)
|
|
||||||
{
|
|
||||||
//Serial.printf("Update could not finish with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" );
|
|
||||||
});
|
|
||||||
|
|
||||||
fota->setProgressCb( [](size_t progress, size_t size)
|
|
||||||
{
|
|
||||||
//if( progress == size || progress == 0 ) Serial.println();
|
|
||||||
//Serial.print(".");
|
|
||||||
});
|
|
||||||
|
|
||||||
fota->setUpdateCheckFailCb( [](int partition, int error_code)
|
|
||||||
{
|
|
||||||
//Serial.printf("Update could validate %s partition (error %d)\n", partition==U_SPIFFS ? "spiffs" : "firmware", error_code );
|
|
||||||
// error codes:
|
|
||||||
// -1 : partition not found
|
|
||||||
// -2 : validation (signature check) failed
|
|
||||||
});
|
|
||||||
|
|
||||||
fota->setUpdateFinishedCb( [](int partition, bool restart_after)
|
|
||||||
{
|
|
||||||
//Serial.printf("Update could not begin with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" );
|
|
||||||
|
|
||||||
if( restart_after ) {
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
#ifndef _OTAUPDATE_H
|
|
||||||
#define _OTAUPDATE_H
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
|
|
||||||
int otaupdate_check(float myVer);
|
|
||||||
|
|
||||||
void otaupdate_perform(String filePath);
|
|
||||||
|
|
||||||
void ota_events(void);
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
|
|
||||||
#include "rf_transceiver.h"
|
|
||||||
|
|
||||||
static const char* tag = "trx433";
|
|
||||||
TaskHandle_t RF_Task_Handle;
|
|
||||||
|
|
||||||
const int txChannel = RMT_CHANNEL_0;
|
|
||||||
const int rxChannel = RMT_CHANNEL_1;
|
|
||||||
|
|
||||||
void Init_RF_Receiver(void)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
// Configure RMT receiver
|
|
||||||
rmt_config_t rxConfig;
|
|
||||||
rxConfig.channel = (rmt_channel_t)rxChannel;
|
|
||||||
rxConfig.gpio_num = static_cast<gpio_num_t>(RMT_RX_PIN);
|
|
||||||
// Set other reception settings as needed
|
|
||||||
rmt_config(&rxConfig);
|
|
||||||
rmt_driver_install(rxConfig.channel, 1000, 0);
|
|
||||||
|
|
||||||
xTaskCreatePinnedToCore(RF_Control_Task, "RF_Task", 8000, NULL, 1, &RF_Task_Handle, 0);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void Init_RF_Transmitter(void)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
rmt_config_t txConfig;
|
|
||||||
txConfig.channel = (rmt_channel_t)txChannel;
|
|
||||||
txConfig.gpio_num = static_cast<gpio_num_t>(RMT_TX_PIN);
|
|
||||||
// Set other transmission settings as needed
|
|
||||||
rmt_config(&txConfig);
|
|
||||||
rmt_driver_install(txConfig.channel, 0, 0);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void RF_Control_Task(void *parameters)
|
|
||||||
{
|
|
||||||
for(;;){
|
|
||||||
vTaskDelay(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
#ifndef _RF_TRANSCEIVER_H
|
|
||||||
#define _RF_TRANSCEIVER_H
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <driver/rmt.h>
|
|
||||||
#include "my_board.h"
|
|
||||||
|
|
||||||
#define RMT_RX_PIN RX433_Pin
|
|
||||||
#define RMT_TX_PIN TX433_Pin
|
|
||||||
#define RTM_RX_CHANNEL 0
|
|
||||||
#define RTM_TX_CHANNEL 1
|
|
||||||
|
|
||||||
void Init_RF_Receiver(void);
|
|
||||||
|
|
||||||
void Init_RF_Transmitter(void);
|
|
||||||
|
|
||||||
void RF_Control_Task(void *parameters);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
.blue {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1{
|
|
||||||
color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secTitleFont{
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
}
|
|
||||||
.secTitle{
|
|
||||||
min-width: none;
|
|
||||||
height: 25px;
|
|
||||||
width: auto;
|
|
||||||
padding: 2px;
|
|
||||||
margin: 2px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
/*border: 3px solid black;*/
|
|
||||||
color: rgb(56, 132, 255);
|
|
||||||
background-color: rgb(61, 118, 153);
|
|
||||||
font-family: Tahoma, Arial, sans-serif;
|
|
||||||
font-size: smaller;
|
|
||||||
font-weight: bold;
|
|
||||||
flex-direction: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secCntrls{
|
|
||||||
height: 100px;
|
|
||||||
width: auto;
|
|
||||||
margin: 5px;
|
|
||||||
border: 1px solid rgb(255, 255, 255);
|
|
||||||
color: rgba(255, 255, 255, 0);
|
|
||||||
font-family: Tahoma, Arial, sans-serif;
|
|
||||||
/*display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropList{
|
|
||||||
margin-right: 40px;
|
|
||||||
}
|
|
||||||
@ -1,190 +0,0 @@
|
|||||||
{
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"remote": "data/ata-boothifier-upgrade.html",
|
|
||||||
"local": "/ata-boothifier-upgrade.html",
|
|
||||||
"md5": "92074ec24fd467545272eb9e837d644c",
|
|
||||||
"size": 16980
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/ata-boothifier-upgradeV2.html",
|
|
||||||
"local": "/ata-boothifier-upgradeV2.html",
|
|
||||||
"md5": "484648993370e4b6aff13124bd1c55c1",
|
|
||||||
"size": 18178
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/ata-boothifier-upgradeV3.html",
|
|
||||||
"local": "/ata-boothifier-upgradeV3.html",
|
|
||||||
"md5": "79426145db379ac15ccc742245a31ecb",
|
|
||||||
"size": 17233
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/favicon.ico",
|
|
||||||
"local": "/favicon.ico",
|
|
||||||
"md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79",
|
|
||||||
"size": 1150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/flashstik-reg.html",
|
|
||||||
"local": "/flashstik-reg.html",
|
|
||||||
"md5": "394fdfe3cd3fb89a8ddb3d6edf2d2da4",
|
|
||||||
"size": 15651
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/css/global-style.css",
|
|
||||||
"local": "/css/global-style.css",
|
|
||||||
"md5": "217a9cca8b4eae2d28fa8bc5a0f6db09",
|
|
||||||
"size": 1239
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/css/nav.css",
|
|
||||||
"local": "/css/nav.css",
|
|
||||||
"md5": "e653a75433056f28e3f410f567b8622c",
|
|
||||||
"size": 1538
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/images/atalogo.png",
|
|
||||||
"local": "/images/atalogo.png",
|
|
||||||
"md5": "5a18c88a4ea80c8d8d0ad52b2fdbbadc",
|
|
||||||
"size": 16690
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/images/favicon-32x32.png",
|
|
||||||
"local": "/images/favicon-32x32.png",
|
|
||||||
"md5": "d80cf74ace3a8be487a5158bca48f9b6",
|
|
||||||
"size": 2428
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/js/event-box.js",
|
|
||||||
"local": "/js/event-box.js",
|
|
||||||
"md5": "553f26707686038e275227a97eb96659",
|
|
||||||
"size": 12341
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/js/fwUoload.js",
|
|
||||||
"local": "/js/fwUoload.js",
|
|
||||||
"md5": "d4a734cce529c3adf831ea46259f9c2d",
|
|
||||||
"size": 1524
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/js/hue-select.js",
|
|
||||||
"local": "/js/hue-select.js",
|
|
||||||
"md5": "0a58f1a339af5c54aecfc5ce1aac7168",
|
|
||||||
"size": 6367
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/js/jquery-3.7.1.js",
|
|
||||||
"local": "/js/jquery-3.7.1.js",
|
|
||||||
"md5": "fdb81281d3773a7462998fdddbe6f5bf",
|
|
||||||
"size": 196885
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/system/ble.json",
|
|
||||||
"local": "/system/ble.json",
|
|
||||||
"md5": "faebefd5b50046a01d4a8aa27f8504d0",
|
|
||||||
"size": 307
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/system/readme.txt",
|
|
||||||
"local": "/system/readme.txt",
|
|
||||||
"md5": "198c92a2cc06b3effce3b5ba313cc6a0",
|
|
||||||
"size": 86
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/system/tunes.json",
|
|
||||||
"local": "/system/tunes.json",
|
|
||||||
"md5": "814999e88296bee179cef5d394f3f696",
|
|
||||||
"size": 1149
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/system/update.json",
|
|
||||||
"local": "/system/update.json",
|
|
||||||
"md5": "01cc6a1935601085df49308cab646673",
|
|
||||||
"size": 156
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/about.html",
|
|
||||||
"local": "/www/about.html",
|
|
||||||
"md5": "23f0c991c5c67e7eefe6c24aa50b59fb",
|
|
||||||
"size": 4222
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/edit.html",
|
|
||||||
"local": "/www/edit.html",
|
|
||||||
"md5": "fed238dcf87d3797b08ed371330ea800",
|
|
||||||
"size": 5546
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/edit_old.html",
|
|
||||||
"local": "/www/edit_old.html",
|
|
||||||
"md5": "9aafba533ac77352d30841cdc34db0c4",
|
|
||||||
"size": 4240
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/failed.html",
|
|
||||||
"local": "/www/failed.html",
|
|
||||||
"md5": "a24025d56bef1cd2ed5375f6e8853dde",
|
|
||||||
"size": 828
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/files.html",
|
|
||||||
"local": "/www/files.html",
|
|
||||||
"md5": "52d82d4d23b038929691cd0fb20e8ee0",
|
|
||||||
"size": 9369
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/home.html",
|
|
||||||
"local": "/www/home.html",
|
|
||||||
"md5": "767fd73b733d8e37dc79252fcd20b610",
|
|
||||||
"size": 7822
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/index.html",
|
|
||||||
"local": "/www/index.html",
|
|
||||||
"md5": "af690aaa4dec02691ffdb2765c837370",
|
|
||||||
"size": 806
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/lights.html",
|
|
||||||
"local": "/www/lights.html",
|
|
||||||
"md5": "272f8dc423923bb5026837240f654efe",
|
|
||||||
"size": 19056
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/navbar.html",
|
|
||||||
"local": "/www/navbar.html",
|
|
||||||
"md5": "89af40b990e297e41e116105b04b66ee",
|
|
||||||
"size": 533
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/ok.html",
|
|
||||||
"local": "/www/ok.html",
|
|
||||||
"md5": "db42bd2ff52293c3b4d6ef62fb4e3a42",
|
|
||||||
"size": 865
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/setup.html",
|
|
||||||
"local": "/www/setup.html",
|
|
||||||
"md5": "19e1f419844852800757ccd36bb7892a",
|
|
||||||
"size": 11349
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/upgrade.html",
|
|
||||||
"local": "/www/upgrade.html",
|
|
||||||
"md5": "f4a3efb67be66b5214d905e605984c93",
|
|
||||||
"size": 9080
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remote": "data/www/wifi.html",
|
|
||||||
"local": "/www/wifi.html",
|
|
||||||
"md5": "c6e55946a02cb0ff28689dd10d265987",
|
|
||||||
"size": 5320
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"firmware": {
|
|
||||||
"md5": "fcec6e659842cc0333880b701a3e2634",
|
|
||||||
"size": 1401712
|
|
||||||
},
|
|
||||||
"release_date": "2025-08-20",
|
|
||||||
"release_time": "08:53:05"
|
|
||||||
}
|
|
||||||
@ -1,228 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Firmware Update</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href="/css/nav.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
h1{
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 700px;
|
|
||||||
min-width: 300px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.outer-container {
|
|
||||||
padding: 4px 20px 4px 20px
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 5px 20px 10px 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
width:90%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.center-text {
|
|
||||||
text-align: center;
|
|
||||||
font-size: medium;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
.row > * {
|
|
||||||
flex: 1;
|
|
||||||
width: calc(50% - 10px);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.row > *:last-child {
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.webupdate {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
progress{
|
|
||||||
flex: 1;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
#lblprogress{
|
|
||||||
flex: 0;
|
|
||||||
}
|
|
||||||
.progress-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-end;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="navbar"></div>
|
|
||||||
<script>
|
|
||||||
fetch('/www/navbar.html')
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => {
|
|
||||||
document.getElementById('navbar').innerHTML = data;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1 name="h1element">Firmware Update</h1>
|
|
||||||
|
|
||||||
<div class="outer-container">
|
|
||||||
<div class="container">
|
|
||||||
<legend class="center-text">Local Update</legend>
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<input type="file" id="update-file" name="update-file" accept=".bin">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="submit-update-local" onclick="uploadFile(this)">Update!</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<label>File name: "ata_fw_booth_xxx.bin" or "ata_fs_booth_xxx.bin"</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="outer-container">
|
|
||||||
<div class="container">
|
|
||||||
<legend class="center-text">Web Update</legend>
|
|
||||||
<div class="webupdate">
|
|
||||||
<button id="submit-update-local" onclick="CheckUpdates(this)">Check for Updates</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="webupdate">
|
|
||||||
<button id="submit-update-local" onclick="InstallFirmware">Install Firmware</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="outer-container">
|
|
||||||
<div class="container">
|
|
||||||
<legend class="center-text">Update Progress</legend>
|
|
||||||
<div class="progress-container">
|
|
||||||
<label id="lblprogress" for="firm-progress">Progress:</label>
|
|
||||||
<progress id="firm-progress" value="0" max="100"></progress>
|
|
||||||
<label id="lbl-firm-progress" for="firm-progress">- - - -</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const fileInput = document.getElementById('update-file');
|
|
||||||
const progressBar = document.getElementById('firm-progress');
|
|
||||||
const progressLabel = document.getElementById('lbl-firm-progress');
|
|
||||||
const submitButton = document.getElementById('submit-update-local');
|
|
||||||
|
|
||||||
|
|
||||||
function uploadFile(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const file = fileInput.files[0];
|
|
||||||
const url = '/update'; // Replace with the actual upload URL
|
|
||||||
|
|
||||||
// File Checks
|
|
||||||
//*********************************************************
|
|
||||||
// Check file extension
|
|
||||||
if (!file.name.toLowerCase().endsWith('.bin')) {
|
|
||||||
alert('Please select a file with the ".bin" extension.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check filename prefix
|
|
||||||
if (!file.name.toLowerCase().startsWith('ata_fw_booth') && !file.name.toLowerCase().startsWith('ata_fs_booth')) {
|
|
||||||
alert('Please select a file with a filename starting with "ata_fw_booth" or "ata_fs_booth".');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check file size
|
|
||||||
const maxSizeBytes = 2.7 * 1024 * 1024; // 2.75Mb in bytes
|
|
||||||
if (file.size > maxSizeBytes) {
|
|
||||||
alert('Please select a file with a size not exceeding 2.7Mb.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//*********************************************************
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file-size', file.size); // Include the file size as a parameter
|
|
||||||
formData.append('update-file', file);
|
|
||||||
let s = "file-size: " + file.size;
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.upload.addEventListener('progress', (event) => {
|
|
||||||
if (event.lengthComputable) {
|
|
||||||
const progressPercent = Math.round((event.loaded * 100) / event.total);
|
|
||||||
progressBar.value = progressPercent;
|
|
||||||
progressLabel.innerHTML =progressBar.value + "%";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function () {
|
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
||||||
console.log("received status: %d", xhr.status);
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
// Upload completed successfully
|
|
||||||
alert('Upload completed!');
|
|
||||||
progressLabel.innerHTML = "Completed!";
|
|
||||||
} else if (xhr.status === 500) {
|
|
||||||
// Request was aborted (server-side)
|
|
||||||
alert('Upload aborted by the server.');
|
|
||||||
progressLabel.innerHTML = "Aborted!";
|
|
||||||
} else {
|
|
||||||
// Handle other error cases
|
|
||||||
alert('An error occurred during the upload.');
|
|
||||||
progressLabel.innerHTML = "Error!";
|
|
||||||
}
|
|
||||||
submitButton.disabled = false;
|
|
||||||
progressBar.value = 0;
|
|
||||||
progressLabel.innerHTML = "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.open('POST', url, true);
|
|
||||||
xhr.send(formData);
|
|
||||||
submitButton.disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAvailableUpdates(){
|
|
||||||
//Read Json
|
|
||||||
|
|
||||||
// Loop to create table
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>BLE Interaction</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
display: block;
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
margin: 10px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>BLE Interaction</h1>
|
|
||||||
<button id="connectBtn">Connect</button>
|
|
||||||
<button id="checkBtn" disabled>Check</button>
|
|
||||||
<textarea id="logArea" readonly></textarea>
|
|
||||||
<button id="startUpgradeBtn" disabled>Start Upgrade</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Constants
|
|
||||||
const BLE_SERVER_NAME = "BLE_Server_Name"; // Replace with your server name
|
|
||||||
const BLE_SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0"; // Replace with your service UUID
|
|
||||||
const BLE_CHARACTERISTIC_UUID = "12345678-1234-5678-1234-56789abcdef1"; // Replace with your characteristic UUID
|
|
||||||
|
|
||||||
let bleDevice = null;
|
|
||||||
let bleCharacteristic = null;
|
|
||||||
|
|
||||||
// Log messages to the textarea
|
|
||||||
function logMessage(message) {
|
|
||||||
const logArea = document.getElementById('logArea');
|
|
||||||
logArea.value += message + '\n';
|
|
||||||
logArea.scrollTop = logArea.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to connect to the BLE server
|
|
||||||
async function connectToBle() {
|
|
||||||
logMessage('Requesting BLE device...');
|
|
||||||
try {
|
|
||||||
bleDevice = await navigator.bluetooth.requestDevice({
|
|
||||||
filters: [{ name: BLE_SERVER_NAME }],
|
|
||||||
optionalServices: [BLE_SERVICE_UUID]
|
|
||||||
});
|
|
||||||
|
|
||||||
logMessage('Connecting to GATT server...');
|
|
||||||
const server = await bleDevice.gatt.connect();
|
|
||||||
|
|
||||||
logMessage('Getting service...');
|
|
||||||
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
|
|
||||||
|
|
||||||
logMessage('Getting characteristic...');
|
|
||||||
bleCharacteristic = await service.getCharacteristic(BLE_CHARACTERISTIC_UUID);
|
|
||||||
|
|
||||||
logMessage('Connected to BLE server.');
|
|
||||||
document.getElementById('checkBtn').disabled = false;
|
|
||||||
document.getElementById('startUpgradeBtn').disabled = false;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logMessage('Connection failed: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to send a JSON packet and wait for a reply
|
|
||||||
async function sendJsonPacket(packet) {
|
|
||||||
if (!bleCharacteristic) {
|
|
||||||
logMessage('Not connected to BLE server.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
logMessage('Sending JSON packet...');
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const encodedPacket = encoder.encode(JSON.stringify(packet));
|
|
||||||
await bleCharacteristic.writeValue(encodedPacket);
|
|
||||||
|
|
||||||
logMessage('Waiting for response...');
|
|
||||||
const response = await bleCharacteristic.readValue();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const decodedResponse = decoder.decode(response);
|
|
||||||
|
|
||||||
logMessage('Received response: ' + decodedResponse);
|
|
||||||
return JSON.parse(decodedResponse);
|
|
||||||
} catch (error) {
|
|
||||||
logMessage('Error during communication: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listeners for buttons
|
|
||||||
document.getElementById('connectBtn').addEventListener('click', connectToBle);
|
|
||||||
document.getElementById('checkBtn').addEventListener('click', () => {
|
|
||||||
const packet = { action: 'check' };
|
|
||||||
sendJsonPacket(packet);
|
|
||||||
});
|
|
||||||
document.getElementById('startUpgradeBtn').addEventListener('click', () => {
|
|
||||||
const packet = { action: 'start_upgrade' };
|
|
||||||
sendJsonPacket(packet);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,533 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>ATA Firmware Update</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-ble {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-wifi {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-internet {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adds space above the WiFi Connect button */
|
|
||||||
.btn-container.wifi {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 130px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover:not(:disabled) {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 90%;
|
|
||||||
max-width: 300px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, textarea {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tabs */
|
|
||||||
.tab-bar { display:flex; gap:8px; justify-content:center; margin:12px 0 16px; flex-wrap:wrap; }
|
|
||||||
.tab-bar button { max-width:none; flex:0 0 auto; background:#6c757d; }
|
|
||||||
.tab-bar button.active { background:#007bff; }
|
|
||||||
.tab-panel { display:none; }
|
|
||||||
.tab-panel.active { display:block; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>ATA Firmware Update</h1>
|
|
||||||
|
|
||||||
<!-- Tab Buttons -->
|
|
||||||
<div class="tab-bar">
|
|
||||||
<button class="tab-btn active" data-tab="tab-upgrade">Upgrade</button>
|
|
||||||
<button class="tab-btn" data-tab="tab-wifi">WiFi Comm</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="tab-upgrade" class="tab-panel active">
|
|
||||||
<!-- Status Indicators -->
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-ble"></span>
|
|
||||||
<label id="status-ble-connection">BLE Status: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-wifi"></span>
|
|
||||||
<label id="status-wifi-client">Wifi Client: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<span class="status-indicator-internet"></span>
|
|
||||||
<label id="status-internet">Internet: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-container">
|
|
||||||
<label id="status-current-version">Curr Version: ...</label>
|
|
||||||
</div>
|
|
||||||
<div class="status-container">
|
|
||||||
<label id="status-new-version">New Version: ...</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<label id="ble-device-name">Device Name:</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Buttons -->
|
|
||||||
<div class="btn-container">
|
|
||||||
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
|
|
||||||
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Log Area -->
|
|
||||||
<textarea id="logArea" readonly></textarea>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
|
|
||||||
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
|
|
||||||
</div>
|
|
||||||
</div> <!-- /tab-upgrade -->
|
|
||||||
|
|
||||||
<div id="tab-wifi" class="tab-panel">
|
|
||||||
<h2 style="margin-top:0; font-size:18px;">WiFi Connection</h2>
|
|
||||||
<div class="input-container">
|
|
||||||
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
|
|
||||||
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
|
|
||||||
<div style="display: flex; align-items: center; gap: 5px;">
|
|
||||||
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
|
|
||||||
<label for="showPassword">Show Password</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="btn-container wifi">
|
|
||||||
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
|
|
||||||
</div>
|
|
||||||
</div><!-- /tab-wifi -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function(){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* ================= Constants & Packet Layout ================= */
|
|
||||||
const BLE_SERVER_NAME = "ATALIGHTS"; // Keep hardcoded per instruction (ignore external JSON)
|
|
||||||
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0";
|
|
||||||
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Control / status
|
|
||||||
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Logs / events
|
|
||||||
|
|
||||||
// Packet layout (mirrors firmware struct updateStatus)
|
|
||||||
// byte 0 : wifiStatus (enum)
|
|
||||||
// byte 1 : wifiOnline (bool)
|
|
||||||
// bytes2-5: wifiIP
|
|
||||||
// bytes6-8: currVersion (major,minor,patch)
|
|
||||||
// bytes9-11: newVersion (major,minor,patch)
|
|
||||||
// bytes12-31: wifiSSID (20 bytes, null padded)
|
|
||||||
const PACKET_LEN = 32;
|
|
||||||
const OFF_WIFI_STATUS = 0;
|
|
||||||
const OFF_WIFI_ONLINE = 1;
|
|
||||||
const OFF_WIFI_IP = 2;
|
|
||||||
const OFF_CURR_VER = 6;
|
|
||||||
const OFF_NEW_VER = 9;
|
|
||||||
const OFF_WIFI_SSID = 12;
|
|
||||||
|
|
||||||
const WIFI_STAT = { DISCONNECTED:0, BAD_CREDS:1, NO_AP:2, CONNECTED:3 };
|
|
||||||
const WIFI_STAT_TEXT = ["Disconnected","Bad Creds","No AP","Connected"];
|
|
||||||
const MAX_LOG_LINES = 400;
|
|
||||||
|
|
||||||
/* ================= State ================= */
|
|
||||||
let bleDevice=null, bleCharacteristic1=null, bleCharacteristic2=null;
|
|
||||||
let bleConnected=false;
|
|
||||||
const state = {
|
|
||||||
wifiStatus: WIFI_STAT.DISCONNECTED,
|
|
||||||
wifiOnline:false,
|
|
||||||
wifiIP:[0,0,0,0],
|
|
||||||
currVersion:[0,0,0],
|
|
||||||
newVersion:[0,0,0],
|
|
||||||
wifiSSID:""
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ================= Cached DOM ================= */
|
|
||||||
const el = {};
|
|
||||||
function cacheDom(){
|
|
||||||
el.bleIndicator = document.querySelector('.status-indicator-ble');
|
|
||||||
el.wifiIndicator = document.querySelector('.status-indicator-wifi');
|
|
||||||
el.internetIndicator = document.querySelector('.status-indicator-internet');
|
|
||||||
el.lblBle = document.getElementById('status-ble-connection');
|
|
||||||
el.lblWifi = document.getElementById('status-wifi-client');
|
|
||||||
el.lblInternet = document.getElementById('status-internet');
|
|
||||||
el.lblCurrVer = document.getElementById('status-current-version');
|
|
||||||
el.lblNewVer = document.getElementById('status-new-version');
|
|
||||||
el.inDeviceName = document.getElementById('input-DeviceName');
|
|
||||||
el.inSsid = document.getElementById('wifissid');
|
|
||||||
el.inPass = document.getElementById('wifipassword');
|
|
||||||
el.chkShowPass = document.getElementById('showPassword');
|
|
||||||
el.btnBleConnect = document.getElementById('bleConnectBtn');
|
|
||||||
el.btnCheckStatus = document.getElementById('checkStatusBtn');
|
|
||||||
el.btnCheckVersion = document.getElementById('checkVersionBtn');
|
|
||||||
el.btnStartUpgrade = document.getElementById('startUpgradeBtn');
|
|
||||||
el.btnWifiConnect = document.getElementById('wifiConnectBtn');
|
|
||||||
el.logArea = document.getElementById('logArea');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= Utilities ================= */
|
|
||||||
function logMessage(msg){
|
|
||||||
const lines = el.logArea.value.trim().length ? el.logArea.value.split(/\n/) : [];
|
|
||||||
lines.push(msg);
|
|
||||||
if(lines.length > MAX_LOG_LINES){
|
|
||||||
lines.splice(0, lines.length - MAX_LOG_LINES);
|
|
||||||
}
|
|
||||||
el.logArea.value = lines.join('\n') + '\n';
|
|
||||||
el.logArea.scrollTop = el.logArea.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareVersions(a,b){
|
|
||||||
for(let i=0;i<3;i++){ if(a[i]>b[i]) return 1; if(a[i]<b[i]) return -1; }
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function colorIndicator(elm, color){ if(elm) elm.style.backgroundColor = color; }
|
|
||||||
|
|
||||||
function ipToString(ip){ return ip.join('.'); }
|
|
||||||
|
|
||||||
/* ================= Packet Handling ================= */
|
|
||||||
function parsePacket(data){
|
|
||||||
if(data.length !== PACKET_LEN) return false;
|
|
||||||
state.wifiStatus = data[OFF_WIFI_STATUS];
|
|
||||||
if(state.wifiStatus > WIFI_STAT.CONNECTED) state.wifiStatus = WIFI_STAT.DISCONNECTED; // clamp
|
|
||||||
state.wifiOnline = !!data[OFF_WIFI_ONLINE];
|
|
||||||
state.wifiIP = Array.from(data.slice(OFF_WIFI_IP, OFF_WIFI_IP+4));
|
|
||||||
state.currVersion = Array.from(data.slice(OFF_CURR_VER, OFF_CURR_VER+3));
|
|
||||||
state.newVersion = Array.from(data.slice(OFF_NEW_VER, OFF_NEW_VER+3));
|
|
||||||
// Extract SSID (stop at first 0)
|
|
||||||
let rawSsidBytes = data.slice(OFF_WIFI_SSID, OFF_WIFI_SSID+20);
|
|
||||||
let zeroIndex = rawSsidBytes.indexOf(0);
|
|
||||||
if(zeroIndex >= 0) rawSsidBytes = rawSsidBytes.slice(0, zeroIndex);
|
|
||||||
state.wifiSSID = rawSsidBytes.length ? String.fromCharCode(...rawSsidBytes) : "";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUI(){
|
|
||||||
// BLE
|
|
||||||
el.lblBle.textContent = 'BLE Status: ' + (bleConnected ? 'Connected' : 'Disconnected');
|
|
||||||
colorIndicator(el.bleIndicator, bleConnected ? 'green' : 'gray');
|
|
||||||
|
|
||||||
// WiFi client
|
|
||||||
const statText = WIFI_STAT_TEXT[state.wifiStatus] || 'Unknown';
|
|
||||||
if(state.wifiStatus === WIFI_STAT.CONNECTED){
|
|
||||||
const ssidPart = state.wifiSSID ? ' SSID: '+state.wifiSSID : '';
|
|
||||||
el.lblWifi.textContent = 'Wifi Client: ' + statText + (state.wifiIP[0] ? ' ('+ipToString(state.wifiIP)+')':'' ) + ssidPart;
|
|
||||||
colorIndicator(el.wifiIndicator, 'green');
|
|
||||||
} else if(state.wifiStatus === WIFI_STAT.BAD_CREDS){
|
|
||||||
el.lblWifi.textContent = 'Wifi Client: Bad Credentials';
|
|
||||||
colorIndicator(el.wifiIndicator, 'orange');
|
|
||||||
} else if(state.wifiStatus === WIFI_STAT.NO_AP){
|
|
||||||
el.lblWifi.textContent = 'Wifi Client: AP Not Found';
|
|
||||||
colorIndicator(el.wifiIndicator, 'orange');
|
|
||||||
} else {
|
|
||||||
el.lblWifi.textContent = 'Wifi Client: ' + statText;
|
|
||||||
colorIndicator(el.wifiIndicator, 'gray');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internet
|
|
||||||
el.lblInternet.textContent = state.wifiOnline ? 'Online' : 'Offline';
|
|
||||||
colorIndicator(el.internetIndicator, state.wifiOnline ? 'green' : 'gray');
|
|
||||||
|
|
||||||
// Versions
|
|
||||||
el.lblCurrVer.textContent = state.currVersion[0] ? 'Curr Version: ' + state.currVersion.join('.') : 'Curr Version: ...';
|
|
||||||
el.lblNewVer.textContent = state.newVersion[0] ? 'New Version: ' + state.newVersion.join('.') : 'New Version: ...';
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
el.btnCheckStatus.disabled = !bleConnected;
|
|
||||||
el.btnWifiConnect.disabled = !bleConnected;
|
|
||||||
el.btnCheckVersion.disabled = !(bleConnected && state.wifiOnline);
|
|
||||||
const newerAvail = state.newVersion[0] && state.wifiOnline && compareVersions(state.newVersion, state.currVersion) > 0;
|
|
||||||
el.btnStartUpgrade.disabled = !newerAvail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= BLE Operations ================= */
|
|
||||||
async function connectToBle(){
|
|
||||||
if(!navigator.bluetooth){ logMessage('Web Bluetooth not supported.'); return; }
|
|
||||||
try{
|
|
||||||
bleDevice = await navigator.bluetooth.requestDevice({
|
|
||||||
filters:[{ name: el.inDeviceName.value || BLE_SERVER_NAME }],
|
|
||||||
optionalServices:[BLE_SERVICE_UUID]
|
|
||||||
});
|
|
||||||
const server = await bleDevice.gatt.connect();
|
|
||||||
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
|
|
||||||
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
|
|
||||||
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
|
|
||||||
await bleCharacteristic2.startNotifications();
|
|
||||||
bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
|
|
||||||
try{
|
|
||||||
const view = e.target.value; // DataView
|
|
||||||
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
||||||
|
|
||||||
// Debug info
|
|
||||||
let debugInfo = `Received ${bytes.length} bytes: `;
|
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
|
||||||
debugInfo += bytes[i].toString(16).padStart(2, '0') + ' ';
|
|
||||||
}
|
|
||||||
console.log(debugInfo);
|
|
||||||
|
|
||||||
// Try to decode as text
|
|
||||||
let txt = '';
|
|
||||||
try {
|
|
||||||
txt = new TextDecoder().decode(bytes);
|
|
||||||
// Remove null terminators and trim
|
|
||||||
const nullIdx = txt.indexOf('\0');
|
|
||||||
if (nullIdx !== -1) txt = txt.slice(0, nullIdx);
|
|
||||||
txt = txt.trim();
|
|
||||||
} catch (decodeErr) {
|
|
||||||
console.error('Text decode error:', decodeErr);
|
|
||||||
// Fallback to showing hex if text decode fails
|
|
||||||
txt = '[Binary data: ' + debugInfo + ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show both raw and processed data in console
|
|
||||||
console.log('--> Raw bytes:', bytes);
|
|
||||||
console.log('--> As text:', txt);
|
|
||||||
|
|
||||||
// Log message with length info for debugging
|
|
||||||
logMessage(`--> (${bytes.length} bytes) ${txt}`);
|
|
||||||
} catch(err) {
|
|
||||||
console.error('Processing error', err);
|
|
||||||
logMessage('--> Error processing message: ' + err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
bleConnected=true;
|
|
||||||
bleDevice.addEventListener('gattserverdisconnected', onDisconnect);
|
|
||||||
const connectedName = bleDevice.name || el.inDeviceName.value || 'Device';
|
|
||||||
logMessage('Connected to ' + connectedName);
|
|
||||||
const nameLabel = document.getElementById('ble-device-name');
|
|
||||||
if(nameLabel){ nameLabel.textContent = 'Device Name: ' + connectedName; }
|
|
||||||
await readPacket();
|
|
||||||
updateUI();
|
|
||||||
}catch(err){
|
|
||||||
logMessage( err.message.includes('cancel') ? 'Connection cancelled.' : ('Connection failed: '+err.message) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDisconnect(){
|
|
||||||
bleConnected=false; updateUI(); logMessage('BLE disconnected');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendPacket(msg){
|
|
||||||
if(!bleCharacteristic1) return;
|
|
||||||
const enc = new TextEncoder();
|
|
||||||
for(let attempt=0; attempt<3; attempt++){
|
|
||||||
try{ await bleCharacteristic1.writeValueWithResponse(enc.encode(msg)); return true; }
|
|
||||||
catch(e){ if(attempt===2) logMessage('Send failed: '+e.message); else await delay(1000); }
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readPacket(){
|
|
||||||
if(!bleCharacteristic1) return false;
|
|
||||||
for(let attempt=0; attempt<3; attempt++){
|
|
||||||
try{
|
|
||||||
const val = await bleCharacteristic1.readValue();
|
|
||||||
const data = new Uint8Array(val.buffer);
|
|
||||||
if(parsePacket(data)) return true; else { logMessage('Packet parse failed (len='+data.length+')'); return false; }
|
|
||||||
}catch(e){ if(attempt===2) logMessage('Read failed: '+e.message); else await delay(1000); }
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= Actions ================= */
|
|
||||||
async function wifiConnect(){
|
|
||||||
const ssid = el.inSsid.value.trim();
|
|
||||||
const pw = el.inPass.value;
|
|
||||||
if(!ssid || !pw){ alert('Enter SSID & password'); return; }
|
|
||||||
logMessage('Sending WiFi credentials...');
|
|
||||||
el.btnWifiConnect.disabled = true; // prevent multiple submissions while polling
|
|
||||||
await sendPacket('wifi-connect {"ssid":"'+ssid+'","pass":"'+pw+'"} ');
|
|
||||||
// Poll for status for up to 15s
|
|
||||||
const start = Date.now();
|
|
||||||
while(Date.now()-start < 15000){
|
|
||||||
await delay(1000);
|
|
||||||
await readPacket();
|
|
||||||
updateUI();
|
|
||||||
if(state.wifiStatus === WIFI_STAT.CONNECTED){
|
|
||||||
logMessage('WiFi Connected: '+ipToString(state.wifiIP));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(state.wifiStatus === WIFI_STAT.BAD_CREDS){ logMessage('WiFi Error: Bad Credentials'); break; }
|
|
||||||
if(state.wifiStatus === WIFI_STAT.NO_AP){ logMessage('WiFi Error: AP Not Found'); break; }
|
|
||||||
}
|
|
||||||
if(state.wifiStatus !== WIFI_STAT.CONNECTED){ logMessage('WiFi connect attempt finished with status: '+WIFI_STAT_TEXT[state.wifiStatus]); }
|
|
||||||
if(!bleConnected) return; // keep disabled if BLE disconnected during process
|
|
||||||
// Re-enable for retry unless connected
|
|
||||||
if(state.wifiStatus !== WIFI_STAT.CONNECTED){ el.btnWifiConnect.disabled = false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkStatus(){ if(await readPacket()) updateUI(); }
|
|
||||||
|
|
||||||
async function checkVersion(){
|
|
||||||
el.btnCheckVersion.disabled = true;
|
|
||||||
logMessage('Checking for new version...');
|
|
||||||
await sendPacket('version-check');
|
|
||||||
const start = Date.now();
|
|
||||||
while(Date.now()-start < 15000){
|
|
||||||
await delay(750);
|
|
||||||
await readPacket();
|
|
||||||
if(state.newVersion[0]){ logMessage('Latest version: '+state.newVersion.join('.')); break; }
|
|
||||||
}
|
|
||||||
if(!state.newVersion[0]) logMessage('No new version info received');
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startUpgrade(){
|
|
||||||
if(el.btnStartUpgrade.disabled) return;
|
|
||||||
logMessage('Starting upgrade...');
|
|
||||||
el.btnStartUpgrade.disabled = true;
|
|
||||||
await sendPacket('upgrade-start');
|
|
||||||
// Progress will arrive via characteristic2 logs
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= Helpers ================= */
|
|
||||||
const delay = ms => new Promise(r=>setTimeout(r,ms));
|
|
||||||
|
|
||||||
function togglePasswordVisibility(){ el.inPass.type = el.chkShowPass.checked ? 'text' : 'password'; }
|
|
||||||
|
|
||||||
function init(){
|
|
||||||
cacheDom();
|
|
||||||
el.inDeviceName.value = BLE_SERVER_NAME;
|
|
||||||
el.chkShowPass.addEventListener('change', togglePasswordVisibility);
|
|
||||||
// Inline onclicks already wired; ensure functions are in scope
|
|
||||||
// Tab switching
|
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn=>{
|
|
||||||
btn.addEventListener('click', ()=>{
|
|
||||||
document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
|
|
||||||
document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('active'));
|
|
||||||
btn.classList.add('active');
|
|
||||||
const id = btn.getAttribute('data-tab');
|
|
||||||
const panel = document.getElementById(id);
|
|
||||||
if(panel) panel.classList.add('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
updateUI();
|
|
||||||
logMessage('Ready. Enter device name or use default and press Connect.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expose functions for inline handlers
|
|
||||||
window.connectToBle = connectToBle;
|
|
||||||
window.checkStatus = checkStatus;
|
|
||||||
window.checkVersion = checkVersion;
|
|
||||||
window.startUpgrade = startUpgrade;
|
|
||||||
window.wifiConnect = wifiConnect;
|
|
||||||
window.togglePasswordVisibility = togglePasswordVisibility;
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', init);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
Choose Control App:
|
|
||||||
1) Open app-events.json
|
|
||||||
2) Change index to corresponding app
|
|
||||||
a. 0=DSLRBooth, 1=...., 2=.....
|
|
||||||
|
|
||||||
Front Constant Light: ( to enable the light)
|
|
||||||
1) Open led-devices.json
|
|
||||||
2) Change "front-light", "en" to true
|
|
||||||
|
|
||||||
Rear Constant Light:
|
|
||||||
1) Open led-devices.json
|
|
||||||
2) Change "rear-light", "en" to true
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user