Add new files for icons, images, documentation, configuration, and initial code structure

This commit is contained in:
admin 2025-03-19 11:55:34 -07:00
commit 02b1be44f8
368 changed files with 64038 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

20
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
// ...existing code...
// Live Server Configuration
"liveServer.settings.root": "/data",
"liveServer.settings.port": 5500,
"liveServer.settings.cors": true,
"liveServer.settings.mount": [
["/", "./data"]
],
"liveServer.settings.CustomBrowser": "chrome",
"liveServer.settings.NoBrowser": false,
"liveServer.settings.ignoreFiles": [
".vscode/**",
"**/*.scss",
"**/*.sass"
],
"liveServer.settings.wait": 100,
"liveServer.settings.host": "localhost"
}

1
Backup/data/Test1.txt Normal file
View File

@ -0,0 +1 @@
asdfada

8
Backup/data/Test2.txt Normal file
View File

@ -0,0 +1,8 @@
adfasdfadfa
adfasdfadfaadfa
sd
adfasdfadfaadfaa
afas

View File

@ -0,0 +1,499 @@
{
"countdown": {
"min": "10",
"max": "75",
"hold": "700",
"ramp": "650"
},
"profile-index": 0,
"profiles": [
{
"name": "Profile test",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": "5",
"speed": "10"
},
{
"anim": 1,
"colmain": "#000020",
"colbase": "#000020",
"density": "25",
"speed": "50"
},
{
"anim": 2,
"colmain": "#000020",
"colbase": "#000020",
"density": "35",
"speed": "65"
},
{
"anim": 3,
"colmain": "#000020",
"colbase": "#000020",
"density": "45",
"speed": "75"
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": "5",
"speed": "10"
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": "5",
"speed": "10"
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": "5",
"speed": "10"
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": "5",
"speed": "70"
}
]
},
{
"name": "Profile 1",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 2",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 3",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 4",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 5",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 6",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 7",
"events": [
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 1,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 70
}
]
}
]
}

View File

@ -0,0 +1,15 @@
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

View File

@ -0,0 +1,25 @@
{
"whitefills":[
"Bottom/Up Fill",
"Snake Fill",
"Tick Fill",
"Smooth Brighten",
"Cycle All",
"Random Select"
],
"animations":[
"Rainbow",
"Hue Spectrum Mirrored",
"Meteors Hue",
"Dashes",
"Sectors",
"Fire (red) mirrored",
"Fire (blue) mirrored",
"Fire (green) mirrored",
"Strobe",
"Twinkle",
"Rain",
"Stacking",
"Snake (Hue Range)"
]
}

View File

@ -0,0 +1,499 @@
{
"countdown":{
"min": 10,
"max": 75,
"hold": 700,
"ramp": 650
},
"profile-index": 0,
"profiles":[
{
"name": "Profile test",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 1,
"colmain": "#000020",
"colbase": "#000020",
"density": 25,
"speed": 50
},
{
"anim": 2,
"colmain": "#000020",
"colbase": "#000020",
"density": 35,
"speed": 65
},
{
"anim": 3,
"colmain": "#000020",
"colbase": "#000020",
"density": 45,
"speed": 75
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 4,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 70
}
]
},
{
"name": "Profile 1",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 2",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 3",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 4",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 5",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 6",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
}
]
},
{
"name": "Profile 7",
"events":[
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 0,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 10
},
{
"anim": 1,
"colmain": "#000020",
"colbase": "#000020",
"density": 5,
"speed": 70
}
]
}
]
}

View File

@ -0,0 +1,97 @@
{
"twinkle":{
"col1": "#000000",
"col2": "#000000",
"density": 1,
"speed": 25,
"pwr": 255
},
"Strobe":{
"col1": "#000000",
"col2": "#000000",
"density": 1,
"speed": 25,
"pwr": 255
},
"Solid":{
"col1": "#000000",
"col2": "#000000",
"density": 1,
"pwr": 255
},
"Fade":{
"col1": "#000000",
"range": 128,
"speed": 25,
"pwr": 255
},
"HueSwirl":{
"col1": "#000000",
"range": 128,
"speed": 25,
"pwr": 255
},
"Meteors":{
"col1": "#000000",
"col2": "#000000",
"range": 1,
"density": 1,
"speed": 25,
"pwr": 255
},
"Dashes":{
"col1": "#000000",
"col2": "#000000",
"col3": "#000000",
"segments": 1,
"density": 1,
"speed": 25,
"pwr": 255
},
"DashesRange":{
"col1": "#000000",
"col2": "#000000",
"range": 128,
"segments": 1,
"density": 1,
"speed": 25,
"pwr": 255
},
"Fire":{
"col1": "#000000",
"cool": 25,
"spark": 25,
"speed": 25,
"pwr": 255
},
"Rain":{
"col1": "#000000",
"col2": "#000000",
"range": 128,
"density": 1,
"speed": 25,
"pwr": 255
},
"Stacking":{
"col1": "#000000",
"col2": "#000000",
"range": 128,
"speed": 25,
"pwr": 255
},
"Snake":{
"col1": "#000000",
"col2": "#000000",
"range": 128,
"speed": 25,
"pwr": 255
},
"Theater":{
"col1": "#000000",
"col2": "#000000",
"range": 128,
"speed": 25,
"step": 20,
"pwr": 255
}
}

View File

@ -0,0 +1,70 @@
{
"index": 0,
"apps":[
{
"name": "DSLR Booth Configuration",
"events":[
"White Fill / Countdown",
"Experience Selection",
"Select Sharing Screen",
"",
"",
"",
"",
""
]
},
{
"name": "FotoFliqs Configuration",
"events":[
"White Fill / Countdown",
"Experience Selection",
"Preview",
"Notice!",
"Idle / Pause",
"Advertisement",
"Printing",
"Phone Input / QR Code / Apple Drop"
]
},
{
"name": "TouchPics Configuration",
"events":[
"White Fill / Countdown",
"Experience Selection",
"Preview",
"Notice!",
"Idle / Pause",
"Advertisement",
"Printing",
"Phone Input / QR Code / Apple Drop"
]
},
{
"name": "FotoZap Configuration",
"events":[
"White Fill / Countdown",
"Experience Selection",
"Select Sharing Screen",
"",
"",
"",
"",
""
]
},
{
"name": "Twineit Configuration",
"events":[
"White Fill / Countdown",
"Experience Selection",
"Select Sharing Screen",
"",
"",
"",
"",
""
]
}
]
}

9
Backup/data/cfg/ble.json Normal file
View File

@ -0,0 +1,9 @@
{
"en": "true",
"core": 1,
"device-name": "ATA_COMM",
"key": "123456",
"service_uuid": "6E400001-B5A3-F393-E0A9-E50E24DCCA9E",
"char-uuid-rx": "6E400002-B5A3-F393-E0A9-E50E24DCCA9E",
"char-uuid-tx": "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
}

View File

