smartScaleService.ts
3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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<void> {
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<string> {
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<string>((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)
})
}