From 274025ed33c074ef8aaf4a1b3a2dc2e0c8c8f8c8 Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Fri, 3 Apr 2026 17:25:27 +0700 Subject: [PATCH] feat: add brew app connection - initialize tcp communication with brew app - WIP value editor sync Signed-off-by: pakintada@gmail.com --- bun.lockb | Bin 203594 -> 203594 bytes package.json | 4 +- src/lib/components/dashboard-quick-adb.svelte | 18 ++- .../recipe-details/recipe-detail.svelte | 88 +++++++++++- .../recipelist-value-editor.svelte | 21 +-- .../recipe-details/recipelist-value.svelte | 8 +- .../components/recipe-editor-dialog.svelte | 36 ++++- src/lib/core/adb/adb.ts | 126 ++++++++++++++---- src/lib/core/brew/command.ts | 46 +++++++ src/lib/core/handlers/adbPayloadHandler.ts | 41 ++++++ src/lib/core/stores/adbWriter.ts | 28 ++++ src/lib/helpers/updater.ts | 1 - src/routes/(authed)/+layout.svelte | 6 + src/routes/(authed)/tools/brew/+page.svelte | 77 +++++++++-- 14 files changed, 431 insertions(+), 69 deletions(-) create mode 100644 src/lib/core/brew/command.ts create mode 100644 src/lib/core/handlers/adbPayloadHandler.ts create mode 100644 src/lib/core/stores/adbWriter.ts diff --git a/bun.lockb b/bun.lockb index 0018d14f4a3740979241c570b6985ec52176c62f..8f834d418057b7dd81d9a0437c7d60e4c12dd67e 100755 GIT binary patch delta 282 zcmX@Ljpx)ho(Xyi6%xYf8rc&&FV@ctnBT+YoV=kM`+U>GtjLGMijr0t*H?uI!Wn?zfGv2Pu#>BzG!5C*~tY-j} YFy>;)egcx1zVIDW5?C$UdnOkh0M-&~qW}N^ delta 282 zcmX@Ljpx)ho(XyihL(bT=IyOZG7ikUV;ODFs9a%rf$ieM?R*9biVPkp4I^VsjAl z5vKR`f6~va-BsTlF~eh*+9z?H-^}j(e+76$4^(tm-A+EW&x(#F3e!X@*iE$c!K zUkqGxY>!uiz|8HsF6TOW&TN-8V@y8BY@lbjy_tn+E+ez4p2>D)HYN@h4koBA+l{%H VvY&t?rZ0TQlmu4G_MXW_2LQ7~Z1w;E diff --git a/package.json b/package.json index 6e5a125..21a2ea8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@chromatic-com/storybook": "^4.1.3", - "@internationalized/date": "^3.10.1", + "@internationalized/date": "^3.12.0", "@lucide/svelte": "^0.561.0", "@storybook/addon-a11y": "^10.2.0", "@storybook/addon-docs": "^10.2.0", @@ -35,7 +35,7 @@ "@tanstack/table-core": "^8.21.3", "@types/node": "^22.19.7", "@vitest/browser": "^3.2.4", - "bits-ui": "^2.15.4", + "bits-ui": "^2.16.3", "clsx": "^2.1.1", "paneforge": "^1.0.0-next.6", "playwright": "^1.57.0", diff --git a/src/lib/components/dashboard-quick-adb.svelte b/src/lib/components/dashboard-quick-adb.svelte index 15b3c33..bec5fc8 100644 --- a/src/lib/components/dashboard-quick-adb.svelte +++ b/src/lib/components/dashboard-quick-adb.svelte @@ -12,11 +12,15 @@ import { handleIncomingMessages } from '$lib/core/handlers/messageHandler'; import { auth as authStore } from '$lib/core/stores/auth'; import { get } from 'svelte/store'; - import { AdbDaemonWebUsbDeviceManager } from '@yume-chan/adb-daemon-webusb'; + import { + AdbDaemonWebUsbDevice, + AdbDaemonWebUsbDeviceManager + } from '@yume-chan/adb-daemon-webusb'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; import { onMount } from 'svelte'; import { deviceCredentialManager } from '$lib/core/adb/deviceCredManager'; import { file } from 'zod/mini'; + import { addNotification } from '$lib/core/stores/noti'; let { enableComponent = true } = $props(); @@ -161,6 +165,9 @@ } else { console.log('push pull not ok', result); } + + // clean file + await adb.executeCmd('rm /sdcard/coffeevending/test.json'); } } catch (error) { console.log('test push file failed', error); @@ -205,6 +212,9 @@ } async function disconnectAdb() { + // clean up no password flag + // return to engine + await adb.disconnect(); connectionButtonText = 'Connect'; connectionButtonVariant = 'default'; @@ -313,6 +323,12 @@ } catch (ignored) {} } + if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) { + addNotification( + 'ERR:Device is already in use by another program, please close the program and try again' + ); + } + return false; } } catch (e: any) { diff --git a/src/lib/components/recipe-details/recipe-detail.svelte b/src/lib/components/recipe-details/recipe-detail.svelte index dfd93af..3d80f8f 100644 --- a/src/lib/components/recipe-details/recipe-detail.svelte +++ b/src/lib/components/recipe-details/recipe-detail.svelte @@ -3,9 +3,11 @@ import * as Card from '$lib/components/ui/card/index'; import Label from '$lib/components/ui/label/label.svelte'; import Input from '$lib/components/ui/input/input.svelte'; - import { onDestroy, onMount } from 'svelte'; + import Button from '$lib/components/ui/button/button.svelte'; + import { createEventDispatcher, onDestroy, onMount } from 'svelte'; import RecipelistTable from './recipelist-table.svelte'; + import * as adb from '$lib/core/adb/adb'; import { columns, type RecipelistMaterial } from './columns'; import { get, readable, writable } from 'svelte/store'; import { @@ -17,6 +19,11 @@ import { machineInfoStore } from '$lib/core/stores/machineInfoStore'; import MachineInfo from '../machine-info.svelte'; import { v4 as uuidv4 } from 'uuid'; + import { addNotification } from '$lib/core/stores/noti'; + import { env } from '$env/dynamic/public'; + import { sendCommand, sendReset } from '$lib/core/brew/command'; + import { isAdbWriterAvailable } from '$lib/core/stores/adbWriter'; + import { sendToAndroid } from '$lib/core/stores/adbWriter'; // let { @@ -35,6 +42,8 @@ let toppingSlotState: any = $state([]); + const recipeDetailDispatch = createEventDispatcher(); + function remappingToColumn(data: any[]): RecipelistMaterial[] { let ret: RecipelistMaterial[] = []; // expect recipelist @@ -90,6 +99,62 @@ return ret; } + async function getCurrentQueue() { + let inst = adb.getAdbInstance(); + if (inst) { + let current_brewing = await adb.pull(env.PUBLIC_BREW_CURRENT_RECIPE); + // console.log(`current brewing queue: ${current_brewing}`); + if (current_brewing === '') { + current_brewing = '{}'; + } + + return JSON.parse(current_brewing ?? '{}'); + } + + return { + error: 'instance lost' + }; + } + + async function resetAllPendingCmds() { + // send reset to brew + try { + await sendReset(); + addNotification(`INFO:Reset completed!`); + } catch (e) { + addNotification(`ERR:${e}`); + } + } + + async function saveRecipe() {} + + async function sendTriggerBrewNow() { + // check queue ready + // let currentBrewingQueue = await getCurrentQueue(); + // console.log('checking queue ... ', Object.keys(currentBrewingQueue).length); + + await sendToAndroid({ + type: 'brew_prep', + payload: { + start: new Date().toLocaleTimeString() + } + }); + + // if (Object.keys(currentBrewingQueue).length != 0) { + // addNotification('ERR:Brewing queue is full, please check machine or press `reset`'); + // return; + // } + + // + let inst = adb.getAdbInstance(); + if (inst) { + console.log('check adb writer', isAdbWriterAvailable()); + recipeDetailDispatch('brewNow'); + } else { + console.log('result check fail'); + } + } + async function checkChanges(original: any) { console.log('old', original, 'updated', recipeListMatState); if (recipeListOriginal.length == 0) { @@ -133,10 +198,23 @@
- - Info - Details - +
+ + Info + Details + + {#if refPage === 'brew'} +
+ + + +
+ {/if} +
diff --git a/src/lib/components/recipe-details/recipelist-value-editor.svelte b/src/lib/components/recipe-details/recipelist-value-editor.svelte index a786a7e..aa04212 100644 --- a/src/lib/components/recipe-details/recipelist-value-editor.svelte +++ b/src/lib/components/recipe-details/recipelist-value-editor.svelte @@ -198,6 +198,8 @@ currentRef ); + sheetOpenState = false; + } else if (value_event_state === ValueEvent.NONE) { sheetOpenState = false; } else { // set noti @@ -296,7 +298,7 @@ onMount(() => { sheetOpenState = false; - console.log('sheet open? ', sheetOpenState); + // console.log('sheet open? ', sheetOpenState); let refFrom = get(referenceFromPage); categories = get( @@ -317,16 +319,7 @@ }); - { - if (!next) { - beforeClosing(); - } else { - sheetOpenState = true; - } - }} -> +
diff --git a/src/lib/components/recipe-details/recipelist-value.svelte b/src/lib/components/recipe-details/recipelist-value.svelte index 6df6aca..ba31fa2 100644 --- a/src/lib/components/recipe-details/recipelist-value.svelte +++ b/src/lib/components/recipe-details/recipelist-value.svelte @@ -77,7 +77,7 @@ let key = kv[0]; let value = kv[1] ?? ''; - console.log('key', key, 'value', value); + // console.log('key', key, 'value', value); currentStringParams[key] = value; } } @@ -204,9 +204,9 @@ console.log('detect mix order', mat_num); } - if (feed.parameter > 0 || feed.pattern > 0) { - console.log('has feed fields', JSON.stringify(feed)); - } + // if (feed.parameter > 0 || feed.pattern > 0) { + // console.log('has feed fields', JSON.stringify(feed)); + // } } } diff --git a/src/lib/components/recipe-editor-dialog.svelte b/src/lib/components/recipe-editor-dialog.svelte index e02f6b5..06b6457 100644 --- a/src/lib/components/recipe-editor-dialog.svelte +++ b/src/lib/components/recipe-editor-dialog.svelte @@ -9,8 +9,9 @@ import { MenuStatus, matchMenuStatus } from '$lib/core/types/menuStatus'; import * as adb from '$lib/core/adb/adb'; - - let open = $state(false); + import { addNotification } from '$lib/core/stores/noti'; + import { sendToAndroid } from '$lib/core/stores/adbWriter'; + import { env } from '$env/dynamic/public'; const isDesktop = new MediaQuery('(min-width: 768px)'); let currentData: any = $state(); @@ -27,6 +28,8 @@ refPage: string; } = $props(); + let ready_to_send_brew: any[] = $state([]); + async function onPendingChange(newChange: { target: string; value: any }) { console.log('detect pending change', matchMenuStatus(currentData.MenuStatus)); hasPendingChange = true; @@ -51,6 +54,9 @@ console.log('pending change recipe list', newChange); } + ready_to_send_brew = []; + ready_to_send_brew.push([env.PUBLIC_BREW_CURRENT_RECIPE, JSON.stringify(currentData)]); + // await adb.push('/sdcard/coffeevending/.curr.brewing.json', JSON.stringify(currentData)); // // @@ -59,6 +65,23 @@ // } + async function sendBrewNow() { + try { + await sendToAndroid({ + type: 'brew', + payload: { + start: new Date().toLocaleTimeString(), + // use this field for unchanged data + target: ready_to_send_brew.length == 1 ? '-' : currentData.productCode, + // use this field for new or modified data + data: ready_to_send_brew.length == 1 ? ready_to_send_brew[0][1] : null + } + }); + } catch (e) { + addNotification(`ERR:Failed to brewing\n${e}`); + } + } + onMount(() => { // if (refPage === 'brew') { @@ -86,7 +109,7 @@ {#if isDesktop.current} - + e.preventDefault()} >View @@ -100,7 +123,12 @@ - + sendBrewNow()} + /> {:else} diff --git a/src/lib/core/adb/adb.ts b/src/lib/core/adb/adb.ts index 91bd950..e51ece7 100644 --- a/src/lib/core/adb/adb.ts +++ b/src/lib/core/adb/adb.ts @@ -1,14 +1,21 @@ import { Adb, AdbDaemonTransport, encodeUtf8 } from '@yume-chan/adb'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; import { + AdbDaemonWebUsbDevice, AdbDaemonWebUsbDeviceManager, - AdbDaemonWebUsbDeviceObserver, - type AdbDaemonWebUsbDevice + AdbDaemonWebUsbDeviceObserver } from '@yume-chan/adb-daemon-webusb'; import { AdbInstance } from '../../../routes/state.svelte'; import { deviceCredentialManager } from './deviceCredManager'; import { Consumable, MaybeConsumable, ReadableStream } from '@yume-chan/stream-extra'; import { AdbScrcpyClient } from '@yume-chan/adb-scrcpy'; +import { addNotification } from '../stores/noti'; +import { handleAdbPayload } from '../handlers/adbPayloadHandler'; +import { adbWriter } from '../stores/adbWriter'; +import { WritableStream } from '@yume-chan/stream-extra'; +import { env } from '$env/dynamic/public'; + +let syncConnection: any = null; export async function connnectViaWebUSB() { const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice(); @@ -27,13 +34,21 @@ export async function connnectViaWebUSB() { }); const adb = new Adb(transport); - saveAdbInstance(adb); + await saveAdbInstance(adb); + await connectToAndroidServer(); // save device info await deviceCredentialManager.saveDeviceInfo(device); } catch (e: any) { console.error('error on connect', e); - throw new Error(e.toString()); + + if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) { + addNotification( + 'ERR:Device is already in use by another program, please close the program and try again' + ); + } + + throw e; } } } @@ -51,7 +66,9 @@ export async function connectDeviceByCred( }); const adb = new Adb(transport); - saveAdbInstance(adb); + + await saveAdbInstance(adb); + await connectToAndroidServer(); return true; } catch (error) { @@ -59,7 +76,8 @@ export async function connectDeviceByCred( } } -export function saveAdbInstance(adb: Adb | undefined) { +export async function saveAdbInstance(adb: Adb | undefined) { + await cleanupSync(); AdbInstance.instance = adb; } @@ -100,11 +118,12 @@ export async function executeCmd(command: string) { }; } } catch (e: any) { - console.log(e.message); + // console.log(e.message); //ExactReadable ended if (e.message.includes('ExactReadable ended')) { - console.error('connection cut off'); return { + output: '', + exitCode: 1, error: 'ExactReadableEndedError' }; } @@ -119,32 +138,49 @@ export async function disconnect() { if (instance) { await instance.close(); console.log('close instance'); - saveAdbInstance(undefined); + await saveAdbInstance(undefined); } } -export async function pull(filename: string) { - let instance = getAdbInstance(); - if (instance) { - let chunkList: Uint8Array[] = []; - let sync = await instance.sync(); - const content = sync.read(filename); - let result = content.values(); - let res; - - let result_string = ''; - - while ((res = await result.next()) != null) { - // console.log(res.value); - if (res.value != undefined) { - result_string += new TextDecoder().decode(res.value); - } - if (res.done) { - break; - } +export async function cleanupSync() { + if (syncConnection) { + try { + await syncConnection.dispose(); + } catch (e) { + console.error('error on dispose sync', e); } + } - return result_string; + syncConnection = null; +} + +export async function pull(filename: string, timeoutMs: number = 5000) { + let instance = getAdbInstance(); + + await cleanupSync(); + + try { + if (instance) { + let chunkList: Uint8Array[] = []; + const syncProm = instance.sync(); + const timeoutProm = new Promise((_, reject) => { + setTimeout(() => reject(new Error('sync timeout')), timeoutMs); + }); + + syncConnection = await Promise.race([syncProm, timeoutProm]); + const content = syncConnection.read(filename); + let result_string = ''; + + for await (const chunk of content) { + result_string += new TextDecoder().decode(chunk); + } + + return result_string; + } + } catch (pull_error: any) { + console.log('pulling error', pull_error); + } finally { + await cleanupSync(); } } @@ -176,6 +212,38 @@ export async function push(path: string, obj: string) { } } +// NOTE: adb reverse is not work by unavailable features support +async function connectToAndroidServer() { + try { + let inst = getAdbInstance(); + if (!inst) { + console.warn('adb instance not found'); + return; + } + const stream = await inst.transport.connect(env.PUBLIC_BREW_CONN_PORT); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + + adbWriter.set(writer); + + (async () => { + try { + while (true) { + const { value, done } = await reader.read(); + if (done) break; + handleAdbPayload(new TextDecoder().decode(value)); + } + } catch (e) { + console.error('read error', e); + } finally { + adbWriter.set(null); + } + })(); + } catch (err) { + console.error('Connection failed. Suspect java running or not', err); + } +} + // logcat stream // TODO: screen mirror diff --git a/src/lib/core/brew/command.ts b/src/lib/core/brew/command.ts new file mode 100644 index 0000000..9d44f81 --- /dev/null +++ b/src/lib/core/brew/command.ts @@ -0,0 +1,46 @@ +import { env } from '$env/dynamic/public'; +import * as adb from '$lib/core/adb/adb'; + +class BrewCommandError extends Error { + public readonly field?: string; + + constructor(message: string, field?: string) { + super(message); + this.name = 'BrewCommandError'; + this.field = field; + } +} + +/// Send command to brew app +/// NOTE: app must enable flag `enable_adb_block_watch` +async function sendCommand(type: string, params?: string[]) { + // check instance + let inst = adb.getAdbInstance(); + if (inst) { + try { + let cmd = type + ' ' + (params?.join(' ') ?? ''); + await adb.push(env.PUBLIC_BREW_CMD_WEB, cmd); + } catch (e) { + throw new BrewCommandError('Command failed', `${e}`); + } + } else { + throw new BrewCommandError('Instance lost'); + } +} + +async function sendReset() { + let inst = adb.getAdbInstance(); + if (inst) { + try { + await adb.push(env.PUBLIC_BREW_CMD_WEB, ''); + await adb.push(env.PUBLIC_BREW_CURRENT_RECIPE, ''); + await adb.push(env.PUBLIC_BREW_WEB_STATUS, ''); + } catch (e) { + throw new BrewCommandError('Reset failed', `${e}`); + } + } else { + throw new BrewCommandError('Instance lost'); + } +} + +export { sendCommand, sendReset, BrewCommandError }; diff --git a/src/lib/core/handlers/adbPayloadHandler.ts b/src/lib/core/handlers/adbPayloadHandler.ts new file mode 100644 index 0000000..af64b41 --- /dev/null +++ b/src/lib/core/handlers/adbPayloadHandler.ts @@ -0,0 +1,41 @@ +import { addNotification } from '../stores/noti'; + +type AdbPayload = { type: string; payload: any }; + +async function handleAdbPayload(raw_payload: string) { + console.log('get payload', raw_payload); + try { + const payload: AdbPayload = JSON.parse(raw_payload); + switch (payload.type) { + case 'log': + let log_level = payload.payload['level'] ?? 'INFO'; + let log_message = payload.payload['msg'] ?? ''; + + if (log_message !== '') addNotification(`${log_level}`); + break; + case 'response': + if (payload.payload instanceof String) { + // single message response + } + break; + case 'ACK': + // acknowledge response from app + if (payload.payload !== 'OK') { + // abnormal + console.error('error from ACK', payload.payload); + addNotification('ERR:Request rejected'); + } + break; + case 'error': + // show error to user from brew app + addNotification(`ERR:${payload.payload}`); + // send message to server if needed + break; + default: + } + } catch (error: any) { + // invalid format + } +} + +export { handleAdbPayload }; diff --git a/src/lib/core/stores/adbWriter.ts b/src/lib/core/stores/adbWriter.ts new file mode 100644 index 0000000..dd19153 --- /dev/null +++ b/src/lib/core/stores/adbWriter.ts @@ -0,0 +1,28 @@ +import { get, writable } from 'svelte/store'; +import { addNotification } from './noti'; + +const adbWriter: any = writable(null); + +async function sendToAndroid(message: any) { + let writer: any = get(adbWriter); + console.log('writer', writer); + if (!writer) { + addNotification('ERR:No active connection'); + return; + } + try { + const encoder = new TextEncoder(); + console.log(writer); + await writer.write(encoder.encode(JSON.stringify(message) + '\n')); + console.log('sent!'); + } catch (error) { + console.error('write failed', error); + } +} + +// helper function for checking if connection is ok +function isAdbWriterAvailable() { + return get(adbWriter) != null; +} + +export { sendToAndroid, adbWriter, isAdbWriterAvailable }; diff --git a/src/lib/helpers/updater.ts b/src/lib/helpers/updater.ts index 5fc23c0..e69de29 100644 --- a/src/lib/helpers/updater.ts +++ b/src/lib/helpers/updater.ts @@ -1 +0,0 @@ -export function update \ No newline at end of file diff --git a/src/routes/(authed)/+layout.svelte b/src/routes/(authed)/+layout.svelte index f1dee64..2e1635d 100644 --- a/src/routes/(authed)/+layout.svelte +++ b/src/routes/(authed)/+layout.svelte @@ -47,6 +47,12 @@ } catch (ignored) {} } + if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) { + addNotification( + 'ERR:Device is already in use by another program, please close the program and try again' + ); + } + return false; } } catch (e) { diff --git a/src/routes/(authed)/tools/brew/+page.svelte b/src/routes/(authed)/tools/brew/+page.svelte index c7a2eee..73c1bdd 100644 --- a/src/routes/(authed)/tools/brew/+page.svelte +++ b/src/routes/(authed)/tools/brew/+page.svelte @@ -20,10 +20,14 @@ import { auth as authStore } from '$lib/core/stores/auth'; import { machineInfoStore } from '$lib/core/stores/machineInfoStore'; import { get } from 'svelte/store'; - import { AdbDaemonWebUsbDeviceManager } from '@yume-chan/adb-daemon-webusb'; + import { + AdbDaemonWebUsbDevice, + AdbDaemonWebUsbDeviceManager + } from '@yume-chan/adb-daemon-webusb'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; import { deviceCredentialManager } from '$lib/core/adb/deviceCredManager'; import { afterNavigate } from '$app/navigation'; + import { env } from '$env/dynamic/public'; const sourceDir = '/sdcard/coffeevending'; @@ -35,15 +39,17 @@ recipes: [] }); + let brew_status: string = $state(''); + async function startFetchRecipeFromMachine() { let instance = adb.getAdbInstance(); - recipeFromMachineLoading.set(true); + // recipeFromMachineLoading.set(true); referenceFromPage.set('brew'); console.log('check instance', instance); if (instance) { console.log('instance passed!'); let dev_recipe = await adb.pull(`${sourceDir}/cfg/recipe_branch_dev.json`); - // console.log('dev recipe ok', dev_recipe); + console.log('dev recipe ok', dev_recipe != undefined); if (dev_recipe) { if (dev_recipe.length == 0) { // case error, do last retry @@ -54,7 +60,7 @@ else if (dev_recipe) { // From coffeethai02 devRecipe = JSON.parse(dev_recipe); - recipeFromMachineLoading.set(false); + // recipeFromMachineLoading.set(false); addNotification('INFO:Fetch recipe success!'); buildOverviewForBrewing(); @@ -62,7 +68,7 @@ } else { // from recipe_branch_dev devRecipe = JSON.parse(dev_recipe); - recipeFromMachineLoading.set(false); + // recipeFromMachineLoading.set(false); addNotification('INFO:Fetch recipe success!'); buildOverviewForBrewing(); @@ -70,7 +76,7 @@ } } else { addNotification('ERROR:Cannot connect to machine'); - recipeFromMachineLoading.set(false); + // recipeFromMachineLoading.set(false); } } @@ -113,7 +119,7 @@ async function connectAdb() { try { if (!('usb' in navigator)) { - throw new Error('WebUSB not supported, try using fallback or different browser'); + throw new Error('WebUSB not supported, try using fallback method or different browser'); } await adb.connnectViaWebUSB(); @@ -131,7 +137,7 @@ async function tryAutoConnect() { try { if (!('usb' in navigator) || !AdbDaemonWebUsbDeviceManager.BROWSER) { - throw new Error('WebUSB not supported, try using fallback or different browser'); + throw new Error('WebUSB not supported, try using fallback method or different browser'); } const devices = await AdbDaemonWebUsbDeviceManager.BROWSER.getDevices(); @@ -155,6 +161,11 @@ await deviceCredentialManager.clearAllCredentials(); } catch (ignored) {} } + if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) { + addNotification( + 'ERR:Device is already in use by another program, please close the program and try again' + ); + } return false; } @@ -320,6 +331,54 @@ toppingGroupFromMachineQuery.set(devRecipe['Topping']['ToppingGroup']); } } + + $effect(() => { + const brewAppStatusInterval = setInterval(async () => { + // schedule status from .brew_web_status.log + let inst = adb.getAdbInstance(); + if (inst && devRecipe) { + await adb.executeCmd( + 'tail -n 1 /sdcard/coffeevending/.brew_web_status.log > /sdcard/coffeevending/.brew_web_status.latest.log' + ); + + let brew_status_log = await adb.pull(env.PUBLIC_BREW_WEB_LATEST_STATUS); + if (brew_status_log) { + let latest_log = brew_status_log; + + if (brew_status !== latest_log) { + brew_status = latest_log; + + // noti from machine + if (latest_log.includes('!!!')) { + let log = latest_log.split('!!!'); + let noti_cfg = log[1]; + + if (noti_cfg.includes('=')) { + let noti_level = noti_cfg.split('=')[1]; + + let spl = log[0].split(':'); + let pure_msg = spl[spl.length - 1]; + + // case special message + if (pure_msg.includes('starting retry process')) { + // is waiting/idle process + pure_msg = 'Wait for brewing'; + } + + addNotification(`${noti_level}:${pure_msg}`); + } + } + } + } + } else if (inst && !devRecipe) { + // try again + // await startFetchRecipeFromMachine(); + } + }, 1000); + return () => { + clearInterval(brewAppStatusInterval); + }; + });
@@ -333,7 +392,7 @@

- {#if !adb.getAdbInstance()} + {#if !adb.getAdbInstance() || !devRecipe} {:else}