From 017790898235fa74f8d55d0d8f15ec68ebbfc7a1 Mon Sep 17 00:00:00 2001 From: Pierre-Gilles Leymarie Date: Mon, 4 May 2020 16:01:51 +0200 Subject: [PATCH] Remove scene execution concurrency, fix camera issues & fix MQTT new value parsing (#769) * Remove scene queue concurrency * Fix #749 : Camera should not create tons of folders + improve image compression * Fix camera tests * Fix scene UI, automatically add actions blocks * Add tests to cameraService.start * Fix #767: MQTT service should parseFloat received device value * Fix MQTT tests --- front/src/routes/scene/edit-scene/index.js | 2 +- server/lib/index.js | 5 +++-- server/lib/scene/index.js | 1 - server/lib/system/index.js | 3 ++- server/lib/system/system.init.js | 4 ++++ .../mqtt/lib/handler/handleGladysMessage.js | 2 +- server/services/rtsp-camera/index.js | 3 +++ server/services/rtsp-camera/lib/getImage.js | 13 +++++++------ server/test/lib/system/system.test.js | 18 +++++++++++------- .../services/mqtt/lib/handleNewMessage.test.js | 2 +- server/test/services/mqtt/mqttHandler.test.js | 2 +- .../services/rtsp-camera/rtspCamera.test.js | 11 ++++++++++- 12 files changed, 44 insertions(+), 22 deletions(-) diff --git a/front/src/routes/scene/edit-scene/index.js b/front/src/routes/scene/edit-scene/index.js index 622f73dca2..8140b3407d 100644 --- a/front/src/routes/scene/edit-scene/index.js +++ b/front/src/routes/scene/edit-scene/index.js @@ -74,7 +74,7 @@ class EditScene extends Component { } } }); - if (newState.scene.actions[columnIndex].length === 0) { + if (columnIndex + 1 === newState.scene.actions.length && newState.scene.actions[columnIndex].length === 1) { newState = update(newState, { scene: { actions: { diff --git a/server/lib/index.js b/server/lib/index.js index 000cc69ce2..88c8db38fa 100644 --- a/server/lib/index.js +++ b/server/lib/index.js @@ -60,7 +60,7 @@ function Gladys(params = {}) { const device = new Device(event, message, stateManager, service, room, variable); const scene = new Scene(stateManager, event, device, message); const scheduler = new Scheduler(event); - const system = new System(db.sequelize, event); + const system = new System(db.sequelize, event, config); const weather = new Weather(service, event, message, house); const gateway = new Gateway(variable, event, system, db.sequelize, config, user); @@ -91,6 +91,8 @@ function Gladys(params = {}) { // Execute DB migrations await db.umzug.up(); + await system.init(); + if (!params.disableBrainLoading) { await brain.load(); } @@ -114,7 +116,6 @@ function Gladys(params = {}) { scheduler.init(); } gateway.init(); - system.init(); }, }; diff --git a/server/lib/scene/index.js b/server/lib/scene/index.js index 6d924bf8d0..3d3dd401a1 100644 --- a/server/lib/scene/index.js +++ b/server/lib/scene/index.js @@ -23,7 +23,6 @@ const SceneManager = function SceneManager(stateManager, event, device, message) // @ts-ignore this.queue = queue({ autostart: true, - concurrency: 1, }); this.event.on(EVENTS.TRIGGERS.CHECK, eventFunctionWrapper(this.checkTrigger.bind(this))); this.event.on(EVENTS.ACTION.TRIGGERED, eventFunctionWrapper(this.executeSingleAction.bind(this))); diff --git a/server/lib/system/index.js b/server/lib/system/index.js index 7658131c20..b9b29aa365 100644 --- a/server/lib/system/index.js +++ b/server/lib/system/index.js @@ -12,13 +12,14 @@ const { getDiskSpace } = require('./system.getDiskSpace'); const { saveLatestGladysVersion } = require('./system.saveLatestGladysVersion'); const { shutdown } = require('./system.shutdown'); -const System = function System(sequelize, event) { +const System = function System(sequelize, event, config) { this.downloadUpgradeError = null; this.downloadUpgradeFinished = null; this.downloadUpgradeLastEvent = null; this.Docker = Docker; this.sequelize = sequelize; this.event = event; + this.config = config; this.dockerode = null; this.event.on(EVENTS.SYSTEM.DOWNLOAD_UPGRADE, eventFunctionWrapper(this.downloadUpgrade.bind(this))); }; diff --git a/server/lib/system/system.init.js b/server/lib/system/system.init.js index 4a915dc362..84888079c8 100644 --- a/server/lib/system/system.init.js +++ b/server/lib/system/system.init.js @@ -8,6 +8,10 @@ const logger = require('../../utils/logger'); * init(); */ async function init() { + // Clean temp folder + await fse.emptyDir(this.config.tempFolder); + // Ensure temp directory exists + await fse.ensureDir(this.config.tempFolder); // we get Gladys version from package.json const packageJsonString = await fse.readFile(path.join(__dirname, '../../../package.json'), 'utf8'); const packageJson = JSON.parse(packageJsonString); diff --git a/server/services/mqtt/lib/handler/handleGladysMessage.js b/server/services/mqtt/lib/handler/handleGladysMessage.js index dde6ae50f9..68f3634d0e 100644 --- a/server/services/mqtt/lib/handler/handleGladysMessage.js +++ b/server/services/mqtt/lib/handler/handleGladysMessage.js @@ -20,7 +20,7 @@ function handleGladysMessage(topic, message) { } const event = { device_feature_external_id: parsedTopic[5], - state: message, + state: parseFloat(message), }; this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, event); } else { diff --git a/server/services/rtsp-camera/index.js b/server/services/rtsp-camera/index.js index bcf6f9fad4..ea9e636344 100644 --- a/server/services/rtsp-camera/index.js +++ b/server/services/rtsp-camera/index.js @@ -1,3 +1,4 @@ +const fse = require('fs-extra'); const logger = require('../../utils/logger'); const RtspCameraHandler = require('./lib'); const RtspCameraController = require('./api/rtspCamera.controller'); @@ -13,6 +14,8 @@ module.exports = function RtspCameraService(gladys, serviceId) { */ async function start() { logger.log('starting RTSP service'); + // make sure the tempFolder exists + await fse.ensureDir(gladys.config.tempFolder); } /** diff --git a/server/services/rtsp-camera/lib/getImage.js b/server/services/rtsp-camera/lib/getImage.js index 93954e28cc..7d77127836 100644 --- a/server/services/rtsp-camera/lib/getImage.js +++ b/server/services/rtsp-camera/lib/getImage.js @@ -4,7 +4,6 @@ const logger = require('../../../utils/logger'); const { NotFoundError } = require('../../../utils/coreErrors'); const DEVICE_PARAM_CAMERA_URL = 'CAMERA_URL'; -const CAMERA_FILENAME = 'output.jpg'; /** * @description Get camera image. @@ -25,19 +24,20 @@ async function getImage(device) { } // we create a temp folder const now = new Date(); - const tempFolder = path.join( + const filePath = path.join( this.gladys.config.tempFolder, - `camera-${device.id}-${now.getMilliseconds()}-${now.getSeconds()}-${now.getMinutes()}-${now.getHours()}`, + `camera-${device.id}-${now.getMilliseconds()}-${now.getSeconds()}-${now.getMinutes()}-${now.getHours()}.jpg`, ); - await fse.ensureDir(tempFolder); - const filePath = path.join(tempFolder, CAMERA_FILENAME); // we create a writestream const writeStream = fse.createWriteStream(filePath); // and send a camera thumbnail to this stream this.ffmpeg(cameraUrlParam.value) .format('image2') .outputOptions('-vframes 1') + // resize the image with max width = 640 .outputOptions('-vf scale=640:-1') + // Effective range for JPEG is 2-31 with 31 being the worst quality. + .outputOptions('-qscale:v 15') .output(writeStream) .on('end', async () => { const image = await fse.readFile(filePath); @@ -47,10 +47,11 @@ async function getImage(device) { resolve(cameraImage); await fse.remove(filePath); }) - .on('error', (err, stdout, stderr) => { + .on('error', async (err, stdout, stderr) => { logger.debug(`Cannot process video: ${err.message}`); logger.debug(stderr); reject(err.message); + await fse.remove(filePath); }) .run(); return null; diff --git a/server/test/lib/system/system.test.js b/server/test/lib/system/system.test.js index c6cb94e2c9..e95e7bcfae 100644 --- a/server/test/lib/system/system.test.js +++ b/server/test/lib/system/system.test.js @@ -13,11 +13,15 @@ const sequelize = { close: fake.resolves(null), }; +const config = { + tempFolder: '/tmp/gladys', +}; + const event = new EventEmitter(); describe('system', () => { it('should get infos', async () => { - const system = new System(sequelize, event); + const system = new System(sequelize, event, config); await system.init(); const infos = await system.getInfos(); expect(infos).to.have.property('hostname'); @@ -38,7 +42,7 @@ describe('system', () => { expect(infos.gladys_version.substr(0, 1)).to.equal('v'); }); it('should return disk space', async () => { - const system = new System(sequelize, event); + const system = new System(sequelize, event, config); const diskSpace = await system.getDiskSpace(); expect(diskSpace).to.have.property('filesystem'); expect(diskSpace).to.have.property('size'); @@ -48,27 +52,27 @@ describe('system', () => { expect(diskSpace).to.have.property('mountpoint'); }); it('should return if process is running inside docker or not', async () => { - const system = new System(sequelize, event); + const system = new System(sequelize, event, config); const isDocker = await system.isDocker(); expect(typeof isDocker).to.equal('boolean'); }); it('should list containers', async () => { - const system = new System(sequelize, event); + const system = new System(sequelize, event, config); await system.init(); const containers = await system.getContainers(); expect(containers).be.instanceOf(Array); }); it('should init system', async () => { - const system = new System(sequelize, event); + const system = new System(sequelize, event, config); await system.init(); }); it('should download upgrade', async () => { - const system = new System(sequelize, event); + const system = new System(sequelize, event, config); await system.init(); await system.downloadUpgrade('latest'); }); it('should install upgrade', async () => { - const system = new System(sequelize, event); + const system = new System(sequelize, event, config); await system.init(); await system.installUpgrade(); }); diff --git a/server/test/services/mqtt/lib/handleNewMessage.test.js b/server/test/services/mqtt/lib/handleNewMessage.test.js index 2b5074836f..88a2472898 100644 --- a/server/test/services/mqtt/lib/handleNewMessage.test.js +++ b/server/test/services/mqtt/lib/handleNewMessage.test.js @@ -48,7 +48,7 @@ describe('Mqtt handle message', () => { assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'my_feature_external_id', - state: '19.8', + state: 19.8, }); }); diff --git a/server/test/services/mqtt/mqttHandler.test.js b/server/test/services/mqtt/mqttHandler.test.js index 5dee13bbc3..5fe6314f92 100644 --- a/server/test/services/mqtt/mqttHandler.test.js +++ b/server/test/services/mqtt/mqttHandler.test.js @@ -62,7 +62,7 @@ describe('MqttHandler', () => { assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: 'my_feature_external_id', - state: '19.8', + state: 19.8, }); }); }); diff --git a/server/test/services/rtsp-camera/rtspCamera.test.js b/server/test/services/rtsp-camera/rtspCamera.test.js index 5837cb3ec2..f43499f575 100644 --- a/server/test/services/rtsp-camera/rtspCamera.test.js +++ b/server/test/services/rtsp-camera/rtspCamera.test.js @@ -1,12 +1,14 @@ const { expect } = require('chai'); +const fse = require('fs-extra'); const assertChai = require('chai').assert; const { fake, assert } = require('sinon'); const FfmpegMock = require('./FfmpegMock.test'); const RtspCameraManager = require('../../../services/rtsp-camera/lib'); +const RtspCameraService = require('../../../services/rtsp-camera'); const gladys = { config: { - tempFolder: './.tmp', + tempFolder: '/tmp/gladys', }, device: { camera: { @@ -39,6 +41,13 @@ const brokenDevice = { describe('RtspCameraManager commands', () => { const rtspCameraManager = new RtspCameraManager(gladys, FfmpegMock, 'de051f90-f34a-4fd5-be2e-e502339ec9bc'); + before(async () => { + await fse.ensureDir(gladys.config.tempFolder); + }); + it('should start service', async () => { + const rtspCameraService = RtspCameraService(gladys, 'de051f90-f34a-4fd5-be2e-e502339ec9bc'); + await rtspCameraService.start(); + }); it('should getImage', async () => { const image = await rtspCameraManager.getImage(device); expect(image).to.equal('image/png;base64,aW1hZ2U=');