@ -0,0 +1,81 @@
{
"en":true,
"boot":
{
"cycles": 1,
"pause": 0,
"tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"restart":
{
"cycles": 1,
"pause": 0,
"tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"wifi-conn":
{
"cycles": 1,
"pause": 0,
"tune": "WifiConnected:d=16,o=5,b=112:32p,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6,f,a#,c6"
},
"wifi-disc":
{
"cycles": 1,
"pause": 0,
"tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"ble-conn":
{
"cycles": 1,
"pause":0,
"tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"ble-disc":
{
"cycles": 1,
"pause": 0,
"tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"click":
{
"cycles": 1,
"pause": 0,
"tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"error":
{
"cycles": 1,
"pause": 0,
"tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"success":
{
"cycles": 1,
"pause": 0,
"tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"download":
{
"cycles": 1,
"pause": 0,
"tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"waiting":
{
"cycles": 1,
"pause": 0,
"tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"beep":
{
"cycles": 1,
"pause": 0,
"tune": "Click:d=16,o=5,b=112:32p,f,g,a,b,b#"
},
"test":
{
"cycles": 1,
"pause": 0,
"tune": "Boot:d=16,o=5,b=112:32p,f,g,a,b,b#"
}
}

View File

@ -0,0 +1,8 @@
{
"firm-index": 0,
"firm-locations":[
"http://mylocation1",
"http://mylocation2",
"http://mylocation3"
]
}

View File

@ -0,0 +1,39 @@
{
"strip1": {
"en": true,
"size": 138,
"chip": "SK6812",
"rgb-order": "gbr",
"shift":0,
"offset": 0,
"bright": 200,
"pin": 3,
"i2s-ch": 0,
"core": 0
},
"strip2": {
"en": false,
"size": 20,
"chip": "SK6812",
"rgb-order": "grb",
"shift":0,
"offset": 0,
"bright": 200,
"pin": 46,
"i2s-ch": 1,
"core": 0
},
"front-light": {
"en": false,
"relay": 0,
"min": 20,
"max": 100,
"core": 1
},
"rear-light": {
"en": false,
"relay": 1,
"min": 20,
"max": 100
}
}

View File

@ -0,0 +1,41 @@
{
"relays":
[
{
"pin": 45,
"freq": 500,
"min": 0,
"max": 100,
"default": 5,
"vision": false,
"deltarate": 0
},
{
"pin": 48,
"freq": 500,
"min": 0,
"max": 100,
"default": 10,
"vision": false,
"deltarate": 0
},
{
"pin": 47,
"freq": 500,
"min": 0,
"max": 100,
"default": 20,
"vision": true,
"deltarate": 0
},
{
"pin": 21,
"freq": 500,
"min": 0,
"max": 100,
"default": 40,
"vision": true,
"deltarate": 0
}
]
}

View File

@ -0,0 +1,37 @@
{
"buttons": [
{
"en": true,
"pin": 8
},
{
"en": true,
"pin": 19
},
{
"en": true,
"pin": 0
}
],
"oled": {
"en": false,
"height": 64,
"width": 128
},
"fan": {
"en": false,
"relay": 3
},
"t-sensor": {
"en": false,
"addr": 72,
"sp1": 85,
"fan-pwr1": 50,
"sp2": 90,
"fan-pwr2": 90,
"hyst": 1
},
"adc": {
"ain1_factor": 1.0
}
}

View File

@ -0,0 +1,30 @@
{
"touchpins":
[
{
"en": true,
"pin": 23,
"min": 100
},
{
"en": true,
"pin": 23,
"min": 100
},
{
"en": true,
"pin": 23,
"min": 100
},
{
"en": true,
"pin": 23,
"min": 100
},
{
"en": true,
"pin": 23,
"min": 100
}
]
}

View File

@ -0,0 +1,10 @@
{
"rx433": {
"en": "true",
"pin": 38
},
"tx433": {
"en": "true",
"pin": 16
}
}

20
Backup/data/cfg/wifi.json Normal file
View File

@ -0,0 +1,20 @@
{
"wifi":
{
"en": true,
"ap-ssid": "ATA-AP",
"ap-pass": "123456",
"mdns-name": "atadev",
"host-name": "ATADeviceXX"
},
"cred1":
{
"ssid": "DPWifi",
"pass": "dave3.14159"
},
"cred2":
{
"ssid": "Default",
"pass": "password"
}
}

BIN
Backup/data/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
Backup/data/img/atalogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,617 @@
{{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;
}
select{
width:170px;
}
#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>
&emsp;&emsp;
<label for="profileName">Name:</label>
<input type="text" name="inputProfileName" id="profileName">
&emsp;&emsp;&emsp;
<button id="saveProfile" onclick="SaveProfilesToServer()">Save Profile</button>
</td>
</tr>
<div ></div>
</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 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 down(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">Main:</label><br>
<input type="color" name="main-color-anim0" id="color-picker" >&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim0" id="color-picker">&emsp;
</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">Main:</label><br>
<input type="color" name="main-color-anim1" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim1" id="color-picker">&emsp;
</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">Main:</label><br>
<input type="color" name="main-color-anim2" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim2" id="color-picker">&emsp;
</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">Main:</label><br>
<input type="color" name="main-color-anim3" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim3" id="color-picker">&emsp;
</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">Main:</label><br>
<input type="color" name="main-color-anim4" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim4" id="color-picker">&emsp;
</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">Main:</label><br>
<input type="color" name="main-color-anim5" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim5" id="color-picker">&emsp;
</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">Main:</label><br>
<input type="color" name="main-color-anim6" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim6" id="color-picker">&emsp;
</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">Main:</label><br>
<input type="color" name="main-color-anim7" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim7" id="color-picker">&emsp;
</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>
<script>
window.onload = function() { getProfilesAndEvents(); };
// Initialize Names
const EVENTCOUNT = 8;
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 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],
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]
};
}
// update the form with animation data when profile list item selected
function setProfile(index){
var changeEvent = new Event("change");
// update profiles
inputProfileName.value = profilesJson.profiles[index].name;
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].colmain.value = profilesJson.profiles[index].events[i].colmain;
animEvent[i].colbase.value = profilesJson.profiles[index].events[i].colbase;
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('Selected value:', event.selectedIndex);
setProfile(event.selectedIndex);
}
//Update the profilesJson obj with updated values
function updateProfilesJson(){
try{
let index = selSavedAnimProfiles.selectedIndex;
profilesJson.profiles[index].name = 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].colmain = animEvent[i].colmain.value;
profilesJson.profiles[index].events[i].colbase = animEvent[i].colbase.value;
profilesJson.profiles[index].events[i].density = animEvent[i].density.value;
profilesJson.profiles[index].events[i].speed = animEvent[i].speed.value;
}
}catch(e){
debugger;
console.log(e);
}
}
// save profilesJson
function SaveProfilesToServer(){
// update profilesJson obj with current settings before posting
updateProfilesJson();
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(animIndex){
var tempAnimProps = profilesJson.profiles[0].events[0];
tempAnimProps.anim = animEvent[animIndex].anim.selectedIndex;
tempAnimProps.colmain = animEvent[animIndex].colmain.value
tempAnimProps.colbase = animEvent[animIndex].colbase.value
tempAnimProps.density = animEvent[animIndex].density.value;
tempAnimProps.speed = animEvent[animIndex].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'];
const changeEvent = new Event('change');
selSavedAnimProfiles.dispatchEvent(changeEvent);
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++){
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>

View File

@ -0,0 +1,16 @@
{{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>

View File

@ -0,0 +1,11 @@
{{NAVBAR}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Photobooth Configuration</title>
</head>
<body>
<h1>Photobooth Configuration</h1>
</body>
</html>

144
Backup/data/www/edit.html Normal file
View File

@ -0,0 +1,144 @@
{{NAVBAR}}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Edit file</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
/*background-color: #f7f7f7;*/
font-family: Tahoma, Arial, sans-serif;
font-size: small;
margin: 0;
display: flex;
flex-direction: column; /* Align contents vertically */
align-items: center;
justify-content: flex-start;
}
h1 {
margin-top: 0; /* Remove top margin for h2 element */
}
#submit {
width:100px;
}
button{
width: 100px;
}
#spacer-50 {
height: 20px;
}
#spacer-20 {
height: 10px;
}
fieldset {
width:700px;
border-radius: 10px;
background-color: lightgray;
}
td, th {
text-align: center;
padding: 1px;
}
legend{
background-color:white;
background-blend-mode: darken;
border-radius: 5px;
padding: 1px 6px 2px 6px;
border-style:solid;
border-width: 1.0;
}
textarea {
width: 700px;
height: 500px;
box-sizing: border-box;
border: 1.5px solid #000000;
border-radius: 8px;
resize: none;
word-wrap: none;
}
</style>
</head>
<body>
<h2>Edit file</h2>
<fieldset>
<legend>Editing file: {{SAVE_PATH_INPUT}}</legend>
<div id="spacer-20"></div>
<table><tr><td colspan="2">
<form name="edit-file" action="/save" onsubmit="return validateForm()">
<textarea name="edit-textarea" id="edit-textarea" wrap="off" ></textarea>
<div id="spacer-20"></div>
</td></tr><tr><td>
{{SAVE_PATH_INPUT}}
<button type="submit" id="submit-edit" >Save</button>
</form>
</td><td>
<button id="submit" onclick="window.location.href='/filemanager';">Cancel</button>
</td></tr></table>
<div id="spacer-50"></div>
</fieldset>
<iframe style="display:none" name="self-page"></iframe>
<script>
window.onload=loadEditFile();
function loadEditFile(){
var savePath = document.getElementById('save-path').value;
if( savePath != "/new.txt"){ // skip if new.txt
fetch(savePath)
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch file');
}
return response.text();
})
.then(fileContents => {
// Put the file contents into a textarea element
document.getElementById('edit-textarea').value = fileContents;
})
.catch(error => {
console.error(error);
});
}
}
function validateForm()
{
var allowedExtensions = "{{ALLOWED_EXTENSIONS_EDIT}}";
var inputMessage = document.getElementById('save-path').value;
var dotIndex = inputMessage.lastIndexOf(".")+1;
var inputMessageExtension = inputMessage.substring(dotIndex);
var extIndex = allowedExtensions.indexOf(inputMessageExtension);
var isSlash = inputMessage.substring(0,1);
if(inputMessage == "")
{
alert("Enter the file name! \ne.g.: /new.txt");
return false;
}
if(isSlash != "/")
{
alert("The slash at the beginning of the file is missing!");
return false;
}
if(dotIndex == 0)
{
alert("The extension is missing at the end of the file!");
return false;
}
if(inputMessageExtension == "")
{
alert("The extension is missing at the end of the file!");
return false;
}
if(extIndex == -1)
{
alert("Extension not supported!");
return false;
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,24 @@
{{NAVBAR}}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Update Failed</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
background-color: #f7f7f7;
}
#spacer-50 {
height: 50px;
}
</style>
</head>
<body>
<center>
<h2>The update has failed.</h2>
<div id="spacer-50"></div>
<button onclick="window.location.href='/filemanager';">to homepage</button>
</center>
</body>
</html>

View File

@ -0,0 +1,297 @@
{{NAVBAR}}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>File Manager</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="global-style.css" rel="stylesheet">
<style>
#file-row:nth-child(odd) {
background-color: #e8e8e8;
}
progress{
width: 380px;
}
#submit-edit, #submit-upload, #submit-delete, #submit-update-local{
width:120px;
}
#dir-path{
width: 75px;
}
fieldset {
width:500px;
}
table {
width:540px;
}
</style>
<body>
<h1>File Manager</h1>
<fieldset>
<legend>File list</legend>
<div id="spacer-20"></div>
<p>&emsp;&emsp;Total storage: {{FS_TOTAL_BYTES}}, Used: {{FS_USED_BYTES}}, Still available: {{FS_FREE_BYTES}}</p>
<div id="spacer-20"></div>
<table><tr><th id="first_td_th">Listing:</th> </th><th>Size:</th></tr>
{{LISTED_FILES}}
</table>
<div id="spacer-20"></div>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>File upload</legend>
<div id="spacer-20"></div>
<form action="/upload" method="POST" enctype="multipart/form-data">
<table><tr>
<td id="first_td_th">
<label for="dir-path">Dir: </label>
<select name="dir-path" id="dir-path">
{{DIR_LIST}}
</select>&emsp;&emsp;
<input type="file" id="upload-file" name="upload-file">
</td>
<td><input type="submit" id="submit-upload" value="File upload!" onclick="return validateFormUpload()"></td>
</tr></table>
</form>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>Edit file</legend>
<div id="spacer-20"></div>
<form action="/edit" method="GET">
<table><tr>
<td id="first_td_th">
<select name="edit-path" id="edit-path">
<option value="choose">Select file to edit</option>
<option value="new">New text file</option>
{{EDIT-DEL_FILES}}
</select></td>
<td><input type="submit" id="submit-edit" value="Edit" onclick="return validateFormEdit()"></td>
</tr></table>
</form>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>Delete file</legend>
<div id="spacer-20"></div>
<form action="/delete" method="GET">
<table><tr>
<td id="first_td_th">
<select name="delete-path" id="select-files">
<option value="choose">Select file to delete</option>
{{EDIT-DEL_FILES}}
</select></td>
<td><input type="submit" id="submit-delete" value="Delete" onclick="return validateFormDelete()"></td>
</tr></table>
</form>
</fieldset>
<div id="spacer-20"></div>
<!--<fieldset>
<legend>Format File System</legend>
<div id="spacer-20"></div>
<form action="/format" method="POST" target="self-page">
<table><tr>
<td id="first_td_th">
<p id="format-notice">Pressing the 'Format' button will immediately delete all data from File System!</p></td>
<td><input type="submit" id="submit-format" value="Format" onclick="return confirmFormat()"></td>
</tr></table>
</form>
<div id="spacer-20"></div>
</fieldset>-->
<h2>Firmware/File System Update</h2>
<fieldset>
<legend>Firmware Update (Local) | Firmware Ver: {{FIRM_VER}} | fwataVxxx.bin or lfsataVxxx.bin</legend>
<div id="spacer-20"></div>
<form action="/update" method="POST" enctype="multipart/form-data">
<table>
<tr>
<td id="first_td_th">
<input type="file" id="update-file" name="update-file">
</td>
<td>
<input type="submit" id="submit-update-local" value="Update!" onclick="return validateFormUpdate()">
</td>
</tr>
</table>
<table>
<tr><div id="spacer-20"></div>
<td >
<label for="firm-progress">Progress:</label>
<progress id="firm-progress" value="0" max="100"></progress>
<label id="lbl-firm-progress" for="firm-progress">---</label>
</td>
</tr>
</table>
</form>
</fieldset>
<div id="spacer-20"></div>
<fieldset hidden>
<legend>Firmware Update (Web)</legend>
<div id="spacer-20"></div>
<form action="/update" method="POST" enctype="multipart/form-data">
<table><tr>
<td id="first_td_th">
<button type="submit" id="submit-update-check" value="check" onclick="return validateFormUpdate()"></button>
<label>Status:</label></td>
<td><input type="submit" id="submit-update-web" value="Update!" onclick="return validateFormUpdate()"></td>
</tr></table>
</form>
</fieldset>
<div id="spacer-50"></div>
<div id="spacer-50"></div>
<div id="spacer-50"></div>
<div id="spacer-50"></div>
<div id="spacer-50"></div>
<div id="spacer-50"></div>
<iframe style="display:none" name="self-page"></iframe>
<script>
function validateFormEdit()
{
var allowedExtensions = "{{ALLOWED_EXTENSIONS_EDIT}}";
var editSelectValue = document.getElementById('edit-path').value;
var dotIndex = editSelectValue.lastIndexOf(".")+1;
var editSelectValueExtension = editSelectValue.substring(dotIndex);
var extIndex = allowedExtensions.indexOf(editSelectValueExtension);
if(editSelectValue == "new"){
return true;
}
if(editSelectValue == "choose" ){
alert("You have not chosen a file!");
return false;
}
if(extIndex == -1){
alert("Editing of this file type is not supported!");
return false;
}
}
function validateFormDelete()
{
var deleteSelectValue = document.getElementById('delete-path').value;
if(deleteSelectValue == "choose" || !deleteSelectValue.indexOf(".")){
alert("You have not chosen a file!");
return false;
}
}
function confirmFormat()
{
var text = 'Pressing the "OK" button immediately deletes all data from SPIFFS and restarts ESP32!';
if (confirm(text) == true) {
return true;
}
else{
return false;
}
}
function validateFormUpload()
{
var inputElement = document.getElementById('upload-file');
var files = inputElement.files;
if(files.length==0){
alert("You have not chosen a file!");
return false;
}
}
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)
{
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>

View File

@ -0,0 +1,74 @@
body {
/*background-color: #f7f7f7;*/
font-family: Tahoma, Arial, sans-serif;
font-size: small;
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
h1 {
margin-top: 0;
}
input{
cursor:pointer;
}
input[type="number"]{
width: 100px;
}
#submit {
width:120px;
}
select{
width:180px;
}
#select-path {
width:250px;
}
#select-dir {
width:90px;
}
#spacer-50 {
height: 50px;
}
#spacer-20 {
height: 20px;
}
#spacer-10 {
height: 10px;
}
table {
/*background-color: #dddddd;*/
border-collapse: collapse;
width:640px;
margin: 0 auto;
overflow: visible;
}
td, th {
/*border: 1px solid #dddddd;*/
text-align: left;
padding: 2px;
}
#first_td_th {
width:400px;
}
fieldset {
width:700px;
/*background-color: #f7f7f7;*/
border-radius: 10px;
border-color: blue;
}
#format-notice {
color: #ff0000;
}
legend {
display: flex;
justify-content: center;
background-color:white;
background-blend-mode: darken;
border-radius: 10px;
padding: 1px 8px 2px 8px;
border-style: solid;
border-width: 1.0;
}

185
Backup/data/www/home.html Normal file
View File

@ -0,0 +1,185 @@
{{NAVBAR}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="global-style.css" rel="stylesheet">
<title>Welcome</title>
<style>
#first_td_th {
width:240px;
}
#second_td_th {
width:240px;
}
</style>
</head>
<body>
<h1>ATA Booth Summary</h1>
<fieldset>
<legend>Booth Overview</legend>
<div id="spacer-20"></div>
<table>
<tr>
<td id="first_td_th">
Mode: Lumia
</td>
<td id="second_td_th">
LED Strip1: Active
</td>
<td>
Front Light: Active
</td>
</tr>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
<tr>
<td>
App: DSLRBooth
</td>
<td>
LED Strip2: Active
</td>
<td>
Rear Light: Active
</td>
</tr>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
<tr>
<td>
...
</td>
<td>
...
</td>
<td>
OLED: Active
</td>
</tr>
</table>
<div id="spacer-20"></div>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>System Info:</legend>
<div id="spacer-20"></div>
<table>
<tr>
<td id="first_td_th">
Firmware Ver: 1.0
</td>
<td id="second_td_th">
Flash Size:
</td>
<td>
RAM Size:
</td>
</tr>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
<tr>
<td>
Booth T&#176: 80.3F
</td>
<td>
Flash Free:
</td>
<td>
Used RAM:
</td>
</tr>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
<tr>
<td>
Setpoint T&#176: 85F
</td>
<td>
...
</td>
<td>
Free RAM:
</td>
</tr>
</table>
<div id="spacer-20"></div>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>Network / WiFi</legend>
<div id="spacer-20"></div>
<table>
<tr>
<td id="first_td_th">
IP Addr: 192.XXX.XXX.XXX
</td>
<td id="second_td_th">
MAC Addr:
</td>
<td>
WiFi SSID:
</td>
</tr>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
<tr>
<td>
WiFi RSSi:
</td>
<td>
WiFi Ch:
</td>
<td>
Wifi Encryp:
</td>
</tr>
</table>
<div id="spacer-20"></div>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>Bluetooth LE</legend>
<div id="spacer-20"></div>
<table>
<tr>
<td id="first_td_th">
Active: true
</td>
<td id="second_td_th">
Connected: true
</td>
<td>
Name: ATADevXX
</td>
</tr>
</table>
<div id="spacer-20"></div>
</fieldset>
</body>
</html>

11
Backup/data/www/info.html Normal file
View File

@ -0,0 +1,11 @@
{{NAVBAR}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome</title>
</head>
<body>
<h1>System Information</h1>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
display: flex;
justify-content: center;
align-items:flex-start;
margin: 0;
padding: 0;
}
.navbar {
display: flex;
justify-content: center;
background-color: rgb(113, 177, 216);
padding: 1px;
max-width: 64em;
list-style-type: none; /* Remove the dot */
margin: 0;
}
.navbar a {
flex: 5;
display: flex;
justify-content: center;
align-items: center;
color: #f2f2f2;
text-decoration: none;
text-transform: uppercase;
padding: 5px;
transition: background-color 0.3s;
font-weight: bold;
font-family: Tahoma, Arial, sans-serif;
white-space: nowrap; /* Prevent text from wrapping */
margin-left: 16px;
margin-right: 16px;
}
.navbar a:hover {
background-color: #ddd;
color: black;
}
</style>
</head>
<body>
<header>
<nav>
<ul class="navbar">
<li></li><a href="/home" target="_top">HOME</a></li>
<li></li><a href="/appcontrol" target="_top">APP CONTROL</a></li>
<li></li><a href="/boothconfig" target="_top">BOOTH CONFIG</a></li>
<li></li><a href="/filemanager" target="_top">FILES MANAGER</a></li>
<li></li><a href="/wifiportal" target="_top">WIFI</a></li>
</ul>
</nav>
</header>
</body>
</html>

24
Backup/data/www/ok.html Normal file
View File

@ -0,0 +1,24 @@
{{NAVBAR}}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Update Success</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
background-color: #f7f7f7;
}
#spacer-50 {
height: 50px;
}
</style>
</head>
<body>
<center>
<h2>The update was successful.</h2>
<div id="spacer-50"></div>
<button onclick="window.location.href='/filemanager';">to homepage</button>
</center>
</body>
</html>

View File

@ -0,0 +1,128 @@
{{NAVBAR}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WiFi Credentials</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
margin: 0;
padding: 0;
}
h1 {
margin: 30px 0 20px;
text-align: center;
}
form {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
margin: auto;
max-width: 500px;
}
input[type="text"], input[type="password"] {
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: none;
border-radius: 4px;
background-color: #f2f2f2;
font-size: 16px;
width: 100%;
}
button {
background-color: #2d2dfa;
color: #fff;
padding: 12px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background-color: #45a049;
}
img{
display: block;
margin-left: auto;
margin-right:auto;
height: auto;
width: auto;
max-width: 150px;
filter: drop-shadow(2px 2px 2px #666666);
}
</style>
</head>
<body>
<h1>Enter WiFi Credentials</h1>
<form id="wifi-credentials-form">
<label for="ssid">SSID:</label>
<input type="text" id="ssid" name="ssid" placeholder="SSID" required>
<label for="password">Password:</label>
<input type="text" id="password" name="password" placeholder="Password" required>
<button type="button" onclick="postCredentials(event)">Submit</button>
<img src="img/atalogo.png" alt="Image not found" >
</form>
<script>
const form = document.querySelector('#wifi-credentials-form');
window.onload = getSSID();
function getSSID(){
const params = new URLSearchParams();
params.append('type', 'wifi');
const url = '/get?' + params.toString();
fetch(url, { method: 'GET'})
.then(response => {
if (response.ok) {
console.log('Request successful');
return response.json();
}
else { throw new Error('Request failed'); }
})
.then(data => {
console.log(data);
form.ssid.value = data.ssid;
form.password.placeholder = '*'.repeat(data.pass);
})
.catch(error => { console.error(error); });
}
function postCredentials(event){
event.preventDefault();
if(!inputValidationOk()){ return; }
var creds = {
ssid : form.ssid.value,
pass : form.password.value
};
const params = new URLSearchParams();
params.append('type', 'wifi');
const url = '/post?' + params.toString();
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(creds)
})
.then(response => {
if (response.ok) { console.log('Request successful'); }
else { throw new Error('Request failed'); }
})
.catch(error => { console.error(error); });
}
function inputValidationOk(){
return true;
}
</script>
</body>
</html>

15
ToDo.txt Normal file
View File

