diff --git a/src/lib/components/recipe-details/recipe-detail.svelte b/src/lib/components/recipe-details/recipe-detail.svelte
index bc4115a..b1100ea 100644
--- a/src/lib/components/recipe-details/recipe-detail.svelte
+++ b/src/lib/components/recipe-details/recipe-detail.svelte
@@ -17,7 +17,8 @@
currentEditingRecipeProductCode,
latestRecipeToppingData,
materialFromMachineQuery,
- materialFromServerQuery
+ materialFromServerQuery,
+ priceRecipeData
} from '$lib/core/stores/recipeStore';
import { generateIcing } from '$lib/helpers/icingGen';
import { getMachineStatus, machineInfoStore } from '$lib/core/stores/machineInfoStore';
@@ -28,6 +29,7 @@
import { sendCommand, sendReset } from '$lib/core/brew/command';
import { isAdbWriterAvailable } from '$lib/core/stores/adbWriter';
import { sendToAndroid } from '$lib/core/stores/adbWriter';
+ import { departmentStore } from '$lib/core/stores/departments';
//
let {
@@ -38,6 +40,8 @@
}: { recipeData: any; onPendingChange: any; productCode: string; refPage: string } = $props();
let menuName: string = $state('');
+ let menuCurrentPrice: number = $state(0);
+ let isMenuHideByPrice: boolean = $state(false);
let materialSnapshot: any = $state();
let machineInfoSnapshot: any = $state();
@@ -197,6 +201,35 @@
currentEditingRecipeProductCode.set(productCode);
+ let currentPricesFromServer = get(priceRecipeData);
+ let currentPrice = currentPricesFromServer[productCode] ?? '';
+
+ console.log(currentPricesFromServer);
+
+ if (currentPrice != '') {
+ // has price
+ let priceParts = currentPrice.split(',');
+ console.log('price part', priceParts);
+ try {
+ let price = parseInt(priceParts[0]);
+ let extraParam: string = priceParts[1] ?? '';
+
+ if (extraParam != '') {
+ isMenuHideByPrice = extraParam.includes('hide=true');
+ }
+
+ console.log('hide = ', extraParam);
+
+ menuCurrentPrice = price;
+
+ let dep = get(departmentStore);
+
+ if (dep && dep != 'tha') {
+ menuCurrentPrice = menuCurrentPrice / 100;
+ }
+ } catch (e) {}
+ }
+
// save old value\
}
});
@@ -239,7 +272,16 @@
-
+
+
+
+
+
diff --git a/src/lib/components/recipe-editor-dialog.svelte b/src/lib/components/recipe-editor-dialog.svelte
index 753de1e..c80a281 100644
--- a/src/lib/components/recipe-editor-dialog.svelte
+++ b/src/lib/components/recipe-editor-dialog.svelte
@@ -308,11 +308,21 @@
// interval check 1s
// machine
interval_get_machine_status = setInterval(() => {
- if (getMachineStatus() == undefined) {
+ if (
+ getMachineStatus() == undefined ||
+ getMachineStatus() == null ||
+ $machineInfoStore?.status === undefined
+ ) {
// set default now
updateMachineStatus('');
}
+ console.log(
+ 'machine status pinging recipe editor dialog',
+ getMachineStatus(),
+ $machineInfoStore?.status
+ );
+
// update machine status
// check-connection
sendToAndroid({
@@ -342,7 +352,7 @@
{#if $machineInfoStore?.status == 'IDLE' || $machineInfoStore?.status == '' || refPage == 'overview'}
Ready
{:else}
- Working
+ Working {$machineInfoStore?.status}
{/if}
diff --git a/src/lib/core/adb/adb.ts b/src/lib/core/adb/adb.ts
index b8b1c09..6abe7ea 100644
--- a/src/lib/core/adb/adb.ts
+++ b/src/lib/core/adb/adb.ts
@@ -14,9 +14,83 @@ import { handleAdbPayload } from '../handlers/adbPayloadHandler';
import { adbWriter } from '../stores/adbWriter';
import { WritableStream } from '@yume-chan/stream-extra';
import { env } from '$env/dynamic/public';
+import type Dice_2 from '@lucide/svelte/icons/dice-2';
let syncConnection: any = null;
+function isRecoverableError(error: any): boolean {
+ if (!error) return false;
+ const errorMessage = error.message ? String(error.message).toLowerCase() : '';
+ const errorName = error.name ? String(error.name).toLowerCase() : '';
+
+ // Network-related errors that are typically recoverable
+ const recoverablePatterns = [
+ 'connection refused',
+ 'connection reset',
+ 'connection timeout',
+ 'network is unreachable',
+ 'host is unreachable',
+ 'temporary failure',
+ 'operation timed out',
+ 'failed to connect',
+ 'connection lost',
+ 'broken pipe',
+ 'socket closed',
+ 'eof',
+ 'end of file',
+ 'disconnected'
+ ];
+
+ for (const pattern of recoverablePatterns) {
+ if (errorMessage.includes(pattern) || errorName.includes(pattern)) {
+ return true;
+ }
+ }
+
+ if (
+ (error.name && error.name.includes('Error')) ||
+ error.name.includes('Exception') ||
+ error.name === 'IOError' ||
+ error.name === 'NetworkError'
+ ) {
+ return true;
+ }
+
+ return false;
+}
+
+async function connectWithRetry(
+ connectionFn: () => Promise,
+ description: string,
+ maxRetries: number = 5,
+ baseDelayMs: number = 1000
+): Promise {
+ let lastError: any;
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
+ try {
+ const timeoutPromise = new Promise((_, reject) => {
+ setTimeout(() => reject(new Error(`Connection timeout for ${description}`)), 10000);
+ });
+ const result = await Promise.race([connectionFn(), timeoutPromise]);
+
+ return result;
+ } catch (e) {
+ lastError = e;
+ if (attempt === maxRetries - 1) {
+ break;
+ }
+ if (!isRecoverableError(e)) {
+ break;
+ }
+ const delay = Math.min(baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000, 10000);
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ }
+ }
+ throw new Error(
+ `failed to ${description} after ${maxRetries} attempts. Last error: ${lastError.message}`
+ );
+}
+
export async function connnectViaWebUSB() {
const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice();
console.log('usb ok', globalThis.navigator.usb);
@@ -216,50 +290,79 @@ 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;
- }
-
- // await push('/sdcard/coffeevending/enable_adb_block_watch', '1');
-
- const stream = await inst.transport.connect(env.PUBLIC_BREW_CONN_PORT);
- const writer = stream.writable.getWriter();
- const reader = stream.readable.getReader();
-
- console.log('checking on writer ', writer);
- adbWriter.set(writer);
- if (writer) {
- addNotification('INFO:Enable Brewing Mode T on machine');
- } else {
- addNotification('WARN:Brewing Mode T unavailable');
-
- setTimeout(async () => {
- console.log('reconnecting android server');
- await connectToAndroidServer();
- }, 5000);
- }
-
- (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);
- addNotification('WARN:Brewing Mode T Offline ...');
+async function connectToAndroidServer(maxRetries = 5) {
+ let lastError: any;
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
+ try {
+ let inst = getAdbInstance();
+ if (!inst) {
+ console.warn('adb instance not found');
+ return;
}
- })();
- } catch (err) {
- console.error('Connection failed. Suspect java running or not', err);
- addNotification('ERR:Fail to enable brewing mode T');
+
+ // add retry mechanism
+ const stream = await connectWithRetry(
+ async () => inst.transport.connect(env.PUBLIC_BREW_CONN_PORT),
+ `connect to Android server port ${env.PUBLIC_BREW_CONN_PORT}`,
+ 3,
+ 500
+ );
+
+ const writer = stream.writable.getWriter();
+ const reader = stream.readable.getReader();
+
+ console.log('checking on writer ', writer);
+ adbWriter.set(writer);
+ if (writer) {
+ addNotification('INFO:Enable Brewing Mode T on machine');
+
+ try {
+ while (true) {
+ const { value, done } = await reader.read();
+ if (done) break;
+ handleAdbPayload(new TextDecoder().decode(value));
+ }
+ } catch (e) {
+ console.error('read error', e);
+ if (isRecoverableError(e)) {
+ throw e;
+ }
+ throw e;
+ } finally {
+ adbWriter.set(null);
+ addNotification('WARN:Brewing Mode T Offline ...');
+ }
+ } else {
+ addNotification('WARN:Brewing Mode T unavailable');
+
+ if (attempt < maxRetries - 1) {
+ const delay = Math.min(500 * Math.pow(2, attempt) + Math.random() * 500, 5000);
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ continue;
+ } else {
+ throw new Error('Brewing Mode T unavailable after all retries');
+ }
+ }
+ } catch (err) {
+ lastError = err;
+
+ if (attempt == maxRetries - 1) {
+ break;
+ }
+
+ if (!isRecoverableError(err)) {
+ break;
+ }
+
+ const delay = Math.min(1000 * Math.pow(2, attempt) + Math.random() * 1000, 10000);
+
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ }
+ }
+
+ if (lastError) {
+ console.error('Connection failed. Suspect java running or not', lastError);
+ addNotification(`ERR:Fail to enable brewing mode T\n${lastError.message ?? ''}`);
}
}
diff --git a/src/lib/core/handlers/messageHandler.ts b/src/lib/core/handlers/messageHandler.ts
index 1c6e394..c7e2088 100644
--- a/src/lib/core/handlers/messageHandler.ts
+++ b/src/lib/core/handlers/messageHandler.ts
@@ -3,6 +3,7 @@ import { addNotification, notiStore } from '../stores/noti';
import {
currentRecipeVersionsSelector,
materialFromServerQuery,
+ priceRecipeData,
recipeData,
recipeDataError,
recipeLoading,
@@ -16,6 +17,9 @@ import { auth } from '../client/firebase';
import { type RecipeVersion } from '$lib/models/recipe_version.model';
import { goto } from '$app/navigation';
import { socketAlreadySendHeartbeat, socketConnectionOfflineCount } from '../stores/websocketStore';
+import type { RecipePrice } from '$lib/models/price.model';
+import { sendMessage } from './ws_messageSender';
+import { auth as authStore } from '../stores/auth';
export const messages = writable([]);
@@ -111,6 +115,32 @@ const handlers: Record void> = {
// console.log('ending stream');
buildOverviewFromServer();
+
+ let current_meta = get(recipeStreamMeta);
+
+ let curr_user = get(authStore);
+
+ let user_info: any;
+ if (curr_user) {
+ user_info = {
+ displayName: curr_user.displayName,
+ email: curr_user.email,
+ uid: curr_user.uid
+ };
+ }
+
+ // send next chain message
+ sendMessage({
+ type: 'price',
+ payload: {
+ action: {
+ View: 'sa=all'
+ },
+ country: current_meta?.country ?? '',
+ parameters: '',
+ user_info
+ }
+ });
},
stream_data_extra: (p) => {
// extended data from server, may be extra infos
@@ -214,7 +244,16 @@ const handlers: Record void> = {
let status = p.status;
let to = p.to;
- let content = p.content ?? [];
+ let content: RecipePrice[] = p.content ?? [];
+
+ console.log('get price length: ', content.length);
+
+ let current_price = get(priceRecipeData);
+ for (const c of content) {
+ current_price[c.ProductCode] = c.NewPrice + (c.StringParam ? `,${c.StringParam}` : '');
+ }
+
+ priceRecipeData.set(current_price);
},
heartbeat: (p) => {
socketConnectionOfflineCount.set(0);
diff --git a/src/lib/core/stores/machineInfoStore.ts b/src/lib/core/stores/machineInfoStore.ts
index e56b31e..9cf3abf 100644
--- a/src/lib/core/stores/machineInfoStore.ts
+++ b/src/lib/core/stores/machineInfoStore.ts
@@ -9,6 +9,21 @@ function updateMachineStatus(new_status: string) {
current.status = new_status;
machineInfoStore.set(current);
+ } else {
+ machineInfoStore.set({
+ boxId: '',
+ versions: {
+ firmware: '',
+ brew: '',
+ xmlengine: '',
+ netcore: '',
+ devbox: ''
+ },
+ devMode: true,
+ country: '',
+ status: new_status,
+ errors: []
+ });
}
}
diff --git a/src/lib/core/stores/recipeStore.ts b/src/lib/core/stores/recipeStore.ts
index 5c7e606..0a97cc2 100644
--- a/src/lib/core/stores/recipeStore.ts
+++ b/src/lib/core/stores/recipeStore.ts
@@ -2,6 +2,7 @@ import { writable } from 'svelte/store';
import type { RecipeOverview } from '../../../routes/(authed)/recipe/overview/columns';
import type { Material } from '$lib/models/material.model';
import type { RecipeVersion } from '$lib/models/recipe_version.model';
+import type { RecipePrice } from '$lib/models/price.model';
export const recipeData = writable(null);
export const recipeLoading = writable(false);
@@ -20,6 +21,8 @@ export const currentRecipeVersionsSelector = writable([]);
// from server
export const recipeOverviewData = writable(null);
export const materialData = writable();
+// price from recipe repo
+export const priceRecipeData = writable<{ [key: string]: any }>({});
// machine recipe
export const recipeFromMachine = writable(null);
diff --git a/src/lib/core/types/outMessage.ts b/src/lib/core/types/outMessage.ts
index 2be6686..afe8c23 100644
--- a/src/lib/core/types/outMessage.ts
+++ b/src/lib/core/types/outMessage.ts
@@ -66,5 +66,6 @@ export type OutMessage =
country: string;
parameters?: string;
override_file?: string;
+ user_info: any;
};
};
diff --git a/src/lib/models/price.model.ts b/src/lib/models/price.model.ts
new file mode 100644
index 0000000..f653051
--- /dev/null
+++ b/src/lib/models/price.model.ts
@@ -0,0 +1,8 @@
+export interface RecipePrice {
+ ProductCode: string;
+ NewPrice: number;
+ StringParam: string | undefined;
+ Discount: number | undefined;
+ Percent: number | undefined;
+ roundup: boolean | undefined;
+}