sync
This commit is contained in:
@@ -3,7 +3,7 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
from core.config import load_config
|
from core.config import load_config
|
||||||
from core.connection import BuzzerConnection, BuzzerError
|
from core.connection import BuzzerConnection, BuzzerError
|
||||||
from core.commands import info, ls, put, mkdir, rm, confirm, reboot, play
|
from core.commands import info, ls, put, mkdir, rm, confirm, reboot, play, check
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Edis Buzzer Host Tool")
|
parser = argparse.ArgumentParser(description="Edis Buzzer Host Tool")
|
||||||
@@ -42,6 +42,10 @@ def main():
|
|||||||
play_parser = subparsers.add_parser("play", help="Spielt eine Datei auf dem Controller ab")
|
play_parser = subparsers.add_parser("play", help="Spielt eine Datei auf dem Controller ab")
|
||||||
play_parser.add_argument("path", type=str, help="Pfad der abzuspielenden Datei (z.B. /lfs/a/neu)")
|
play_parser.add_argument("path", type=str, help="Pfad der abzuspielenden Datei (z.B. /lfs/a/neu)")
|
||||||
|
|
||||||
|
# Befehl: check
|
||||||
|
check_parser = subparsers.add_parser("check", help="Holt die CRC32 einer Datei und zeigt sie an")
|
||||||
|
check_parser.add_argument("path", type=str, help="Pfad der zu prüfenden Datei (z.B. /lfs/a/neu)")
|
||||||
|
|
||||||
# Befehl: confirm
|
# Befehl: confirm
|
||||||
confirm_parser = subparsers.add_parser("confirm", help="Bestätigt die aktuell laufende Firmware")
|
confirm_parser = subparsers.add_parser("confirm", help="Bestätigt die aktuell laufende Firmware")
|
||||||
|
|
||||||
@@ -95,6 +99,12 @@ def main():
|
|||||||
reboot.execute(conn)
|
reboot.execute(conn)
|
||||||
elif args.command == "play":
|
elif args.command == "play":
|
||||||
play.execute(conn, path=args.path)
|
play.execute(conn, path=args.path)
|
||||||
|
elif args.command == "check":
|
||||||
|
CRC32 = check.execute(conn, path=args.path)
|
||||||
|
if CRC32:
|
||||||
|
print(f"CRC32 von '{args.path}': 0x{CRC32['crc32']:08x}")
|
||||||
|
else:
|
||||||
|
print(f"Fehler: Keine CRC32-Information für '{args.path}' erhalten.")
|
||||||
elif args.command == "info" or args.command is None:
|
elif args.command == "info" or args.command is None:
|
||||||
# Wurde kein Befehl oder explizit 'info' angegeben, sind wir hier schon fertig
|
# Wurde kein Befehl oder explizit 'info' angegeben, sind wir hier schon fertig
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ int cmd_reboot_device()
|
|||||||
void cmd_play(const char *filename)
|
void cmd_play(const char *filename)
|
||||||
{
|
{
|
||||||
LOG_DBG("Play command received with filename: '%s'", filename);
|
LOG_DBG("Play command received with filename: '%s'", filename);
|
||||||
|
audio_stop();
|
||||||
audio_play(filename);
|
audio_play(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,6 +367,7 @@ int cmd_check(const char *param)
|
|||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
uint32_t crc32 = 0;
|
uint32_t crc32 = 0;
|
||||||
|
uint32_t start_time = k_uptime_get_32();
|
||||||
uint8_t buffer[256];
|
uint8_t buffer[256];
|
||||||
ssize_t read;
|
ssize_t read;
|
||||||
while ((read = fs_read(&file, buffer, sizeof(buffer))) > 0)
|
while ((read = fs_read(&file, buffer, sizeof(buffer))) > 0)
|
||||||
@@ -378,7 +380,8 @@ int cmd_check(const char *param)
|
|||||||
LOG_ERR("Check failed: error reading file '%s': %d", param, (int)read);
|
LOG_ERR("Check failed: error reading file '%s': %d", param, (int)read);
|
||||||
return (int)read;
|
return (int)read;
|
||||||
}
|
}
|
||||||
LOG_DBG("Check successful: file '%s' has CRC32 0x%08x", param, crc32);
|
uint32_t duration = k_uptime_get_32() - start_time;
|
||||||
|
LOG_DBG("Check successful: file '%s' has CRC32 0x%08x, check took %u ms", param, crc32, duration);
|
||||||
char response[64];
|
char response[64];
|
||||||
snprintf(response, sizeof(response), "CRC32 %s 0x%08x\n", param, crc32);
|
snprintf(response, sizeof(response), "CRC32 %s 0x%08x\n", param, crc32);
|
||||||
usb_write_buffer((const uint8_t *)response, strlen(response));
|
usb_write_buffer((const uint8_t *)response, strlen(response));
|
||||||
|
|||||||
20
webpage/package-lock.json
generated
20
webpage/package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"astro": "^5.17.1",
|
"astro": "^5.17.1",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
|
"phosphor-svelte": "^3.1.0",
|
||||||
"svelte": "^5.53.5",
|
"svelte": "^5.53.5",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
@@ -4981,6 +4982,25 @@
|
|||||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/phosphor-svelte": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/phosphor-svelte/-/phosphor-svelte-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-nldtxx+XCgNREvrb7O5xgDsefytXpSkPTx8Rnu3f2qQCUZLDV1rLxYSd2Jcwckuo9lZB1qKMqGR17P4UDC0PrA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"magic-string": "^0.30.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^5.0.0 || ^5.0.0-next.96",
|
||||||
|
"vite": ">=5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/piccolore": {
|
"node_modules/piccolore": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"astro": "^5.17.1",
|
"astro": "^5.17.1",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
|
"phosphor-svelte": "^3.1.0",
|
||||||
"svelte": "^5.53.5",
|
"svelte": "^5.53.5",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
|
|||||||
@@ -1,42 +1,58 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { buzzer } from '../lib/buzzerStore';
|
import { buzzer } from '../lib/buzzerStore';
|
||||||
import { playFile, deleteFile } from '../lib/buzzerActions';
|
import { playFile, deleteFile } from '../lib/buzzerActions';
|
||||||
|
import { MusicNotesIcon, WrenchIcon, PlayIcon, TrashIcon, ArrowsLeftRightIcon, QuestionMarkIcon } from "phosphor-svelte";
|
||||||
|
|
||||||
// Die Datei-Daten werden vom Parent (FileStorage) übergeben
|
export let file: { name: string, size: string, isSystem: boolean, crc32?: number};
|
||||||
export let file: { name: string, size: string, isSystem: boolean };
|
|
||||||
export let selected = false;
|
export let selected = false;
|
||||||
|
|
||||||
// Wir benötigen den Port für die Kommandos
|
|
||||||
// In einem echten Szenario würden wir den Port in einem Store speichern.
|
|
||||||
// Hier nehmen wir an, er wird über die Actions gehandelt.
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between p-3 cursor-pointer transition-all group border-b border-slate-700/30
|
class="flex items-center justify-between p-3 cursor-pointer transition-all group border-b border-slate-700/30
|
||||||
{selected ? 'bg-blue-600/20 border-l-4 border-blue-500 shadow-[inset_0_0_20px_rgba(59,130,246,0.1)]' : 'hover:bg-slate-700/40 border-l-4 border-transparent'}"
|
{selected ? 'bg-blue-600/20 border-l-4 border-blue-500 shadow-[inset_0_0_20px_rgba(59,130,246,0.1)]' : 'hover:bg-slate-700/40 border-l-4 border-transparent'}"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-4 overflow-hidden">
|
<div class="flex-1 flex items-center gap-4 min-w-0">
|
||||||
<div class="text-2xl {selected ? 'text-blue-400' : 'text-slate-500'} shrink-0">
|
<div class="text-2xl {selected ? 'text-blue-400' : 'text-slate-500'} shrink-0">
|
||||||
{#if file.isSystem}
|
{#if file.isSystem}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M225.9,103.11l-40.45,40.44a8,8,0,0,1-11.31,0L148,117.41a8,8,0,0,1,0-11.31l40.44-40.45a72,72,0,0,0-85.3,109.11l-51.5,51.5a24,24,0,0,0,33.94,33.94l51.5-51.5A72,72,0,0,0,225.9,103.11ZM160,80a8,8,0,1,1-8-8A8,8,0,0,1,160,80Z"></path></svg>
|
<WrenchIcon size={24} weight="fill" />
|
||||||
{:else}
|
{:else}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M210.3,56.34l-80-24A8,8,0,0,0,120,40V148.26A48,48,0,1,0,136,184V98.71l69.7,20.91a8,8,0,0,0,10.3-7.62V64A8,8,0,0,0,210.3,56.34ZM88,216a32,32,0,1,1,32-32A32,32,0,0,1,88,216Zm112-112.71L136,83.89V49.11L200,68.31Z"></path></svg>
|
<MusicNotesIcon size={24} weight="fill" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col overflow-hidden">
|
<div class="flex-1 flex flex-col min-w-0">
|
||||||
<span class="text-sm truncate {selected ? 'font-bold text-white' : 'text-slate-200'}">{file.name}</span>
|
<span class="text-sm truncate overflow-hidden {selected ? 'font-bold text-white' : 'text-slate-200'}">
|
||||||
<span class="text-[10px] text-slate-500 font-mono tracking-tighter uppercase">{file.size}</span>
|
{file.name}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="relative group/sync flex items-center">
|
||||||
|
{#if !file.crc32 || isNaN(file.crc32)}
|
||||||
|
<QuestionMarkIcon size={12} weight="bold" class="text-slate-700" />
|
||||||
|
{:else}
|
||||||
|
<ArrowsLeftRightIcon size={12} weight="bold" class="text-slate-300" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 bg-slate-900 text-[10px] text-white rounded opacity-0 group-hover/sync:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50 border border-slate-700 shadow-xl">
|
||||||
|
{file.crc32 ? `CRC32: 0x${file.crc32.toString(16).toUpperCase()}` : 'CRC unbekannt'}
|
||||||
|
<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-slate-900"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="text-[10px] text-slate-500 font-mono tracking-tighter uppercase">
|
||||||
|
{file.size}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
<div class="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0 ml-4">
|
||||||
<button
|
<button
|
||||||
on:click|stopPropagation={() => playFile(file.name)}
|
on:click|stopPropagation={() => playFile(file.name)}
|
||||||
class="p-2 hover:bg-blue-500/20 rounded-lg text-blue-400 transition-colors"
|
class="p-2 hover:bg-blue-500/20 rounded-lg text-blue-400 transition-colors"
|
||||||
title="Play Sound"
|
title="Play Sound"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M240,128a15.74,15.74,0,0,1-7.6,13.51L88.32,229.75a16,16,0,0,1-16.2,0A15.86,15.86,0,0,1,64,216.24V39.76a15.86,15.86,0,0,1,8.12-13.51,16,16,0,0,1,16.2,0L232.4,114.49A15.74,15.74,0,0,1,240,128Z"></path></svg>
|
<PlayIcon size={16} weight="fill" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -44,7 +60,7 @@
|
|||||||
class="p-2 hover:bg-red-500/20 rounded-lg text-red-400 transition-colors"
|
class="p-2 hover:bg-red-500/20 rounded-lg text-red-400 transition-colors"
|
||||||
title="Delete File"
|
title="Delete File"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
<TrashIcon size={16} weight="fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2,10 +2,9 @@
|
|||||||
import { buzzer } from '../lib/buzzerStore';
|
import { buzzer } from '../lib/buzzerStore';
|
||||||
import { refreshFileList } from '../lib/buzzerActions';
|
import { refreshFileList } from '../lib/buzzerActions';
|
||||||
import FileRow from './FileRow.svelte';
|
import FileRow from './FileRow.svelte';
|
||||||
|
import { ArrowsCounterClockwiseIcon } from 'phosphor-svelte';
|
||||||
async function handleRefresh() {
|
async function handleRefresh() {
|
||||||
// Falls wir den Port im Store haben, hier nutzen
|
console.log("Aktualisiere Dateiliste...");
|
||||||
// ansonsten wird er über die Actions verwaltet
|
|
||||||
await refreshFileList();
|
await refreshFileList();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -19,9 +18,10 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={handleRefresh}
|
on:click={handleRefresh}
|
||||||
class="text-slate-500 hover:text-blue-400 transition-colors active:rotate-180 duration-500">
|
class="text-slate-500 hover:text-blue-400 transition-colors active:rotate-180 duration-500"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256">
|
title="Refresh File List"
|
||||||
<rect width="256" height="256" fill="none"/><polyline points="88 96 40 96 40 48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M40,96,68.28,67.72A88,88,0,0,1,192,67" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="168 160 216 160 216 208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M216,160l-28.28,28.28A88,88,0,0,1,64,189" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/> </svg>
|
>
|
||||||
|
<ArrowsCounterClockwiseIcon size={16} weight="fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,14 +44,4 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute bottom-6 right-6 text-slate-700 opacity-5 pointer-events-none">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path d="M208,40V216a16,16,0,0,1-16,16H64a16,16,0,0,1-16-16V40A16,16,0,0,1,64,24H192A16,16,0,0,1,208,40Z" opacity="0.2"></path>
|
|
||||||
<path d="M208,40V216a16,16,0,0,1-16,16H64a16,16,0,0,1-16-16V40A16,16,0,0,1,64,24H192A16,16,0,0,1,208,40Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></path>
|
|
||||||
<line x1="80" y1="24" x2="80" y2="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line>
|
|
||||||
<line x1="128" y1="24" x2="128" y2="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line>
|
|
||||||
<line x1="176" y1="24" x2="176" y2="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// src/lib/buzzerActions.ts
|
// src/lib/buzzerActions.ts
|
||||||
|
import { nan } from 'astro:schema';
|
||||||
import { buzzer } from './buzzerStore';
|
import { buzzer } from './buzzerStore';
|
||||||
|
|
||||||
let activePort: SerialPort | null = null;
|
let activePort: SerialPort | null = null;
|
||||||
@@ -53,7 +54,10 @@ export async function updateDeviceInfo(port: SerialPort) {
|
|||||||
|
|
||||||
// WICHTIG: Auch hier "export"
|
// WICHTIG: Auch hier "export"
|
||||||
export async function refreshFileList(port: SerialPort) {
|
export async function refreshFileList(port: SerialPort) {
|
||||||
if (!port) return;
|
if (!port) {
|
||||||
|
if (!activePort) return;
|
||||||
|
port = activePort;
|
||||||
|
}
|
||||||
let audioSize = 0;
|
let audioSize = 0;
|
||||||
let sysSize = 0;
|
let sysSize = 0;
|
||||||
const audioFiles: {name: string, size: string, crc32: number, isSystem: boolean}[] = [];
|
const audioFiles: {name: string, size: string, crc32: number, isSystem: boolean}[] = [];
|
||||||
@@ -73,7 +77,7 @@ export async function refreshFileList(port: SerialPort) {
|
|||||||
if (path.startsWith('/lfs/a')) {
|
if (path.startsWith('/lfs/a')) {
|
||||||
audioSize += size;
|
audioSize += size;
|
||||||
console.log("Audio-Datei gefunden:", name);
|
console.log("Audio-Datei gefunden:", name);
|
||||||
audioFiles.push({ name, size: (size / 1024).toFixed(1) + " KB", crc32: 0, isSystem: false });
|
audioFiles.push({ name, size: (size / 1024).toFixed(1) + " KB", crc32: NaN, isSystem: false, isSynced: false });
|
||||||
} else if (path.startsWith('/lfs/sys')) {
|
} else if (path.startsWith('/lfs/sys')) {
|
||||||
sysSize += size;
|
sysSize += size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ export const buzzer = writable({
|
|||||||
build: 'unknown',
|
build: 'unknown',
|
||||||
storage: {
|
storage: {
|
||||||
total: 8.0, // 8 MB Flash laut Spezifikation
|
total: 8.0, // 8 MB Flash laut Spezifikation
|
||||||
unknown: 8.0,
|
|
||||||
available: 0.0,
|
available: 0.0,
|
||||||
|
unknown: 8.0,
|
||||||
usedSys: 0.0,
|
usedSys: 0.0,
|
||||||
usedAudio: 0.0
|
usedAudio: 0.0
|
||||||
},
|
},
|
||||||
files: [] as {name: string, size: string, crc32: number, isSystem: boolean}[]
|
files: [] as {name: string, size: string, crc32: number, isSystem: boolean, isSynced: boolean}[]
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user