@ -0,0 +1,15 @@
1 - Second Strip Animation
a - How to synchronize the 2 Animation
b - Set Default directions?
2 - Wifi Server
3 - OTA Upgrade
4 - menu
a - file manager & editor
b - wifi credentials
c - firmware update
d - mode selection page
i - booth config and reboot
ii -

View File

@ -0,0 +1,51 @@
{
"build": {
"arduino":{
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": [
"wifi"
],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": [
"esp-builtin"
],
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "My ESP32-S3-DevKitN8R2 (8 MB QD, 2M PSRAM)",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 524288,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html",
"vendor": "Espressif"
}

37
boards/wroom-32d-8mb.json Normal file
View File

@ -0,0 +1,37 @@
{
"build": {
"arduino":{
"ldscript": "esp32_out.ld"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ESP32_DEV",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "esp32"
},
"connectivity": [
"wifi",
"bluetooth",
"ethernet",
"can"
],
"debug": {
"openocd_board": "esp-wroom-32.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "WROOM-32D DEV MODULE",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 460800
},
"url": "https://en.wikipedia.org/wiki/ESP32",
"vendor": "Consius"
}

View File

@ -0,0 +1,508 @@
<!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>

30
data/boards/board14.json Normal file
View File

@ -0,0 +1,30 @@
{
"rgb1": 3,
"rgb2": 46,
"btn1": 8,
"btn2": 19,
"btn3": 0,
"buzzer": 37,
"touch1": 9,
"touch2": 10,
"touch3": 11,
"touch4": 12,
"touch5": 13,
"shield": 9,
"relay1": 1,
"relay2": 2,
"relay3": 3,
"relay4": 4,
"stat1": 17,
"stat2": 18,
"adc1": 20,
"oled_dc": 7,
"oled_rst": 6,
"oled_mosi": 5,
"oled_sck": 4,
"oled_cs": 15,
"ext1": 35,
"ext2": 36,
"rf433tx": 38,
"rf433rx": 16
}

30
data/boards/board15.json Normal file
View File

@ -0,0 +1,30 @@
{
"rgb1": 3,
"rgb2": 9,
"btn1": 8,
"btn2": 18,
"btn3": 0,
"buzzer": 42,
"touch1": 11,
"touch2": 12,
"touch3": 13,
"touch4": 21,
"touch5": -1,
"shield": 14,
"relay1": 38,
"relay2": 48,
"relay3": 47,
"relay4": 21,
"stat1": 39,
"stat2": 37,
"adc1": 10,
"oled_dc": 7,
"oled_rst": 6,
"oled_mosi": 5,
"oled_sck": 4,
"oled_cs": 15,
"ext1": 41,
"ext2": -1,
"rf433tx": 40,
"rf433rx": 16
}

30
data/boards/test.json Normal file
View File

@ -0,0 +1,30 @@
{
"rgb1": 3,
"rgb2": 38,
"btn1": 8,
"btn2": 19,
"btn3": -1,
"buzzer": -1,
"touch1": -1,
"touch2": -1,
"touch3": -1,
"touch4": -1,
"touch5": -1,
"shield": -1,
"relay1": 1,
"relay2": 2,
"relay3": 3,
"relay4": 4,
"stat1": -1,
"stat2": -1,
"adc1": 10,
"oled_dc": 7,
"oled_rst": 6,
"oled_mosi": 5,
"oled_sck": 4,
"oled_cs": 15,
"ext1": -1,
"ext2": -1,
"rf433tx": -1,
"rf433rx": -1
}

135
data/booths/custom.json Normal file
View File

@ -0,0 +1,135 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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": "WS6812",
"rgb-order": "rgb",
"shift":-27,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1
},
{
"en": true,
"size": 30,
"chip": "WS6812",
"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
}
}

View File

@ -0,0 +1,137 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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": 168,
"chip": "SK6812",
"rgb-order": "RGB",
"shift":-42,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1,
"bright": 200
},
{
"en": true,
"size": 20,
"chip": "SK6812",
"rgb-order": "RGB",
"shift":0,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1,
"bright": 200
}
],
"rx433": {
"en": "true"
},
"tx433": {
"en": "true"
},
"ble":{
"en": true,
"name": "Ata_Lights",
"core": 1
}
}

134
data/booths/helio-posh.json Normal file
View File

@ -0,0 +1,134 @@
{
"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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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
}
}

View File

@ -0,0 +1,135 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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
}
}

138
data/booths/light-stik.json Normal file
View File

@ -0,0 +1,138 @@
{
"mode": "stik",
"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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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": 144,
"chip": "SK6812",
"rgb-order": "RGB",
"shift":0,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1,
"bright": 200
},
{
"en": false,
"size": 20,
"chip": "SK6812",
"rgb-order": "RGB",
"shift":0,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1,
"bright": 200
}
],
"rx433": {
"en": "true"
},
"tx433": {
"en": "true"
},
"ble":{
"en": true,
"name": "FlashStik",
"core": 1
}
}

140
data/booths/lumia-m.json Normal file
View File

@ -0,0 +1,140 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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
},
"buzzer":{
"en": true
},
"strips":
[
{
"en": true,
"size": 168,
"chip": "SK6812",
"rgb-order": "RGB",
"shift": 0,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1,
"bright": 200
},
{
"en": true,
"size": 30,
"chip": "SK6812",
"rgb-order": "RGB",
"shift": 0,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1,
"bright": 200
}
],
"rx433": {
"en": "true"
},
"tx433": {
"en": "true"
},
"ble":{
"en": true,
"name": "Ata_Lights",
"core": 1
}
}

View File

@ -0,0 +1,135 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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
}
}

159
data/booths/lumia-xl.json Normal file
View File

@ -0,0 +1,159 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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
},
"wifi-client":{
"en": true,
"mdns-name": "atadev",
"ssid": "padillaguest",
"pass": "padillaguest"
},
"wifi-ap":{
"ssid": "ATA_AP",
"append-id": true,
"pass": "12345678",
"ip": [192,168,10.1],
"gateway": [192,168,10,200],
"subnet": [255,255,255,0]
},
"animation1":{
"white-max-time": 30,
"white-min": 20,
"white-max": 100,
"toggle-cycles": 2,
"comet-size": 0.2,
"comet-fade": 96,
"fire-cooling": 66,
"fire-sparking": 62
}
}

135
data/booths/m1.json Normal file
View File

@ -0,0 +1,135 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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
}
}

135
data/booths/marquee.json Normal file
View File

@ -0,0 +1,135 @@
{
"mode": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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
}
}

136
data/booths/roamer-big.json Normal file
View File

@ -0,0 +1,136 @@
{
"mode": "roamer",
"button": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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": 168,
"chip": "SK6812",
"rgb-order": "rgb",
"shift":-42,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1
},
{
"en": false,
"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
}
}

136
data/booths/roamer.json Normal file
View File

@ -0,0 +1,136 @@
{
"mode": "roamer",
"button": 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,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": true,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"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": false,
"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
}
}

135
data/booths/testbooth.json Normal file
View File

@ -0,0 +1,135 @@
{
"mode": 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": false,
"relay-index": 0,
"button-index": 0,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
},
{
"en": false,
"relay-index": 1,
"button-index": 1,
"min": 5.0,
"max": 100.0,
"step": 1.5,
"skip-count": 5,
"vision": true
}
],
"oled": {
"en": false,
"height": 64,
"width": 128
},
"t-sensor": {
"en": false,
"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": "WS6812",
"rgb-order": "rgb",
"shift":-27,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1
},
{
"en": true,
"size": 30,
"chip": "WS6812",
"rgb-order": "rgb",
"shift":-27,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,
"core": 1
}
],
"rx433": {
"en": false
},
"tx433": {
"en": false
},
"ble":{
"en": true,
"name": "Ata_Lights",
"core": 1
}
}

79
data/css/global-style.css Normal file
View File

@ -0,0 +1,79 @@
body {
/*background-color: #f7f7f7;*/
font-family: Tahoma, Arial, sans-serif;
font-size: small;
margin: 0;
display: flex;
min-height: 100vh;
}
.main-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 20px;
}
h1 {
margin-top: 0;
}
input{
cursor:pointer;
}
input[type="number"]{
width: 100px;
}
#submit {
width:120px;
}
select{
width:160px;
}
#select-path {
width:250px;
}
#select-dir {
width:90px;
}
#spacer-50 {
height: 50px;
}
#spacer-20 {
height: 20px;
}
#spacer-10 {
height: 10px;
}
table {
/*background-color: #dddddd;*/
border-collapse: collapse;
width:600px;
margin: 0 auto;
overflow: visible;
}
td, th {
/*border: 1px solid #dddddd;*/
text-align: left;
padding: 2px;
}
#first_td_th {
width:400px;
}
fieldset {
width:620px;
/*background-color: #f7f7f7;*/
border-radius: 10px;
border-color: blue;
}
#format-notice {
color: #ff0000;
}
legend {
display: flex;
justify-content: center;
background-color:white;
background-blend-mode: darken;
border-radius: 10px;
padding: 1px 8px 2px 8px;
border-style: solid;
border-width: 1.0;
}

72
data/css/nav.css Normal file
View File

@ -0,0 +1,72 @@
.navbar {
width: 100%;
background-color: black;
border-bottom: 2px solid white;
display: flex;
justify-content: flex-start;
align-items: center;
position: sticky;
top: 0;
z-index: 1000;
}
.navbar-left {
padding: 0 10px;
}
.navbar ul {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
align-items: center;
position: relative; /* Ensure relative positioning for the submenu */
}
.navbar ul li {
float: left;
position: relative; /* Ensure relative positioning for the submenu */
}
.navbar ul li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
border-right: 1px solid white;
}
.navbar ul li a:hover {
background-color: grey;
}
.navbar ul li a.disabled {
color: grey;
pointer-events: none;
}
.navbar ul .submenu {
display: none;
position: absolute;
top: 100%;
left: 0;
background-color: black;
list-style-type: none;
margin: 0;
padding: 0;
border-top: 2px solid white;
z-index: 1000;
}
.navbar ul .submenu li {
float: none;
border-right: none;
}
.navbar ul .submenu li a {
padding: 10px 16px;
border-bottom: 1px solid white;
}
.navbar ul .submenu li a:hover {
background-color: grey;
}
.navbar ul li:hover > .submenu {
display: block;
}
.nav-image {
height: 40px;
width: auto;
}

BIN
data/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

472
data/flashstik-reg.html Normal file
View File

@ -0,0 +1,472 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATA Light Stick Reg</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: 97%;
height: 100px;
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 Flash-Stick Link/Registration</h1>
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">Master: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-registration"></span>
<label id="status-registration">Registration: ...</label>
</div>
<div class="btn-container">
<button id="bleConnectBtn">Connect</button>
<button id="bleDisconnectBtn">Disconnect</button>
<button id="bleSaveBtn">Save</button>
</div>
<textarea id="logArea" readonly></textarea>
<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 = {
name: "",
};
// 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>

BIN
data/images/atalogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

442
data/js/event-box.js Normal file
View File

