Commit 589f4562 by James Yuan

fix: 改点位倍率后即时重算并自动下发网关,补充 E 栋冰箱点表

保存/导入点位后同步 Windows 网关;倍率变更用 lastRaw 重算 lastEng,避免实时值仍按旧倍率显示。

Co-authored-by: Cursor <cursoragent@cursor.com>
parent fd50d6bd
...@@ -908,6 +908,33 @@ export async function createPoint( ...@@ -908,6 +908,33 @@ export async function createPoint(
return p return p
} }
function usesLinearScale(dataType: string): boolean {
const t = (dataType || '').toLowerCase()
return !t.includes('float') && !t.includes('double') && !t.includes('bool')
}
/** 倍率/偏置变更时用 lastRaw 立即重算工程值(Float/Double 不乘 scale) */
function recomputeEngAfterScaleChange(cur: DevicePoint, next: DevicePoint): boolean {
const scaleChanged =
next.scale !== cur.scale ||
next.bias !== cur.bias ||
next.scaleOverride !== cur.scaleOverride
if (!scaleChanged) return false
if (next.lastRaw === null || next.lastRaw === undefined) return false
if (!usesLinearScale(next.dataType)) return false
next.lastEng = toEngineering(
next.lastRaw,
next.scale,
next.bias,
next.scaleOverride,
next.minRawRange,
next.maxRawRange,
next.minEngRange,
next.maxEngRange,
)
return true
}
export async function updatePoint( export async function updatePoint(
id: string, id: string,
patch: Partial<DevicePoint>, patch: Partial<DevicePoint>,
...@@ -921,6 +948,7 @@ export async function updatePoint( ...@@ -921,6 +948,7 @@ export async function updatePoint(
id: cur.id, id: cur.id,
updatedAt: now(), updatedAt: now(),
} }
const engRecomputed = recomputeEngAfterScaleChange(cur, next)
if (!isPgEnabled()) { if (!isPgEnabled()) {
const i = mem.points.findIndex((x) => x.id === id) const i = mem.points.findIndex((x) => x.id === id)
mem.points[i] = next mem.points[i] = next
...@@ -934,7 +962,9 @@ export async function updatePoint( ...@@ -934,7 +962,9 @@ export async function updatePoint(
modbus_id=$8, modbus_register=$9, modbus_address=$10, description=$11, mask_bit=$12, bit=$13, modbus_id=$8, modbus_register=$9, modbus_address=$10, description=$11, mask_bit=$12, bit=$13,
scale=$14, bias=$15, min_raw_range=$16, max_raw_range=$17, min_eng_range=$18, max_eng_range=$19, scale=$14, bias=$15, min_raw_range=$16, max_raw_range=$17, min_eng_range=$18, max_eng_range=$19,
item_id=$20, snapshot_path=$21, rw=$22, energy_kind=$23, scale_override=$24, item_id=$20, snapshot_path=$21, rw=$22, energy_kind=$23, scale_override=$24,
alarm_low=$25, alarm_high=$26, enabled=$27, updated_at=NOW() alarm_low=$25, alarm_high=$26, enabled=$27,
last_eng=CASE WHEN $28::boolean THEN $29::double precision ELSE last_eng END,
updated_at=NOW()
WHERE id=$1`, WHERE id=$1`,
[ [
id, id,
...@@ -964,6 +994,8 @@ export async function updatePoint( ...@@ -964,6 +994,8 @@ export async function updatePoint(
next.alarmLow, next.alarmLow,
next.alarmHigh, next.alarmHigh,
next.enabled, next.enabled,
engRecomputed,
next.lastEng,
], ],
) )
await bumpVersionPg() await bumpVersionPg()
......
...@@ -116,6 +116,18 @@ devicesRouter.delete('/:id', authRequired, requireRole('admin'), async (req, res ...@@ -116,6 +116,18 @@ devicesRouter.delete('/:id', authRequired, requireRole('admin'), async (req, res
res.json({ ok: true }) res.json({ ok: true })
}) })
/** 点表变更后异步下发网关;不阻塞保存成功响应,失败只打日志 */
function pushPointsToGatewayInBackground(reason: string) {
void syncPointsToGateway()
.then((r) => {
if (!r.ok) console.warn(`[devices] sync-gateway after ${reason}: ${r.message}`)
else console.log(`[devices] sync-gateway after ${reason}: ok v=${r.pointsVersion}`)
})
.catch((e) => {
console.warn(`[devices] sync-gateway after ${reason} threw`, e)
})
}
devicesRouter.get('/points', authRequired, async (req, res) => { devicesRouter.get('/points', authRequired, async (req, res) => {
const gatewayId = req.query.gatewayId ? String(req.query.gatewayId) : undefined const gatewayId = req.query.gatewayId ? String(req.query.gatewayId) : undefined
const deviceId = req.query.deviceId ? String(req.query.deviceId) : undefined const deviceId = req.query.deviceId ? String(req.query.deviceId) : undefined
...@@ -135,6 +147,7 @@ devicesRouter.post('/points', authRequired, requireRole('admin'), async (req, re ...@@ -135,6 +147,7 @@ devicesRouter.post('/points', authRequired, requireRole('admin'), async (req, re
modbusAddress: Number(body.modbusAddress), modbusAddress: Number(body.modbusAddress),
...body, ...body,
}) })
pushPointsToGatewayInBackground(`create-point ${point.id}`)
res.status(201).json(point) res.status(201).json(point)
} catch (e) { } catch (e) {
res.status(400).json({ message: e instanceof Error ? e.message : '创建失败' }) res.status(400).json({ message: e instanceof Error ? e.message : '创建失败' })
...@@ -148,6 +161,7 @@ devicesRouter.patch('/points/:id', authRequired, requireRole('admin'), async (re ...@@ -148,6 +161,7 @@ devicesRouter.patch('/points/:id', authRequired, requireRole('admin'), async (re
res.status(404).json({ message: '点位不存在' }) res.status(404).json({ message: '点位不存在' })
return return
} }
pushPointsToGatewayInBackground(`patch-point ${p.id}`)
res.json(p) res.json(p)
} catch (e) { } catch (e) {
res.status(400).json({ message: e instanceof Error ? e.message : '更新失败' }) res.status(400).json({ message: e instanceof Error ? e.message : '更新失败' })
...@@ -160,6 +174,7 @@ devicesRouter.delete('/points/:id', authRequired, requireRole('admin'), async (r ...@@ -160,6 +174,7 @@ devicesRouter.delete('/points/:id', authRequired, requireRole('admin'), async (r
res.status(404).json({ message: '点位不存在' }) res.status(404).json({ message: '点位不存在' })
return return
} }
pushPointsToGatewayInBackground(`delete-point ${req.params.id}`)
res.json({ ok: true }) res.json({ ok: true })
}) })
...@@ -186,6 +201,7 @@ devicesRouter.post('/points/import', authRequired, requireRole('admin'), async ( ...@@ -186,6 +201,7 @@ devicesRouter.post('/points/import', authRequired, requireRole('admin'), async (
encoding: req.body?.encoding, encoding: req.body?.encoding,
replaceGateway: Boolean(req.body?.replaceGateway), replaceGateway: Boolean(req.body?.replaceGateway),
}) })
pushPointsToGatewayInBackground(`import-points ${gatewayId}`)
res.json(result) res.json(result)
} catch (e) { } catch (e) {
res.status(400).json({ message: e instanceof Error ? e.message : '导入失败' }) res.status(400).json({ message: e instanceof Error ? e.message : '导入失败' })
......
id,Name,RegType,RegAddress,DataType,Modbus ID,Modbus Register,Modbus Address,Description,MaskBit,Bit,Scale,Bias,MinRawRange,MaxRawRange,MinEngRange,MaxEngRange,ItemID
id,Name,RegType,RegAddress,DataType,Modbus ID,Modbus Register,Modbus Address,Description,MaskBit,Bit,Scale,Bias,MinRawRange,MaxRawRange,MinEngRange,MaxEngRange,ItemID
1,冰箱1温度,4X(Holding Register),1,Short,1,4X(Holding Register),1,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱1.温度
2,冰箱2温度,4X(Holding Register),2,Short,1,4X(Holding Register),2,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱2.温度
3,冰箱3温度,4X(Holding Register),3,Short,1,4X(Holding Register),3,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱3.温度
4,冰箱4温度,4X(Holding Register),4,Short,1,4X(Holding Register),4,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱4.温度
5,冰箱5温度,4X(Holding Register),5,Short,1,4X(Holding Register),5,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱5.温度
6,冰箱6温度,4X(Holding Register),6,Short,1,4X(Holding Register),6,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱6.温度
7,冰箱7温度,4X(Holding Register),7,Short,1,4X(Holding Register),7,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱7.温度
8,冰箱8温度,4X(Holding Register),8,Short,1,4X(Holding Register),8,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱8.温度
9,冰箱1湿度,4X(Holding Register),21,Short,1,4X(Holding Register),21,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱1.湿度
10,冰箱2湿度,4X(Holding Register),22,Short,1,4X(Holding Register),22,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱2.湿度
11,冰箱3湿度,4X(Holding Register),23,Short,1,4X(Holding Register),23,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱3.湿度
12,冰箱4湿度,4X(Holding Register),24,Short,1,4X(Holding Register),24,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱4.湿度
13,冰箱5湿度,4X(Holding Register),25,Short,1,4X(Holding Register),25,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱5.湿度
14,冰箱6湿度,4X(Holding Register),26,Short,1,4X(Holding Register),26,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱6.湿度
15,冰箱7湿度,4X(Holding Register),27,Short,1,4X(Holding Register),27,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱7.湿度
16,冰箱8湿度,4X(Holding Register),28,Short,1,4X(Holding Register),28,E栋冰箱网关192.168.10.105,0,0,0,0,0,0,0,0,mb-105.E栋冰箱8.湿度
...@@ -422,7 +422,7 @@ async function savePoint() { ...@@ -422,7 +422,7 @@ async function savePoint() {
enabled: editPoint.value.enabled, enabled: editPoint.value.enabled,
}), }),
}) })
message.value = '点位已保存' message.value = '点位已保存(倍率已即时重算,并自动下发网关)'
editPoint.value = null editPoint.value = null
error.value = '' error.value = ''
if (returnToDeviceView.value && selectedDevice.value) { if (returnToDeviceView.value && selectedDevice.value) {
...@@ -1206,12 +1206,12 @@ onUnmounted(stopViewRefresh) ...@@ -1206,12 +1206,12 @@ onUnmounted(stopViewRefresh)
</div> </div>
<div class="field"><label>ItemID</label><input v-model="editPoint.itemId" /></div> <div class="field"><label>ItemID</label><input v-model="editPoint.itemId" /></div>
<div class="field"> <div class="field">
<label>Scale</label> <label>Scale(底表)</label>
<input v-model.number="editPoint.scale" type="number" step="any" /> <input v-model.number="editPoint.scale" type="number" step="any" />
</div> </div>
<div class="field"> <div class="field">
<label>Scale 覆盖</label> <label>倍率(覆盖,优先生效)</label>
<input v-model.number="editPoint.scaleOverride" type="number" step="any" /> <input v-model.number="editPoint.scaleOverride" type="number" step="any" placeholder="空=用 Scale" />
</div> </div>
<div class="field"> <div class="field">
<label>告警下限</label> <label>告警下限</label>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment