feat: camera diagnostics (#1681)
This commit is contained in:
parent
5ae9b76c3b
commit
e1c2cc8619
13 changed files with 395 additions and 3 deletions
|
|
@ -231,6 +231,7 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
|
|||
_.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid),
|
||||
_.set('emptyUnit', !!pid && state.emptyUnit?.[operatorId]?.[deviceId] === pid),
|
||||
_.set('refillUnit', !!pid && state.refillUnit?.[operatorId]?.[deviceId] === pid),
|
||||
_.set('diagnostics', !!pid && state.diagnostics?.[operatorId]?.[deviceId] === pid),
|
||||
)(pq)
|
||||
|
||||
// Clean up the state middleware and prevent commands from being issued more than once
|
||||
|
|
@ -242,6 +243,10 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
|
|||
delete state.refillUnit?.[operatorId]?.[deviceId]
|
||||
}
|
||||
|
||||
if (!_.isNil(state.diagnostics?.[operatorId]?.[deviceId])) {
|
||||
delete state.diagnostics?.[operatorId]?.[deviceId]
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ type DynamicConfig {
|
|||
restartServices: Boolean!
|
||||
emptyUnit: Boolean!
|
||||
refillUnit: Boolean!
|
||||
diagnostics: Boolean!
|
||||
}
|
||||
|
||||
type Configs {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
const fsPromises = require('fs').promises
|
||||
const path = require('path')
|
||||
const _ = require('lodash/fp')
|
||||
const pgp = require('pg-promise')()
|
||||
const uuid = require('uuid')
|
||||
const makeDir = require('make-dir')
|
||||
|
||||
const batching = require('./cashbox-batches')
|
||||
const db = require('./db')
|
||||
|
|
@ -13,10 +16,12 @@ const notifierUtils = require('./notifier/utils')
|
|||
const notifierQueries = require('./notifier/queries')
|
||||
const { ApolloError } = require('apollo-server-errors');
|
||||
const { loadLatestConfig } = require('./new-settings-loader')
|
||||
const logger = require('./logger')
|
||||
|
||||
const fullyFunctionalStatus = { label: 'Fully functional', type: 'success' }
|
||||
const unresponsiveStatus = { label: 'Unresponsive', type: 'error' }
|
||||
const stuckStatus = { label: 'Stuck', type: 'error' }
|
||||
const OPERATOR_DATA_DIR = process.env.OPERATOR_DATA_DIR
|
||||
|
||||
const MACHINE_WITH_CALCULATED_FIELD_SQL = `
|
||||
select d.*, COALESCE(emptybills, 0) + COALESCE(regularbills, 0) as cashbox from devices d
|
||||
|
|
@ -53,6 +58,11 @@ function toMachineObject (r) {
|
|||
numberOfRecyclers: r.number_of_recyclers,
|
||||
version: r.version,
|
||||
model: r.model,
|
||||
diagnostics: {
|
||||
timestamp: r.diagnostics_timestamp? new Date(r.diagnostics_timestamp) : null,
|
||||
scanTimestamp: r.diagnostics_scan_timestamp? new Date(r.diagnostics_scan_timestamp) : null,
|
||||
frontTimestamp: r.diagnostics_front_timestamp? new Date(r.diagnostics_front_timestamp) : null
|
||||
},
|
||||
pairedAt: new Date(r.created),
|
||||
lastPing: new Date(r.last_online),
|
||||
name: r.name,
|
||||
|
|
@ -362,6 +372,36 @@ function refillUnit (rec) {
|
|||
)])
|
||||
}
|
||||
|
||||
function diagnostics (rec) {
|
||||
const directory = `${OPERATOR_DATA_DIR}/diagnostics/${rec.deviceId}/`
|
||||
const sql = `UPDATE devices
|
||||
SET diagnostics_timestamp = NULL,
|
||||
diagnostics_scan_updated_at = NULL,
|
||||
diagnostics_front_updated_at = NULL
|
||||
WHERE device_id = $1`
|
||||
|
||||
const scanPath = path.join(directory, 'scan.jpg')
|
||||
const frontPath = path.join(directory, 'front.jpg')
|
||||
|
||||
const removeFiles = [scanPath, frontPath].map(filePath => {
|
||||
return fsPromises.unlink(filePath).catch(err => {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
// File doesn't exist, no problem
|
||||
})
|
||||
})
|
||||
|
||||
return Promise.all(removeFiles)
|
||||
.then(() => db.none(sql, [rec.deviceId]))
|
||||
.then(() => db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
|
||||
{
|
||||
action: 'diagnostics',
|
||||
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||
}
|
||||
)]))
|
||||
}
|
||||
|
||||
function setMachine (rec, operatorId) {
|
||||
rec.operatorId = operatorId
|
||||
switch (rec.action) {
|
||||
|
|
@ -374,6 +414,7 @@ function setMachine (rec, operatorId) {
|
|||
case 'restartServices': return restartServices(rec)
|
||||
case 'emptyUnit': return emptyUnit(rec)
|
||||
case 'refillUnit': return refillUnit(rec)
|
||||
case 'diagnostics': return diagnostics(rec)
|
||||
default: throw new Error('No such action: ' + rec.action)
|
||||
}
|
||||
}
|
||||
|
|
@ -436,6 +477,44 @@ function getNetworkHeartbeatByDevice (deviceId) {
|
|||
.then(res => _.mapKeys(_.camelCase, _.find(it => it.device_id === deviceId, res)))
|
||||
}
|
||||
|
||||
function updateDiagnostics (deviceId, images) {
|
||||
const sql = `UPDATE devices
|
||||
SET diagnostics_timestamp = NOW(),
|
||||
diagnostics_scan_updated_at = CASE WHEN $2 THEN NOW() ELSE diagnostics_scan_updated_at END,
|
||||
diagnostics_front_updated_at = CASE WHEN $3 THEN NOW() ELSE diagnostics_front_updated_at END
|
||||
WHERE device_id = $1`
|
||||
|
||||
const directory = `${OPERATOR_DATA_DIR}/diagnostics/${deviceId}/`
|
||||
const { scan, front } = images
|
||||
|
||||
return updatePhotos(scan, front, directory)
|
||||
.then(() => db.none(sql, [deviceId, !!scan, !!front]))
|
||||
.catch(err => logger.error('while running machine diagnostics: ', err))
|
||||
}
|
||||
|
||||
function createPhoto (name, data, dir) {
|
||||
if (!data) {
|
||||
logger.error(`Diagnostics error: No data to save for ${name} photo`)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const decodedImageData = Buffer.from(data, 'base64')
|
||||
const filename = path.join(dir, name)
|
||||
return fsPromises.writeFile(filename, decodedImageData)
|
||||
}
|
||||
|
||||
function updatePhotos (scan, front, dir) {
|
||||
const dirname = path.join(dir)
|
||||
_.attempt(() => makeDir.sync(dirname))
|
||||
|
||||
const promises = [
|
||||
createPhoto('scan.jpg', scan, dirname),
|
||||
createPhoto('front.jpg', front, dirname)
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMachineName,
|
||||
getMachines,
|
||||
|
|
@ -450,5 +529,6 @@ module.exports = {
|
|||
getConfig,
|
||||
getMachineIds,
|
||||
emptyMachineUnits,
|
||||
refillMachineUnits
|
||||
refillMachineUnits,
|
||||
updateDiagnostics
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ function machineAction (type, value) {
|
|||
logger.debug(`Refilling recyclers from machine '${deviceId}' from operator ${operatorId}`)
|
||||
state.refillUnit[operatorId] = { [deviceId]: pid }
|
||||
break
|
||||
case 'diagnostics':
|
||||
logger.debug(`Running diagnostics on machine '${deviceId}' from operator ${operatorId}`)
|
||||
state.diagnostics[operatorId] = { [deviceId]: pid }
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ module.exports = (function () {
|
|||
restartServicesMap: {},
|
||||
emptyUnit: {},
|
||||
refillUnit: {},
|
||||
diagnostics: {},
|
||||
mnemonic: null
|
||||
}
|
||||
}())
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const typeDef = gql`
|
|||
paired: Boolean!
|
||||
lastPing: Date
|
||||
pairedAt: Date
|
||||
diagnostics: Diagnostics
|
||||
version: String
|
||||
model: String
|
||||
cashUnits: CashUnits
|
||||
|
|
@ -24,6 +25,12 @@ const typeDef = gql`
|
|||
packetLoss: String
|
||||
}
|
||||
|
||||
type Diagnostics {
|
||||
timestamp: Date
|
||||
frontTimestamp: Date
|
||||
scanTimestamp: Date
|
||||
}
|
||||
|
||||
type CashUnits {
|
||||
cashbox: Int
|
||||
cassette1: Int
|
||||
|
|
@ -81,6 +88,7 @@ const typeDef = gql`
|
|||
restartServices
|
||||
emptyUnit
|
||||
refillUnit
|
||||
diagnostics
|
||||
}
|
||||
|
||||
type Query {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const cashboxRoutes = require('./routes/cashboxRoutes')
|
|||
const customerRoutes = require('./routes/customerRoutes')
|
||||
const logsRoutes = require('./routes/logsRoutes')
|
||||
const pairingRoutes = require('./routes/pairingRoutes')
|
||||
const diagnosticsRoutes = require('./routes/diagnosticsRoutes')
|
||||
const performanceRoutes = require('./routes/performanceRoutes')
|
||||
const phoneCodeRoutes = require('./routes/phoneCodeRoutes')
|
||||
const pollingRoutes = require('./routes/pollingRoutes')
|
||||
|
|
@ -73,6 +74,7 @@ app.use('/state', stateRoutes)
|
|||
app.use('/cashbox', cashboxRoutes)
|
||||
|
||||
app.use('/network', performanceRoutes)
|
||||
app.use('/diagnostics', diagnosticsRoutes)
|
||||
|
||||
app.use('/verify_user', verifyUserRoutes)
|
||||
app.use('/verify_transaction', verifyTxRoutes)
|
||||
|
|
|
|||
14
lib/routes/diagnosticsRoutes.js
Normal file
14
lib/routes/diagnosticsRoutes.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
const { updateDiagnostics } = require('../machine-loader')
|
||||
|
||||
function diagnostics (req, res, next) {
|
||||
return updateDiagnostics(req.deviceId, req.body)
|
||||
.then(() => res.status(200).send({ status: 'OK' }))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
router.post('/', diagnostics)
|
||||
|
||||
module.exports = router
|
||||
Loading…
Add table
Add a link
Reference in a new issue