@ -0,0 +1,442 @@
class EventBox extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.outer-container {
justify-content: center;
align-items: center;
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%;
}
.select-container {
margin-bottom: 15px;
}
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
}
.row > * {
flex: 0 0 calc(50% - 20px);
margin-bottom: 15px;
}
.row:last-child {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
label {
display: block;
margin-bottom: 1px;
}
select, input[type="range"], input[type="color"] {
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
}
select{
width: 90%;
font-size: large;
height: auto;
}
input[type="color"] {
height: 30px;
padding: 0;
width: 30px;
}
.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;
font-weight: bold;
}
input[type="checkbox"] {
transform: scale(2.5);
margin-left: 20px;
}
input[type="checkbox"].css-checkbox + label.css-label {
display: inline-block;
margin-left: 30px;
}
.label-check{
margin-left: 10px;
}
</style>
<div class="outer-container">
<div class="container">
<legend class="center-text" id="center-text">Event0</legend><br>
<div class="row">
<div>
<label for="animation-list" id="list-label">Animations:</label>
<select id="animation-list">
</select>
</div>
<div>
<hue-select id="hue-selector"></hue-select>
</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="huerange" id="huerange-label">Hue Range:</label>
<input type="range" id="huerange" 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">
<label class="label-check" id="check1-label">Check1</label>
</div>
<div class="checkbox-container">
<input type="checkbox" id="check3">
<label class="label-check" id="check3-label">Check3</label>
</div>
</div>
<div class="row">
<div class="checkbox-container">
<input type="checkbox" id="check2">
<label class="label-check" id="check2-label">Check2</label>
</div>
<div class="checkbox-container">
<input type="checkbox" id="check4">
<label class="label-check" id="check4-label">50% Lum</label>
<div class="try-button-container">
<button id="try-button">Try</button>
</div>
</div>
</div>
</div>
</div>
`;
this.Title = this.shadowRoot.querySelector('#center-text');
this.hueSelect = this.shadowRoot.querySelector('#hue-selector');
this.AnimationList = this.shadowRoot.getElementById('animation-list');
this.Speed = this.shadowRoot.getElementById('speed');
this.HueRange = this.shadowRoot.querySelector('#huerange');
this.Param1 = this.shadowRoot.querySelector('#param1');
this.Param2 = this.shadowRoot.querySelector('#param2');
this.Check1 = this.shadowRoot.querySelector('#check1');
this.Check2 = this.shadowRoot.querySelector('#check2');
this.Check3 = this.shadowRoot.querySelector('#check3');
this.Check4 = this.shadowRoot.querySelector('#check4');
this.AnimationListLabel = this.shadowRoot.querySelector('#list-label');
this.SpeedLabel = this.shadowRoot.querySelector('#speed-label');
this.HueRangeLabel = this.shadowRoot.querySelector('#huerange-label');
this.Param1Label = this.shadowRoot.getElementById('param1-label');
this.Param2Label = this.shadowRoot.getElementById('param2-label');
this.Check1Label = this.shadowRoot.getElementById('check1-label');
this.Check2Label = this.shadowRoot.getElementById('check2-label');
this.Check3Label = this.shadowRoot.getElementById('check3-label');
this.Check4Label = this.shadowRoot.getElementById('check4-label');
this.AnimationPropsJson;
this.SpeedCaption = "";
this.HueRangeCaption = "";
this.Param1Caption = "";
this.Param2Caption = "";
this.Check1Caption = "";
this.Check2Caption = "";
this.Check3Caption = "";
this.setSpeedCaption(`Speed: `);
this.Speed.min = 0;
this.Speed.max = 100;
this.setHueRangeCaption(`Hue Range: `);
this.HueRange.min = 0;
this.HueRange.max = 360;
this.setParam1Caption(`Param1: `);
this.Param1.min = 0;
this.Param1.max = 100;
this.setParam2Caption(`Param2: `);
this.Param2.min = 0;
this.Param2.max = 100;
this.setCheck1Caption(`Check1`);
this.setCheck2Caption(`Check2`);
this.setCheck3Caption(`Check3`);
this.setCheck4Caption(`50% Lum`);
this.Index = 0;
this.Speed.addEventListener('input', () => { this.updateSpeedLabel(); });
this.HueRange.addEventListener('input', () => { this.updateHueRangeLabel(); });
this.Param1.addEventListener('input', () => { this.updateParam1Label(); });
this.Param2.addEventListener('input', () => { this.updateParam2Label(); });
this.TryButton = this.shadowRoot.querySelector('#try-button');
this.TryButton.addEventListener('click', () => {
this.handleTryButtonClick();
});
this.AnimationList.addEventListener('change', this.handleAnimationListChange.bind(this));
}
setIndex(i){ this.Index = i; }
getIndex(){ return this.Index; }
updateSpeedLabel(){
if(!this.Speed.hidden){
this.SpeedLabel.innerHTML = this.SpeedCaption + this.getSpeedValue();
}
}
updateHueRangeLabel(){
if(!this.HueRange.hidden){
this.HueRangeLabel.innerHTML = this.HueRangeCaption + this.getHueRangeValue();
}
}
updateParam1Label(){
if(!this.Param1.hidden){
this.Param1Label.innerHTML = this.Param1Caption + this.getParam1Value();
}
}
updateParam2Label(){
if(!this.Param2.hidden){
this.Param2Label.innerHTML = this.Param2Caption + this.getParam2Value();
}
}
setTitle(text){
this.Title.textContent = text;
}
addOptionToList(value, text){
const optionElement = document.createElement('option');
optionElement.value = value;
optionElement.textContent = text;
this.AnimationList.appendChild(optionElement);
}
setHidden(hidden){ this.hidden = hidden; }
getHidden(){ return this.hidden; }
setHueValue(value){ this.hueSelect.setHue(value); }
getHueValue(){ return this.hueSelect.getSelectedHue(); }
setSpeedValue(value){
this.Speed.value = value;
this.updateSpeedLabel();
}
setSpeedCaption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.SpeedCaption = caption;
this.SpeedLabel.innerHTML = caption;
this.Speed.hidden = hid;
this.SpeedLabel.hidden = hid;
}
getSpeedValue(){ return this.Speed.value; }
setHueRangeValue(value){
this.HueRange.value = value;
this.updateHueRangeLabel()
}
setHueRangeCaption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.HueRangeCaption = caption;
this.HueRangeLabel.innerHTML = caption;
this.HueRange.hidden = hid;
this.HueRangeLabel.hidden = hid;
}
getHueRangeValue(){ return this.HueRange.value; }
setParam1Value(value){
this.Param1.value = value;
this.updateParam1Label()
}
setParam1Caption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.Param1Caption = caption;
this.Param1Label.innerHTML = caption;
this.Param1.hidden = hid;
this.Param1Label.hidden = hid;
}
getParam1Value(){ return this.Param1.value; }
setParam2Value(value){
this.Param2.value = value;
this.updateParam2Label()
}
setParam2Caption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.Param2Caption = caption;
this.Param2Label.innerHTML = caption;
this.Param2.hidden = hid;
this.Param2Label.hidden = hid;
}
getParam2Value(){ return this.Param2.value; }
setCheck1Value(value){ this.Check1.checked = value; }
setCheck1Caption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.Check1Label.innerHTML = caption;
this.Check1.hidden = hid;
this.Check1Label.hidden = hid;
}
getCheck1Value(){ return this.Check1.checked; }
setCheck2Value(value){ this.Check2.checked = value; }
setCheck2Caption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.Check2Label.innerHTML = caption;
this.Check2.hidden = hid;
this.Check2Label.hidden = hid;
}
getCheck2Value(){ return this.Check2.checked; }
setCheck3Value(value){ this.Check3.checked = value; }
setCheck3Caption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.Check3Label.innerHTML = caption;
this.Check3.hidden = hid;
this.Check3Label.hidden = hid;
}
getCheck3Value(){ return this.Check3.checked; }
setCheck4Value(value){ this.Check4.checked = value; }
setCheck4Caption(caption){
let hid = false;
if(caption.trim() === ""){
hid = true;
}
this.Check4Label.innerHTML = caption;
this.Check4.hidden = hid;
this.Check4Label.hidden = hid;
}
getCheck4Value(){ return this.Check4.checked; }
setAnimationIndex(index){
this.AnimationList.selectedIndex = index;
const changeEvent = new Event('change');
this.AnimationList.dispatchEvent(changeEvent);
}
getAnimationIndex(){
return this.AnimationList.selectedIndex;
}
handleTryButtonClick() {
const eventIndexValue = this.getIndex();
const animIndexValue = this.getAnimationIndex();
const hueValue = this.getHueValue();
const speedValue = this.getSpeedValue();
const colorRangeValue = this.getHueRangeValue();
const param1Value = this.getParam1Value();
const param2Value = this.getParam2Value();
const check1Value = this.getCheck1Value();
const check2Value = this.getCheck2Value();
const check3Value = this.getCheck3Value();
const check4Value = this.getCheck4Value();
this.dispatchEvent(new CustomEvent('tryClick', {
detail: {
eventIndex: eventIndexValue,
animIndex: animIndexValue,
hue: hueValue,
speed: speedValue,
colorRange: colorRangeValue,
param1: param1Value,
param2: param2Value,
check1: check1Value,
check2: check2Value,
check3: check3Value,
check4: check4Value
}
}));
}
setAnimationCaptions(propsJson){
this.AnimationPropsJson = propsJson;
//add options to list
let x = 0;
this.AnimationPropsJson.forEach(props => {
this.addOptionToList(x, props.name);
x++;
});
}
handleAnimationListChange(){
const selectedIndex = this.getAnimationIndex();
const selectedProps = this.AnimationPropsJson[selectedIndex];
this.updateControlProps(selectedProps);
}
updateControlProps(props){
this.setSpeedCaption(props.speed);
let s = props['hue-range'];
this.setHueRangeCaption(s || "");
this.setParam1Caption(props.param1 || "");
this.setParam2Caption(props.param2 || "");
this.setCheck1Caption(props.check1 || "");
this.setCheck2Caption(props.check2 || "");
this.setCheck3Caption(props.check3 || "");
this.setCheck4Caption(props.check4 || "");
this.updateSpeedLabel();
this.updateHueRangeLabel();
this.updateParam1Label();
this.updateParam2Label();
}
}
customElements.define('event-box', EventBox);

64
data/js/fwUoload.js Normal file
View File

@ -0,0 +1,64 @@
async function uploadFile(event) {
event.preventDefault();
const file = fileInput.files[0];
if (!file) {
alert("Please select a file.");
return;
}
// Existing file validation code...
const formData = new FormData();
formData.append('file', file);
// Create a custom reader function
const reader = file.stream().getReader();
const totalLength = file.size;
let uploaded = 0;
// Create a readable stream and use the custom reader function
const uploadStream = new ReadableStream({
async start(controller) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
uploaded += value.length;
let percentUploaded = (uploaded / totalLength * 100).toFixed(2);
// Update progress bar and label
progressBar.value = percentUploaded;
progressLabel.innerHTML = `${percentUploaded}%`;
controller.enqueue(value);
}
controller.close();
},
});
// Create a new Request with the readable stream as body
const req = new Request('/update', {
method: 'POST',
body: uploadStream,
headers: {
// Add any relevant headers here
},
});
try {
const response = await fetch(req);
if (response.ok) {
alert('Upload completed!');
progressLabel.innerHTML = "Completed!";
} else {
alert('An error occurred during the upload.');
progressLabel.innerHTML = "Error!";
}
} catch (error) {
console.error('Upload failed:', error);
}
}

244
data/js/hue-select.js Normal file
View File

@ -0,0 +1,244 @@
class HueSelect extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.color-option {
display: flex;
align-items: center;
cursor: pointer;
}
.color-patch {
width: 30px;
height: 30px;
margin-top: 18px;
margin-right: 10px;
border: 1px solid #000;
}
.hue-label {
margin-top: 18px;
width: 90px;
text-align: left;
}
.label-container {
display: flex;
align-items: center;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 180px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
max-height: 300px;
overflow-y: auto;
}
.dropdown-content .color-option {
padding: 8px 12px;
}
.dropdown:hover .dropdown-content {
display: block;
}
</style>
<div class="dropdown">
<div class="color-option" id="selectedColor">
<span class="color-patch" style="background-color: hsl(0, 100%, 50%)"></span>
<div class="label-container">
<label class="hue-label" id="hue-label">0</label>
</div>
</div>
<div class="dropdown-content" id="colorPicker">
<!-- The options will be added dynamically using JavaScript -->
</div>
</div>
`;
this.currentHue = 0;
this.colorList;
this.hueLabel = this.shadowRoot.getElementById('hue-label');
const selectedColorDiv = this.shadowRoot.getElementById('selectedColor');
const colorPatch = selectedColorDiv.querySelector('.color-patch');
selectedColorDiv.addEventListener('click', () => {
const dropdownContent = this.shadowRoot.querySelector('.dropdown-content');
dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block';
});
this.createColorOptions();
this.setHue(0);
}
generateColors() {
const colors = [];
for (let hue = 0; hue <= 360; hue += 10) {
if (hue == 360) { hue = 359; }
colors.push(hue);
}
colors.push(-1);
colors.push(-2);
return colors;
}
createColorOption(hue) {
const colorOption = document.createElement('div');
colorOption.classList.add('color-option');
const colorPatch = document.createElement('span');
colorPatch.classList.add('color-patch');
const colorText = document.createElement('span');
colorText.classList.add('color-text');
colorText.textContent = hue;
const rgbHex = document.createElement('span');
rgbHex.classList.add('rgb-hex');
if (hue === -2) {
colorPatch.style.backgroundColor = 'rgb(0,0,0)';
rgbHex.innerHTML = '&nbsp; #000000';
} else if (hue === -1) {
colorPatch.style.backgroundColor = 'rgb(255,255,255)';
rgbHex.innerHTML = '&nbsp; #FFFFFF';
} else {
colorPatch.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;
const hexColor = this.hslToRgb(hue, 100, 50).toUpperCase();
rgbHex.innerHTML = `<span>&nbsp; ${hexColor}</span>`;
}
colorOption.appendChild(colorPatch);
colorOption.appendChild(colorText);
colorOption.appendChild(rgbHex);
colorOption.addEventListener('click', () => this.handleColorSelection(hue));
return colorOption;
}
createColorOptions() {
const dropdownContent = this.shadowRoot.querySelector('.dropdown-content');
this.colorList = this.generateColors();
this.colorList.forEach(hue => {
const colorOption = this.createColorOption(hue);
dropdownContent.appendChild(colorOption);
});
}
// Function to convert HSL to RGB
hslToRgb(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
const toHex = (x) => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
handleColorSelection(hue) {
this.currentHue = hue;
const selectedColorDiv = this.shadowRoot.getElementById('selectedColor');
const colorPatch = selectedColorDiv.querySelector('.color-patch');
// Update the color patch and hue label
const rgb = this.getRGBfromHue(hue);
colorPatch.style.backgroundColor = rgb;
this.setHueLabel(hue);
this.hideDropdown();
this.dispatchEvent(new CustomEvent('change', { detail: { hue, rgb } }));
}
// Method to get the hue value of the selected item
getSelectedHue() {
return this.currentHue;
}
getSelectedRGB() {
const selectedColorText = this.shadowRoot.getElementById('selectedColor').textContent.trim();
return parseFloat(selectedColorText);
}
getRGBfromHue(hue){
if(hue == -1){
return '#FFFFFF';
}else if(hue == -2){
return '#000000';
}else{
return this.hslToRgb(hue, 100, 50);;
}
}
// Method to get the RGB value of the selected item
getSelectedRgb() {
const selectedHue = this.getSelectedHue();
return getRGBfromHue(selectedHue);
}
setHue(hue) {
// Round the input hue to the nearest 10th
let roundedHue = hue;
if(hue === -1 || hue === -2){
roundedHue = hue;
}else{
roundedHue = Math.round(hue / 10) * 10;
if (roundedHue >= 360) {
roundedHue = 359;
}else if (roundedHue < 0) {
roundedHue = 0;
}
}
this.currentHue = roundedHue;
this.colorList.forEach(colorVal => {
if (colorVal === roundedHue) {
this.handleColorSelection(roundedHue);
}
});
this.hideDropdown();
}
hideDropdown() {
const dropdownContent = this.shadowRoot.querySelector('.dropdown-content');
dropdownContent.style.display = 'none';
}
setHueLabel(value){
this.hueLabel.textContent = "Hue: " + value;
}
}
customElements.define('hue-select', HueSelect);

8673
data/js/jquery-3.7.1.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"stiks":[
{
"en": true,
"master": false,
"encrypt": false,
"ch": 1,
"ssid": "lumastikXXXX",
"mac": "6A:B6:B3:2D:E5:64",
"master-pass": "atapass",
"slave-name": "FlashStick"
}
]
}

5
data/system/readme.txt Normal file
View File

@ -0,0 +1,5 @@
#Setting up /system/system.json
1 - Choose the
modes:
booth, roamer, stick

4
data/system/system.json Normal file
View File

@ -0,0 +1,4 @@
{
"boardfile": "/boards/board15.json",
"configfile": "/booths/helio-posh.json"
}

70
data/system/tunes.json Normal file
View File

@ -0,0 +1,70 @@
{
"tunes":
[
{
"cycles": 1,
"pause": 0,
"tune": "Boot:d=8,o=5,b=200:g,g,c6,e6,g6"
},
{
"cycles": 1,
"pause": 0,
"tune": "Shutdown:d=8,o=5,b=200:g6,e6,c6,g"
},
{
"cycles": 1,
"pause": 0,
"tune": "Connected:d=16,o=5,b=240:c,e,g,c6"
},
{
"cycles": 1,
"pause": 0,
"tune": "Disconnected:d=16,o=5,b=160:g,f,e,d,c"
},
{
"cycles": 1,
"pause": 0,
"tune": "Done:d=16,o=5,b=240:g,c6,e"
},
{
"cycles": 1,
"pause":0,
"tune": "Warning:d=16,o=6,b=300:e6,d6,e6,d6"
},
{
"cycles": 1,
"pause": 0,
"tune": "Error:d=16,o=4,b=100:f,f"
},
{
"cycles": 1,
"pause": 0,
"tune": "Blip:d=16,o=6,b=220:c6,g6"
},
{
"cycles": 1,
"pause": 0,
"tune": "Thump:d=8,o=4,b=180:c,c"
},
{
"cycles": 1,
"pause": 0,
"tune": "Ack:d=16,o=5,b=200:c,e,g"
},
{
"cycles": 1,
"pause": 0,
"tune": "Waiting:d=16,o=5,b=112:b"
},
{
"cycles": 1,
"pause": 0,
"tune": "LowBeep:d=4,o=5,b=240:c"
},
{
"cycles": 1,
"pause": 0,
"tune": "HighBeep:d=4,o=5,b=240:b"
}
]
}

4
data/system/update.json Normal file
View File

@ -0,0 +1,4 @@
{
"baseurl": "https://storage.googleapis.com/boothifier/",
"folder": "latest/"
}

16
data/system/wifi.json Normal file
View File

@ -0,0 +1,16 @@
{
"wifi-client":{
"en": true,
"mdns-name": "atadev",
"ssid": "DPWifi",
"pass": "dave3.14159"
},
"wifi-ap":{
"ssid": "ATA_AP",
"append-id": true,
"pass": "12345678",
"ip": "192.168.10.1",
"gateway": "192.168.10.1",
"subnet": "255.255.255.0"
}
}

169
data/www/about.html Normal file
View File

