Compare commits
2 Commits
87cba0b419
...
e74437a846
| Author | SHA1 | Date | |
|---|---|---|---|
| e74437a846 | |||
| 2d3ea34603 |
@@ -1,6 +1,6 @@
|
||||
### Logging
|
||||
CONFIG_LOG=y
|
||||
CONFIG_LOG_MODE_IMMEDIATE=y
|
||||
#CONFIG_LOG_MODE_IMMEDIATE=y
|
||||
CONFIG_DEBUG=y
|
||||
|
||||
CONFIG_DEBUG_OPTIMIZATIONS=y
|
||||
@@ -14,15 +14,15 @@ CONFIG_BATT_MGMT_LOG_LEVEL_DBG=y
|
||||
CONFIG_USB_MGMT_LOG_LEVEL_DBG=y
|
||||
|
||||
### Bluetooth
|
||||
CONFIG_BLE_MGMT=n
|
||||
CONFIG_BT_LOG_LEVEL_WRN=n
|
||||
CONFIG_BLE_MGMT=y
|
||||
CONFIG_BT_LOG_LEVEL_WRN=y
|
||||
|
||||
### Audio
|
||||
CONFIG_BUZZ_AUDIO=n
|
||||
|
||||
### Shell features shared by all debug variants
|
||||
CONFIG_SHELL=y
|
||||
CONFIG_SHELL_LOG_BACKEND=n
|
||||
CONFIG_SHELL_LOG_BACKEND=y
|
||||
CONFIG_FILE_SYSTEM_SHELL=y
|
||||
CONFIG_SHELL_STACK_SIZE=2048
|
||||
CONFIG_FILE_SYSTEM_SHELL_LS_SIZE=y
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#define BATT_MGMT_OVERSAMPLING_16X 16U
|
||||
|
||||
typedef enum {
|
||||
BATT_STATE_DISCHARGING = 0,
|
||||
BATT_STATE_FULL,
|
||||
BATT_STATE_CHARGING,
|
||||
BATT_STATE_ERROR,
|
||||
BATT_STATE_UNKNOWN,
|
||||
BATT_STATE_DISCHARGING = 0x00,
|
||||
BATT_STATE_FULL= 0x01,
|
||||
BATT_STATE_CHARGING = 0x02,
|
||||
BATT_STATE_ERROR = 0x03,
|
||||
BATT_STATE_UNKNOWN = 0x04,
|
||||
} batt_mgmt_state_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -36,6 +36,7 @@ enum buzz_data_type
|
||||
BUZZ_DATA_DEVICE_INFO = 0x02,
|
||||
BUZZ_DATA_FS_INFO = 0x03,
|
||||
BUZZ_DATA_FW_INFO = 0x04,
|
||||
BUZZ_DATA_BATT_INFO = 0x05,
|
||||
|
||||
BUZZ_DATA_FILE_GET = 0x20,
|
||||
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 */
|
||||
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 */
|
||||
struct __attribute__((packed)) buzz_rm_file_payload
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "buzz_proto.h"
|
||||
#include "batt_mgmt.h"
|
||||
#include "fs_mgmt.h"
|
||||
#include "fw_mgmt.h"
|
||||
|
||||
@@ -293,6 +294,41 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_ls_request(struct buzz_frame_msg *msg)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
@@ -698,6 +734,11 @@ static void handle_request(struct buzz_frame_msg *msg)
|
||||
handle_fw_info_request(msg);
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_BATT_INFO:
|
||||
LOG_DBG("Received BATT Info Request");
|
||||
handle_batt_info_request(msg);
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_FILE_GET:
|
||||
LOG_DBG("Received FILE_GET Request");
|
||||
handle_file_get_request(msg, false);
|
||||
|
||||
@@ -93,6 +93,20 @@ int main(void)
|
||||
} else {
|
||||
LOG_WRN("Battery info read failed: %d", batt_rc);
|
||||
}
|
||||
for (;;) {
|
||||
k_sleep(K_SECONDS(5));
|
||||
batt_rc = batt_mgmt_get_info(&batt_info);
|
||||
if (batt_rc == 0) {
|
||||
LOG_INF("Battery: %d mV, %u%%, level=%u, state=%s (%d)",
|
||||
batt_info.voltage_mv,
|
||||
batt_info.percent,
|
||||
batt_info.level,
|
||||
battery_state_to_str(batt_info.state),
|
||||
batt_info.state);
|
||||
} else {
|
||||
LOG_WRN("Battery info read failed: %d", batt_rc);
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_BATT_MGMT
|
||||
#endif // CONFIG_LOG
|
||||
|
||||
|
||||
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) |
|
||||
| `0x03` | `FS_INFO` | aktiv | Dateisystem- und Pfadinfos |
|
||||
| `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 |
|
||||
| `0x21` | `FILE_PUT` | aktiv | Datei zum Device hochladen |
|
||||
| `0x22` | `TAGS_GET` | aktiv | nur Tag-Bereich streamen |
|
||||
@@ -238,6 +239,7 @@ Request: keine Zusatzdaten
|
||||
Response:
|
||||
|
||||
```c
|
||||
uint8_t data_type; /* 0x04 */
|
||||
uint8_t fw_status; /* 0x00: Confirmed, 0x01: Pending, 0x02: Testing, 0xFF: Unbekannt */
|
||||
uint32_t slot1_size; /* (LE) Grösse des Firmware Update Slots */
|
||||
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.
|
||||
|
||||
### `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`)
|
||||
|
||||
Request-Payload:
|
||||
|
||||
@@ -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,
|
||||
@@ -66,3 +67,11 @@ export const FW_STATUS = {
|
||||
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);
|
||||
|
||||
@@ -7,6 +7,7 @@ export const SETTINGS = {
|
||||
bluetooth: {
|
||||
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
|
||||
batteryPollIntervalMs: 60_000, // Intervall für periodische BATT_INFO-Abfragen
|
||||
},
|
||||
ui: {
|
||||
toastDurationMs: 5000,
|
||||
|
||||
@@ -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 = Math.max(1_000, SETTINGS.bluetooth.batteryPollIntervalMs);
|
||||
|
||||
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