Require SD card or >4MB flash (#545)
This commit is contained in:
committed by
GitHub
parent
90afba647e
commit
05720821f8
@@ -11,6 +11,8 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
uses: ./.github/actions/build-simulator
|
uses: ./.github/actions/build-simulator
|
||||||
with:
|
with:
|
||||||
@@ -21,6 +23,8 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
uses: ./.github/actions/build-simulator
|
uses: ./.github/actions/build-simulator
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: "Build SDK"
|
- name: "Build SDK"
|
||||||
uses: ./.github/actions/build-sdk
|
uses: ./.github/actions/build-sdk
|
||||||
with:
|
with:
|
||||||
@@ -66,7 +68,6 @@ jobs:
|
|||||||
{ id: m5stack-cores3, arch: esp32s3 },
|
{ id: m5stack-cores3, arch: esp32s3 },
|
||||||
{ id: m5stack-papers3, arch: esp32s3 },
|
{ id: m5stack-papers3, arch: esp32s3 },
|
||||||
{ id: m5stack-stackchan, arch: esp32s3 },
|
{ id: m5stack-stackchan, arch: esp32s3 },
|
||||||
{ id: m5stack-stickc-plus, arch: esp32 },
|
|
||||||
{ id: m5stack-stickc-plus2, arch: esp32 },
|
{ id: m5stack-stickc-plus2, arch: esp32 },
|
||||||
{ id: m5stack-sticks3, arch: esp32s3 },
|
{ id: m5stack-sticks3, arch: esp32s3 },
|
||||||
{ id: m5stack-tab5, arch: esp32p4 },
|
{ id: m5stack-tab5, arch: esp32p4 },
|
||||||
@@ -82,6 +83,8 @@ jobs:
|
|||||||
needs: [ BuildSdk ]
|
needs: [ BuildSdk ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: "Build Firmware"
|
- name: "Build Firmware"
|
||||||
uses: ./.github/actions/build-firmware
|
uses: ./.github/actions/build-firmware
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout repo"
|
- name: "Checkout repo"
|
||||||
uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: "Install Python Dependencies"
|
- name: "Install Python Dependencies"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ else ()
|
|||||||
add_compile_definitions(CONFIG_TT_DEVICE_NAME_SIMPLE="Simulator")
|
add_compile_definitions(CONFIG_TT_DEVICE_NAME_SIMPLE="Simulator")
|
||||||
add_compile_definitions(CONFIG_TT_LAUNCHER_APP_ID="Launcher")
|
add_compile_definitions(CONFIG_TT_LAUNCHER_APP_ID="Launcher")
|
||||||
add_compile_definitions(CONFIG_TT_AUTO_START_APP_ID="")
|
add_compile_definitions(CONFIG_TT_AUTO_START_APP_ID="")
|
||||||
|
add_compile_definitions(CONFIG_TT_USER_DATA_LOCATION_INTERNAL)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
project(Tactility)
|
project(Tactility)
|
||||||
|
|||||||
@@ -485,7 +485,7 @@
|
|||||||
<button class="btn btn-secondary btn-sm" onclick="goUp()" id="upBtn">Up</button>
|
<button class="btn btn-secondary btn-sm" onclick="goUp()" id="upBtn">Up</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-sep"></div>
|
<div class="toolbar-sep"></div>
|
||||||
<input type="text" id="pathInput" value="/data" onkeydown="if(event.key==='Enter')refreshFiles()">
|
<input type="text" id="pathInput" value="" onkeydown="if(event.key==='Enter')refreshFiles()">
|
||||||
<button class="btn btn-primary btn-sm" onclick="refreshFiles()">Go</button>
|
<button class="btn btn-primary btn-sm" onclick="refreshFiles()">Go</button>
|
||||||
<div class="toolbar-sep"></div>
|
<div class="toolbar-sep"></div>
|
||||||
<div class="toolbar-group">
|
<div class="toolbar-group">
|
||||||
@@ -739,7 +739,6 @@ function renderDashboard(data) {
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Quick Actions</h2>
|
<h2>Quick Actions</h2>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-primary" onclick="syncAssets(this)">Sync Assets</button>
|
|
||||||
${data.features_enabled?.screenshot ? '<button class="btn btn-secondary" onclick="captureScreenshot(this)">Screenshot</button>' : ''}
|
${data.features_enabled?.screenshot ? '<button class="btn btn-secondary" onclick="captureScreenshot(this)">Screenshot</button>' : ''}
|
||||||
<button class="btn btn-danger" onclick="rebootDevice(this)">Reboot</button>
|
<button class="btn btn-danger" onclick="rebootDevice(this)">Reboot</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Tactility Dashboard</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 20px auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.placeholder {
|
|
||||||
background: white;
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
text-align: center;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
.placeholder h2 {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
.placeholder p {
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #007bff;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Tactility Default Dashboard</h1>
|
|
||||||
|
|
||||||
<div class="placeholder">
|
|
||||||
<h2>Version 0 - Default Placeholder</h2>
|
|
||||||
<p>This is the default dashboard bundled with firmware.</p>
|
|
||||||
<p>To customize this interface:</p>
|
|
||||||
<ol style="text-align: left; display: inline-block;">
|
|
||||||
<li>Create your custom dashboard HTML/CSS/JS files</li>
|
|
||||||
<li>Add them to <code>/sdcard/tactility/webserver/</code></li>
|
|
||||||
<li>Create <code>version.json</code> with <code>{"version": 1}</code> or higher</li>
|
|
||||||
<li>Reboot or click "Sync Assets" on the <a href="/">Core Interface</a></li>
|
|
||||||
</ol>
|
|
||||||
<p><strong>Your custom assets will automatically replace this page!</strong></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="placeholder">
|
|
||||||
<p><a href="/">← Back to Core Interface</a></p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 0
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,8 @@ hardware.esptoolFlashFreq=120M
|
|||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
hardware.usbHostEnabled=true
|
hardware.usbHostEnabled=true
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=5"
|
display.size=5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=187
|
display.dpi=187
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.4"
|
display.size=2.4"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=167
|
display.dpi=167
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.4"
|
display.size=2.4"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=167
|
display.dpi=167
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=3.2"
|
display.size=3.2"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=125
|
display.dpi=125
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=3.5"
|
display.size=3.5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=165
|
display.dpi=165
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ hardware.spiRamMode=OCT
|
|||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=4"
|
display.size=4"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=170
|
display.dpi=170
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.spiRamSpeed=80M
|
|||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=4.3"
|
display.size=4.3"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=217
|
display.dpi=217
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=125
|
display.dpi=125
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=3.5"
|
display.size=3.5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=165
|
display.dpi=165
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=5"
|
display.size=5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=187
|
display.dpi=187
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=3.5"
|
display.size=3.5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=165
|
display.dpi=165
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=5.0"
|
display.size=5.0"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=187
|
display.dpi=187
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ general.name=ESP32
|
|||||||
apps.launcherAppId=Launcher
|
apps.launcherAppId=Launcher
|
||||||
|
|
||||||
hardware.target=ESP32
|
hardware.target=ESP32
|
||||||
hardware.flashSize=4MB
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ general.name=ESP32-C6
|
|||||||
apps.launcherAppId=Launcher
|
apps.launcherAppId=Launcher
|
||||||
|
|
||||||
hardware.target=ESP32C6
|
hardware.target=ESP32C6
|
||||||
hardware.flashSize=4MB
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ general.name=ESP32-P4
|
|||||||
apps.launcherAppId=Launcher
|
apps.launcherAppId=Launcher
|
||||||
|
|
||||||
hardware.target=ESP32P4
|
hardware.target=ESP32P4
|
||||||
hardware.flashSize=4MB
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ general.name=ESP32-S3
|
|||||||
apps.launcherAppId=Launcher
|
apps.launcherAppId=Launcher
|
||||||
|
|
||||||
hardware.target=ESP32S3
|
hardware.target=ESP32S3
|
||||||
hardware.flashSize=4MB
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ hardware.spiRamSpeed=200M
|
|||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=7"
|
display.size=7"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=187
|
display.dpi=187
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ hardware.target=ESP32
|
|||||||
hardware.flashSize=4MB
|
hardware.flashSize=4MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=3.5"
|
display.size=3.5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=165
|
display.dpi=165
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ hardware.spiRamSpeed=80M
|
|||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=5"
|
display.size=5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=187
|
display.dpi=187
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=0.96"
|
display.size=0.96"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=149
|
display.dpi=149
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=1.9"
|
display.size=1.9"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=191
|
display.dpi=191
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=1.14"
|
display.size=1.14"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=242
|
display.dpi=242
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=0.96"
|
display.size=0.96"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=186
|
display.dpi=186
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=125
|
display.dpi=125
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=40M
|
hardware.esptoolFlashFreq=40M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2.33"
|
display.size=2.33"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=227
|
display.dpi=227
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=1.14"
|
display.size=1.14"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
# TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced)
|
# TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=1.14"
|
display.size=1.14"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
# TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced)
|
# TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ hardware.spiRamMode=QUAD
|
|||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2"
|
display.size=2"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=200
|
display.dpi=200
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2"
|
display.size=2"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=200
|
display.dpi=200
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ hardware.esptoolFlashFreq=80M
|
|||||||
hardware.tinyUsb=true
|
hardware.tinyUsb=true
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=4.7"
|
display.size=4.7"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=235
|
display.dpi=235
|
||||||
@@ -22,5 +24,3 @@ lvgl.fontSize=24
|
|||||||
lvgl.theme=Mono
|
lvgl.theme=Mono
|
||||||
|
|
||||||
sdkconfig.CONFIG_EPD_DISPLAY_TYPE_ED047TC2=y
|
sdkconfig.CONFIG_EPD_DISPLAY_TYPE_ED047TC2=y
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=2"
|
display.size=2"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=200
|
display.dpi=200
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
|
||||||
|
|
||||||
idf_component_register(
|
|
||||||
SRCS ${SOURCE_FILES}
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility esp_lvgl_port esp_lcd AXP192 ST7789 ButtonControl
|
|
||||||
)
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#include "devices/Display.h"
|
|
||||||
#include "devices/Power.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
#include <ButtonControl.h>
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static bool initBoot() {
|
|
||||||
// CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity
|
|
||||||
// Setting output to high adds a bias of 3.3 V and suppresses over-voltage:
|
|
||||||
gpio::configure(0, gpio::Mode::Output, false, false);
|
|
||||||
gpio::setLevel(0, true);
|
|
||||||
|
|
||||||
return initAxp();
|
|
||||||
}
|
|
||||||
|
|
||||||
static DeviceVector createDevices() {
|
|
||||||
return {
|
|
||||||
getAxp192(),
|
|
||||||
ButtonControl::createTwoButtonControl(37, 39),
|
|
||||||
createDisplay()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#include "Display.h"
|
|
||||||
#include "Power.h"
|
|
||||||
|
|
||||||
#include <Tactility/Logger.h>
|
|
||||||
|
|
||||||
#include <St7789Display.h>
|
|
||||||
#include <bitset>
|
|
||||||
|
|
||||||
static const auto LOGGER = tt::Logger("StickCPlus");
|
|
||||||
|
|
||||||
static void setBacklightOn(bool on) {
|
|
||||||
const auto axp = getAxp192();
|
|
||||||
const auto* driver = axp->getAxp192();
|
|
||||||
uint8_t state;
|
|
||||||
if (axp192_read(driver, AXP192_DCDC13_LDO23_CONTROL, &state) != AXP192_OK) {
|
|
||||||
LOGGER.info("Failed to read LCD brightness state");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::bitset<8> new_state = state;
|
|
||||||
if (new_state[2] != on) {
|
|
||||||
new_state[2] = on;
|
|
||||||
const auto new_state_long = new_state.to_ulong();
|
|
||||||
axp192_write(driver, AXP192_DCDC13_LDO23_CONTROL, static_cast<uint8_t>(new_state_long)); // Display on/off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setBrightness(uint8_t brightness) {
|
|
||||||
const auto axp = getAxp192();
|
|
||||||
if (brightness)
|
|
||||||
{
|
|
||||||
brightness = (((brightness >> 1) + 8) / 13) + 5;
|
|
||||||
setBacklightOn(true);
|
|
||||||
axp192_write(axp->getAxp192(), AXP192_LDO23_VOLTAGE, brightness << 4); // Display brightness
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
setBacklightOn(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|
||||||
St7789Display::Configuration panel_configuration = {
|
|
||||||
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
|
|
||||||
.verticalResolution = LCD_VERTICAL_RESOLUTION,
|
|
||||||
.gapX = 52,
|
|
||||||
.gapY = 40,
|
|
||||||
.swapXY = false,
|
|
||||||
.mirrorX = false,
|
|
||||||
.mirrorY = false,
|
|
||||||
.invertColor = true,
|
|
||||||
.bufferSize = LCD_BUFFER_SIZE,
|
|
||||||
.touch = nullptr,
|
|
||||||
.backlightDutyFunction = setBrightness,
|
|
||||||
.resetPin = LCD_PIN_RESET,
|
|
||||||
.lvglSwapBytes = false
|
|
||||||
};
|
|
||||||
|
|
||||||
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
|
|
||||||
.spiHostDevice = LCD_SPI_HOST,
|
|
||||||
.csPin = LCD_PIN_CS,
|
|
||||||
.dcPin = LCD_PIN_DC,
|
|
||||||
.pixelClockFrequency = 40'000'000,
|
|
||||||
.transactionQueueDepth = 10
|
|
||||||
});
|
|
||||||
|
|
||||||
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <driver/spi_common.h>
|
|
||||||
|
|
||||||
constexpr auto LCD_SPI_HOST = SPI2_HOST;
|
|
||||||
constexpr auto LCD_PIN_CS = GPIO_NUM_5;
|
|
||||||
constexpr auto LCD_PIN_DC = GPIO_NUM_23;
|
|
||||||
constexpr auto LCD_PIN_RESET = GPIO_NUM_18;
|
|
||||||
constexpr auto LCD_HORIZONTAL_RESOLUTION = 135;
|
|
||||||
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
|
|
||||||
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 3;
|
|
||||||
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
|
|
||||||
constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8;
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#include <Axp192.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
|
|
||||||
static std::shared_ptr<Axp192> axp192 = nullptr;
|
|
||||||
|
|
||||||
std::shared_ptr<Axp192> createAxp192() {
|
|
||||||
assert(axp192 == nullptr);
|
|
||||||
auto configuration = std::make_unique<Axp192::Configuration>(device_find_by_name("i2c_internal"));
|
|
||||||
axp192 = std::make_shared<Axp192>(std::move(configuration));
|
|
||||||
return axp192;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Axp192> getAxp192() {
|
|
||||||
assert(axp192 != nullptr);
|
|
||||||
return axp192;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool initAxp() {
|
|
||||||
const auto axp = createAxp192();
|
|
||||||
return axp->init([](auto* driver) {
|
|
||||||
// Reference: https://github.com/pr3y/Bruce/blob/main/lib/utility/AXP192.cpp
|
|
||||||
axp192_ioctl(driver, AXP192_LDO23_VOLTAGE, 3300); // LCD backlight
|
|
||||||
axp192_ioctl(driver, AXP192_ADC_ENABLE_1, 0xff); // Set all ADC enabled
|
|
||||||
axp192_ioctl(driver, AXP192_CHARGE_CONTROL_1, 0xc0); // Battery charging at 4.2 V and 100 mA
|
|
||||||
axp192_ioctl(driver, AXP192_ADC_ENABLE_1, 0xff); // Enable battery, AC in, Vbus, APS ADC
|
|
||||||
uint8_t buffer;
|
|
||||||
axp192_read(driver, AXP192_DCDC13_LDO23_CONTROL, &buffer);
|
|
||||||
axp192_ioctl(driver, AXP192_DCDC13_LDO23_CONTROL, buffer | 0x4D); // Enable Ext, LDO2, LDO3, DCDC1
|
|
||||||
axp192_ioctl(driver, AXP192_PEK, 0x0C); // 128 ms power on, 4 s power off
|
|
||||||
axp192_ioctl(driver, AXP192_GPIO0_LDOIO0_VOLTAGE, 0xF0); // 3.3 V RTC voltage
|
|
||||||
axp192_ioctl(driver, AXP192_GPIO0_CONTROL, 0x02); // Set GPIO0 to LDO
|
|
||||||
axp192_ioctl(driver, AXP192_VBUS_IPSOUT_CHANNEL, 0x80); // Disable Vbus hold limit
|
|
||||||
axp192_ioctl(driver, AXP192_BATTERY_CHARGE_HIGH_TEMP, 0xFC); // Set temperature protection
|
|
||||||
axp192_ioctl(driver, AXP192_BATTERY_CHARGE_CONTROL, 0xA2); // Enable RTC BAT charge
|
|
||||||
axp192_ioctl(driver, AXP192_SHUTDOWN_BATTERY_CHGLED_CONTROL, 0x46); // Enable bat detection
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Axp192.h>
|
|
||||||
|
|
||||||
bool initAxp();
|
|
||||||
|
|
||||||
// Must call initAxp() first before this returns a non-nullptr response
|
|
||||||
std::shared_ptr<Axp192> getAxp192();
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#include <tactility/module.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module m5stack_stickc_plus_module = {
|
|
||||||
.name = "m5stack-stickc-plus",
|
|
||||||
.start = start,
|
|
||||||
.stop = stop,
|
|
||||||
.symbols = nullptr,
|
|
||||||
.internal = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
general.vendor=M5Stack
|
|
||||||
general.name=StickC Plus
|
|
||||||
general.incubating=true
|
|
||||||
|
|
||||||
apps.launcherAppId=Launcher
|
|
||||||
apps.autoStartAppId=ApWebServer
|
|
||||||
|
|
||||||
hardware.target=ESP32
|
|
||||||
hardware.flashSize=4MB
|
|
||||||
hardware.spiRam=false
|
|
||||||
hardware.esptoolFlashFreq=80M
|
|
||||||
|
|
||||||
display.size=1.14"
|
|
||||||
display.shape=rectangle
|
|
||||||
display.dpi=242
|
|
||||||
|
|
||||||
lvgl.colorDepth=16
|
|
||||||
lvgl.uiDensity=compact
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
- Platforms/platform-esp32
|
|
||||||
- Drivers/mpu6886-module
|
|
||||||
- Drivers/bm8563-module
|
|
||||||
dts: m5stack,stickc-plus.dts
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/dts-v1/;
|
|
||||||
|
|
||||||
#include <tactility/bindings/root.h>
|
|
||||||
#include <tactility/bindings/esp32_gpio.h>
|
|
||||||
#include <tactility/bindings/esp32_i2c.h>
|
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
|
||||||
#include <bindings/mpu6886.h>
|
|
||||||
#include <bindings/bm8563.h>
|
|
||||||
#include <tactility/bindings/display_placeholder.h>
|
|
||||||
|
|
||||||
/ {
|
|
||||||
compatible = "root";
|
|
||||||
model = "M5Stack StickC Plus";
|
|
||||||
|
|
||||||
gpio0 {
|
|
||||||
compatible = "espressif,esp32-gpio";
|
|
||||||
gpio-count = <40>;
|
|
||||||
};
|
|
||||||
|
|
||||||
i2c_internal {
|
|
||||||
compatible = "espressif,esp32-i2c";
|
|
||||||
port = <I2C_NUM_0>;
|
|
||||||
clock-frequency = <400000>;
|
|
||||||
pin-sda = <&gpio0 21 GPIO_FLAG_NONE>;
|
|
||||||
pin-scl = <&gpio0 22 GPIO_FLAG_NONE>;
|
|
||||||
|
|
||||||
mpu6886 {
|
|
||||||
compatible = "invensense,mpu6886";
|
|
||||||
reg = <0x68>;
|
|
||||||
};
|
|
||||||
|
|
||||||
bm8563 {
|
|
||||||
compatible = "belling,bm8563";
|
|
||||||
reg = <0x51>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
i2c_grove {
|
|
||||||
compatible = "espressif,esp32-i2c";
|
|
||||||
port = <I2C_NUM_1>;
|
|
||||||
clock-frequency = <400000>;
|
|
||||||
pin-sda = <&gpio0 32 GPIO_FLAG_NONE>;
|
|
||||||
pin-scl = <&gpio0 33 GPIO_FLAG_NONE>;
|
|
||||||
};
|
|
||||||
|
|
||||||
spi0 {
|
|
||||||
compatible = "espressif,esp32-spi";
|
|
||||||
host = <SPI2_HOST>;
|
|
||||||
cs-gpios = <&gpio0 5 GPIO_FLAG_NONE>;
|
|
||||||
pin-mosi = <&gpio0 15 GPIO_FLAG_NONE>;
|
|
||||||
pin-sclk = <&gpio0 13 GPIO_FLAG_NONE>;
|
|
||||||
|
|
||||||
display {
|
|
||||||
compatible = "display-placeholder";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
uart_grove: uart1 {
|
|
||||||
compatible = "espressif,esp32-uart";
|
|
||||||
status = "disabled";
|
|
||||||
port = <UART_NUM_1>;
|
|
||||||
pin-tx = <&gpio0 33 GPIO_FLAG_NONE>;
|
|
||||||
pin-rx = <&gpio0 32 GPIO_FLAG_NONE>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -12,6 +12,8 @@ hardware.spiRamMode=QUAD
|
|||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=1.14"
|
display.size=1.14"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=242
|
display.dpi=242
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.esptoolFlashFreq=80M
|
|||||||
hardware.tinyUsb=true
|
hardware.tinyUsb=true
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=1.14"
|
display.size=1.14"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=242
|
display.dpi=242
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ hardware.bluetooth=true
|
|||||||
hardware.usbHostEnabled=true
|
hardware.usbHostEnabled=true
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsb=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=5"
|
display.size=5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=294
|
display.dpi=294
|
||||||
|
|||||||
@@ -5,5 +5,7 @@ apps.launcherAppId=Launcher
|
|||||||
|
|
||||||
hardware.target=POSIX
|
hardware.target=POSIX
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
lvgl.colorDepth=16
|
lvgl.colorDepth=16
|
||||||
lvgl.fontSize=14
|
lvgl.fontSize=14
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ hardware.spiRamMode=OCT
|
|||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=3.5"
|
display.size=3.5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=165
|
display.dpi=165
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=1.14"
|
display.size=1.14"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=143
|
display.dpi=143
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=1.3"
|
display.size=1.3"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=261
|
display.dpi=261
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=1.28"
|
display.size=1.28"
|
||||||
display.shape=circle
|
display.shape=circle
|
||||||
display.dpi=265
|
display.dpi=265
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=1.47"
|
display.size=1.47"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=247
|
display.dpi=247
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ general.name=S3 Touch LCD 4.3"
|
|||||||
apps.launcherAppId=Launcher
|
apps.launcherAppId=Launcher
|
||||||
|
|
||||||
hardware.target=ESP32S3
|
hardware.target=ESP32S3
|
||||||
hardware.flashSize=4MB
|
# Some models have 16MB flash, but some have 8MB. We only support the latter to avoid making more builds.
|
||||||
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
@@ -12,6 +13,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=4.3"
|
display.size=4.3"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=217
|
display.dpi=217
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
display.size=3.5"
|
display.size=3.5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=165
|
display.dpi=165
|
||||||
|
|||||||
@@ -77,6 +77,18 @@ menu "Tactility App"
|
|||||||
config TT_TDECK_WORKAROUND
|
config TT_TDECK_WORKAROUND
|
||||||
bool "Temporary work-around until we fix the T-Deck keyboard and trackball settings"
|
bool "Temporary work-around until we fix the T-Deck keyboard and trackball settings"
|
||||||
default n
|
default n
|
||||||
|
choice TT_USER_DATA_LOCATION
|
||||||
|
prompt "User Data Location"
|
||||||
|
default TT_USER_DATA_LOCATION_INTERNAL
|
||||||
|
help
|
||||||
|
Where user data is stored on this device.
|
||||||
|
"Internal Flash" assumes /data always exists.
|
||||||
|
"SD Card" looks for the first mounted SD card at runtime and requires one to be present.
|
||||||
|
config TT_USER_DATA_LOCATION_SD
|
||||||
|
bool "SD Card"
|
||||||
|
config TT_USER_DATA_LOCATION_INTERNAL
|
||||||
|
bool "Internal Flash"
|
||||||
|
endchoice
|
||||||
config TT_SPLASH_DURATION
|
config TT_SPLASH_DURATION
|
||||||
int "Splash Duration (ms)"
|
int "Splash Duration (ms)"
|
||||||
default 1000
|
default 1000
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
|||||||
if (NOT DEFINED TACTILITY_SKIP_SPIFFS)
|
if (NOT DEFINED TACTILITY_SKIP_SPIFFS)
|
||||||
# Read-only
|
# Read-only
|
||||||
fatfs_create_rawflash_image(system "${CMAKE_CURRENT_SOURCE_DIR}/../Data/system" FLASH_IN_PROJECT PRESERVE_TIME)
|
fatfs_create_rawflash_image(system "${CMAKE_CURRENT_SOURCE_DIR}/../Data/system" FLASH_IN_PROJECT PRESERVE_TIME)
|
||||||
# Read-write
|
# Read-write (skipped when user data lives on the SD card instead of internal flash)
|
||||||
|
if (NOT CONFIG_TT_USER_DATA_LOCATION_SD)
|
||||||
fatfs_create_spiflash_image(data "${CMAKE_CURRENT_SOURCE_DIR}/../Data/data" FLASH_IN_PROJECT PRESERVE_TIME)
|
fatfs_create_spiflash_image(data "${CMAKE_CURRENT_SOURCE_DIR}/../Data/data" FLASH_IN_PROJECT PRESERVE_TIME)
|
||||||
endif ()
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ bool findFirstMountedSdCardPath(std::string& path);
|
|||||||
|
|
||||||
FileSystem* findSdcardFileSystem(bool mustBeMounted);
|
FileSystem* findSdcardFileSystem(bool mustBeMounted);
|
||||||
|
|
||||||
std::string getSystemRootPath();
|
std::string getUserDataRootPath();
|
||||||
|
|
||||||
|
std::string getUserDataPath();
|
||||||
|
|
||||||
std::string getTempPath();
|
std::string getTempPath();
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ std::string getAppInstallPath();
|
|||||||
|
|
||||||
std::string getAppInstallPath(const std::string& appId);
|
std::string getAppInstallPath(const std::string& appId);
|
||||||
|
|
||||||
std::string getUserPath();
|
std::string getUserHomePath();
|
||||||
|
|
||||||
std::string getAppUserPath(const std::string& appId);
|
std::string getAppUserPath(const std::string& appId);
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
namespace tt::app::chat {
|
namespace tt::app::chat {
|
||||||
|
|
||||||
constexpr auto* CHAT_SETTINGS_FILE = "/data/settings/chat.properties";
|
|
||||||
|
|
||||||
struct ChatSettingsData {
|
struct ChatSettingsData {
|
||||||
uint32_t senderId = 0; // Unique device ID (randomly generated on first launch)
|
uint32_t senderId = 0; // Unique device ID (randomly generated on first launch)
|
||||||
std::string nickname = "Device";
|
std::string nickname = "Device";
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ public:
|
|||||||
// Global accessor for controlling the WebServer service
|
// Global accessor for controlling the WebServer service
|
||||||
void setWebServerEnabled(bool enabled);
|
void setWebServerEnabled(bool enabled);
|
||||||
|
|
||||||
|
// Returns whether the HTTP server is actually running right now (not just the persisted setting)
|
||||||
|
bool isWebServerEnabled();
|
||||||
|
|
||||||
// Get the pubsub for subscribing to WebServer events
|
// Get the pubsub for subscribing to WebServer events
|
||||||
std::shared_ptr<PubSub<WebServerEvent>> getPubsub();
|
std::shared_ptr<PubSub<WebServerEvent>> getPubsub();
|
||||||
|
|
||||||
|
|||||||
+32
-17
@@ -4,6 +4,9 @@
|
|||||||
#include <Tactility/MountPoints.h>
|
#include <Tactility/MountPoints.h>
|
||||||
|
|
||||||
#include <format>
|
#include <format>
|
||||||
|
#include <tactility/check.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/sdcard.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
#include <tactility/filesystem/file_system.h>
|
||||||
|
|
||||||
namespace tt {
|
namespace tt {
|
||||||
@@ -20,14 +23,12 @@ bool findFirstMountedSdCardPath(std::string& path) {
|
|||||||
FileSystem* findSdcardFileSystem(bool mustBeMounted) {
|
FileSystem* findSdcardFileSystem(bool mustBeMounted) {
|
||||||
FileSystem* found = nullptr;
|
FileSystem* found = nullptr;
|
||||||
file_system_for_each(&found, [](auto* fs, void* context) {
|
file_system_for_each(&found, [](auto* fs, void* context) {
|
||||||
char path[128];
|
auto* owner = file_system_get_owner(fs);
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
if (owner == nullptr || device_get_type(owner) != &SDCARD_TYPE) {
|
||||||
// TODO: Find a better way to identify SD card paths
|
return true;
|
||||||
if (std::string(path).starts_with("/sdcard")) {
|
}
|
||||||
*static_cast<FileSystem**>(context) = fs;
|
*static_cast<FileSystem**>(context) = fs;
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
if (found && mustBeMounted && !file_system_is_mounted(found)) {
|
if (found && mustBeMounted && !file_system_is_mounted(found)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -35,24 +36,38 @@ FileSystem* findSdcardFileSystem(bool mustBeMounted) {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getSystemRootPath() {
|
std::string getUserDataRootPath() {
|
||||||
std::string root_path;
|
#ifdef CONFIG_TT_USER_DATA_LOCATION_INTERNAL
|
||||||
if (!findFirstMountedSdCardPath(root_path)) {
|
return file::MOUNT_POINT_DATA;
|
||||||
root_path = file::MOUNT_POINT_DATA;
|
#elif CONFIG_TT_USER_DATA_LOCATION_SD
|
||||||
}
|
auto* fs = findSdcardFileSystem(false);
|
||||||
return root_path;
|
check(fs);
|
||||||
|
char fs_path[32];
|
||||||
|
check(file_system_get_path(fs, fs_path, sizeof(fs_path)) == ERROR_NONE);
|
||||||
|
return std::string(fs_path);
|
||||||
|
#else
|
||||||
|
#error CONFIG_TT_USER_DATA_* not set or unsupported
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getUserDataPath() {
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
return getUserDataRootPath() + "/tactility";
|
||||||
|
#else
|
||||||
|
return "data";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getTempPath() {
|
std::string getTempPath() {
|
||||||
return getSystemRootPath() + "/tmp";
|
return getUserDataPath() + "/tmp";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getAppInstallPath() {
|
std::string getAppInstallPath() {
|
||||||
return getSystemRootPath() + "/app";
|
return getUserDataPath() + "/app";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getUserPath() {
|
std::string getUserHomePath() {
|
||||||
return getSystemRootPath() + "/user";
|
return getUserDataPath() + "/user";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getAppInstallPath(const std::string& appId) {
|
std::string getAppInstallPath(const std::string& appId) {
|
||||||
@@ -62,7 +77,7 @@ std::string getAppInstallPath(const std::string& appId) {
|
|||||||
|
|
||||||
std::string getAppUserPath(const std::string& appId) {
|
std::string getAppUserPath(const std::string& appId) {
|
||||||
assert(app::isValidId(appId));
|
assert(app::isValidId(appId));
|
||||||
return std::format("{}/app/{}", getUserPath(), appId);
|
return std::format("{}/app/{}", getUserHomePath(), appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,9 @@
|
|||||||
#include <Tactility/InitEsp.h>
|
#include <Tactility/InitEsp.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "Tactility/Paths.h"
|
||||||
|
|
||||||
|
|
||||||
#include <Tactility/bluetooth/Bluetooth.h>
|
#include <Tactility/bluetooth/Bluetooth.h>
|
||||||
|
|
||||||
namespace tt {
|
namespace tt {
|
||||||
@@ -242,7 +245,7 @@ static void registerInstalledAppsFromFileSystems() {
|
|||||||
if (!file_system_is_mounted(fs)) return true;
|
if (!file_system_is_mounted(fs)) return true;
|
||||||
char path[128];
|
char path[128];
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
||||||
const auto app_path = std::format("{}/app", path);
|
const auto app_path = std::format("{}/tactility/app", path);
|
||||||
if (!app_path.starts_with(file::MOUNT_POINT_SYSTEM) && file::isDirectory(app_path)) {
|
if (!app_path.starts_with(file::MOUNT_POINT_SYSTEM) && file::isDirectory(app_path)) {
|
||||||
LOGGER.info("Registering apps from {}", app_path);
|
LOGGER.info("Registering apps from {}", app_path);
|
||||||
registerInstalledApps(app_path);
|
registerInstalledApps(app_path);
|
||||||
@@ -284,18 +287,21 @@ static void registerAndStartPrimaryServices() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void createTempDirectory(const std::string& rootPath) {
|
void createTempDirectory() {
|
||||||
auto temp_path = std::format("{}/tmp", rootPath);
|
auto data_path = getUserDataPath();
|
||||||
|
auto temp_path = std::format("{}/tmp", data_path);
|
||||||
if (!file::isDirectory(temp_path)) {
|
if (!file::isDirectory(temp_path)) {
|
||||||
auto lock = file::getLock(rootPath)->asScopedLock();
|
auto lock = file::getLock(data_path)->asScopedLock();
|
||||||
if (lock.lock(1000 / portTICK_PERIOD_MS)) {
|
if (lock.lock(1000 / portTICK_PERIOD_MS)) {
|
||||||
if (mkdir(temp_path.c_str(), 0777) == 0) {
|
if (!file::findOrCreateParentDirectory(temp_path, 0777)) {
|
||||||
|
LOGGER.error("Failed to create {}", data_path);
|
||||||
|
} else if (mkdir(temp_path.c_str(), 0777) == 0) {
|
||||||
LOGGER.info("Created {}", temp_path);
|
LOGGER.info("Created {}", temp_path);
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("Failed to create {}", temp_path);
|
LOGGER.error("Failed to create {}", temp_path);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, rootPath);
|
LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, data_path);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("Found existing {}", temp_path);
|
LOGGER.info("Found existing {}", temp_path);
|
||||||
@@ -303,14 +309,7 @@ void createTempDirectory(const std::string& rootPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void prepareFileSystems() {
|
void prepareFileSystems() {
|
||||||
file_system_for_each(nullptr, [](auto* fs, void* context) {
|
createTempDirectory();
|
||||||
if (!file_system_is_mounted(fs)) return true;
|
|
||||||
char path[128];
|
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
|
||||||
if (std::string(path) == file::MOUNT_POINT_SYSTEM) return true;
|
|
||||||
createTempDirectory(path);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerApps() {
|
void registerApps() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <Tactility/TactilityCore.h>
|
#include <Tactility/TactilityCore.h>
|
||||||
#include <Tactility/TactilityPrivate.h>
|
#include <Tactility/TactilityPrivate.h>
|
||||||
|
#include <Tactility/app/alertdialog/AlertDialog.h>
|
||||||
#include <Tactility/app/AppContext.h>
|
#include <Tactility/app/AppContext.h>
|
||||||
#include <Tactility/app/AppPaths.h>
|
#include <Tactility/app/AppPaths.h>
|
||||||
#include <Tactility/CpuAffinity.h>
|
#include <Tactility/CpuAffinity.h>
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#include <Tactility/kernel/SystemEvents.h>
|
#include <Tactility/kernel/SystemEvents.h>
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/lvgl/Style.h>
|
#include <Tactility/lvgl/Style.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
#include <Tactility/service/loader/Loader.h>
|
#include <Tactility/service/loader/Loader.h>
|
||||||
#include <Tactility/settings/BootSettings.h>
|
#include <Tactility/settings/BootSettings.h>
|
||||||
#include <Tactility/settings/DisplaySettings.h>
|
#include <Tactility/settings/DisplaySettings.h>
|
||||||
@@ -44,6 +46,10 @@ class BootApp : public App {
|
|||||||
// onShow() reads this instead of the live flag to avoid a race between the two.
|
// onShow() reads this instead of the live flag to avoid a race between the two.
|
||||||
static std::atomic<bool> isUsbBootSplash;
|
static std::atomic<bool> isUsbBootSplash;
|
||||||
|
|
||||||
|
// Set by bootThreadCallback() when CONFIG_TT_USER_DATA_LOCATION_SD is defined but no SD card is mounted.
|
||||||
|
// onShow() reads this to show an error instead of the normal splash, and boot halts instead of starting the launcher.
|
||||||
|
static std::atomic<bool> sdCardMissing;
|
||||||
|
|
||||||
Thread thread = Thread(
|
Thread thread = Thread(
|
||||||
"boot",
|
"boot",
|
||||||
5120,
|
5120,
|
||||||
@@ -122,11 +128,20 @@ class BootApp : public App {
|
|||||||
setupDisplay(); // Set backlight
|
setupDisplay(); // Set backlight
|
||||||
prepareFileSystems();
|
prepareFileSystems();
|
||||||
|
|
||||||
|
#ifdef CONFIG_TT_USER_DATA_LOCATION_SD
|
||||||
|
std::string sd_path;
|
||||||
|
if (!findFirstMountedSdCardPath(sd_path)) {
|
||||||
|
LOGGER.error("SD card not found");
|
||||||
|
sdCardMissing = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!setupUsbBootMode()) {
|
if (!setupUsbBootMode()) {
|
||||||
LOGGER.info("initFromBootApp");
|
LOGGER.info("initFromBootApp");
|
||||||
registerApps();
|
registerApps();
|
||||||
waitForMinimalSplashDuration(start_time);
|
waitForMinimalSplashDuration(start_time);
|
||||||
stop(manifest.appId);
|
// When SD card is missing, wait for dialog result
|
||||||
|
if (!sdCardMissing) stop(manifest.appId);
|
||||||
startNextApp();
|
startNextApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +177,11 @@ class BootApp : public App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void startNextApp() {
|
static void startNextApp() {
|
||||||
|
if (sdCardMissing) {
|
||||||
|
alertdialog::start("Error", "SD card not found.\nPlease insert one and reboot.", std::vector<const char*> { "Reboot" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
if (esp_reset_reason() == ESP_RST_PANIC) {
|
if (esp_reset_reason() == ESP_RST_PANIC) {
|
||||||
crashdiagnostics::start();
|
crashdiagnostics::start();
|
||||||
@@ -195,6 +215,12 @@ public:
|
|||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onResult(AppContext& /*app*/, LaunchId /*launchId*/, Result /*result*/, std::unique_ptr<Bundle> /*bundle*/) override {
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
esp_restart();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||||
lvgl::obj_set_style_bg_blacken(parent);
|
lvgl::obj_set_style_bg_blacken(parent);
|
||||||
lv_obj_set_style_border_width(parent, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_border_width(parent, 0, LV_STATE_DEFAULT);
|
||||||
@@ -232,6 +258,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::atomic<bool> BootApp::isUsbBootSplash = false;
|
std::atomic<bool> BootApp::isUsbBootSplash = false;
|
||||||
|
std::atomic<bool> BootApp::sdCardMissing = false;
|
||||||
|
|
||||||
extern const AppManifest manifest = {
|
extern const AppManifest manifest = {
|
||||||
.appId = "Boot",
|
.appId = "Boot",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <Tactility/crypt/Crypt.h>
|
#include <Tactility/crypt/Crypt.h>
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
|
|
||||||
#include <esp_random.h>
|
#include <esp_random.h>
|
||||||
|
|
||||||
@@ -24,6 +25,10 @@ namespace tt::app::chat {
|
|||||||
|
|
||||||
static const auto LOGGER = Logger("ChatSettings");
|
static const auto LOGGER = Logger("ChatSettings");
|
||||||
|
|
||||||
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/chat.properties";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* KEY_SENDER_ID = "senderId";
|
constexpr auto* KEY_SENDER_ID = "senderId";
|
||||||
constexpr auto* KEY_NICKNAME = "nickname";
|
constexpr auto* KEY_NICKNAME = "nickname";
|
||||||
constexpr auto* KEY_ENCRYPTION_KEY = "encryptionKey";
|
constexpr auto* KEY_ENCRYPTION_KEY = "encryptionKey";
|
||||||
@@ -120,7 +125,7 @@ ChatSettingsData loadSettings() {
|
|||||||
ChatSettingsData settings = getDefaultSettings();
|
ChatSettingsData settings = getDefaultSettings();
|
||||||
|
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(CHAT_SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||||
settings.senderId = generateSenderId();
|
settings.senderId = generateSenderId();
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
@@ -171,11 +176,11 @@ bool saveSettings(const ChatSettingsData& settings) {
|
|||||||
map[KEY_ENCRYPTION_KEY] = "";
|
map[KEY_ENCRYPTION_KEY] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return file::savePropertiesFile(CHAT_SETTINGS_FILE, map);
|
return file::savePropertiesFile(getSettingsFilePath(), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool settingsFileExists() {
|
bool settingsFileExists() {
|
||||||
return access(CHAT_SETTINGS_FILE, F_OK) == 0;
|
return access(getSettingsFilePath().c_str(), F_OK) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace tt::app::chat
|
} // namespace tt::app::chat
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class WebServerSettingsApp final : public App {
|
|||||||
settings::webserver::WebServerSettings originalSettings;
|
settings::webserver::WebServerSettings originalSettings;
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
bool wifiSettingsChanged = false;
|
bool wifiSettingsChanged = false;
|
||||||
bool webServerEnabledChanged = false;
|
|
||||||
lv_obj_t* dropdownWifiMode = nullptr;
|
lv_obj_t* dropdownWifiMode = nullptr;
|
||||||
lv_obj_t* textAreaApPassword = nullptr;
|
lv_obj_t* textAreaApPassword = nullptr;
|
||||||
lv_obj_t* switchApOpenNetwork = nullptr;
|
lv_obj_t* switchApOpenNetwork = nullptr;
|
||||||
@@ -61,11 +60,19 @@ class WebServerSettingsApp final : public App {
|
|||||||
getMainDispatcher().dispatch([app, enabled] {
|
getMainDispatcher().dispatch([app, enabled] {
|
||||||
app->wsSettings.webServerEnabled = enabled;
|
app->wsSettings.webServerEnabled = enabled;
|
||||||
app->updated = true;
|
app->updated = true;
|
||||||
app->webServerEnabledChanged = true;
|
|
||||||
if (lvgl::lock(100)) {
|
if (lvgl::lock(100)) {
|
||||||
app->updateUrlDisplay();
|
app->updateUrlDisplay();
|
||||||
lvgl::unlock();
|
lvgl::unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply immediately instead of waiting for app exit
|
||||||
|
const auto copy = app->wsSettings;
|
||||||
|
if (!settings::webserver::save(copy)) {
|
||||||
|
LOGGER.warn("Failed to persist WebServer settings; changes may be lost on reboot");
|
||||||
|
}
|
||||||
|
service::webserver::getPubsub()->publish(service::webserver::WebServerEvent::WebServerSettingsChanged);
|
||||||
|
LOGGER.info("WebServer {}", enabled ? "enabling..." : "disabling...");
|
||||||
|
service::webserver::setWebServerEnabled(enabled);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,30 +138,6 @@ class WebServerSettingsApp final : public App {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onSyncAssets(lv_event_t* e) {
|
|
||||||
auto* app = static_cast<WebServerSettingsApp*>(lv_event_get_user_data(e));
|
|
||||||
auto* btn = static_cast<lv_obj_t*>(lv_event_get_target_obj(e));
|
|
||||||
lv_obj_add_state(btn, LV_STATE_DISABLED);
|
|
||||||
LOGGER.info("Manual asset sync triggered");
|
|
||||||
|
|
||||||
getMainDispatcher().dispatch([app, btn]{
|
|
||||||
bool success = service::webserver::syncAssets();
|
|
||||||
if (success) {
|
|
||||||
LOGGER.info("Asset sync completed successfully");
|
|
||||||
} else {
|
|
||||||
LOGGER.error("Asset sync failed");
|
|
||||||
}
|
|
||||||
// Only re-enable if button still exists (user hasn't navigated away)
|
|
||||||
// Must acquire LVGL lock since we're not in an LVGL event callback context
|
|
||||||
if (lvgl::lock(1000)) {
|
|
||||||
if (lv_obj_is_valid(btn)) {
|
|
||||||
lv_obj_remove_state(btn, LV_STATE_DISABLED);
|
|
||||||
}
|
|
||||||
lvgl::unlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateUrlDisplay() {
|
void updateUrlDisplay() {
|
||||||
if (!labelUrlValue) return;
|
if (!labelUrlValue) return;
|
||||||
|
|
||||||
@@ -197,6 +180,8 @@ class WebServerSettingsApp final : public App {
|
|||||||
public:
|
public:
|
||||||
void onCreate(AppContext& app) override {
|
void onCreate(AppContext& app) override {
|
||||||
wsSettings = settings::webserver::loadOrGetDefault();
|
wsSettings = settings::webserver::loadOrGetDefault();
|
||||||
|
// Reflect the server's actual running state, in case it differs from the persisted setting
|
||||||
|
wsSettings.webServerEnabled = service::webserver::isWebServerEnabled();
|
||||||
originalSettings = wsSettings;
|
originalSettings = wsSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,32 +326,6 @@ public:
|
|||||||
|
|
||||||
updateUrlDisplay();
|
updateUrlDisplay();
|
||||||
|
|
||||||
// Sync Assets button
|
|
||||||
auto* sync_wrapper = lv_obj_create(main_wrapper);
|
|
||||||
lv_obj_set_size(sync_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
|
||||||
lv_obj_set_style_pad_all(sync_wrapper, 10, LV_STATE_DEFAULT);
|
|
||||||
lv_obj_set_style_border_width(sync_wrapper, 1, LV_STATE_DEFAULT);
|
|
||||||
lv_obj_set_flex_flow(sync_wrapper, LV_FLEX_FLOW_COLUMN);
|
|
||||||
lv_obj_set_style_flex_cross_place(sync_wrapper, LV_FLEX_ALIGN_START, 0);
|
|
||||||
|
|
||||||
auto* sync_label = lv_label_create(sync_wrapper);
|
|
||||||
lv_label_set_text(sync_label, "Asset Synchronization");
|
|
||||||
|
|
||||||
auto* sync_info = lv_label_create(sync_wrapper);
|
|
||||||
lv_label_set_long_mode(sync_info, LV_LABEL_LONG_WRAP);
|
|
||||||
lv_obj_set_width(sync_info, LV_PCT(95));
|
|
||||||
if (lv_display_get_color_format(lv_obj_get_display(parent)) != LV_COLOR_FORMAT_L8) {
|
|
||||||
lv_obj_set_style_text_color(sync_info, lv_palette_main(LV_PALETTE_GREY), 0);
|
|
||||||
}
|
|
||||||
lv_label_set_text(sync_info, "Sync web assets between Data partition and SD card backup");
|
|
||||||
|
|
||||||
auto* sync_button = lv_btn_create(sync_wrapper);
|
|
||||||
lv_obj_set_width(sync_button, LV_SIZE_CONTENT);
|
|
||||||
auto* sync_button_label = lv_label_create(sync_button);
|
|
||||||
lv_label_set_text(sync_button_label, "Sync Assets Now");
|
|
||||||
lv_obj_center(sync_button_label);
|
|
||||||
lv_obj_add_event_cb(sync_button, onSyncAssets, LV_EVENT_CLICKED, this);
|
|
||||||
|
|
||||||
// Info text
|
// Info text
|
||||||
auto* info_label = lv_label_create(main_wrapper);
|
auto* info_label = lv_label_create(main_wrapper);
|
||||||
lv_label_set_long_mode(info_label, LV_LABEL_LONG_WRAP);
|
lv_label_set_long_mode(info_label, LV_LABEL_LONG_WRAP);
|
||||||
@@ -394,11 +353,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save to flash only (settings sync at boot handles SD restore)
|
// Save to flash only (settings sync at boot handles SD restore)
|
||||||
|
// Note: the enable/disable toggle already saved and applied itself immediately
|
||||||
const auto copy = wsSettings;
|
const auto copy = wsSettings;
|
||||||
const bool wifiChanged = wifiSettingsChanged;
|
const bool wifiChanged = wifiSettingsChanged;
|
||||||
const bool webServerChanged = webServerEnabledChanged;
|
|
||||||
|
|
||||||
getMainDispatcher().dispatch([copy, wifiChanged, webServerChanged]{
|
getMainDispatcher().dispatch([copy, wifiChanged]{
|
||||||
// Save to flash (fast, low memory pressure)
|
// Save to flash (fast, low memory pressure)
|
||||||
if (!settings::webserver::save(copy)) {
|
if (!settings::webserver::save(copy)) {
|
||||||
LOGGER.warn("Failed to persist WebServer settings; changes may be lost on reboot");
|
LOGGER.warn("Failed to persist WebServer settings; changes may be lost on reboot");
|
||||||
@@ -411,12 +370,6 @@ public:
|
|||||||
if (wifiChanged) {
|
if (wifiChanged) {
|
||||||
LOGGER.info("WiFi mode changed to {}", copy.wifiMode == settings::webserver::WiFiMode::AccessPoint ? "AP" : "Station");
|
LOGGER.info("WiFi mode changed to {}", copy.wifiMode == settings::webserver::WiFiMode::AccessPoint ? "AP" : "Station");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Control WebServer service immediately
|
|
||||||
if (webServerChanged) {
|
|
||||||
LOGGER.info("WebServer {}", copy.webServerEnabled ? "enabling..." : "disabling...");
|
|
||||||
service::webserver::setWebServerEnabled(copy.webServerEnabled);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,16 @@
|
|||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/Mutex.h>
|
#include <Tactility/Mutex.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
|
|
||||||
namespace tt::bluetooth::settings {
|
namespace tt::bluetooth::settings {
|
||||||
|
|
||||||
static const auto LOGGER = Logger("BluetoothSettings");
|
static const auto LOGGER = Logger("BluetoothSettings");
|
||||||
|
|
||||||
// Use the same path as the old service so existing settings survive migration.
|
static std::string getSettingsPath() {
|
||||||
constexpr auto* SETTINGS_PATH = "/data/service/bluetooth/settings.properties";
|
return getUserDataPath() + "/settings/bluetooth.settings";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
constexpr auto* KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
||||||
constexpr auto* KEY_SPP_AUTO_START = "sppAutoStart";
|
constexpr auto* KEY_SPP_AUTO_START = "sppAutoStart";
|
||||||
constexpr auto* KEY_MIDI_AUTO_START = "midiAutoStart";
|
constexpr auto* KEY_MIDI_AUTO_START = "midiAutoStart";
|
||||||
@@ -27,7 +30,7 @@ static bool cached_valid = false;
|
|||||||
|
|
||||||
static bool load(BluetoothSettings& out) {
|
static bool load(BluetoothSettings& out) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_PATH, map)) {
|
if (!file::loadPropertiesFile(getSettingsPath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto it = map.find(KEY_ENABLE_ON_BOOT);
|
auto it = map.find(KEY_ENABLE_ON_BOOT);
|
||||||
@@ -44,11 +47,11 @@ static bool load(BluetoothSettings& out) {
|
|||||||
|
|
||||||
static bool save(const BluetoothSettings& s) {
|
static bool save(const BluetoothSettings& s) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
file::loadPropertiesFile(SETTINGS_PATH, map); // ignore failure — may not exist yet
|
file::loadPropertiesFile(getSettingsPath(), map); // ignore failure — may not exist yet
|
||||||
map[KEY_ENABLE_ON_BOOT] = s.enableOnBoot ? "true" : "false";
|
map[KEY_ENABLE_ON_BOOT] = s.enableOnBoot ? "true" : "false";
|
||||||
map[KEY_SPP_AUTO_START] = s.sppAutoStart ? "true" : "false";
|
map[KEY_SPP_AUTO_START] = s.sppAutoStart ? "true" : "false";
|
||||||
map[KEY_MIDI_AUTO_START] = s.midiAutoStart ? "true" : "false";
|
map[KEY_MIDI_AUTO_START] = s.midiAutoStart ? "true" : "false";
|
||||||
return file::savePropertiesFile(SETTINGS_PATH, map);
|
return file::savePropertiesFile(getSettingsPath(), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
static BluetoothSettings getCachedOrLoad() {
|
static BluetoothSettings getCachedOrLoad() {
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
#include <Tactility/service/ServicePaths.h>
|
#include <Tactility/service/ServicePaths.h>
|
||||||
|
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
#include <Tactility/MountPoints.h>
|
#include <Tactility/Paths.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <format>
|
#include <format>
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
|
||||||
constexpr auto PARTITION_PREFIX = std::string("/");
|
|
||||||
#else
|
|
||||||
constexpr auto PARTITION_PREFIX = std::string("");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
std::string ServicePaths::getUserDataDirectory() const {
|
std::string ServicePaths::getUserDataDirectory() const {
|
||||||
return std::format("{}{}/service/{}", PARTITION_PREFIX, file::DATA_PARTITION_NAME, manifest->id);
|
return std::format("{}/service/{}", tt::getUserDataPath(), manifest->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
|
std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
|
||||||
@@ -24,7 +18,7 @@ std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string ServicePaths::getAssetsDirectory() const {
|
std::string ServicePaths::getAssetsDirectory() const {
|
||||||
return std::format("{}{}/service/{}/assets", PARTITION_PREFIX, file::SYSTEM_PARTITION_NAME, manifest->id);
|
return std::format("{}/service/{}/assets", tt::getUserDataPath(), manifest->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ServicePaths::getAssetsPath(const std::string& childPath) const {
|
std::string ServicePaths::getAssetsPath(const std::string& childPath) const {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
#include <Tactility/service/development/DevelopmentSettings.h>
|
#include <Tactility/service/development/DevelopmentSettings.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -9,7 +10,10 @@ namespace tt::service::development {
|
|||||||
|
|
||||||
static const auto LOGGER = Logger("DevSettings");
|
static const auto LOGGER = Logger("DevSettings");
|
||||||
|
|
||||||
constexpr auto* SETTINGS_FILE = "/data/settings/development.properties";
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/development.properties";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* SETTINGS_KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
constexpr auto* SETTINGS_KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
||||||
|
|
||||||
struct DevelopmentSettings {
|
struct DevelopmentSettings {
|
||||||
@@ -18,7 +22,7 @@ struct DevelopmentSettings {
|
|||||||
|
|
||||||
static bool load(DevelopmentSettings& settings) {
|
static bool load(DevelopmentSettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,13 +38,13 @@ static bool load(DevelopmentSettings& settings) {
|
|||||||
static bool save(const DevelopmentSettings& settings) {
|
static bool save(const DevelopmentSettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
|
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
|
||||||
return file::savePropertiesFile(SETTINGS_FILE, map);
|
return file::savePropertiesFile(getSettingsFilePath(), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEnableOnBoot(bool enable) {
|
void setEnableOnBoot(bool enable) {
|
||||||
DevelopmentSettings properties { .enableOnBoot = enable };
|
DevelopmentSettings properties { .enableOnBoot = enable };
|
||||||
if (!save(properties)) {
|
if (!save(properties)) {
|
||||||
LOGGER.error("Failed to save {}", SETTINGS_FILE);
|
LOGGER.error("Failed to save {}", getSettingsFilePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
namespace tt::service::webserver {
|
namespace tt::service::webserver {
|
||||||
|
|
||||||
static const auto LOGGER = tt::Logger("AssetVersion");
|
static const auto LOGGER = tt::Logger("AssetVersion");
|
||||||
constexpr auto* DATA_VERSION_FILE = "/data/webserver/version.json";
|
constexpr auto* DATA_VERSION_FILE = "/system/app/WebServer/version.json";
|
||||||
constexpr auto* SD_VERSION_FILE = "/sdcard/tactility/webserver/version.json";
|
constexpr auto* SD_VERSION_FILE = "/sdcard/tactility/webserver/version.json";
|
||||||
constexpr auto* DATA_ASSETS_DIR = "/data/webserver";
|
constexpr auto* DATA_ASSETS_DIR = "/system/app/WebServer";
|
||||||
constexpr auto* SD_ASSETS_DIR = "/sdcard/tactility/webserver";
|
constexpr auto* SD_ASSETS_DIR = "/sdcard/tactility/webserver";
|
||||||
|
|
||||||
static bool loadVersionFromFile(const char* path, AssetVersion& version) {
|
static bool loadVersionFromFile(const char* path, AssetVersion& version) {
|
||||||
@@ -349,17 +349,6 @@ bool syncAssets() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST-FLASH RECOVERY: Data empty but SD card exists
|
|
||||||
if (!dataExists) {
|
|
||||||
LOGGER.info("Data partition empty - copying from SD card (recovery mode)");
|
|
||||||
if (!copyDirectory(SD_ASSETS_DIR, DATA_ASSETS_DIR)) {
|
|
||||||
LOGGER.error("Failed to copy assets from SD card to Data");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOGGER.info("Recovery complete - assets restored from SD card");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NORMAL OPERATION: Both exist - compare versions
|
// NORMAL OPERATION: Both exist - compare versions
|
||||||
AssetVersion dataVersion, sdVersion;
|
AssetVersion dataVersion, sdVersion;
|
||||||
bool hasDataVer = loadDataVersion(dataVersion);
|
bool hasDataVer = loadDataVersion(dataVersion);
|
||||||
|
|||||||
@@ -207,11 +207,6 @@ bool WebServerService::onStart(ServiceContext& service) {
|
|||||||
statusbarIconId = lvgl::statusbar_icon_add();
|
statusbarIconId = lvgl::statusbar_icon_add();
|
||||||
lvgl::statusbar_icon_set_visibility(statusbarIconId, false);
|
lvgl::statusbar_icon_set_visibility(statusbarIconId, false);
|
||||||
|
|
||||||
// Run asset synchronization on startup
|
|
||||||
if (!syncAssets()) {
|
|
||||||
LOGGER.warn("Asset sync failed, but continuing with available assets");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load and cache settings once at boot
|
// Load and cache settings once at boot
|
||||||
bool serverEnabled;
|
bool serverEnabled;
|
||||||
{
|
{
|
||||||
@@ -619,7 +614,7 @@ static bool isAllowedBasePath(const std::string& path, bool allowRoot = false) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (allowRoot && path == "/") return true;
|
if (allowRoot && path == "/") return true;
|
||||||
return path == "/data" || path.starts_with("/data/") || path == "/sdcard" || path.starts_with("/sdcard/");
|
return path.starts_with("/data") || path.starts_with("/system/app/WebServer") || path.starts_with("/sdcard");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize client-supplied path: URL-decode, trim quotes/control chars, ensure leading slash, collapse duplicate slashes
|
// Normalize client-supplied path: URL-decode, trim quotes/control chars, ensure leading slash, collapse duplicate slashes
|
||||||
@@ -990,7 +985,6 @@ esp_err_t WebServerService::handleAdminPost(httpd_req_t* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const char* uri = request->uri;
|
const char* uri = request->uri;
|
||||||
if (strncmp(uri, "/admin/sync", 11) == 0) return handleSync(request);
|
|
||||||
if (strncmp(uri, "/admin/reboot", 13) == 0) return handleReboot(request);
|
if (strncmp(uri, "/admin/reboot", 13) == 0) return handleReboot(request);
|
||||||
LOGGER.info("POST {} - not found in admin dispatcher", uri);
|
LOGGER.info("POST {} - not found in admin dispatcher", uri);
|
||||||
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
|
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
|
||||||
@@ -1460,10 +1454,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
|
|||||||
|
|
||||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||||
// Determine save location: prefer SD card root if mounted, otherwise /data
|
// Determine save location: prefer SD card root if mounted, otherwise /data
|
||||||
std::string save_path;
|
std::string save_path = getUserDataRootPath();
|
||||||
if (!findFirstMountedSdCardPath(save_path)) {
|
|
||||||
save_path = file::MOUNT_POINT_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find next available filename with incrementing number
|
// Find next available filename with incrementing number
|
||||||
std::string screenshot_path;
|
std::string screenshot_path;
|
||||||
@@ -1683,25 +1674,9 @@ esp_err_t WebServerService::handleFsRename(httpd_req_t* request) {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
esp_err_t WebServerService::handleSync(httpd_req_t* request) {
|
|
||||||
|
|
||||||
LOGGER.info("POST /sync");
|
|
||||||
|
|
||||||
bool success = syncAssets();
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
httpd_resp_sendstr(request, "Assets synchronized successfully");
|
|
||||||
} else {
|
|
||||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Asset sync failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return success ? ESP_OK : ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t WebServerService::handleReboot(httpd_req_t* request) {
|
esp_err_t WebServerService::handleReboot(httpd_req_t* request) {
|
||||||
|
|
||||||
LOGGER.info("POST /reboot");
|
LOGGER.info("POST /reboot");
|
||||||
|
|
||||||
httpd_resp_sendstr(request, "Rebooting...");
|
httpd_resp_sendstr(request, "Rebooting...");
|
||||||
|
|
||||||
// Reboot after a short delay to allow response to be sent
|
// Reboot after a short delay to allow response to be sent
|
||||||
@@ -1724,7 +1699,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
|
|||||||
|
|
||||||
// Special case: serve favicon from system assets
|
// Special case: serve favicon from system assets
|
||||||
if (strcmp(uri, "/favicon.ico") == 0) {
|
if (strcmp(uri, "/favicon.ico") == 0) {
|
||||||
const char* faviconPath = "/data/system/spinner.png";
|
const char* faviconPath = "/system/spinner.png";
|
||||||
if (file::isFile(faviconPath)) {
|
if (file::isFile(faviconPath)) {
|
||||||
httpd_resp_set_type(request, "image/png");
|
httpd_resp_set_type(request, "image/png");
|
||||||
httpd_resp_set_hdr(request, "Cache-Control", "public, max-age=86400");
|
httpd_resp_set_hdr(request, "Cache-Control", "public, max-age=86400");
|
||||||
@@ -1767,11 +1742,9 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
|
|||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string dataPath = std::string("/data/webserver") + requestedPath;
|
std::string dataPath = std::string("/system/app/WebServer") + requestedPath;
|
||||||
|
|
||||||
if (requestedPath == "/dashboard.html" && !file::isFile(dataPath.c_str())) {
|
if (requestedPath == "/dashboard.html" && !file::isFile(dataPath.c_str())) {
|
||||||
// Dashboard doesn't exist, try default.html
|
|
||||||
dataPath = "/data/webserver/default.html";
|
|
||||||
LOGGER.info("dashboard.html not found, serving default.html");
|
LOGGER.info("dashboard.html not found, serving default.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1854,6 +1827,11 @@ void setWebServerEnabled(bool enabled) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isWebServerEnabled() {
|
||||||
|
WebServerService* instance = g_webServerInstance.load();
|
||||||
|
return instance != nullptr && instance->isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#endif // ESP_PLATFORM
|
#endif // ESP_PLATFORM
|
||||||
|
|||||||
@@ -181,6 +181,10 @@ bool save(const WifiApSettings& apSettings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto file_path = getApPropertiesFilePath(service_context->getPaths(), apSettings.ssid);
|
const auto file_path = getApPropertiesFilePath(service_context->getPaths(), apSettings.ssid);
|
||||||
|
if (!file::findOrCreateParentDirectory(file_path, 0755)) {
|
||||||
|
LOGGER.error("Failed to create {}", file_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
|
|
||||||
|
|||||||
@@ -124,16 +124,9 @@ void bootSplashInit() {
|
|||||||
getMainDispatcher().dispatch([] {
|
getMainDispatcher().dispatch([] {
|
||||||
LOGGER.info("bootSplashInit dispatch begin");
|
LOGGER.info("bootSplashInit dispatch begin");
|
||||||
// First import any provisioning files placed on the system data partition.
|
// First import any provisioning files placed on the system data partition.
|
||||||
const std::string data_settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings");
|
const std::string data_settings_path = file::getChildPath(getUserDataPath(), "provisioning");
|
||||||
importWifiApSettingsFromDir(data_settings_path);
|
importWifiApSettingsFromDir(data_settings_path);
|
||||||
|
|
||||||
// Then scan attached SD cards as before.
|
|
||||||
std::string sdcard_path;
|
|
||||||
if (findFirstMountedSdCardPath((sdcard_path))) {
|
|
||||||
const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings");
|
|
||||||
importWifiApSettingsFromDir(sd_settings_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings::shouldEnableOnBoot()) {
|
if (settings::shouldEnableOnBoot()) {
|
||||||
LOGGER.info("Auto-enabling due to setting");
|
LOGGER.info("Auto-enabling due to setting");
|
||||||
getMainDispatcher().dispatch([] -> void { setEnabled(true); });
|
getMainDispatcher().dispatch([] -> void { setEnabled(true); });
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ static WifiSettings cachedSettings {
|
|||||||
|
|
||||||
static bool cached = false;
|
static bool cached = false;
|
||||||
|
|
||||||
static bool load(WifiSettings& settings) {
|
static bool hasWifiSettingsFile(std::shared_ptr<ServiceContext> context) {
|
||||||
auto service_context = findServiceContext();
|
std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
|
||||||
if (service_context == nullptr) {
|
return file::isFile(settings_path);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
static bool load(std::shared_ptr<ServiceContext> context, WifiSettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
std::string settings_path = service_context->getPaths()->getUserDataPath("settings.properties");
|
std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
|
||||||
if (!file::loadPropertiesFile(settings_path, map)) {
|
if (!file::loadPropertiesFile(settings_path, map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -42,23 +42,26 @@ static bool load(WifiSettings& settings) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool save(const WifiSettings& settings) {
|
static bool save(std::shared_ptr<ServiceContext> context, const WifiSettings& settings) {
|
||||||
auto service_context = findServiceContext();
|
|
||||||
if (service_context == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
|
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
|
||||||
std::string settings_path = service_context->getPaths()->getUserDataPath("settings.properties");
|
std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
|
||||||
|
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
|
||||||
|
LOGGER.error("Failed to create {}", settings_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return file::savePropertiesFile(settings_path, map);
|
return file::savePropertiesFile(settings_path, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
WifiSettings getCachedOrLoad() {
|
WifiSettings getCachedOrLoad() {
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
if (!load(cachedSettings)) {
|
auto context = findServiceContext();
|
||||||
LOGGER.error("Failed to load");
|
if (context && hasWifiSettingsFile(context)) {
|
||||||
} else {
|
if (load(context, cachedSettings)) {
|
||||||
cached = true;
|
cached = true;
|
||||||
|
} else {
|
||||||
|
LOGGER.info("Failed to load settings, using defaults");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +70,9 @@ WifiSettings getCachedOrLoad() {
|
|||||||
|
|
||||||
void setEnableOnBoot(bool enable) {
|
void setEnableOnBoot(bool enable) {
|
||||||
cachedSettings.enableOnBoot = enable;
|
cachedSettings.enableOnBoot = enable;
|
||||||
if (!save(cachedSettings)) {
|
auto context = findServiceContext();
|
||||||
LOGGER.error("Failed to save");
|
if (context && !save(context, cachedSettings)) {
|
||||||
|
LOGGER.error("Failed to save settings");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <Tactility/Paths.h>
|
#include <Tactility/Paths.h>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace tt::settings {
|
namespace tt::settings {
|
||||||
|
|
||||||
@@ -18,14 +17,7 @@ constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId";
|
|||||||
constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId";
|
constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId";
|
||||||
|
|
||||||
static std::string getPropertiesFilePath() {
|
static std::string getPropertiesFilePath() {
|
||||||
std::string sdcard_path;
|
return std::format(PROPERTIES_FILE_FORMAT, getUserDataPath());
|
||||||
if (findFirstMountedSdCardPath(sdcard_path)) {
|
|
||||||
std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard_path);
|
|
||||||
if (file::isFile(path)) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::format(PROPERTIES_FILE_FORMAT, file::MOUNT_POINT_DATA);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loadBootSettings(BootSettings& properties) {
|
bool loadBootSettings(BootSettings& properties) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <Tactility/settings/DisplaySettings.h>
|
#include <Tactility/settings/DisplaySettings.h>
|
||||||
|
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
#include <tactility/hal/Device.h>
|
#include <tactility/hal/Device.h>
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
|
||||||
@@ -10,7 +11,10 @@
|
|||||||
|
|
||||||
namespace tt::settings::display {
|
namespace tt::settings::display {
|
||||||
|
|
||||||
constexpr auto* SETTINGS_FILE = "/data/settings/display.properties";
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/display.properties";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* SETTINGS_KEY_ORIENTATION = "orientation";
|
constexpr auto* SETTINGS_KEY_ORIENTATION = "orientation";
|
||||||
constexpr auto* SETTINGS_KEY_GAMMA_CURVE = "gammaCurve";
|
constexpr auto* SETTINGS_KEY_GAMMA_CURVE = "gammaCurve";
|
||||||
constexpr auto* SETTINGS_KEY_BACKLIGHT_DUTY = "backlightDuty";
|
constexpr auto* SETTINGS_KEY_BACKLIGHT_DUTY = "backlightDuty";
|
||||||
@@ -106,7 +110,7 @@ static bool fromString(const std::string& str, ScreensaverType& type) {
|
|||||||
|
|
||||||
bool load(DisplaySettings& settings) {
|
bool load(DisplaySettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +190,7 @@ bool save(const DisplaySettings& settings) {
|
|||||||
map[SETTINGS_KEY_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0";
|
map[SETTINGS_KEY_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0";
|
||||||
map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs);
|
map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs);
|
||||||
map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType);
|
map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType);
|
||||||
return file::savePropertiesFile(SETTINGS_FILE, map);
|
return file::savePropertiesFile(getSettingsFilePath(), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_display_rotation_t toLvglDisplayRotation(Orientation orientation) {
|
lv_display_rotation_t toLvglDisplayRotation(Orientation orientation) {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#include <Tactility/settings/KeyboardSettings.h>
|
#include <Tactility/settings/KeyboardSettings.h>
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace tt::settings::keyboard {
|
namespace tt::settings::keyboard {
|
||||||
|
|
||||||
constexpr auto* SETTINGS_FILE = "/data/settings/keyboard.properties";
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/keyboard.properties";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* KEY_BACKLIGHT_ENABLED = "backlightEnabled";
|
constexpr auto* KEY_BACKLIGHT_ENABLED = "backlightEnabled";
|
||||||
constexpr auto* KEY_BACKLIGHT_BRIGHTNESS = "backlightBrightness";
|
constexpr auto* KEY_BACKLIGHT_BRIGHTNESS = "backlightBrightness";
|
||||||
constexpr auto* KEY_BACKLIGHT_TIMEOUT_ENABLED = "backlightTimeoutEnabled";
|
constexpr auto* KEY_BACKLIGHT_TIMEOUT_ENABLED = "backlightTimeoutEnabled";
|
||||||
@@ -14,7 +18,7 @@ constexpr auto* KEY_BACKLIGHT_TIMEOUT_MS = "backlightTimeoutMs";
|
|||||||
|
|
||||||
bool load(KeyboardSettings& settings) {
|
bool load(KeyboardSettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +58,7 @@ bool save(const KeyboardSettings& settings) {
|
|||||||
map[KEY_BACKLIGHT_BRIGHTNESS] = std::to_string(settings.backlightBrightness);
|
map[KEY_BACKLIGHT_BRIGHTNESS] = std::to_string(settings.backlightBrightness);
|
||||||
map[KEY_BACKLIGHT_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0";
|
map[KEY_BACKLIGHT_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0";
|
||||||
map[KEY_BACKLIGHT_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs);
|
map[KEY_BACKLIGHT_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs);
|
||||||
return file::savePropertiesFile(SETTINGS_FILE, map);
|
return file::savePropertiesFile(getSettingsFilePath(), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/MountPoints.h>
|
|
||||||
#include <Tactility/Mutex.h>
|
#include <Tactility/Mutex.h>
|
||||||
|
#include <Tactility/file/File.h>
|
||||||
#include <Tactility/file/FileLock.h>
|
#include <Tactility/file/FileLock.h>
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/settings/Language.h>
|
#include <Tactility/settings/Language.h>
|
||||||
#include <Tactility/settings/SystemSettings.h>
|
#include <Tactility/settings/SystemSettings.h>
|
||||||
|
|
||||||
|
#include "Tactility/Paths.h"
|
||||||
|
|
||||||
#include <format>
|
#include <format>
|
||||||
|
|
||||||
namespace tt::settings {
|
namespace tt::settings {
|
||||||
@@ -17,12 +19,16 @@ constexpr auto* FILE_PATH_FORMAT = "{}/settings/system.properties";
|
|||||||
static bool cached = false;
|
static bool cached = false;
|
||||||
static SystemSettings cachedSettings;
|
static SystemSettings cachedSettings;
|
||||||
|
|
||||||
|
static bool hasSystemSettingsFile() {
|
||||||
|
auto file_path = std::format(FILE_PATH_FORMAT, getUserDataPath());
|
||||||
|
return file::isFile(file_path);
|
||||||
|
}
|
||||||
|
|
||||||
static bool loadSystemSettingsFromFile(SystemSettings& properties) {
|
static bool loadSystemSettingsFromFile(SystemSettings& properties) {
|
||||||
auto file_path = std::format(FILE_PATH_FORMAT, file::MOUNT_POINT_DATA);
|
auto file_path = std::format(FILE_PATH_FORMAT, getUserDataPath());
|
||||||
LOGGER.info("System settings loading from {}", file_path);
|
LOGGER.info("System settings loading from {}", file_path);
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(file_path, map)) {
|
if (!file::loadPropertiesFile(file_path, map)) {
|
||||||
LOGGER.error("Failed to load {}", file_path);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,11 +61,12 @@ static bool loadSystemSettingsFromFile(SystemSettings& properties) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool loadSystemSettings(SystemSettings& properties) {
|
bool loadSystemSettings(SystemSettings& properties) {
|
||||||
if (!cached) {
|
if (!cached && hasSystemSettingsFile()) {
|
||||||
if (!loadSystemSettingsFromFile(cachedSettings)) {
|
if (loadSystemSettingsFromFile(cachedSettings)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
cached = true;
|
cached = true;
|
||||||
|
} else {
|
||||||
|
LOGGER.error("Failed to load");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
properties = cachedSettings;
|
properties = cachedSettings;
|
||||||
@@ -67,7 +74,7 @@ bool loadSystemSettings(SystemSettings& properties) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool saveSystemSettings(const SystemSettings& properties) {
|
bool saveSystemSettings(const SystemSettings& properties) {
|
||||||
auto file_path = std::format(FILE_PATH_FORMAT, file::MOUNT_POINT_DATA);
|
auto file_path = std::format(FILE_PATH_FORMAT, getUserDataPath());
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
map["language"] = toString(properties.language);
|
map["language"] = toString(properties.language);
|
||||||
map["timeFormat24h"] = properties.timeFormat24h ? "true" : "false";
|
map["timeFormat24h"] = properties.timeFormat24h ? "true" : "false";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/Mutex.h>
|
#include <Tactility/Mutex.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -12,7 +13,10 @@
|
|||||||
|
|
||||||
namespace tt::settings::touch {
|
namespace tt::settings::touch {
|
||||||
|
|
||||||
constexpr auto* SETTINGS_FILE = "/data/settings/touch-calibration.properties";
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/touch-calibration.properties";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* SETTINGS_KEY_ENABLED = "enabled";
|
constexpr auto* SETTINGS_KEY_ENABLED = "enabled";
|
||||||
constexpr auto* SETTINGS_KEY_X_MIN = "xMin";
|
constexpr auto* SETTINGS_KEY_X_MIN = "xMin";
|
||||||
constexpr auto* SETTINGS_KEY_X_MAX = "xMax";
|
constexpr auto* SETTINGS_KEY_X_MAX = "xMax";
|
||||||
@@ -61,7 +65,7 @@ bool isValid(const TouchCalibrationSettings& settings) {
|
|||||||
|
|
||||||
bool load(TouchCalibrationSettings& settings) {
|
bool load(TouchCalibrationSettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +116,7 @@ bool save(const TouchCalibrationSettings& settings) {
|
|||||||
map[SETTINGS_KEY_Y_MIN] = std::to_string(settings.yMin);
|
map[SETTINGS_KEY_Y_MIN] = std::to_string(settings.yMin);
|
||||||
map[SETTINGS_KEY_Y_MAX] = std::to_string(settings.yMax);
|
map[SETTINGS_KEY_Y_MAX] = std::to_string(settings.yMax);
|
||||||
|
|
||||||
if (!file::savePropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::savePropertiesFile(getSettingsFilePath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <Tactility/settings/TrackballSettings.h>
|
#include <Tactility/settings/TrackballSettings.h>
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -7,7 +8,10 @@
|
|||||||
|
|
||||||
namespace tt::settings::trackball {
|
namespace tt::settings::trackball {
|
||||||
|
|
||||||
constexpr auto* SETTINGS_FILE = "/data/settings/trackball.properties";
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/trackball.properties";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto* KEY_TRACKBALL_ENABLED = "trackballEnabled";
|
constexpr auto* KEY_TRACKBALL_ENABLED = "trackballEnabled";
|
||||||
constexpr auto* KEY_TRACKBALL_MODE = "trackballMode";
|
constexpr auto* KEY_TRACKBALL_MODE = "trackballMode";
|
||||||
constexpr auto* KEY_ENCODER_SENSITIVITY = "encoderSensitivity";
|
constexpr auto* KEY_ENCODER_SENSITIVITY = "encoderSensitivity";
|
||||||
@@ -20,7 +24,7 @@ constexpr uint8_t MAX_POINTER_SENSITIVITY = 10;
|
|||||||
|
|
||||||
bool load(TrackballSettings& settings) {
|
bool load(TrackballSettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +84,7 @@ bool save(const TrackballSettings& settings) {
|
|||||||
map[KEY_TRACKBALL_MODE] = (settings.trackballMode == TrackballMode::Pointer) ? "1" : "0";
|
map[KEY_TRACKBALL_MODE] = (settings.trackballMode == TrackballMode::Pointer) ? "1" : "0";
|
||||||
map[KEY_ENCODER_SENSITIVITY] = std::to_string(std::clamp(settings.encoderSensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY));
|
map[KEY_ENCODER_SENSITIVITY] = std::to_string(std::clamp(settings.encoderSensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY));
|
||||||
map[KEY_POINTER_SENSITIVITY] = std::to_string(std::clamp(settings.pointerSensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY));
|
map[KEY_POINTER_SENSITIVITY] = std::to_string(std::clamp(settings.pointerSensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY));
|
||||||
return file::savePropertiesFile(SETTINGS_FILE, map);
|
return file::savePropertiesFile(getSettingsFilePath(), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/file/File.h>
|
#include <Tactility/file/File.h>
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
|
#include <Tactility/Paths.h>
|
||||||
|
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -17,8 +18,11 @@
|
|||||||
|
|
||||||
namespace tt::settings::webserver {
|
namespace tt::settings::webserver {
|
||||||
|
|
||||||
static const auto LOGGER = tt::Logger("WebServerSettings");
|
static const auto LOGGER = Logger("WebServerSettings");
|
||||||
constexpr auto* SETTINGS_FILE = "/data/service/webserver/settings.properties";
|
|
||||||
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/settings/webserver.properties";
|
||||||
|
}
|
||||||
|
|
||||||
// Property keys
|
// Property keys
|
||||||
constexpr auto* KEY_WIFI_ENABLED = "wifiEnabled";
|
constexpr auto* KEY_WIFI_ENABLED = "wifiEnabled";
|
||||||
@@ -87,7 +91,7 @@ static bool isEmptyCredential(const std::string& value) {
|
|||||||
|
|
||||||
bool load(WebServerSettings& settings) {
|
bool load(WebServerSettings& settings) {
|
||||||
std::map<std::string, std::string> map;
|
std::map<std::string, std::string> map;
|
||||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +150,7 @@ bool load(WebServerSettings& settings) {
|
|||||||
|
|
||||||
// Persist the generated password immediately
|
// Persist the generated password immediately
|
||||||
map[KEY_AP_PASSWORD] = settings.apPassword;
|
map[KEY_AP_PASSWORD] = settings.apPassword;
|
||||||
if (file::savePropertiesFile(SETTINGS_FILE, map)) {
|
if (file::savePropertiesFile(getSettingsFilePath(), map)) {
|
||||||
LOGGER.info("Generated and saved new secure AP password");
|
LOGGER.info("Generated and saved new secure AP password");
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("Failed to save generated AP password");
|
LOGGER.error("Failed to save generated AP password");
|
||||||
@@ -187,7 +191,7 @@ bool load(WebServerSettings& settings) {
|
|||||||
// We need to save these to the file so they're consistent across reboots
|
// We need to save these to the file so they're consistent across reboots
|
||||||
map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername;
|
map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername;
|
||||||
map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword;
|
map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword;
|
||||||
if (file::savePropertiesFile(SETTINGS_FILE, map)) {
|
if (file::savePropertiesFile(getSettingsFilePath(), map)) {
|
||||||
LOGGER.info("Generated and saved new secure credentials");
|
LOGGER.info("Generated and saved new secure credentials");
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("Failed to save generated credentials - auth may be inconsistent across reboots");
|
LOGGER.error("Failed to save generated credentials - auth may be inconsistent across reboots");
|
||||||
@@ -253,8 +257,7 @@ bool save(const WebServerSettings& settings) {
|
|||||||
map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername;
|
map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername;
|
||||||
map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword;
|
map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword;
|
||||||
|
|
||||||
// Save to flash storage only (no SD backup - settings sync at boot handles restore)
|
return file::savePropertiesFile(getSettingsFilePath(), map);
|
||||||
return file::savePropertiesFile(SETTINGS_FILE, map);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import tarfile
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
ttbuild_path = ".tactility"
|
ttbuild_path = ".tactility"
|
||||||
ttbuild_version = "4.0.0"
|
ttbuild_version = "4.1.0"
|
||||||
ttbuild_cdn = "https://cdn.tactilityproject.org"
|
ttbuild_cdn = "https://cdn.tactilityproject.org"
|
||||||
ttbuild_sdk_json_validity = 3600 # seconds
|
ttbuild_sdk_json_validity = 3600 # seconds
|
||||||
ttport = 6666
|
ttport = 6666
|
||||||
@@ -192,7 +192,7 @@ def fetch_sdkconfig_files(platform_targets):
|
|||||||
for platform in platform_targets:
|
for platform in platform_targets:
|
||||||
sdkconfig_filename = f"sdkconfig.app.{platform}"
|
sdkconfig_filename = f"sdkconfig.app.{platform}"
|
||||||
target_path = os.path.join(ttbuild_path, sdkconfig_filename)
|
target_path = os.path.join(ttbuild_path, sdkconfig_filename)
|
||||||
if not download_file(f"{ttbuild_cdn}/{sdkconfig_filename}", target_path):
|
if not download_file(f"{ttbuild_cdn}/sdk/{sdkconfig_filename}", target_path):
|
||||||
exit_with_error(f"Failed to download sdkconfig file for {platform}")
|
exit_with_error(f"Failed to download sdkconfig file for {platform}")
|
||||||
|
|
||||||
#endregion SDK helpers
|
#endregion SDK helpers
|
||||||
|
|||||||
@@ -94,18 +94,31 @@ def write_defaults(output_file):
|
|||||||
default_properties = read_file(default_properties_path)
|
default_properties = read_file(default_properties_path)
|
||||||
output_file.write(default_properties)
|
output_file.write(default_properties)
|
||||||
|
|
||||||
|
def get_user_data_location(device_properties: dict):
|
||||||
|
user_data_location = get_property_or_exit(device_properties, "storage", "userDataLocation")
|
||||||
|
if user_data_location not in ("SD", "Internal"):
|
||||||
|
exit_with_error(f"storage.userDataLocation must be 'SD' or 'Internal', but was: '{user_data_location}'")
|
||||||
|
return user_data_location
|
||||||
|
|
||||||
def write_partition_table(output_file, device_properties: dict, is_dev: bool):
|
def write_partition_table(output_file, device_properties: dict, is_dev: bool):
|
||||||
if is_dev:
|
|
||||||
flash_size_number = 4
|
|
||||||
else:
|
|
||||||
flash_size = get_property_or_exit(device_properties, "hardware", "flashSize")
|
flash_size = get_property_or_exit(device_properties, "hardware", "flashSize")
|
||||||
if not flash_size.endswith("MB"):
|
if not flash_size.endswith("MB"):
|
||||||
exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)")
|
exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)")
|
||||||
flash_size_number = flash_size[:-2]
|
flash_size_number = flash_size[:-2]
|
||||||
|
user_data_location = get_user_data_location(device_properties)
|
||||||
|
if flash_size_number == "4" and user_data_location == "Internal":
|
||||||
|
exit_with_error("4MB devices must use storage.userDataLocation=SD; there is no internal-storage partition table for 4MB flash")
|
||||||
|
variant = "with-sd" if user_data_location == "SD" else "no-sd"
|
||||||
|
partition_filename = f"partitions-{flash_size_number}mb-{variant}.csv"
|
||||||
|
if is_dev:
|
||||||
|
dev_partition_filename = f"partitions-{flash_size_number}mb-{variant}-dev.csv"
|
||||||
|
if os.path.isfile(dev_partition_filename):
|
||||||
|
partition_filename = dev_partition_filename
|
||||||
|
print(f"Using partition table: {partition_filename}")
|
||||||
output_file.write("# Partition Table\n")
|
output_file.write("# Partition Table\n")
|
||||||
output_file.write("CONFIG_PARTITION_TABLE_CUSTOM=y\n")
|
output_file.write("CONFIG_PARTITION_TABLE_CUSTOM=y\n")
|
||||||
output_file.write(f"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions-{flash_size_number}mb.csv\"\n")
|
output_file.write(f"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"{partition_filename}\"\n")
|
||||||
output_file.write(f"CONFIG_PARTITION_TABLE_FILENAME=\"partitions-{flash_size_number}mb.csv\"\n")
|
output_file.write(f"CONFIG_PARTITION_TABLE_FILENAME=\"{partition_filename}\"\n")
|
||||||
|
|
||||||
def write_tactility_variables(output_file, device_properties: dict, device_id: str):
|
def write_tactility_variables(output_file, device_properties: dict, device_id: str):
|
||||||
# Board and vendor
|
# Board and vendor
|
||||||
@@ -128,6 +141,11 @@ def write_tactility_variables(output_file, device_properties: dict, device_id: s
|
|||||||
if auto_start_app_id is not None:
|
if auto_start_app_id is not None:
|
||||||
safe_auto_start_app_id = auto_start_app_id.replace("\"", "\\\"")
|
safe_auto_start_app_id = auto_start_app_id.replace("\"", "\\\"")
|
||||||
output_file.write(f"CONFIG_TT_AUTO_START_APP_ID=\"{safe_auto_start_app_id}\"\n")
|
output_file.write(f"CONFIG_TT_AUTO_START_APP_ID=\"{safe_auto_start_app_id}\"\n")
|
||||||
|
# User data location
|
||||||
|
if get_user_data_location(device_properties) == "SD":
|
||||||
|
output_file.write("CONFIG_TT_USER_DATA_LOCATION_SD=y\n")
|
||||||
|
else:
|
||||||
|
output_file.write("CONFIG_TT_USER_DATA_LOCATION_INTERNAL=y\n")
|
||||||
|
|
||||||
def write_core_variables(output_file, device_properties: dict):
|
def write_core_variables(output_file, device_properties: dict):
|
||||||
idf_target = get_property_or_exit(device_properties, "hardware", "target").lower()
|
idf_target = get_property_or_exit(device_properties, "hardware", "target").lower()
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
nvs, data, nvs, 0x9000, 0x6000,
|
nvs, data, nvs, 0x9000, 0x6000,
|
||||||
phy_init, data, phy, 0xf000, 0x1000,
|
phy_init, data, phy, 0xf000, 0x1000,
|
||||||
factory, app, factory, 0x10000, 4M,
|
factory, app, factory, 0x10000, 4M,
|
||||||
system, data, fat, , 300k,
|
system, data, fat, , 100k,
|
||||||
data, data, fat, , 3600k,
|
data, data, fat, , 1000k,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user