@ -0,0 +1,169 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About Printio</title>
<link href="/css/nav.css" rel="stylesheet">
<style>
h1 {
margin-bottom: 20px;
}
label, select, #media-sizes, button {
margin: 5px 0;
text-align: center;
}
select {
padding: 3px;
font-size: 1.1em;
}
#media-sizes ul {
list-style-type: none;
padding: 0;
}
#media-sizes li {
display: flex;
align-items: center;
margin-bottom: 5px;
}
#media-sizes input[type="checkbox"] {
transform: scale(1.5);
margin-right: 12px;
}
#media-sizes span {
font-size: 1.2em;
}
button {
background-color: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 1.2em;
cursor: pointer;
}
button:hover {
background-color: darkblue;
}
p {
display: block;
margin-top: 1em;
margin-bottom: 1em;
margin-left: 0;
margin-right: 0;
}
.license-section {
margin-top: 30px;
text-align: center;
}
.license-status {
font-size: 1.1em;
margin-bottom: 15px;
}
.license-input {
margin-top: 10px;
}
.license-input input {
padding: 10px;
width: 200px;
font-size: 1.1em;
margin-right: 10px;
}
.license-input button {
background-color: #4CAF50;
padding: 10px 20px;
font-size: 1.1em;
cursor: pointer;
border-radius: 5px;
border: none;
}
.license-input button:hover {
background-color: #45a049;
}
/* New Styles for Layout */
.content-wrapper {
display: flex;
}
.left-column {
width: 280px;
padding-right: 20px;
box-sizing: border-box;
}
.right-column {
width: 70%;
}
.info-box {
padding: 15px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
.license-box {
padding: 15px;
border: 1px solid #ccc;
border-radius: 5px;
}
#p {
margin-left: 10px;
}
</style>
</head>
<body>
<div id="navbar"></div>
<div class="content-wrapper">
<div class="left-column">
<div class="info-box">
<h3>System Info</h3>
<p>Version: {{FIRM_VER}}
<p>CPU: {{info.cpu}}</p>
<p>CPU T: {{info.cpu_t}}</p>
<p>Disk Size: {{FS_TOTAL_BYTES}}</p>
<p>Disk Used: {{FS_USED_BYTES}}</p>
<p>RAM Size: {{RAM_TOTAL_BYTES}}</p>
<p>RAM Used: {{RAM_USED_BYTES}}</p>
</div>
</div>
<div class="right-column">
<h1>About ATA Boothifier</h1>
<p>
ATA Boothifier is photobooth control board designed to work with ATA's line of photobooths and beyond.
</p>
<p>
Warning: Unauthorized reproduction or distribution of this product
is strictly prohibited and may result in severe civil and criminal
penalties.
</p>
</div>
</div>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
</body>
</html>

191
data/www/edit.html Normal file
View File

@ -0,0 +1,191 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Edit file</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: 10px;
}
body {
font-family: Arial, sans-serif;
padding: 0;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
width: 100%;
max-width: auto;
min-width: 400px;
margin: 0 auto;
}
.outer-container {
justify-content: center;
align-items: center;
padding: 20px;
padding-top: 0;
}
.container {
border: 1px solid #ccc;
padding: 10px; /* Adjust padding */
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
width: 90%;
min-width: 400px;
height: calc(100vh - 120px); /* Adjust for navbar and margins */
margin: auto; /* Center horizontally */
margin-bottom: 20px;
}
.center-text {
text-align: center;
font-size: larger;
font-weight: bold;
margin-bottom: 10;
}
input[type="text"] {
width: 100%;
box-sizing: border-box;
}
.row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: nowrap;
min-width: 400px;
}
.row > * {
margin-bottom: 8px;
}
.row label {
flex: 0 0 auto;
white-space: nowrap;
}
.row div {
display: flex;
flex: 1;
align-items: center;
gap: 10px;
}
input[type="number"], select {
border: 1px solid #ccc;
border-radius: 4px;
width: 50%;
}
button {
background-color: #007bff;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 20px 5px 10px;
}
textarea {
width: 100%; /* Use full width */
height: calc(100vh - 200px); /* Use vh unit for height */
box-sizing: border-box;
border: 1.5px solid gray;
resize: vertical; /* Allow vertical resizing */
word-wrap: none;
}
</style>
</head>
<body>
<div id="navbar"></div>
<h1>Edit file</h2>
<div class="outer-container">
<div class="container">
<form name="edit-file" action="/files/save" onsubmit="return validateForm()">
<div>
<textarea name="edit-textarea" id="edit-textarea" wrap="off" ></textarea>
</div>
<div class="row">
<div>
<label>File Name:</label>
<input type="text" id="save-path" value="{{SAVE_PATH_INPUT}}">
</div>
</div>
<div class="row">
<div>
<button type="submit" id="submit-edit" >Save</button>
<button id="cancel" onclick="window.location.href='/files';">Cancel</button>
</div>
</div>
</form>
</div>
</div>
<script>
// Load navbar
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
window.onload=loadEditFile();
function loadEditFile(){
var savePath = document.getElementById('save-path').value;
if( savePath != "/new.txt"){ // skip if new.txt
fetch(savePath)
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch file');
}
return response.text();
})
.then(fileContents => {
// Put the file contents into a textarea element
document.getElementById('edit-textarea').value = fileContents;
document.getElementById('save-path').disabled = true;
})
.catch(error => {
console.error(error);
});
}
}
function validateForm()
{
var allowedExtensions = "{{ALLOWED_EXTENSIONS_EDIT}}";
var inputMessage = document.getElementById('save-path').value;
var dotIndex = inputMessage.lastIndexOf(".")+1;
var inputMessageExtension = inputMessage.substring(dotIndex);
var extIndex = allowedExtensions.indexOf(inputMessageExtension);
var isSlash = inputMessage.substring(0,1);
if(inputMessage == "")
{
alert("Enter the file name! \ne.g.: /new.txt");
return false;
}
if(isSlash != "/")
{
alert("The slash at the beginning of the file is missing!");
return false;
}
if(dotIndex == 0)
{
alert("The extension is missing at the end of the file!");
return false;
}
if(inputMessageExtension == "")
{
alert("The extension is missing at the end of the file!");
return false;
}
if(extIndex == -1)
{
alert("Extension not supported!");
return false;
}
}
</script>
</body>
</html>

151
data/www/edit_old.html Normal file
View File

@ -0,0 +1,151 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Edit file</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Tahoma, Arial, sans-serif;
font-size: small;
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
h1 {
margin-top: 0;
}
#submit {
width:100px;
}
button{
width: 100px;
}
#spacer-50 {
height: 20px;
}
#spacer-20 {
height: 10px;
}
fieldset {
width:700px;
border-radius: 10px;
background-color: lightgray;
}
td, th {
text-align: center;
padding: 1px;
}
legend{
background-color:white;
background-blend-mode: darken;
border-radius: 5px;
padding: 1px 6px 2px 6px;
border-style:solid;
border-width: 1.0;
}
textarea {
width: 700px;
height: 500px;
box-sizing: border-box;
border: 1.5px solid #000000;
border-radius: 8px;
resize: none;
word-wrap: none;
}
</style>
</head>
<body>
<div id="navbar"></div>
<h2>Edit file</h2>
<fieldset>
<legend>Editing file: {{SAVE_PATH_INPUT}}</legend>
<div id="spacer-20"></div>
<table><tr><td colspan="2">
<form name="edit-file" action="/save" onsubmit="return validateForm()">
<textarea name="edit-textarea" id="edit-textarea" wrap="off" ></textarea>
<div id="spacer-20"></div>
</td></tr><tr><td>
{{SAVE_PATH_INPUT}}
<button type="submit" id="submit-edit" >Save</button>
</form>
</td><td>
<button id="submit" onclick="window.location.href='/files';">Cancel</button>
</td></tr></table>
<div id="spacer-50"></div>
</fieldset>
<iframe style="display:none" name="self-page"></iframe>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
window.onload=loadEditFile();
function loadEditFile(){
var savePath = document.getElementById('save-path').value;
if( savePath != "/new.txt"){ // skip if new.txt
fetch(savePath)
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch file');
}
return response.text();
})
.then(fileContents => {
// Put the file contents into a textarea element
document.getElementById('edit-textarea').value = fileContents;
})
.catch(error => {
console.error(error);
});
}
}
function validateForm()
{
var allowedExtensions = "{{ALLOWED_EXTENSIONS_EDIT}}";
var inputMessage = document.getElementById('save-path').value;
var dotIndex = inputMessage.lastIndexOf(".")+1;
var inputMessageExtension = inputMessage.substring(dotIndex);
var extIndex = allowedExtensions.indexOf(inputMessageExtension);
var isSlash = inputMessage.substring(0,1);
if(inputMessage == "")
{
alert("Enter the file name! \ne.g.: /new.txt");
return false;
}
if(isSlash != "/")
{
alert("The slash at the beginning of the file is missing!");
return false;
}
if(dotIndex == 0)
{
alert("The extension is missing at the end of the file!");
return false;
}
if(inputMessageExtension == "")
{
alert("The extension is missing at the end of the file!");
return false;
}
if(extIndex == -1)
{
alert("Extension not supported!");
return false;
}
}
</script>
</body>
</html>

33
data/www/failed.html Normal file
View File

@ -0,0 +1,33 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Update Failed</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/nav.css" rel="stylesheet">
<style>
body {
background-color: #f7f7f7;
}
#spacer-50 {
height: 50px;
}
</style>
</head>
<body>
<div id="navbar"></div>
<center>
<h2>The update has failed.</h2>
<div id="spacer-50"></div>
<button onclick="window.location.href='/files';">to homepage</button>
</center>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
</body>
</html>

276
data/www/files.html Normal file
View File

@ -0,0 +1,276 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>File Manager</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/global-style.css" rel="stylesheet">
<link href="/css/nav.css" rel="stylesheet">
<style>
#file-row:nth-child(odd) {
background-color: #e8e8e8;
}
#first_td_th {
width:300px;
}
progress{
width: 290px;
}
#submit-edit, #submit-upload, #submit-delete, #submit-update-local{
width:120px;
}
#dir-path{
width: 75px;
}
fieldset {
width: 100%;
max-width: 600px;
}
table {
width: 100%;
max-width: 600px;
border-collapse: collapse;
margin: 1rem 0;
/*width:380px; */
}
</style>
<body>
<div id="navbar"></div>
<div class="main-container">
<h1>File Manager</h1>
<fieldset>
<legend>File list</legend>
<div id="spacer-20"></div>
<p>&emsp;&emsp;Total: {{FS_TOTAL_BYTES}}, Used: {{FS_USED_BYTES}}, Available: {{FS_FREE_BYTES}}</p>
<div id="spacer-20"></div>
<table><tr><th id="first_td_th">Listing:</th> </th><th>Size:</th></tr>
{{LISTED_FILES}}
</table>
<div id="spacer-20"></div>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>File upload</legend>
<div id="spacer-20"></div>
<form action="/upload" method="POST" enctype="multipart/form-data">
<table>
<tr>
<td id="first_td_th">
<label for="dir-path">Dir: </label><br>
<select name="dir-path" id="dir-path">
{{DIR_LIST}}
</select>&emsp;&emsp;
</td>
<td><input type="submit" id="submit-upload" value="File upload!" onclick="return validateFormUpload()"></td>
</tr>
<tr>
<td>
<div id="spacer-20"></div>
</td>
</tr>
<tr>
<td>
<input type="file" id="upload-file" name="upload-file">
</td>
</tr>
</table>
</form>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>Edit file</legend>
<div id="spacer-20"></div>
<form action="/files/edit" method="GET">
<table><tr>
<td id="first_td_th">
<select name="edit-path" id="edit-path">
<option value="choose">Select file to edit</option>
<option value="new">New text file</option>
{{EDIT-DEL_FILES}}
</select></td>
<td><input type="submit" id="submit-edit" value="Edit" onclick="return validateFormEdit()"></td>
</tr></table>
</form>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend>Delete file</legend>
<div id="spacer-20"></div>
<form action="/files/delete" method="GET">
<table><tr>
<td id="first_td_th">
<select name="delete-path" id="select-files">
<option value="choose">Select file to delete</option>
{{EDIT-DEL_FILES}}
</select></td>
<td><input type="submit" id="submit-delete" value="Delete" onclick="return validateFormDelete()"></td>
</tr></table>
</form>
</fieldset>
<div id="spacer-20"></div>
<!--<fieldset>
<legend>Format File System</legend>
<div id="spacer-20"></div>
<form action="/format" method="POST" target="self-page">
<table><tr>
<td id="first_td_th">
<p id="format-notice">Pressing the 'Format' button will immediately delete all data from File System!</p></td>
<td><input type="submit" id="submit-format" value="Format" onclick="return confirmFormat()"></td>
</tr></table>
</form>
<div id="spacer-20"></div>
</fieldset>-->
<!--<iframe style="display:none" name="self-page"></iframe>-->
</div>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
function validateFormEdit()
{
var allowedExtensions = "{{ALLOWED_EXTENSIONS_EDIT}}";
var editSelectValue = document.getElementById('edit-path').value;
var dotIndex = editSelectValue.lastIndexOf(".")+1;
var editSelectValueExtension = editSelectValue.substring(dotIndex);
var extIndex = allowedExtensions.indexOf(editSelectValueExtension);
if(editSelectValue == "new"){
return true;
}
if(editSelectValue == "choose" ){
alert("You have not chosen a file!");
return false;
}
if(extIndex == -1){
alert("Editing of this file type is not supported!");
return false;
}
}
function validateFormDelete()
{
var deleteSelectValue = document.getElementById('delete-path').value;
if(deleteSelectValue == "choose" || !deleteSelectValue.indexOf(".")){
alert("You have not chosen a file!");
return false;
}
}
function confirmFormat()
{
var text = 'Pressing the "OK" button immediately deletes all data from SPIFFS and restarts ESP32!';
if (confirm(text) == true) {
return true;
}
else{
return false;
}
}
function validateFormUpload()
{
var inputElement = document.getElementById('upload-file');
var files = inputElement.files;
if(files.length==0){
alert("You have not chosen a file!");
return false;
}
}
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) {
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>

303
data/www/home.html Normal file
View File

@ -0,0 +1,303 @@
<!DOCTYPE html>
<html>
<head>
<title>System Summary</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;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
width: 100%;
max-width: 600px;
min-width: 400px;
margin: 0 auto;
}
.outer-container {
justify-content: center;
align-items: center;
padding: 4 20px 4 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;
gap: 10px;
align-items: flex-end;
}
.row > * {
flex: 1;
width: 0 1 calc(52% - 20px);
margin-bottom: 8px;
}
.row > *:last-child {
flex: 0 1 calc(48% - 10px); /* 40% width with 10px gap */
}
input[type="number"], select {
border: 1px solid #ccc;
border-radius: 4px;
width: 50%;
}
button {
background-color: #007bff;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: auto;
}
.lbldata{
font-style:italic;
font-weight: bold;
color: purple;
}
</style>
</head>
<body>
<div id="navbar"></div>
<h1 name="h1element">System Summary</h1>
<div class="outer-container">
<div class="container">
<legend class="center-text">Booth Overview</legend>
<div class="row">
<div>
<label>App:</label>
<label class="lbldata">{{APP_NAME}}</label>
</div>
<div>
<label>OLED:</label>
<label class="lbldata">{{OLED}}</label>
</div>
</div>
<div class="row">
<div>
<label>RGB Ch1:</label>
<label class="lbldata">{{STRIP1}}</label>
</div>
<div>
<label>RGB Ch2:</label>
<label class="lbldata">{{STRIP2}}</label>
</div>
</div>
<div class="row">
<div>
<label>Front Light:</label>
<label class="lbldata">{{FRONT_LIGHT}}</label>
</div>
<div>
<label>Rear Light:</label>
<label class="lbldata">{{REAR_LIGHT}}</label>
</div>
</div>
</div>
<div class="container">
<legend class="center-text">System Information</legend>
<div class="row">
<div>
<label>Firmware Ver:</label>
<label class="lbldata">{{FIRMWARE}}</label>
</div>
</div>
<div class="row">
<div>
<label>Booth T&deg;:</label>
<label class="lbldata">{{BOOTH_T}}</label>
</div>
<div>
<label>Setpoint:</label>
<label class="lbldata">{{SETPOINT}}</label>
</div>
</div>
<div class="row">
<div>
<label>Flash Size:</label>
<label class="lbldata">{{FLASH_SIZE}}</label>
</div>
<div>
<label>Flash Free:</label>
<label class="lbldata">{{FLASH_FREE}}</label>
</div>
</div>
<div class="row">
<div>
<label>Heap Size:</label>
<label class="lbldata">{{HEAP_SIZE}}</label>
</div>
<div>
<label>Heap Free:</label>
<label class="lbldata">{{HEAP_FREE}}</label>
</div>
</div>
<div class="row">
<div>
</div>
<div>
<label>CPU Freq:</label>
<label class="lbldata">{{CPU_FREQ}}</label>
</div>
</div>
</div>
<div class="container">
<legend class="center-text">Network / WiFi</legend>
<div class="row">
<div>
<label>IP:</label>
<label class="lbldata">{{IP}}</label>
</div>
<div>
<label>MAC:</label>
<label class="lbldata">{{MAC}}</label>
</div>
</div>
<div class="row">
<div>
<label>SSID:</label>
<label class="lbldata">{{SSID}}</label>
</div>
<div>
<label>RSSi:</label>
<label class="lbldata">{{RSSI}}</label>
</div>
</div>
<div class="row">
<div>
<label>Ch:</label>
<label class="lbldata">{{WIFI_CH}}</label>
</div>
<div>
<label>Encryp:</label>
<label class="lbldata">{{ENCRYP}}</label>
</div>
</div>
<div class="row">
<div>
<label>AP SSID:</label>
<label class="lbldata">{{AP_SSID}}</label>
</div>
<div>
<label>AP Clients:</label>
<label class="lbldata">{{AP_CLIENTS}}</label>
</div>
</div>
<div class="row">
<div>
<label>AP MAC:</label>
<label class="lbldata">{{AP_MAC}}</label>
</div>
<div>
<label>---</label>
<label class="lbldata"></label>
</div>
</div>
</div>
<div class="container">
<legend class="center-text">Bluetoth LE</legend>
<div class="row">
<div>
<label>Active:</label>
<label class="lbldata">{{BLE}}</label>
</div>
</div>
<div class="row">
<div>
<label>SSID:</label>
<label class="lbldata">{{BLE_SSID}}</label>
</div>
<div>
<label>Clients:</label>
<label class="lbldata">{{BLE_CLIENTS}}</label>
</div>
</div>
</div>
<div class="container">
<legend class="center-text">Luma Stiks</legend>
<div class="row">
<div>
<label>Stik1:</label>
<label class="lbldata">{{STIK1}}</label>
</div>
<div>
<label>Stik2:</label>
<label class="lbldata">{{STIK2}}</label>
</div>
</div>
<div class="row">
<div>
<label>Stik3:</label>
<label class="lbldata">{{STIK3}}</label>
</div>
<div>
<label>Stik4:</label>
<label class="lbldata">{{STIK4}}</label>
</div>
</div>
<div class="row">
<div>
<label>Stik5:</label>
<label class="lbldata">{{STIK5}}</label>
</div>
<div>
<label>Stik6:</label>
<label class="lbldata">{{STIK6}}</label>
</div>
</div>
</div>
</div>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
</body>
</html>

