Compare commits
3 Commits
87cba0b419
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 78f0bce5dd | |||
| e74437a846 | |||
| 2d3ea34603 |
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@@ -7,6 +7,13 @@
|
|||||||
"name": "Launch firmware/build_nrf52840dk_debug",
|
"name": "Launch firmware/build_nrf52840dk_debug",
|
||||||
"config": "${workspaceFolder}/firmware/build_nrf52840dk_debug",
|
"config": "${workspaceFolder}/firmware/build_nrf52840dk_debug",
|
||||||
"runToEntryPoint": "main"
|
"runToEntryPoint": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "nrf-connect",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch firmware/build_buzzy/firmware",
|
||||||
|
"config": "${workspaceFolder}/firmware/build_buzzy/firmware",
|
||||||
|
"runToEntryPoint": "main"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -8,6 +8,7 @@
|
|||||||
],
|
],
|
||||||
"cmake.sourceDirectory": "C:/Projekte/buzzer_2/firmware/libs/ble_mgmt",
|
"cmake.sourceDirectory": "C:/Projekte/buzzer_2/firmware/libs/ble_mgmt",
|
||||||
"nrf-connect.debugging.bindings": {
|
"nrf-connect.debugging.bindings": {
|
||||||
"${workspaceFolder}/firmware/build_nrf52840dk_debug": "Launch firmware/build_nrf52840dk_debug"
|
"${workspaceFolder}/firmware/build_nrf52840dk_debug": "Launch firmware/build_nrf52840dk_debug",
|
||||||
|
"${workspaceFolder}/firmware/build_buzzy/firmware": "Launch firmware/build_buzzy/firmware"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
26
.vscode/tasks.json
vendored
Normal file
26
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "MAC RTT Streamer",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "bash",
|
||||||
|
"args": [
|
||||||
|
"-c",
|
||||||
|
"while true; do nc localhost 19021; sleep 0.2; done"
|
||||||
|
],
|
||||||
|
"presentation": {
|
||||||
|
"echo": false,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": false,
|
||||||
|
"clear": true
|
||||||
|
},
|
||||||
|
"runOptions": {
|
||||||
|
"runOn": "folderOpen"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
### Logging
|
### Logging
|
||||||
CONFIG_LOG=y
|
CONFIG_LOG=y
|
||||||
CONFIG_LOG_MODE_IMMEDIATE=y
|
#CONFIG_LOG_MODE_IMMEDIATE=y
|
||||||
CONFIG_DEBUG=y
|
CONFIG_DEBUG=y
|
||||||
|
|
||||||
CONFIG_DEBUG_OPTIMIZATIONS=y
|
CONFIG_DEBUG_OPTIMIZATIONS=y
|
||||||
@@ -9,20 +9,21 @@ CONFIG_INIT_STACKS=y
|
|||||||
CONFIG_THREAD_STACK_INFO=y
|
CONFIG_THREAD_STACK_INFO=y
|
||||||
|
|
||||||
### Lib logging levels
|
### Lib logging levels
|
||||||
CONFIG_BATT_MGMT_LOG_LEVEL_DBG=y
|
# CONFIG_BATT_MGMT_LOG_LEVEL_DBG=y
|
||||||
# CONFIG_BATT_MGMT_BLINK_INTERVAL_LOGGING=y
|
# CONFIG_BATT_MGMT_BLINK_INTERVAL_LOGGING=y
|
||||||
CONFIG_USB_MGMT_LOG_LEVEL_DBG=y
|
# CONFIG_USB_MGMT_LOG_LEVEL_DBG=y
|
||||||
|
CONFIG_BUZZ_PROTO_LOG_LEVEL_DBG=y
|
||||||
|
|
||||||
### Bluetooth
|
### Bluetooth
|
||||||
CONFIG_BLE_MGMT=n
|
CONFIG_BLE_MGMT=y
|
||||||
CONFIG_BT_LOG_LEVEL_WRN=n
|
CONFIG_BT_LOG_LEVEL_WRN=y
|
||||||
|
|
||||||
### Audio
|
### Audio
|
||||||
CONFIG_BUZZ_AUDIO=n
|
CONFIG_BUZZ_AUDIO=n
|
||||||
|
|
||||||
### Shell features shared by all debug variants
|
### Shell features shared by all debug variants
|
||||||
CONFIG_SHELL=y
|
CONFIG_SHELL=y
|
||||||
CONFIG_SHELL_LOG_BACKEND=n
|
CONFIG_SHELL_LOG_BACKEND=y
|
||||||
CONFIG_FILE_SYSTEM_SHELL=y
|
CONFIG_FILE_SYSTEM_SHELL=y
|
||||||
CONFIG_SHELL_STACK_SIZE=2048
|
CONFIG_SHELL_STACK_SIZE=2048
|
||||||
CONFIG_FILE_SYSTEM_SHELL_LS_SIZE=y
|
CONFIG_FILE_SYSTEM_SHELL_LS_SIZE=y
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
#define BATT_MGMT_OVERSAMPLING_16X 16U
|
#define BATT_MGMT_OVERSAMPLING_16X 16U
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
BATT_STATE_DISCHARGING = 0,
|
BATT_STATE_DISCHARGING = 0x00,
|
||||||
BATT_STATE_FULL,
|
BATT_STATE_FULL= 0x01,
|
||||||
BATT_STATE_CHARGING,
|
BATT_STATE_CHARGING = 0x02,
|
||||||
BATT_STATE_ERROR,
|
BATT_STATE_ERROR = 0x03,
|
||||||
BATT_STATE_UNKNOWN,
|
BATT_STATE_UNKNOWN = 0x04,
|
||||||
} batt_mgmt_state_t;
|
} batt_mgmt_state_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ enum buzz_data_type
|
|||||||
BUZZ_DATA_DEVICE_INFO = 0x02,
|
BUZZ_DATA_DEVICE_INFO = 0x02,
|
||||||
BUZZ_DATA_FS_INFO = 0x03,
|
BUZZ_DATA_FS_INFO = 0x03,
|
||||||
BUZZ_DATA_FW_INFO = 0x04,
|
BUZZ_DATA_FW_INFO = 0x04,
|
||||||
|
BUZZ_DATA_BATT_INFO = 0x05,
|
||||||
|
|
||||||
BUZZ_DATA_FILE_GET = 0x20,
|
BUZZ_DATA_FILE_GET = 0x20,
|
||||||
BUZZ_DATA_FILE_PUT = 0x21,
|
BUZZ_DATA_FILE_PUT = 0x21,
|
||||||
@@ -122,6 +123,17 @@ struct __attribute__((packed)) buzz_resp_fw_info
|
|||||||
uint8_t kernel_version_length; /* Länge der Kernel-Versionszeichenkette */
|
uint8_t kernel_version_length; /* Länge der Kernel-Versionszeichenkette */
|
||||||
char data[]; /* Variabler String ohne Null-Terminierung: [fw_version][kernel_version] */
|
char data[]; /* Variabler String ohne Null-Terminierung: [fw_version][kernel_version] */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Payload für die Batterie-Infos */
|
||||||
|
struct __attribute__((packed)) buzz_resp_batt_info
|
||||||
|
{
|
||||||
|
uint8_t data_type; /* BUZZ_DATA_BATT_INFO */
|
||||||
|
uint8_t batt_status; /* batt_mgmt_state_t */
|
||||||
|
uint8_t batt_level; /* 0..4 */
|
||||||
|
uint8_t batt_percent; /* 0..100 */
|
||||||
|
uint16_t batt_voltage_mv; /* Little Endian */
|
||||||
|
};
|
||||||
|
|
||||||
/* Payload für das Entfernen einer Datei */
|
/* Payload für das Entfernen einer Datei */
|
||||||
struct __attribute__((packed)) buzz_rm_file_payload
|
struct __attribute__((packed)) buzz_rm_file_payload
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "buzz_proto.h"
|
#include "buzz_proto.h"
|
||||||
|
#include "batt_mgmt.h"
|
||||||
#include "fs_mgmt.h"
|
#include "fs_mgmt.h"
|
||||||
#include "fw_mgmt.h"
|
#include "fw_mgmt.h"
|
||||||
|
|
||||||
@@ -293,6 +294,43 @@ static void handle_fw_info_request(struct buzz_frame_msg *msg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void handle_batt_info_request(struct buzz_frame_msg *msg)
|
||||||
|
{
|
||||||
|
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||||
|
struct buzz_resp_batt_info *resp_data = (struct buzz_resp_batt_info *)(msg->data_ptr + sizeof(*hdr));
|
||||||
|
batt_mgmt_info_t batt_info;
|
||||||
|
uint16_t voltage_mv = 0;
|
||||||
|
int rc = batt_mgmt_get_info(&batt_info);
|
||||||
|
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
LOG_WRN("Failed to get battery info: %d", rc);
|
||||||
|
send_error_frame(msg, abs(rc));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batt_info.voltage_mv > 0)
|
||||||
|
{
|
||||||
|
voltage_mv = (batt_info.voltage_mv > UINT16_MAX) ? UINT16_MAX : (uint16_t)batt_info.voltage_mv;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr->frame_type = BUZZ_FRAME_RESPONSE;
|
||||||
|
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_batt_info));
|
||||||
|
|
||||||
|
resp_data->data_type = BUZZ_DATA_BATT_INFO;
|
||||||
|
resp_data->batt_status = (uint8_t)batt_info.state;
|
||||||
|
resp_data->batt_level = batt_info.level;
|
||||||
|
resp_data->batt_percent = batt_info.percent;
|
||||||
|
resp_data->batt_voltage_mv = sys_cpu_to_le16(voltage_mv);
|
||||||
|
|
||||||
|
if (msg->reply_cb)
|
||||||
|
{
|
||||||
|
msg->reply_cb(msg->data_ptr, sizeof(struct buzz_proto_header) + sizeof(struct buzz_resp_batt_info));
|
||||||
|
}
|
||||||
|
LOG_DBG("Battery Info sent: state=%u, level=%u, percent=%u, voltage=%u mV",
|
||||||
|
batt_info.state, batt_info.level, batt_info.percent, batt_info.voltage_mv);
|
||||||
|
}
|
||||||
|
|
||||||
static void handle_ls_request(struct buzz_frame_msg *msg)
|
static void handle_ls_request(struct buzz_frame_msg *msg)
|
||||||
{
|
{
|
||||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||||
@@ -698,6 +736,11 @@ static void handle_request(struct buzz_frame_msg *msg)
|
|||||||
handle_fw_info_request(msg);
|
handle_fw_info_request(msg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BUZZ_DATA_BATT_INFO:
|
||||||
|
LOG_DBG("Received BATT Info Request");
|
||||||
|
handle_batt_info_request(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
case BUZZ_DATA_FILE_GET:
|
case BUZZ_DATA_FILE_GET:
|
||||||
LOG_DBG("Received FILE_GET Request");
|
LOG_DBG("Received FILE_GET Request");
|
||||||
handle_file_get_request(msg, false);
|
handle_file_get_request(msg, false);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
#endif // CONFIG_BATT_MGMT
|
#endif // CONFIG_BATT_MGMT
|
||||||
#endif // CONFIG_LOG
|
#endif // CONFIG_LOG
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int32_t rem_ms = k_sleep(K_FOREVER);
|
int32_t rem_ms = k_sleep(K_FOREVER);
|
||||||
LOG_WRN("main woke unexpectedly (remaining=%d ms)", rem_ms);
|
LOG_WRN("main woke unexpectedly (remaining=%d ms)", rem_ms);
|
||||||
|
|||||||
16
protocol.md
16
protocol.md
@@ -92,6 +92,7 @@ Das Protokoll ist so ausgelegt, dass es mit mindestens 100 Bytes auskommen sollt
|
|||||||
| `0x02` | `DEVICE_INFO` | aktiv | Device-Infos (Board, Revision, SOC, ID) |
|
| `0x02` | `DEVICE_INFO` | aktiv | Device-Infos (Board, Revision, SOC, ID) |
|
||||||
| `0x03` | `FS_INFO` | aktiv | Dateisystem- und Pfadinfos |
|
| `0x03` | `FS_INFO` | aktiv | Dateisystem- und Pfadinfos |
|
||||||
| `0x04` | `FW_INFO` | aktiv | Info über Firmware-Status und -Version sowie Kernelversion |
|
| `0x04` | `FW_INFO` | aktiv | Info über Firmware-Status und -Version sowie Kernelversion |
|
||||||
|
| `0x05` | `BATT_INFO` | aktiv | Info über die Batterie |
|
||||||
| `0x20` | `FILE_GET` | aktiv | Datei vom Device streamen |
|
| `0x20` | `FILE_GET` | aktiv | Datei vom Device streamen |
|
||||||
| `0x21` | `FILE_PUT` | aktiv | Datei zum Device hochladen |
|
| `0x21` | `FILE_PUT` | aktiv | Datei zum Device hochladen |
|
||||||
| `0x22` | `TAGS_GET` | aktiv | nur Tag-Bereich streamen |
|
| `0x22` | `TAGS_GET` | aktiv | nur Tag-Bereich streamen |
|
||||||
@@ -238,6 +239,7 @@ Request: keine Zusatzdaten
|
|||||||
Response:
|
Response:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
uint8_t data_type; /* 0x04 */
|
||||||
uint8_t fw_status; /* 0x00: Confirmed, 0x01: Pending, 0x02: Testing, 0xFF: Unbekannt */
|
uint8_t fw_status; /* 0x00: Confirmed, 0x01: Pending, 0x02: Testing, 0xFF: Unbekannt */
|
||||||
uint32_t slot1_size; /* (LE) Grösse des Firmware Update Slots */
|
uint32_t slot1_size; /* (LE) Grösse des Firmware Update Slots */
|
||||||
uint8_t fw_version_len; /* Länge des Firmware-Versionsstring */
|
uint8_t fw_version_len; /* Länge des Firmware-Versionsstring */
|
||||||
@@ -247,6 +249,20 @@ uint8_t data[]; /* FW-Version und Kernelversion, ohne Nullterminier
|
|||||||
|
|
||||||
***Hinweis:*** in der Aktuellen implementierung werden die Versionen auf 32 Zeichen limitiert.
|
***Hinweis:*** in der Aktuellen implementierung werden die Versionen auf 32 Zeichen limitiert.
|
||||||
|
|
||||||
|
### `BATT_INFO` (`0x05`)
|
||||||
|
|
||||||
|
Request: keine Zusatzdaten
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```c
|
||||||
|
uint8_t data_type; /* 0x05 */
|
||||||
|
uint8_t batt_status; /* 0x00: Discharging, 0x01: Full, 0x02: Charging, 0x03: Error, 0x04: Unknown */
|
||||||
|
uint8_t batt_level; /* 0-4, Anzahl Striche für den Akku */
|
||||||
|
uint8_t batt_percent; /* Akku-Füllstand in Prozent */
|
||||||
|
uint16_t batt_voltage_mv; /* (LE) Batteriespannung in mV */
|
||||||
|
```
|
||||||
|
|
||||||
### `LS` (`0x40`)
|
### `LS` (`0x40`)
|
||||||
|
|
||||||
Request-Payload:
|
Request-Payload:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FlashUsage from "./FlashUsage.svelte";
|
import FlashUsage from "./FlashUsage.svelte";
|
||||||
import { deviceInfo, fwInfo } from "../lib/store";
|
import { battInfo, deviceInfo, fwInfo } from "../lib/store";
|
||||||
import { FW_STATUS } from "../lib/protocol/constants";
|
import { BATT_STATUS, FW_STATUS } from "../lib/protocol/constants";
|
||||||
import { tooltip } from "../lib/actions/tooltip";
|
import { tooltip } from "../lib/actions/tooltip";
|
||||||
import {
|
import {
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
@@ -13,6 +13,52 @@
|
|||||||
BatteryFullIcon,
|
BatteryFullIcon,
|
||||||
BatteryChargingIcon,
|
BatteryChargingIcon,
|
||||||
} from "phosphor-svelte";
|
} 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>
|
</script>
|
||||||
|
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
@@ -127,7 +173,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="key">Batterie</td>
|
<td class="key">Batterie</td>
|
||||||
<td class="value flex items-center gap-2">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const DATA = {
|
|||||||
DEVICE_INFO: 0x02,
|
DEVICE_INFO: 0x02,
|
||||||
FS_INFO: 0x03,
|
FS_INFO: 0x03,
|
||||||
FW_INFO: 0x04,
|
FW_INFO: 0x04,
|
||||||
|
BATT_INFO: 0x05,
|
||||||
|
|
||||||
FILE_GET: 0x20,
|
FILE_GET: 0x20,
|
||||||
FILE_PUT: 0x21,
|
FILE_PUT: 0x21,
|
||||||
@@ -65,4 +66,12 @@ export const FW_STATUS = {
|
|||||||
PENDING: 0x01,
|
PENDING: 0x01,
|
||||||
TESTING: 0x02,
|
TESTING: 0x02,
|
||||||
UNKNOWN: 0xFF,
|
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 { 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 { addToast } from '../toast';
|
||||||
import { SETTINGS } from '../settings';
|
import { SETTINGS } from '../settings';
|
||||||
import { crc32 } from './crc32';
|
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 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));
|
const kernelVersion = new TextDecoder().decode(new Uint8Array(view.buffer, 11 + fw_version_length, kernel_version_length));
|
||||||
fwInfo.set({ fwStatus, slot1Size, fwVersion, kernelVersion });
|
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;
|
break;
|
||||||
|
|
||||||
@@ -365,6 +377,17 @@ export function buildFWInfoRequest(): ArrayBuffer {
|
|||||||
return buffer;
|
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 {
|
export function buildLSRequest(path: string): ArrayBuffer {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const pathBytes = encoder.encode(path);
|
const pathBytes = encoder.encode(path);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const SETTINGS = {
|
|||||||
bluetooth: {
|
bluetooth: {
|
||||||
connectionTimeoutMs: 3000, // Timeout für den Verbindungsaufbau
|
connectionTimeoutMs: 3000, // Timeout für den Verbindungsaufbau
|
||||||
appleMaxInflight: 15, // iOS erlaubt nur wenige unbestätigte Nachrichten, daher begrenzen wir die Anzahl der gleichzeitig gesendeten Frames
|
appleMaxInflight: 15, // iOS erlaubt nur wenige unbestätigte Nachrichten, daher begrenzen wir die Anzahl der gleichzeitig gesendeten Frames
|
||||||
|
batteryPollIntervalMs: 60_000, // Intervall für periodische BATT_INFO-Abfragen
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
toastDurationMs: 5000,
|
toastDurationMs: 5000,
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ export interface FwInfo {
|
|||||||
kernelVersion: string;
|
kernelVersion: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BattInfo {
|
||||||
|
battStatus: number;
|
||||||
|
battLevel: number;
|
||||||
|
battPercent: number;
|
||||||
|
battVoltageMv: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StorageUsage {
|
export interface StorageUsage {
|
||||||
totalBytes: number;
|
totalBytes: number;
|
||||||
freeBytes: number;
|
freeBytes: number;
|
||||||
@@ -84,6 +91,7 @@ export const protocolInfo = writable<ProtocolInfo | null>(null);
|
|||||||
export const deviceInfo = writable<DeviceInfo | null>(null);
|
export const deviceInfo = writable<DeviceInfo | null>(null);
|
||||||
export const fsInfo = writable<FsInfo | null>(null);
|
export const fsInfo = writable<FsInfo | null>(null);
|
||||||
export const fwInfo = writable<FwInfo | null>(null);
|
export const fwInfo = writable<FwInfo | null>(null);
|
||||||
|
export const battInfo = writable<BattInfo | null>(null);
|
||||||
|
|
||||||
// Dateilisten
|
// Dateilisten
|
||||||
export const buzzerAudioFiles = writable<BuzzerFile[]>([]);
|
export const buzzerAudioFiles = writable<BuzzerFile[]>([]);
|
||||||
@@ -277,6 +285,7 @@ export function resetRemote(): void {
|
|||||||
deviceInfo.set(null);
|
deviceInfo.set(null);
|
||||||
fsInfo.set(null);
|
fsInfo.set(null);
|
||||||
fwInfo.set(null);
|
fwInfo.set(null);
|
||||||
|
battInfo.set(null);
|
||||||
activeDeviceId.set(null);
|
activeDeviceId.set(null);
|
||||||
buzzerAudioFiles.set([]);
|
buzzerAudioFiles.set([]);
|
||||||
buzzerSysFiles.set([]);
|
buzzerSysFiles.set([]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { isConnected, deviceInfo, fsInfo, fwInfo, buzzerAudioFiles, buzzerSysFiles, isTransferingRemote, isFetchingLocal, storageUsage, localAudioFiles, transferStats } from './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 type { BuzzerFile } from './types';
|
||||||
import { addToast } from './toast';
|
import { addToast } from './toast';
|
||||||
import { getLocalFiles, deleteLocalFile, getLocalFile } from './db';
|
import { getLocalFiles, deleteLocalFile, getLocalFile } from './db';
|
||||||
@@ -28,6 +28,7 @@ export async function refreshRemote() {
|
|||||||
await requestProtocolInfo();
|
await requestProtocolInfo();
|
||||||
await requestFSInfo();
|
await requestFSInfo();
|
||||||
await requestFWInfo();
|
await requestFWInfo();
|
||||||
|
await requestBattInfo();
|
||||||
await requestDeviceInfo();
|
await requestDeviceInfo();
|
||||||
|
|
||||||
// Kurze Verzögerung für Store-Propagation
|
// 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 { crc32 } from './protocol/crc32';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { protocolInfo, transferStats, } from './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
|
const MAX_INFLIGHT = isMac ? SETTINGS.bluetooth.appleMaxInflight : Infinity; // iOS erlaubt nur wenige unbestätigte Nachrichten
|
||||||
|
|
||||||
console.log("Transport: Max Inflight Frames =", MAX_INFLIGHT);
|
console.log("Transport: Max Inflight Frames =", MAX_INFLIGHT);
|
||||||
|
const BATT_POLL_INTERVAL_MS = Math.max(1_000, SETTINGS.bluetooth.batteryPollIntervalMs);
|
||||||
|
|
||||||
export type FrameSender = (buffer: ArrayBuffer) => Promise<void>;
|
export type FrameSender = (buffer: ArrayBuffer) => Promise<void>;
|
||||||
let currentSender: FrameSender | null = null;
|
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) {
|
export function registerTransport(sender: FrameSender | null) {
|
||||||
currentSender = sender;
|
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
|
// NEU: Wird von bluetooth.ts oder serial.ts nach dem physischen Connect gerufen
|
||||||
export async function handleTransportConnect(sender: FrameSender) {
|
export async function handleTransportConnect(sender: FrameSender) {
|
||||||
registerTransport(sender);
|
registerTransport(sender);
|
||||||
|
stopBattPolling();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Basis-Informationen zwingend vorab laden
|
// Basis-Informationen zwingend vorab laden
|
||||||
@@ -28,9 +65,11 @@ export async function handleTransportConnect(sender: FrameSender) {
|
|||||||
await requestFSInfo();
|
await requestFSInfo();
|
||||||
await requestDeviceInfo();
|
await requestDeviceInfo();
|
||||||
await requestFWInfo();
|
await requestFWInfo();
|
||||||
|
await requestBattInfo();
|
||||||
|
|
||||||
// Erst wenn diese Basisdaten da sind, wird die UI freigeschaltet
|
// Erst wenn diese Basisdaten da sind, wird die UI freigeschaltet
|
||||||
isConnected.set(true);
|
isConnected.set(true);
|
||||||
|
startBattPolling();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Transport-Initialisierung fehlgeschlagen:", error);
|
console.error("Transport-Initialisierung fehlgeschlagen:", error);
|
||||||
handleTransportDisconnect();
|
handleTransportDisconnect();
|
||||||
@@ -58,6 +97,10 @@ export async function requestFWInfo() {
|
|||||||
await sendFrame(buildFWInfoRequest());
|
await sendFrame(buildFWInfoRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function requestBattInfo() {
|
||||||
|
await sendFrame(buildBattInfoRequest());
|
||||||
|
}
|
||||||
|
|
||||||
let isListing = false;
|
let isListing = false;
|
||||||
|
|
||||||
export async function fetchDirectory(path: string): Promise<any[]> {
|
export async function fetchDirectory(path: string): Promise<any[]> {
|
||||||
@@ -85,6 +128,7 @@ export async function fetchDirectory(path: string): Promise<any[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function handleTransportDisconnect() {
|
export function handleTransportDisconnect() {
|
||||||
|
stopBattPolling();
|
||||||
registerTransport(null);
|
registerTransport(null);
|
||||||
resetRemote();
|
resetRemote();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user