sync
This commit is contained in:
@@ -3,7 +3,7 @@ import argparse
|
||||
import sys
|
||||
from core.config import load_config
|
||||
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():
|
||||
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.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
|
||||
confirm_parser = subparsers.add_parser("confirm", help="Bestätigt die aktuell laufende Firmware")
|
||||
|
||||
@@ -95,6 +99,12 @@ def main():
|
||||
reboot.execute(conn)
|
||||
elif args.command == "play":
|
||||
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:
|
||||
# Wurde kein Befehl oder explizit 'info' angegeben, sind wir hier schon fertig
|
||||
pass
|
||||
|
||||
@@ -351,6 +351,7 @@ int cmd_reboot_device()
|
||||
void cmd_play(const char *filename)
|
||||
{
|
||||
LOG_DBG("Play command received with filename: '%s'", filename);
|
||||
audio_stop();
|
||||
audio_play(filename);
|
||||
}
|
||||
|
||||
@@ -366,6 +367,7 @@ int cmd_check(const char *param)
|
||||
return -ENOENT;
|
||||
}
|
||||
uint32_t crc32 = 0;
|
||||
uint32_t start_time = k_uptime_get_32();
|
||||
uint8_t buffer[256];
|
||||
ssize_t read;
|
||||
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);
|
||||
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];
|
||||
snprintf(response, sizeof(response), "CRC32 %s 0x%08x\n", param, crc32);
|
||||
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",
|
||||
"astro": "^5.17.1",
|
||||
"astro-icon": "^1.1.5",
|
||||
"phosphor-svelte": "^3.1.0",
|
||||
"svelte": "^5.53.5",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "^5.9.3"
|
||||
@@ -4981,6 +4982,25 @@
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"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": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"astro": "^5.17.1",
|
||||
"astro-icon": "^1.1.5",
|
||||
"phosphor-svelte": "^3.1.0",
|
||||
"svelte": "^5.53.5",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
@@ -1,42 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { buzzer } from '../lib/buzzerStore';
|
||||
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 };
|
||||
export let file: { name: string, size: string, isSystem: boolean, crc32?: number};
|
||||
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>
|
||||
|
||||
<div
|
||||
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'}"
|
||||
>
|
||||
<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">
|
||||
{#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}
|
||||
<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}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<span class="text-sm truncate {selected ? 'font-bold text-white' : 'text-slate-200'}">{file.name}</span>
|
||||
<span class="text-[10px] text-slate-500 font-mono tracking-tighter uppercase">{file.size}</span>
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<span class="text-sm truncate overflow-hidden {selected ? 'font-bold text-white' : 'text-slate-200'}">
|
||||
{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 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
|
||||
on:click|stopPropagation={() => playFile(file.name)}
|
||||
class="p-2 hover:bg-blue-500/20 rounded-lg text-blue-400 transition-colors"
|
||||
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
|
||||
@@ -44,7 +60,7 @@
|
||||
class="p-2 hover:bg-red-500/20 rounded-lg text-red-400 transition-colors"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,10 +2,9 @@
|
||||
import { buzzer } from '../lib/buzzerStore';
|
||||
import { refreshFileList } from '../lib/buzzerActions';
|
||||
import FileRow from './FileRow.svelte';
|
||||
|
||||
import { ArrowsCounterClockwiseIcon } from 'phosphor-svelte';
|
||||
async function handleRefresh() {
|
||||
// Falls wir den Port im Store haben, hier nutzen
|
||||
// ansonsten wird er über die Actions verwaltet
|
||||
console.log("Aktualisiere Dateiliste...");
|
||||
await refreshFileList();
|
||||
}
|
||||
</script>
|
||||
@@ -19,9 +18,10 @@
|
||||
|
||||
<button
|
||||
on:click={handleRefresh}
|
||||
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">
|
||||
<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>
|
||||
class="text-slate-500 hover:text-blue-400 transition-colors active:rotate-180 duration-500"
|
||||
title="Refresh File List"
|
||||
>
|
||||
<ArrowsCounterClockwiseIcon size={16} weight="fill" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -44,14 +44,4 @@
|
||||
</div>
|
||||
{/if}
|
||||
</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>
|
||||
@@ -1,4 +1,5 @@
|
||||
// src/lib/buzzerActions.ts
|
||||
import { nan } from 'astro:schema';
|
||||
import { buzzer } from './buzzerStore';
|
||||
|
||||
let activePort: SerialPort | null = null;
|
||||
@@ -53,7 +54,10 @@ export async function updateDeviceInfo(port: SerialPort) {
|
||||
|
||||
// WICHTIG: Auch hier "export"
|
||||
export async function refreshFileList(port: SerialPort) {
|
||||
if (!port) return;
|
||||
if (!port) {
|
||||
if (!activePort) return;
|
||||
port = activePort;
|
||||
}
|
||||
let audioSize = 0;
|
||||
let sysSize = 0;
|
||||
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')) {
|
||||
audioSize += size;
|
||||
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')) {
|
||||
sysSize += size;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ export const buzzer = writable({
|
||||
build: 'unknown',
|
||||
storage: {
|
||||
total: 8.0, // 8 MB Flash laut Spezifikation
|
||||
unknown: 8.0,
|
||||
available: 0.0,
|
||||
unknown: 8.0,
|
||||
usedSys: 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