29
data/www/index.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<link href="/css/nav.css" rel="stylesheet">
<title>ESP32 AP</title>
</head>
<body>
<div id="navbar"></div>
<h1>Welcome to ESP32 AP</h1>
<p>This is a simple web server running on ESP32 in Access Point mode.</p>
<div id="status"></div>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
// Fetch status
fetch('/api/status')
.then(response => response.json())
.then(data => {
document.getElementById('status').innerHTML =
`Status: ${data.status}`;
});
</script>
</body>
</html>

527
data/www/lights.html Normal file
View File

@ -0,0 +1,527 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/nav.css" rel="stylesheet">
<script src="event-box.js"></script>
<script src="hue-select.js"></script>
<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: 350px;
margin: 0 auto;
}
.outer-container {
justify-content: center;
align-items: center;
padding: 4 20px 20 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%;
}
.center-text {
text-align: center;
font-size: larger;
font-weight: bold;
margin-bottom: 10;
}
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
}
.row > * {
flex: 0 0 calc(50% - 20px);
margin-bottom: 15px;
}
.row:last-child {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
input[type="range"] {
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
}
select{
width: 50%;
}
button {
background-color: #007bff;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
#set-countdown-button {
margin-left: 30px; /* Adjust top margin if necessary */
}
</style>
</head>
<body>
<div id="navbar"></div>
<div class="outer-container">
<h1 name="h1element">Lights Configuration</h1>
<div class="container">
<legend class="center-text" id="center-text">Countdown ( Constant Light )</legend>
<div class="row">
<div>
<label id="constLightMin-label" for="constlightMin">Light Min:</label>
<input type="range" name="constLightMin" id="constlightMin" min="0" max="99" value="25" step="1" onchange="updateLabel('Light Min: ', this)">
</div>
<div>
<label id="constLightMax-label" for="constlightMax">Light Max:</label>
<input type="range" name="constLightMax" id="constlightMax" min="1" max="100" value="90" step="1" onchange="updateLabel('Light Max: ', this)">
</div>
</div>
<div class="row">
<div>
<label for="Holdtime">Hold time(ms):</label><br>
<input type="number" name="holdTime" id="holdtime" min="0" max="5000" value="500">
</div>
<div>
<label for="Ramptime">Ramp dn(ms):</label><br>
<input type="number" name="rampTime" id="Ramptime" min="0" max="5000" value="500">
</div>
</div>
<div class="row">
<br>
</div>
<div class="row">
<div>
<label>Active Profile:</label>
<select name= "selActiveAnimProfiles" id="selActivedAnimProfiles" 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>
</div>
<div>
<button id="testCommon" onclick="SaveProfileCommonToServer(true)">Try</button>
&nbsp;&nbsp;&nbsp;&nbsp;
<button id="saveCommon" onclick="SaveProfileCommonToServer(false)">Save</button>
</div>
</div>
</div>
</div>
<div class="outer-container">
<div class="container">
<legend class="center-text" id="center-text">Saved Animation Profiles</legend>
<div class="row">
<div>
<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>
</div>
<div>
<label for="profileName">Rename:</label>
<input type="text" name="inputProfileName" id="profileName" style="width:140px">
<button id="saveProfile" onclick="SaveProfileToServer()">Save</button>
</div>
</div>
</div>
</div>
<div id="events-container"></div>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
window.onload = function() { getProfilesAndEvents(); };
// Initialize Names
const EVENTCOUNT = 12;
const PROFILECOUNT = 8;
var profileCommonJson;
var profileJson = [PROFILECOUNT];
var eventsJson;
var animListJson;
const inputProfileName = document.getElementsByName('inputProfileName')[0];
const selSavedAnimProfiles = document.getElementsByName('selSavedAnimProfiles')[0];
const selActiveAnimProfiles = document.getElementsByName('selActiveAnimProfiles')[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 lastAnimProfileIndex = 0;
var h1element = document.getElementsByName("h1element")[0];
var eventBox = [EVENTCOUNT];
function createEventForms(count){
const eventContainer = document.getElementById('events-container');
for(let i = 0; i < count; i++ ){
const div = document.createElement('div');
eventBox[i] = document.createElement('event-box');
eventBox[i].id = `event${i}`;
eventBox[i].setHidden(true);
eventBox[i].setIndex(i);
eventBox[i].addEventListener('tryClick', handleEventTryClick);
div.appendChild(eventBox[i]);
eventContainer.appendChild(div);
}
}
// Get profiles
// fetch anim-profile-common.json and anim-profile(1-8), events and anim-list
function getProfilesAndEvents() {
try {
fetch_file('cfg/anim-profile-common.json').then(profilesResult => {
profileCommonJson = profilesResult.data;
let profilePromises = [];
for (let i = 1; i <= PROFILECOUNT; i++) {
profilePromises.push(fetch_file(`cfg/anim-profile${i}.json`));
}
Promise.all(profilePromises).then(results => {
profileJson = results.map(result => result.data);
fetch_file('cfg/app-events.json').then(eventsResult => {
eventsJson = eventsResult.data.apps[eventsResult.data.index];
fetch_file('cfg/anim-list.json').then(animListResult => {
animListJson = animListResult.data;
// Title for page
h1element.textContent = eventsJson.name;
// Event Boxes
createEventForms(EVENTCOUNT);
NameAndHideEvents(animListJson);
// Rest
FillProfilesList();
selSavedAnimProfiles.selectedIndex = profileCommonJson['profile-index'];
lastAnimProfileIndex = selSavedAnimProfiles.selectedIndex;
setProfile(selSavedAnimProfiles.selectedIndex);
selActiveAnimProfiles.selectedIndex = profileCommonJson['profile-index'];
});
});
});
}).catch(error => {
console.error(error);
});
} catch (error) {
console.error(error);
}
}
// update the form with animation data when profile list item selected
function setProfile(index){
var changeEvent = new Event("change");
// update common
inputProfileName.value = "";
constLightMin.value = profileCommonJson.countdown.min;
constLightMin.dispatchEvent(changeEvent);
constLightMax.value = profileCommonJson.countdown.max;
constLightMax.dispatchEvent(changeEvent);
holdTime.value = profileCommonJson.countdown.hold;
rampTime.value = profileCommonJson.countdown.ramp;
// Update Events
for (let i = 0; i < EVENTCOUNT; i++) {
if(!eventBox[i].getHidden()){
let thisEvent = profileJson[index].events[i];
eventBox[i].setAnimationIndex(thisEvent.anim);
eventBox[i].setHueValue(thisEvent.hue);
eventBox[i].setSpeedValue(thisEvent.speed);
eventBox[i].setHueRangeValue(thisEvent["hue-range"]);
eventBox[i].setParam1Value(thisEvent.param1);
eventBox[i].setParam2Value(thisEvent.param2);
eventBox[i].setCheck1Value(thisEvent.check1);
eventBox[i].setCheck2Value(thisEvent.check2);
eventBox[i].setCheck3Value(thisEvent.check3);
eventBox[i].setCheck4Value(thisEvent.check4);
}
}
}
// When new profile is selected update
function OnSavedAnimProfilesChanged( event ){
console.log('Anim Profile Selected index:', event.selectedIndex);
console.log('Anim Profile Updated index:', lastAnimProfileIndex);
updateSingleProfileJson(lastAnimProfileIndex); // save current setting in ram but not in server yet
setProfile(event.selectedIndex);
lastAnimProfileIndex = event.selectedIndex;
}
//Update the anim-profilesX.json obj with updated values
function updateSingleProfileJson(index){
if(inputProfileName.value != ""){
profileJson[index].name = inputProfileName.value;
selSavedAnimProfiles.options[index].text = '(' + (index + 1) + ') ' + inputProfileName.value;
selSavedAnimProfiles.options[index].value = inputProfileName.value;
}
// Update Events
for (let i = 0; i < EVENTCOUNT; i++) {
profileJson[index].events[i].anim = eventBox[i].getAnimationIndex();
profileJson[index].events[i].hue = eventBox[i].getHueValue();
profileJson[index].events[i].speed = eventBox[i].getSpeedValue();
profileJson[index].events[i]["hue-range"] = eventBox[i].getHueRangeValue();
profileJson[index].events[i].param1 = eventBox[i].getParam1Value();
profileJson[index].events[i].param2 = eventBox[i].getParam2Value();
profileJson[index].events[i].check1 = eventBox[i].getCheck1Value();
profileJson[index].events[i].check2 = eventBox[i].getCheck2Value();
profileJson[index].events[i].check3 = eventBox[i].getCheck3Value();
profileJson[index].events[i].check4 = eventBox[i].getCheck4Value();
}
}
// save profileX.Json
function SaveProfileToServer(){
updateSingleProfileJson(selSavedAnimProfiles.selectedIndex);
const params = new URLSearchParams();
let i = selSavedAnimProfiles.selectedIndex + 1;
params.append('type', `anim-profile${i}`);
const url = '/post?' + params.toString();
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' } ,
body: JSON.stringify(profileJson[selSavedAnimProfiles.selectedIndex]) // convert to string
})
.then(response => {
if (response.ok) { console.log('Request successful');}
else { throw new Error('Request failed');}
})
.catch(error => { console.error(error);});
}
// save profileCommonJson
function SaveProfileCommonToServer(TestOnly){
profileCommonJson.countdown.min = constLightMin.value;
profileCommonJson.countdown.max = constLightMax.value;
profileCommonJson.countdown.hold = holdTime.value;
profileCommonJson.countdown.ramp = rampTime.value;
profileCommonJson["profile-index"] = selActiveAnimProfiles.selectedIndex; // Set at active profile
const params = new URLSearchParams();
params.append('type', TestOnly ? 'set-countdown' : 'anim-profile-common');
const url = '/post?' + params.toString();
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/json' } ,
body: JSON.stringify(profileCommonJson) // convert to string
})
.then(response => {
if (response.ok) { console.log('Request successful');}
else { throw new Error('Request failed');}
})
.catch(error => { console.error(error);});
}
//
function fetch_file(filename){
return fetch(filename)
.then(response => {
if (response.ok) { return response.json(); }
else { throw new Error('fetching ' + fileName + ' failed'); }
})
.then(data => { return{data:data}; })
}
//
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(props){
for(let i = 0; i < EVENTCOUNT; i++){
if(eventsJson.events[i] === ''){
break;
}
eventBox[i].setTitle(eventsJson.events[i]);
eventBox[i].setHidden(false);
}
// load captions for white fills box
eventBox[0].setAnimationCaptions(props.whitefills);
// load captions for the rest of the eventBox
for(let i = 1; i < EVENTCOUNT; i++){
eventBox[i].setAnimationCaptions(props.animations);
}
}
// Fill Profiles List
function FillProfilesList(){
for(let i = 0; i < PROFILECOUNT; i++){
selSavedAnimProfiles.options[i].text = '(' + (i + 1) + ') ' + profileJson[i].name;
selSavedAnimProfiles.options[i].value = profileJson[i].name;
selActiveAnimProfiles.options[i].text = selSavedAnimProfiles.options[i].text;
selActiveAnimProfiles.options[i].value = selSavedAnimProfiles.options[i].value;
}
}
// Fill White Animation drop down lists
function FillWhitefilList(whiteJson){
for(let x = 0; x < whiteJson.length; x++){
eventBox[0].addOptionToList(x, whiteJson[x])
}
}
// Fill Animations drop down lists
function FillAnimationsList(animJson){
for(let x = 0; x < animJson.length; x++){
if(animJson[x].name == ""){break;}// stop if blank
for(let i = 1; i < EVENTCOUNT; i++){
eventBox[i].addOptionToList(i, animJson[x].name);
}
}
}
// for const light props
function updateLabel(labelText, slider) {
let label = document.getElementById(slider.name + "-label");
label.innerHTML = labelText + slider.value;
}
// send anim event to test out
function postPlayAnim(i){
let tempAnimProps = {};
tempAnimProps.event = eventIndex;
tempAnimProps.anim = eventBox[i].getAnimationIndex();
tempAnimProps.hue = eventBox[i].getHueValue();
tempAnimProps.speed = eventBox[i].getSpeedValue();
tempAnimProps["hue-range"] = eventBox[i].getHueRangeValue();
tempAnimProps.param1 = eventBox[i].getParam1Value();
tempAnimProps.param2 = eventBox[i].getParam2Value();
tempAnimProps.check1 = eventBox[i].getCheck1Value();
tempAnimProps.check2 = eventBox[i].getCheck2Value();
tempAnimProps.check3 = eventBox[i].getCheck3Value();
tempAnimProps.check4 = eventBox[i].getCheck4Value();
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); });
}
// send anim event to test out
function postTestCountdown(){
let tempCountdown = {};
tempCountdown.min = constLightMin.value;
tempCountdown.max = constLightMax.value;
tempCountdown.hold = holdTime.value;
tempCountdown.ramp = rampTime.value;
tempCountdown.active_profile = selSavedAnimProfiles.selectedIndex;
const params = new URLSearchParams();
params.append('type', 'set-countdown');
const url = '/post?' + params.toString();
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain'} ,
body: JSON.stringify(tempCountdown)
})
.then(response => {
if (!response.ok) { throw new Error('Request failed'); }
})
.catch(error => { console.error(error); });
}
// Post Play Anim
function handleEventTryClick(event) {
let tempAnimProps = {};
tempAnimProps.index = event.detail.eventIndex;
tempAnimProps.anim = event.detail.animIndex;
tempAnimProps.hue = event.detail.hue;
tempAnimProps.speed = event.detail.speed;
tempAnimProps["hue-range"] = event.detail.colorRange;
tempAnimProps.param1 = event.detail.param1;
tempAnimProps.param2 = event.detail.param2;
tempAnimProps.check1= event.detail.check1;
tempAnimProps.check2 = event.detail.check2;
tempAnimProps.check3 = event.detail.check3;
tempAnimProps.check4 = event.detail.check4;
//console.log(tempAnimProps);
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); });
};
</script>
</body>
</html>

