sync
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FlashUsage from "./FlashUsage.svelte";
|
||||
import { deviceInfo, fwInfo } from "../lib/store";
|
||||
import { FW_STATUS } from "../lib/protocol/constants";
|
||||
import { battInfo, deviceInfo, fwInfo } from "../lib/store";
|
||||
import { BATT_STATUS, FW_STATUS } from "../lib/protocol/constants";
|
||||
import { tooltip } from "../lib/actions/tooltip";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
@@ -13,6 +13,52 @@
|
||||
BatteryFullIcon,
|
||||
BatteryChargingIcon,
|
||||
} from "phosphor-svelte";
|
||||
|
||||
function clampBatteryLevel(level: number): number {
|
||||
if (Number.isNaN(level)) return 0;
|
||||
return Math.max(0, Math.min(4, Math.trunc(level)));
|
||||
}
|
||||
|
||||
$: resolvedBattIcon = (() => {
|
||||
if (!$battInfo) return BatteryEmptyIcon;
|
||||
if ($battInfo.battStatus === BATT_STATUS.CHARGING) return BatteryChargingIcon;
|
||||
|
||||
switch (clampBatteryLevel($battInfo.battLevel)) {
|
||||
case 0:
|
||||
return BatteryEmptyIcon;
|
||||
case 1:
|
||||
return BatteryLowIcon;
|
||||
case 2:
|
||||
return BatteryMediumIcon;
|
||||
case 3:
|
||||
return BatteryHighIcon;
|
||||
default:
|
||||
return BatteryFullIcon;
|
||||
}
|
||||
})();
|
||||
|
||||
$: battStatusText = (() => {
|
||||
if (!$battInfo) return "unbekannt";
|
||||
switch ($battInfo.battStatus) {
|
||||
case BATT_STATUS.DISCHARGING:
|
||||
return "Entladen";
|
||||
case BATT_STATUS.FULL:
|
||||
return "Voll";
|
||||
case BATT_STATUS.CHARGING:
|
||||
return "Laden";
|
||||
case BATT_STATUS.ERROR:
|
||||
return "Fehler";
|
||||
default:
|
||||
return "Unbekannt";
|
||||
}
|
||||
})();
|
||||
|
||||
$: battIconClass =
|
||||
$battInfo?.battStatus === BATT_STATUS.ERROR
|
||||
? "w-5 h-5 text-red-500"
|
||||
: $battInfo?.battStatus === BATT_STATUS.CHARGING
|
||||
? "w-5 h-5 text-emerald-600"
|
||||
: "w-5 h-5";
|
||||
</script>
|
||||
|
||||
<div class="text-sm">
|
||||
@@ -127,7 +173,14 @@
|
||||
<tr>
|
||||
<td class="key">Batterie</td>
|
||||
<td class="value flex items-center gap-2">
|
||||
85% <BatteryChargingIcon weight="bold" class="w-5 h-5" /> 1200mAh
|
||||
{#if $battInfo}
|
||||
<span>{$battInfo.battPercent}%</span>
|
||||
<svelte:component this={resolvedBattIcon} weight="bold" class={battIconClass} />
|
||||
<span class="text-text-muted">{$battInfo.battVoltageMv} mV</span>
|
||||
<span class="text-text-muted">({battStatusText})</span>
|
||||
{:else}
|
||||
unbekannt
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -27,6 +27,7 @@ export const DATA = {
|
||||
DEVICE_INFO: 0x02,
|
||||
FS_INFO: 0x03,
|
||||
FW_INFO: 0x04,
|
||||
BATT_INFO: 0x05,
|
||||
|
||||
FILE_GET: 0x20,
|
||||
FILE_PUT: 0x21,
|
||||
@@ -65,4 +66,12 @@ export const FW_STATUS = {
|
||||
PENDING: 0x01,
|
||||
TESTING: 0x02,
|
||||
UNKNOWN: 0xFF,
|
||||
}
|
||||
}
|
||||
|
||||
export const BATT_STATUS = {
|
||||
DISCHARGING: 0x00,
|
||||
FULL: 0x01,
|
||||
CHARGING: 0x02,
|
||||
ERROR: 0x03,
|
||||
UNKNOWN: 0x04,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FRAME, DATA, ZEPHYR_ERRORS } from './constants';
|
||||
import { protocolInfo, deviceInfo, fsInfo, transferStats, fwInfo, resetTransferStats, transferDetails } from '../store';
|
||||
import { protocolInfo, deviceInfo, fsInfo, transferStats, fwInfo, battInfo, resetTransferStats, transferDetails } from '../store';
|
||||
import { addToast } from '../toast';
|
||||
import { SETTINGS } from '../settings';
|
||||
import { crc32 } from './crc32';
|
||||
@@ -84,6 +84,18 @@ export function parseIncomingFrame(view: DataView, sender: FrameSender) {
|
||||
const fwVersion = new TextDecoder().decode(new Uint8Array(view.buffer, 11, fw_version_length));
|
||||
const kernelVersion = new TextDecoder().decode(new Uint8Array(view.buffer, 11 + fw_version_length, kernel_version_length));
|
||||
fwInfo.set({ fwStatus, slot1Size, fwVersion, kernelVersion });
|
||||
break;
|
||||
case DATA.BATT_INFO:
|
||||
if (payloadLength < 6) {
|
||||
console.warn(`Invalid BATT_INFO payload length: ${payloadLength}`);
|
||||
break;
|
||||
}
|
||||
const battStatus = view.getUint8(4);
|
||||
const battLevel = view.getUint8(5);
|
||||
const battPercent = view.getUint8(6);
|
||||
const battVoltageMv = view.getUint16(7, true);
|
||||
battInfo.set({ battStatus, battLevel, battPercent, battVoltageMv });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -365,6 +377,17 @@ export function buildFWInfoRequest(): ArrayBuffer {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function buildBattInfoRequest(): ArrayBuffer {
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint8(0, FRAME.REQUEST);
|
||||
view.setUint16(1, 1, true);
|
||||
view.setUint8(3, DATA.BATT_INFO);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function buildLSRequest(path: string): ArrayBuffer {
|
||||
const encoder = new TextEncoder();
|
||||
const pathBytes = encoder.encode(path);
|
||||
|
||||
@@ -53,6 +53,13 @@ export interface FwInfo {
|
||||
kernelVersion: string;
|
||||
}
|
||||
|
||||
export interface BattInfo {
|
||||
battStatus: number;
|
||||
battLevel: number;
|
||||
battPercent: number;
|
||||
battVoltageMv: number;
|
||||
}
|
||||
|
||||
export interface StorageUsage {
|
||||
totalBytes: number;
|
||||
freeBytes: number;
|
||||
@@ -84,6 +91,7 @@ export const protocolInfo = writable<ProtocolInfo | null>(null);
|
||||
export const deviceInfo = writable<DeviceInfo | null>(null);
|
||||
export const fsInfo = writable<FsInfo | null>(null);
|
||||
export const fwInfo = writable<FwInfo | null>(null);
|
||||
export const battInfo = writable<BattInfo | null>(null);
|
||||
|
||||
// Dateilisten
|
||||
export const buzzerAudioFiles = writable<BuzzerFile[]>([]);
|
||||
@@ -277,6 +285,7 @@ export function resetRemote(): void {
|
||||
deviceInfo.set(null);
|
||||
fsInfo.set(null);
|
||||
fwInfo.set(null);
|
||||
battInfo.set(null);
|
||||
activeDeviceId.set(null);
|
||||
buzzerAudioFiles.set([]);
|
||||
buzzerSysFiles.set([]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { isConnected, deviceInfo, fsInfo, fwInfo, buzzerAudioFiles, buzzerSysFiles, isTransferingRemote, isFetchingLocal, storageUsage, localAudioFiles, transferStats } from './store';
|
||||
import { requestProtocolInfo, requestFSInfo, fetchDirectory, getFile, putFile, deleteRemoteFile, requestDeviceInfo, requestFWInfo } from './transport';
|
||||
import { requestProtocolInfo, requestFSInfo, fetchDirectory, getFile, putFile, deleteRemoteFile, requestDeviceInfo, requestFWInfo, requestBattInfo } from './transport';
|
||||
import type { BuzzerFile } from './types';
|
||||
import { addToast } from './toast';
|
||||
import { getLocalFiles, deleteLocalFile, getLocalFile } from './db';
|
||||
@@ -28,6 +28,7 @@ export async function refreshRemote() {
|
||||
await requestProtocolInfo();
|
||||
await requestFSInfo();
|
||||
await requestFWInfo();
|
||||
await requestBattInfo();
|
||||
await requestDeviceInfo();
|
||||
|
||||
// Kurze Verzögerung für Store-Propagation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { buildLSRequest, buildProtocolInfoRequest, buildDeviceInfoRequest, buildFSInfoRequest, buildFWInfoRequest, setLsResolver, buildFileGetRequest, setFileGetResolver, buildTagsGetRequest, uploadState } from './protocol/parser';
|
||||
import { buildLSRequest, buildProtocolInfoRequest, buildDeviceInfoRequest, buildFSInfoRequest, buildFWInfoRequest, buildBattInfoRequest, setLsResolver, buildFileGetRequest, setFileGetResolver, buildTagsGetRequest, uploadState } from './protocol/parser';
|
||||
import { crc32 } from './protocol/crc32';
|
||||
import { get } from 'svelte/store';
|
||||
import { protocolInfo, transferStats, } from './store';
|
||||
@@ -10,9 +10,45 @@ const isMac = navigator.userAgent.includes('Macintosh') || navigator.userAgent.i
|
||||
const MAX_INFLIGHT = isMac ? SETTINGS.bluetooth.appleMaxInflight : Infinity; // iOS erlaubt nur wenige unbestätigte Nachrichten
|
||||
|
||||
console.log("Transport: Max Inflight Frames =", MAX_INFLIGHT);
|
||||
const BATT_POLL_INTERVAL_MS = 60_000;
|
||||
|
||||
export type FrameSender = (buffer: ArrayBuffer) => Promise<void>;
|
||||
let currentSender: FrameSender | null = null;
|
||||
let battPollTimer: ReturnType<typeof setInterval> | null = null;
|
||||
let isBattPollInFlight = false;
|
||||
|
||||
function stopBattPolling() {
|
||||
if (battPollTimer) {
|
||||
clearInterval(battPollTimer);
|
||||
battPollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldSkipBattPoll(): boolean {
|
||||
return isListing || isFileTransferring || uploadState.active;
|
||||
}
|
||||
|
||||
async function pollBatteryInfo() {
|
||||
if (!currentSender || isBattPollInFlight || shouldSkipBattPoll()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isBattPollInFlight = true;
|
||||
try {
|
||||
await requestBattInfo();
|
||||
} catch (error) {
|
||||
console.debug("Periodic BATT_INFO request failed:", error);
|
||||
} finally {
|
||||
isBattPollInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
function startBattPolling() {
|
||||
stopBattPolling();
|
||||
battPollTimer = setInterval(() => {
|
||||
void pollBatteryInfo();
|
||||
}, BATT_POLL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
export function registerTransport(sender: FrameSender | null) {
|
||||
currentSender = sender;
|
||||
@@ -21,6 +57,7 @@ export function registerTransport(sender: FrameSender | null) {
|
||||
// NEU: Wird von bluetooth.ts oder serial.ts nach dem physischen Connect gerufen
|
||||
export async function handleTransportConnect(sender: FrameSender) {
|
||||
registerTransport(sender);
|
||||
stopBattPolling();
|
||||
|
||||
try {
|
||||
// Basis-Informationen zwingend vorab laden
|
||||
@@ -28,9 +65,11 @@ export async function handleTransportConnect(sender: FrameSender) {
|
||||
await requestFSInfo();
|
||||
await requestDeviceInfo();
|
||||
await requestFWInfo();
|
||||
await requestBattInfo();
|
||||
|
||||
// Erst wenn diese Basisdaten da sind, wird die UI freigeschaltet
|
||||
isConnected.set(true);
|
||||
startBattPolling();
|
||||
} catch (error) {
|
||||
console.error("Transport-Initialisierung fehlgeschlagen:", error);
|
||||
handleTransportDisconnect();
|
||||
@@ -58,6 +97,10 @@ export async function requestFWInfo() {
|
||||
await sendFrame(buildFWInfoRequest());
|
||||
}
|
||||
|
||||
export async function requestBattInfo() {
|
||||
await sendFrame(buildBattInfoRequest());
|
||||
}
|
||||
|
||||
let isListing = false;
|
||||
|
||||
export async function fetchDirectory(path: string): Promise<any[]> {
|
||||
@@ -85,6 +128,7 @@ export async function fetchDirectory(path: string): Promise<any[]> {
|
||||
}
|
||||
|
||||
export function handleTransportDisconnect() {
|
||||
stopBattPolling();
|
||||
registerTransport(null);
|
||||
resetRemote();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user