export type SmartScaleReadKind = 'tared' | 'gross' const STORAGE_IP = 'smart_scale_ip' const STORAGE_PORT = 'smart_scale_port' const DEFAULT_IP = '127.0.0.1' const DEFAULT_PORT = 6900 const READ_TIMEOUT_MS = 4000 type MoeTcpPlugin = { connect: (opts: { ip: string; port: number }, cb: (res: { code?: number; msg?: string }) => void) => void disconnect: () => void sendStr: (opts: { message: string }) => void onReceive: (cb: (res: { code?: number; data?: string; msg?: string }) => void) => void onDisconnect: (cb: (res: unknown) => void) => void } function getTcpPlugin(): MoeTcpPlugin | null { // #ifdef APP-PLUS try { const u = uni as any return u?.requireNativePlugin ? (u.requireNativePlugin('moe-tcp-client') as MoeTcpPlugin) : null } catch { return null } // #endif // #ifndef APP-PLUS return null // #endif } function readScaleSettings(): { ip: string; port: number } { let ip = DEFAULT_IP let port = DEFAULT_PORT try { const storedIp = uni.getStorageSync(STORAGE_IP) const storedPort = uni.getStorageSync(STORAGE_PORT) if (storedIp) ip = String(storedIp).trim() || ip const p = Number(storedPort) if (Number.isFinite(p) && p > 0) port = p } catch { /* ignore */ } return { ip, port } } function parseWeightFromMessage(msg: string): string | null { const text = String(msg ?? '').replace(/,/g, '.') const matches = text.match(/-?\d+(?:\.\d+)?/g) if (!matches?.length) return null const last = matches[matches.length - 1] const n = Number(last) if (!Number.isFinite(n)) return null return String(n) } function connectTcp(plugin: MoeTcpPlugin, ip: string, port: number): Promise { return new Promise((resolve, reject) => { plugin.connect({ ip, port }, (res) => { if (res?.code === 1) resolve() else reject(new Error(res?.msg || 'Could not connect to smart scale.')) }) }) } /** Read weight from smart scale (TCP). `tared` sends tare then reads net; `gross` reads gross. */ export async function readWeightFromSmartScale(kind: SmartScaleReadKind): Promise { const plugin = getTcpPlugin() if (!plugin) { throw new Error('Smart scale is only available in the mobile app.') } const { ip, port } = readScaleSettings() let latest: string | null = null let settled = false return new Promise((resolve, reject) => { const finish = (err?: Error) => { if (settled) return settled = true try { plugin.onReceive(() => {}) plugin.onDisconnect(() => {}) plugin.disconnect() } catch { /* ignore */ } if (err) reject(err) else if (latest) resolve(latest) else reject(new Error('No weight reading received from scale.')) } const timer = setTimeout(() => finish(new Error('Smart scale read timed out.')), READ_TIMEOUT_MS) plugin.onReceive((res) => { if (res?.code !== 1) return const parsed = parseWeightFromMessage(String(res.data ?? '')) if (parsed) latest = parsed }) plugin.onDisconnect(() => { if (!settled && latest) { clearTimeout(timer) finish() } }) connectTcp(plugin, ip, port) .then(() => { if (kind === 'tared') { try { plugin.sendStr({ message: 'T\r\n' }) } catch { /* ignore */ } setTimeout(() => { try { plugin.sendStr({ message: 'W\r\n' }) } catch { /* ignore */ } }, 600) } else { try { plugin.sendStr({ message: 'W\r\n' }) } catch { /* ignore */ } } }) .catch((e) => { clearTimeout(timer) finish(e instanceof Error ? e : new Error(String(e))) }) const poll = setInterval(() => { if (latest && !settled) { clearInterval(poll) clearTimeout(timer) finish() } }, 200) }) }