17
data/www/navbar.html Normal file
View File

@ -0,0 +1,17 @@
<nav class="navbar">
<div class="navbar-left">
<img src="/images/atalogo.png" alt="Left Image" class="nav-image">
</div>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/setup">Setup</a></li>
<li>
<a href="#"> System</a>
<ul class="submenu">
<li><a href="/www/wifi.html">Internet</a></li>
<li><a href="/www/firmware.html">Update</a></li>
<li><a href="/www/about.html">About</a></li>
</ul>
</li>
</ul>
</nav>

34
data/www/ok.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Update Success</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/nav.css" rel="stylesheet">
<style>
body {
background-color: #f7f7f7;
}
#spacer-50 {
height: 50px;
}
</style>
</head>
<body>
<div id="navbar"></div>
<center>
<h2>The update was successful.</h2>
<div id="spacer-50"></div>
<button onclick="window.location.href='/files';">to homepage</button>
</center>
<script>
// Load navbar
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
</body>
</html>

385
data/www/setup.html Normal file
View File

@ -0,0 +1,385 @@
<!DOCTYPE html>
<html>
<head>
<title>Booth Configuration Tools</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/nav.css" rel="stylesheet">
<script src="hue-select.js"></script>
<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: 600px;
min-width: 400px;
margin: 0 auto;
}
.outer-container {
justify-content: center;
align-items: center;
padding: 4 20px 4 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;
gap: 10px;
align-items: flex-end;
}
.row > * {
flex: 1;
width: calc(50% - 20px);
margin-bottom: 8px;
}
input[type="number"], select {
border: 1px solid #ccc;
border-radius: 4px;
width: 50%;
}
button {
background-color: #007bff;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: auto;
}
</style>
</head>
<body>
<div id="navbar"></div>
<h1 name="h1element">System Setup</h1>
<div class="outer-container">
<div class="container">
<legend class="center-text" id="center-text">LED Strip #1 Settings</legend>
<div class="row">
<div>
<label>Control App:</label><br>
<select id="app-names-list" style="width: 80%;"></select>
</div>
<div>
<button onclick="OnSaveAllClick()">Save All</button>
</div>
<div>
<button onclick="OnReStartClick()">ReStart</button>
</div>
</div>
</div>
<div class="container">
<legend class="center-text" id="center-text">LED Strip #1 Settings</legend>
<div class="row">
<div>
<label>Enabled</label><br>
<input type="checkbox" id="strip1-en">
</div>
<div>
<label for="inputLEDS">LED Count:</label><br>
<input type="number" id="strip1-led-count" min="1" max="200" step="1">
</div>
<div>
<label for=" ">Shift:</label><br>
<input type="number" id="strip1-shift" min="-199" max="199" step="1">
</div>
</div>
<div class="row">
<div>
<label for=" ">Offset:</label><br>
<input type="number" id="strip1-offset" min="0" max="100" step="1">
</div>
<div>
<label>RGB Order:</label><br>
<select id="strip1-rgb">
<option> RGB</option>
<option> RBG</option>
<option> GRB</option>
<option> GBR</option>
<option> BRG</option>
<option> BGR</option>
</select>
</div>
<div>
<label>Power:</label><br>
<select id="strip1-power">
<option> 100%</option>
<option> 50%</option>
<option> 25%</option>
<option> 12.5%</option>
</select>
</div>
</div>
</div>
<div class="container">
<legend class="center-text" id="center-text">Test Tool (Single LED)</legend>
<div class="row">
<div>
<input type="radio" id="test-radio-strip1" name="strip" value="0" checked>
<label>Strip #1</label><br>
</div>
<div hidden>
<input type="radio" name="strip" value="1">
<label>Strip #2</label><br>
</div>
</div>
<div class="row">
<div>
<label>Pixel Index:</label><br>
<input type="number" id="test-pixel-index" min="-199" max="199" step="1" style="width: 80px;">
</div>
<div>
<hue-select id="test-hue"></hue-select>
</div>
</div>
<div class="row">
<div>
<button onclick="OnSetPixelClick()">Set</button>
</div>
<div>
<button onclick="OnClearClick()">Clear</button>
</div>
</div>
</div>
<div class="container">
<legend class="center-text" id="center-text">Luma Stiks Found</legend>
<div class="row">
<div>
<label>Stik #1 SSID:</label><br>
<input type="checkbox" id="stick1-en">
<input id="stick1" >
</div>
<div>
<label>Stik #2 SSID:</label><br>
<input type="checkbox" id="stick2-en">
<input id="stick2" >
</div>
</div>
<div class="row">
<div>
<label>Stik #3 SSID:</label><br>
<input type="checkbox" id="stick3-en">
<input id="stick3" >
</div>
<div>
<label>Stik #4 SSID:</label><br>
<input type="checkbox" id="stick4-en">
<input id="stick4" >
</div>
</div>
<div class="row">
<div>
<label>Stik #5 SSID:</label><br>
<input type="checkbox" id="stick5-en">
<input id="stick5" >
</div>
<div>
<label>Stik #6 SSID:</label><br>
<input type="checkbox" id="stick6-en">
<input id="stick6" >
</div>
</div>
<div class="row">
<div>
<label>Stik #7 SSID:</label><br>
<input type="checkbox" id="stick7-en">
<input id="stick7" >
</div>
<div>
<label>Stik #8 SSID:</label><br>
<input type="checkbox" id="stick8-en">
<input id="stick8" >
</div>
</div>
<div class="row" >
<div>
<button id="save-stiks">Scan</button>
</div>
<div>
<button id="save-stiks">Save</button>
</div>
</div>
</div>
</div>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
window.onload = function() { OnPageLoad(); };
const AppList = document.getElementById('app-names-list');
const Strip1Enabled = document.getElementById('strip1-en');
const Strip1LedCount = document.getElementById('strip1-led-count');
const Strip1Shift = document.getElementById('strip1-shift');
const Strip1Offset = document.getElementById('strip1-offset');
const Strip1RGB = document.getElementById('strip1-rgb');
const Strip1Power = document.getElementById('strip1-power');
const TestHue = document.getElementById('test-hue');
const PixelIndex = document.getElementById('test-pixel-index');
PixelIndex.value = 0;
function OnPageLoad(){
fetch_json_file('cfg/app-events.json').then(result =>{
jsAppEvents = result.data;
fetch_json_file('cfg/led-devices.json').then(result =>{
jsLedDevices = result.data;
FillControlAppList(jsAppEvents);
FillStripValues(jsLedDevices);
});
});
}
function FillStripValues(js){
Strip1Enabled.checked = js.strip1.en;
Strip1LedCount.value = js.strip1.size;
Strip1Shift.value = js.strip1.shift;
Strip1Offset.value = js.strip1.offset;
Strip1RGB.selectedIndex = js.strip1['rgb-order'];
Strip1Power.selectedIndex = js.strip1['power-div'];
SelectRgbByText(js.strip1['rgb-order'].toLowerCase());
}
function SelectRgbByText(rgbText){
let x = 0;
if(rgbText === "rgb"){x = 0;}
else if(rgbText === "rbg"){x = 0;}
else if(rgbText === "grb"){x = 0;}
else if(rgbText === "gbr"){x = 0;}
else if(rgbText === "brg"){x = 0;}
else if(rgbText === "bgr"){x = 0;}
Strip1RGB.selectedIndex = x;
}
function FillControlAppList(js){
jsApps = js.apps;
let x = 0;
jsApps.forEach(app => {
AddOptionToList(x, app.name);
x++;
});
AppList.selectedIndex = js.index;
}
function AddOptionToList(value, text){
const optionElement = document.createElement('option');
optionElement.value = value;
optionElement.textContent = text;
AppList.appendChild(optionElement);
}
function OnSetPixelClick(){
let jsPacket = {};
jsPacket.strip = 0;
i = PixelIndex.value;
if( i < -200){
PixelIndex.value = i;
return;
}
jsPacket.index = PixelIndex.value;
jsPacket.hue = TestHue.getSelectedHue();
SendJson(jsPacket, 'set-pixel');
}
function SendJson(js, type){
const params = new URLSearchParams();
params.append('type', type);
const url = '/post?' + params.toString();
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain'} ,
body: JSON.stringify(js)
})
.then(response => {
if (!response.ok) { throw new Error('Request failed'); }
})
.catch(error => { console.error(error); });
}
function OnClearClick(){
let jsPacket = {};
jsPacket.strip = 0;
SendJson(jsPacket, 'clear-strip');
}
function OnSaveAllClick(){
let jsPacket = {};
jsPacket.appindex = AppList.selectedIndex;
jsPacket.en1 = Strip1Enabled.checked;
jsPacket.count1 = Strip1LedCount.value;
jsPacket.shift1 = Strip1Shift.value;
jsPacket.offset1 = Strip1Offset.value;
jsPacket.rgb1 = Strip1RGB.value.toLowerCase();
jsPacket.power1 = Strip1Power.selectedIndex;
SendJson(jsPacket, 'setup-save');
}
function OnReStartClick(){
let jsPacket = {};
jsPacket.test = "restart";
SendJson(jsPacket, 'restart');
}
function fetch_json_file(filename){
return fetch(filename)
.then(response => {
if (response.ok) { return response.json(); }
else { throw new Error('fetching ' + fileName + ' failed'); }
})
.then(data => { return{data:data}; })
}
</script>
</body>
</html>

234
data/www/upgrade.html Normal file
View File

@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="/css/nav.css">
<title>Firmware Upgrade</title>
<style>
.status-circle {
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
}
.connected { background-color: #4CAF50; }
.disconnected { background-color: #f44336; }
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.version-info {
margin: 20px 0;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
}
.progress-box {
height: calc(100vh - 500px);
min-height: 200px;
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
overflow-y: auto;
font-family: monospace;
}
button {
background-color: #2196F3;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div id="navbar"></div>
<div class="container">
<h1>Firmware Upgrade</h1>
<div>
<span class="status-circle disconnected" id="status-indicator"></span>
<span id="connection-status">Disconnected</span>
</div>
<div class="version-info">
<p>Current Version: <span id="current-version">-</span></p>
<p>Latest Version: <span id="latest-version">-</span></p>
</div>
<div>
<button id="check-upgrades" onclick="checkUpdates()">Check for Upgrades</button>
<button id="start-upgrade" onclick="startUpdate()" disabled>Start upgrade</button>
</div>
<div class="progress-box" id="progress-log"></div>
</div>
<script>
// Load the navigation bar from an external HTML file and insert it into the page
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
// Flag to track if a firmware upgrade is available
let updateAvailable = false;
// Updates the UI to show connection status
// Changes the color and text of the status indicator
function updateStatus(connected) {
const indicator = document.getElementById('status-indicator');
const status = document.getElementById('connection-status');
indicator.className = `status-circle ${connected ? 'connected' : 'disconnected'}`;
status.textContent = connected ? 'Connected' : 'Disconnected';
}
// Adds a message to the progress log box
// Auto-scrolls to the bottom to show latest messages
function log(message) {
const logBox = document.getElementById('progress-log');
logBox.innerHTML += `${message}<br>`;
logBox.scrollTop = logBox.scrollHeight;
}
// Checks for firmware updates by calling the server API
// Updates the UI with version information and enables/disables upgrade button
async function checkUpdates() {
const checkButton = document.getElementById('check-upgrades');
checkButton.disabled = true; // Disable button while checking
try {
const response = await fetch('/upgrade/check');
const data = await response.json();
// Upgrade version information in the UI
document.getElementById('current-version').textContent = data.currentVersion;
document.getElementById('latest-version').textContent = data.latestVersion;
// Enable/disable upgrade button based on availability
updateAvailable = data.updateAvailable;
document.getElementById('start-upgrade').disabled = !updateAvailable;
log(updateAvailable ? 'Upgrade available!' : 'No upgrades available');
} catch (error) {
log('Error checking for upgrades: ' + error);
} finally {
checkButton.disabled = false; // Re-enable button when done
}
}
// Initiates the firmware upgrade process
// Uses Websocket to receive progress updates
async function startUpdate() {
const startButton = document.getElementById('start-upgrade');
const checkButton = document.getElementById('check-upgrades');
let retryCount = 0;
const maxRetries = 3;
// Disable buttons during the update
startButton.disabled = true;
checkButton.disabled = true;
try {
// Start the upgrade process with a timeout
log('Starting update...');
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timed out")), 10000)
);
const response = await Promise.race([
fetch('/upgrade/start', {
method: 'POST',
credentials: 'same-origin'
}),
timeout
]);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
log('Update initiated successfully.');
// Create SSE connection
const eventSource = new EventSource('/upgrade-progress');
log('Connecting to update server...');
eventSource.onopen = () => {
console.log("EventSource connected");
log('Update connection established.');
};
eventSource.addEventListener('update', (event) => {
try {
console.log("Received message:", event.data);
const data = JSON.parse(event.data);
log(data.message); // Log the update message
// Handle completion
if (data.complete) {
eventSource.close();
log('Upgrade complete! Rebooting...');
//setTimeout(() => window.location.reload(), 5000);
}
} catch (error) {
console.error("Message parsing error:", error);
log(`Error processing update message: ${error.message}`);
}
});
eventSource.onerror = (event) => {
const errorDetails = event.error ? `Error: ${event.error}` : event.status ? `Status: ${event.status}` : 'Unknown error';
log('Connection error occurred' + errorDetails);
if (retryCount++ >= maxRetries || eventSource.readyState === EventSource.CLOSED) {
log('Max retries reached. Please refresh the page to retry.');
eventSource.close();
startButton.disabled = false;
checkButton.disabled = false;
} else {
log(`Attempting to reconnect... (${retryCount}/${maxRetries})`);
}
};
} catch (error) {
console.error("Update error:", error);
log(`Update error: ${error.message}`);
} finally {
startButton.disabled = false;
checkButton.disabled = false;
}
}
// Poll server every 5 seconds to check if device is still connected
// Updates the status indicator accordingly
setInterval(async () => {
try {
const response = await fetch('/api/status');
updateStatus(response.ok);
} catch {
updateStatus(false);
}
}, 5000);
</script>
</body>
</html>

159
data/www/wifi.html Normal file
View File

@ -0,0 +1,159 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="/css/nav.css">
<title>Wifi Access</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
margin: 0;
padding: 0;
}
h1 {
margin: 30px 0 20px;
text-align: center;
}
form {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
margin: auto;
max-width: 500px;
}
input[type="text"], input[type="password"], select {
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: none;
border-radius: 4px;
background-color: #f2f2f2;
font-size: 16px;
width: 100%;
}
button {
background-color: #2d2dfa;
color: #fff;
padding: 12px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background-color: #45a049;
}
img{
display: block;
margin-left: auto;
margin-right:auto;
height: auto;
width: auto;
max-width: 150px;
filter: drop-shadow(2px 2px 2px #666666);
}
</style>
</head>
<body>
<div id="navbar"></div>
<h1>Wifi Access</h1>
<form id="wifi-credentials-form">
<div style="text-align: center; margin-bottom: 15px;">
<span style="display: inline-block; width: 25px; height: 25px; border-radius: 50%; background-color: gray; margin-right: 10px; vertical-align: middle;"></span>
<label id="status-label">Status: Not Connected</label>
</div>
<label for="ssid">SSID:</label>
<select id="ssid" name="ssid" required>
<option value="">Select a network...</option>
</select>
<label for="password">Password:</label>
<input type="text" id="password" name="password" placeholder="Password" required>
<button type="button" onclick="postConnectToAP(event)">Connect</button>
</form>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
const form = document.querySelector('#wifi-credentials-form');
window.onload = getWifiNetworks();
function onStart(){
getConnectionStatus();
getWifiNetworks();
}
async function getWifiNetworks() {
try {
const response = await fetch('/wifi/scans', { method: 'GET' });
const data = await response.json();
const ssidSelect = document.getElementById('ssid');
ssidSelect.innerHTML = '<option value="">Select a network...</option>';
if (data.networks && Array.isArray(data.networks)) {
data.networks.forEach(network => {
const option = document.createElement('option');
option.value = network.ssid;
option.textContent = `${network.ssid} (${network.rssi}dBm)`;
ssidSelect.appendChild(option);
});
} else {
console.error('Network data is not in expected format');
}
} catch (error) {
console.error('WiFi scan failed:', error);
}
}
async function postConnectToAP(event) {
event.preventDefault();
const connectButton = document.querySelector('button[onclick="postConnectToAP(event)"]');
connectButton.disabled = true;
try {
const params = new URLSearchParams({
ssid: document.getElementById('ssid').value,
pass: document.getElementById('password').value
});
await fetch(`/wifi/connect?${params.toString()}`, {
method: 'POST',
headers: { 'Accept': 'application/json' }
});
document.getElementById('status-label').textContent = `Status: Disconnected`;
const circle = document.querySelector('span');
circle.style.backgroundColor = 'gray';
} catch (error) {
console.error('Connection failed:', error);
} finally {
connectButton.disabled = false;
}
}
async function getConnectionStatus() {
try {
const data = await fetch('/wifi/status', { method: 'GET'}).then(res => res.json());
document.getElementById('status-label').textContent = `Status: ${data.status}`;
const circle = document.querySelector('span');
circle.style.backgroundColor = data.status === 'Connected' ? '#45a049' : 'gray';
} catch (error) {
console.error('Failed to get connection status:', error);
}
}
// Run getConnectionStatus every second
setInterval(getConnectionStatus, 10000);
</script>
</body>
</html>

8
diagram.json Normal file
View File

@ -0,0 +1,8 @@
{
"version": 1,
"author": "David P",
"editor": "wokwi",
"parts": ["board-esp32-s3-devkitc-1"],
"connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ],
"dependencies": {}
}

18
docs/!instructions.txt Normal file
View File

@ -0,0 +1,18 @@
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
**anim-profiles

View File

@ -0,0 +1,2 @@
# DSLRBooth Mode Wifi no bluetooth

3
docs/Manual Mode.md Normal file
View File

@ -0,0 +1,3 @@
# Manual Mode
asdad

51
docs/Sys Report.txt Normal file
View File

@ -0,0 +1,51 @@
********** APP CONTROL **********
App: DSLRBooth
Mode:
********** PERIPHERALS **********
RGB Strip1 Active: true, Size: 164, Core: 0, I2S_Ch: 0
Shift: 0, Offset: 0, RGB Order: grb, Pin: 30
RGB Strip2 Active: true, Size: 164, Core: 0, I2S_Ch: 0
Shift: 0, Offset: 0, RGB Order: grb, Pin: 30
Front Light Active: true, Relay: 0, Freq: 500
Rear Light Active: false, Relay: 1, Freq: 500
Buzzer: On
OLED: Off
********** NETWORK **********
Wifi Client Active: true, Status: Connected
SSID: ATA, RSSi:-107, Ch:12, Encryp: asdasda,
IP: 192.168.0.15, MAC: XX:XX:XX:XX:XX,
Wifi AP Active: false, Status: Client connected,
IP: 192.168.4.1
********** Chip **********
Clock: 240Mhz, RAM: asdfas, Used: 23423, Free: 23423
ID: 23:23:43:32:23:43
********** Bluetooth LE **********
Active: true, Server SSID: ATA_COMMXX, Connected:
SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
********** FAN / T SENSOR **********
Fan Active: true, Relay: 3
TSensor Active: true, Setpoint1: 80, Setpoint2: 85
T Now: 78.2F
********** 12V Monitor **********
Volt: 12.2V
********** Buttons **********
********** Touch Sensors **********
********** 433 RX/TX **********
RX Active: true
TX Active: true
********** Tasks & CPU Usage **********
Task1... CPU Usage: 23 Priority:3
Task2...
Task3...

22
docs/Todo (add).doc Normal file
View File

@ -0,0 +1,22 @@
2) Make I2S buffer setting automatic based on pixel size ---------------- DONE
3) Add Address Input box for firmware web location
4) cpu / thread water mark diagnostics
5) ble multi client
6) Strip 2 simulataneous function Test
7) Add button to control Rear Lights
**** Add LED Color Indication on BLE Connection established and disconnect
***
**** Saving from web to LittleFS.... Write to temporary file then rename.

10
docs/Todo.(fixes)doc Normal file
View File

@ -0,0 +1,10 @@
1) Helio and Lumia Lighting Logic
2) Rear Light Button Ramp control
3) Better Tunes
4) Save Wifi Creds in Flash...not LittleFS
5) Config page,
Booth Type, Front active, Rear Active,

23
docs/cli commands.txt Normal file
View File

@ -0,0 +1,23 @@
1. set-relay -value -freq
2. set-event -index
3. set-front-light -value -freq
4. playtune -index x
playtune "dasdasafsdfasdf"
5. readtemp -c
6. restart
7. set-bright
8. readfile -f filename
9. list directory
10. trigger button
11. echo
12. ble-msg
13. ping
14. post ble command
15. post tcp command
16. set wifi creds
17. start AP
18. start ble
19. test event anim colmain colbase density speed
20. send rf
21. get 12V value
22.

View File

@ -0,0 +1,74 @@
{
"folders": [
{
"path": "."
},
{
"path": "../esp32s3_module_8mb"
}
],
"settings": {
"files.associations": {
"*.service": "ini",
"new": "cpp",
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"set": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp"
},
"liveServer.settings.multiRootWorkspaceName": "esp32s3_atabooth_8mb"
}
}

View File

@ -0,0 +1,165 @@
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()

View File

@ -0,0 +1,152 @@
#!/usr/bin/env python3
import os
import datetime
from pathlib import Path
from google.cloud import storage
# =============================================================================
# CONFIGURATION CONSTANTS
# =============================================================================
CREATE_BACKUP = False
UPLOAD_FIRMWARE = True
UPLOAD_MANIFEST = True
UPLOAD_DATA = True
# The name of your Google Cloud Storage bucket.
GCS_BUCKET_NAME = 'boothifier' # <-- Change this
# The destination directory (prefix) inside your bucket.
# For example, 'release' or 'prod'. Use an empty string ('') to use the bucket root.
DESTINATION_DIR = 'latest' # <-- Change this (or leave '' for bucket root)
BACKUPS_DIR = 'backups'
LOCAL_ROOT_PATH = Path(__file__).parent.resolve()
# Path to your Google Cloud credentials JSON file.
GOOGLE_APPLICATION_CREDENTIALS_PATH = str(LOCAL_ROOT_PATH / 'loyal-column-439819-e3-8cddff2ee2c2.json')
# Local path to the firmware file.
LOCAL_FIRMWARE_PATH = str(LOCAL_ROOT_PATH / 'firmware.bin') # <-- Change this if needed
LOCAL_MANIFEST_PATH = str(LOCAL_ROOT_PATH / 'update.json') # <-- Change this if needed
# Local path to the data directory.
LOCAL_DATA_DIRECTORY = 'data' # <-- Change this if needed
# =============================================================================
# SET UP GOOGLE CLOUD CREDENTIALS
# =============================================================================
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = GOOGLE_APPLICATION_CREDENTIALS_PATH
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
def backup_existing_files(bucket, destination_prefix, backups_prefix, backup_folder):
"""
Copy every blob in the bucket with a name starting with destination_prefix
(except any files already in a backup folder) into a backup folder under backups_prefix.
Files will be copied into:
<backups_prefix>/<backup_folder>/<relative_path>
"""
blobs = bucket.list_blobs(prefix=destination_prefix)
for blob in blobs:
# Compute the file's relative path by removing the destination_prefix.
if destination_prefix:
# Remove the destination_prefix and any leading slashes.
relative_path = blob.name[len(destination_prefix):].lstrip('/')
else:
relative_path = blob.name
# Construct the new blob name in the backup folder under backups_prefix.
if backups_prefix:
new_blob_name = f"{backups_prefix}/{backup_folder}/{relative_path}"
else:
new_blob_name = f"{backup_folder}/{relative_path}"
print(f"Backing up: copying '{blob.name}' to '{new_blob_name}'")
bucket.copy_blob(blob, bucket, new_blob_name)
def upload_file(bucket, local_path, destination_blob_name):
"""
Upload a single file to the bucket with the specified blob name.
Overwrites the blob if it already exists.
"""
print(f"Uploading file '{local_path}' to '{destination_blob_name}'")
blob = bucket.blob(destination_blob_name)
# Set Cache-Control header to force clients to always fetch fresh content.
blob.cache_control = 'private, max-age=0, no-transform'
blob.upload_from_filename(local_path)
def upload_directory(bucket, local_directory, destination_prefix):
"""
Recursively upload the contents of the local directory to the bucket under
destination_prefix. The directory structure is preserved.
"""
for root, _, files in os.walk(local_directory):
for file in files:
file_local_path = os.path.join(root, file)
# Compute the relative path of the file from the base directory.
rel_path = os.path.relpath(file_local_path, local_directory)
if destination_prefix:
dest_blob_name = f"{destination_prefix}/{rel_path}"
else:
dest_blob_name = rel_path
upload_file(bucket, file_local_path, dest_blob_name)
# =============================================================================
# MAIN FUNCTION
# =============================================================================
def main():
# Initialize the Google Cloud Storage client.
client = storage.Client()
bucket = client.bucket(GCS_BUCKET_NAME)
# Normalize the destination prefix by stripping any trailing slashes.
destination_prefix = DESTINATION_DIR.strip('/')
backups_prefix = BACKUPS_DIR.strip('/')
if(CREATE_BACKUP):
# Create a backup folder name with a timestamp.
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_folder = f"backup_{timestamp}"
print(f"Creating backup folder '{backup_folder}' inside '{destination_prefix}' and copying existing files...")
backup_existing_files(bucket, destination_prefix, backups_prefix, backup_folder)
if(UPLOAD_FIRMWARE):
print("Uploading firmware...")
# Upload the firmware file.
if destination_prefix:
firmware_destination = f"{destination_prefix}/firmware.bin"
else:
firmware_destination = "firmware.bin"
upload_file(bucket, LOCAL_FIRMWARE_PATH, firmware_destination)
if(UPLOAD_MANIFEST):
print("Uploading manifest...")
# Upload the manifest.
if destination_prefix:
manifest_destination = f"{destination_prefix}/update.json"
else:
manifest_destination = "update.json"
upload_file(bucket, LOCAL_MANIFEST_PATH, manifest_destination)
if(UPLOAD_DATA):
print("Uploading data directory...")
# Upload the data directory.
if destination_prefix:
data_destination = f"{destination_prefix}/data"
else:
data_destination = "data"
upload_directory(bucket, LOCAL_DATA_DIRECTORY, data_destination)
print("Upload complete.")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,501 @@
<!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('.');
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('checkVersionBtn').disabled = true;
document.getElementById('startUpgradeBtn').disabled = false;
} else {
//disable start upgrade button
document.getElementById('checkVersionBtn').disabled = false;
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
success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
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 () => {
await sendPacket('upgrade-start');
});
processUpdatePacket(updatePacket);
</script>
</body>
</html>

View File

@ -0,0 +1,79 @@
body {
/*background-color: #f7f7f7;*/
font-family: Tahoma, Arial, sans-serif;
font-size: small;
margin: 0;
display: flex;
min-height: 100vh;
}
.main-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 20px;
}
h1 {
margin-top: 0;
}
input{
cursor:pointer;
}
input[type="number"]{
width: 100px;
}
#submit {
width:120px;
}
select{
width:160px;
}
#select-path {
width:250px;
}
#select-dir {
width:90px;
}
#spacer-50 {
height: 50px;
}
#spacer-20 {
height: 20px;
}
#spacer-10 {
height: 10px;
}
table {
/*background-color: #dddddd;*/
border-collapse: collapse;
width:600px;
margin: 0 auto;
overflow: visible;
}
td, th {
/*border: 1px solid #dddddd;*/
text-align: left;
padding: 2px;
}
#first_td_th {
width:400px;
}
fieldset {
width:620px;
/*background-color: #f7f7f7;*/
border-radius: 10px;
border-color: blue;
}
#format-notice {
color: #ff0000;
}
legend {
display: flex;
justify-content: center;
background-color:white;
background-blend-mode: darken;
border-radius: 10px;
padding: 1px 8px 2px 8px;
border-style: solid;
border-width: 1.0;
}

View File

@ -0,0 +1,72 @@
.navbar {
width: 100%;
background-color: black;
border-bottom: 2px solid white;
display: flex;
justify-content: flex-start;
align-items: center;
position: sticky;
top: 0;
z-index: 1000;
}
.navbar-left {
padding: 0 10px;
}
.navbar ul {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
align-items: center;
position: relative; /* Ensure relative positioning for the submenu */
}
.navbar ul li {
float: left;
position: relative; /* Ensure relative positioning for the submenu */
}
.navbar ul li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
border-right: 1px solid white;
}
.navbar ul li a:hover {
background-color: grey;
}
.navbar ul li a.disabled {
color: grey;
pointer-events: none;
}
.navbar ul .submenu {
display: none;
position: absolute;
top: 100%;
left: 0;
background-color: black;
list-style-type: none;
margin: 0;
padding: 0;
border-top: 2px solid white;
z-index: 1000;
}
.navbar ul .submenu li {
float: none;
border-right: none;
}
.navbar ul .submenu li a {
padding: 10px 16px;
border-bottom: 1px solid white;
}
.navbar ul .submenu li a:hover {
background-color: grey;
}
.navbar ul li:hover > .submenu {
display: block;
}
.nav-image {
height: 40px;
width: auto;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More