From 85363919bbc9b95c858c6d0d64771b5de1078dbf Mon Sep 17 00:00:00 2001 From: SeydX Date: Thu, 13 Jan 2022 10:22:00 +0100 Subject: [PATCH] v1.0.5 --- CHANGELOG.md | 9 + package-lock.json | 29 +++- package.json | 5 +- src/api/components/files/files.controller.js | 17 +- .../settings/settings.controller.js | 29 ---- .../components/system/system.controller.js | 12 +- src/api/database.js | 2 +- src/common/ping.js | 32 +++- .../camera/services/stream.service.js | 161 +++++++----------- .../camera/services/videoanalysis.service.js | 101 +++++++---- src/controller/event/event.controller.js | 4 +- src/controller/motion/motion.controller.js | 10 +- ui/src/components/footer.vue | 2 +- 13 files changed, 218 insertions(+), 195 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c94ec38a..f8e39211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,19 @@ All notable changes to this project will be documented in this file. # Next ## Other Changes +- Reduced default videoanalysis sensitivity to 25 +- Removed session control +- Videoanalysis improvements +- Refactored web stream +- Improved camera pinging +- Moved ENOENT messages to debug (eg. if recording not found) - Minor UI improvements ## Bugfixes +- Fixed an issue where resetting motion via MQTT didnt work - Fixed an issue where the video analysis sensitivity does not work as desired +- Fixed an issue where mapping video/audio stream didnt work (ffmpeg) +- Minor Bugfixes # [1.0.4] - 2022-01-11 diff --git a/package-lock.json b/package-lock.json index 62445740..5ddacf9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "camera.ui", - "version": "1.0.5-beta.0", + "version": "1.0.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "camera.ui", - "version": "1.0.5-beta.0", + "version": "1.0.5", "funding": [ { "type": "paypal", @@ -46,7 +46,8 @@ "morgan": "^1.10.0", "mqtt": "^4.3.4", "multer": "^1.4.4", - "nanoid": "^3.1.31", + "nanoid": "^3.1.32", + "nodejs-tcp-ping": "^1.0.3", "os": "^0.1.2", "pam-diff": "^1.1.0", "piexifjs": "^1.0.6", @@ -7754,9 +7755,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.1.31", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.31.tgz", - "integrity": "sha512-ZivnJm0o9bb13p2Ot5CpgC2rQdzB9Uxm/mFZweqm5eMViqOJe3PV6LU2E30SiLgheesmcPrjquqraoolONSA0A==", + "version": "3.1.32", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.32.tgz", + "integrity": "sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7844,6 +7845,11 @@ "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, + "node_modules/nodejs-tcp-ping": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/nodejs-tcp-ping/-/nodejs-tcp-ping-1.0.3.tgz", + "integrity": "sha1-8m14+fNZQ3OdRxx2rOAUT5EJXuE=" + }, "node_modules/nodemailer": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.1.tgz", @@ -16772,9 +16778,9 @@ "optional": true }, "nanoid": { - "version": "3.1.31", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.31.tgz", - "integrity": "sha512-ZivnJm0o9bb13p2Ot5CpgC2rQdzB9Uxm/mFZweqm5eMViqOJe3PV6LU2E30SiLgheesmcPrjquqraoolONSA0A==" + "version": "3.1.32", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.32.tgz", + "integrity": "sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==" }, "natural-compare": { "version": "1.4.0", @@ -16844,6 +16850,11 @@ "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, + "nodejs-tcp-ping": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/nodejs-tcp-ping/-/nodejs-tcp-ping-1.0.3.tgz", + "integrity": "sha1-8m14+fNZQ3OdRxx2rOAUT5EJXuE=" + }, "nodemailer": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.1.tgz", diff --git a/package.json b/package.json index 525758ec..25ccffb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "camera.ui", - "version": "1.0.5-beta.0", + "version": "1.0.5", "description": "User Interface for RTSP capable cameras.", "author": "SeydX (https://github.com/SeydX/camera.ui)", "scripts": { @@ -39,7 +39,8 @@ "morgan": "^1.10.0", "mqtt": "^4.3.4", "multer": "^1.4.4", - "nanoid": "^3.1.31", + "nanoid": "^3.1.32", + "nodejs-tcp-ping": "^1.0.3", "os": "^0.1.2", "pam-diff": "^1.1.0", "piexifjs": "^1.0.6", diff --git a/src/api/components/files/files.controller.js b/src/api/components/files/files.controller.js index a1b58b45..dc81a5ee 100644 --- a/src/api/components/files/files.controller.js +++ b/src/api/components/files/files.controller.js @@ -3,11 +3,14 @@ const path = require('path'); +const SettingsModel = require('../settings/settings.model'); + const { ConfigService } = require('../../../services/config/config.service'); +const { LoggerService } = require('../../../services/logger/logger.service'); -const SettingsModel = require('../settings/settings.model'); +const { log } = LoggerService; -exports.serve = async (req, res) => { +exports.serve = async (req, res, next) => { try { const recordingSettings = await SettingsModel.getByTarget(false, 'recordings'); @@ -23,7 +26,15 @@ exports.serve = async (req, res) => { root: path.join(recPath), }; - res.sendFile(file, options); + res.sendFile(file, options, (err) => { + if (err) { + if (err?.status === 404 || err?.statusCode === 404) { + log.debug(err.message); + } + + next(); + } + }); } catch (error) { res.status(500).send({ statusCode: 500, diff --git a/src/api/components/settings/settings.controller.js b/src/api/components/settings/settings.controller.js index 9bf9bddb..e79cf9af 100644 --- a/src/api/components/settings/settings.controller.js +++ b/src/api/components/settings/settings.controller.js @@ -80,39 +80,10 @@ exports.patchTarget = async (req, res) => { await SettingsModel.patchByTarget(req.query.all, req.params.target, req.body); if (req.params.target === 'cameras') { - /*if (req.query.stopStream === 'true') { - for (const controller of cameras.values()) { - controller.stream?.stop(); - } - }*/ - - //log.debug('Camera settings changed. The changes take effect when the camera stream is restarted.'); - const cameraSettings = req.body; for (const camera of cameraSettings) { const controller = cameras.get(camera.name); - - let setting = { - '-s': camera.resolution, - }; - - if (camera.audio) { - controller?.stream?.delStreamOptions(['-an']); - - setting = { - ...setting, - '-codec:a': 'mp2', - '-ar': '44100', - '-ac': '1', - '-b:a': '128k', - }; - } else { - controller?.stream?.delStreamOptions(['-codec:a', '-ar', '-ac', '-b:a']); - setting['-an'] = ''; - } - - controller?.stream.setStreamOptions(setting); controller?.videoanalysis.changeZone(camera.videoanalysis.regions, camera.videoanalysis.sensitivity); } } diff --git a/src/api/components/system/system.controller.js b/src/api/components/system/system.controller.js index a0140241..8fb7a1ee 100644 --- a/src/api/components/system/system.controller.js +++ b/src/api/components/system/system.controller.js @@ -83,13 +83,21 @@ exports.clearLog = async (req, res) => { } }; -exports.downloadDb = async (req, res) => { +exports.downloadDb = async (req, res, next) => { try { const dbPath = ConfigService.databaseFilePath; //const dbJson = JSON.stringify((await fs.readJSON(dbPath, { throws: false })) || {}); res.header('Content-Type', 'application/json'); - res.sendFile(dbPath); + res.sendFile(dbPath, (err) => { + if (err) { + if (err?.status === 404 || err?.statusCode === 404) { + log.debug(err.message); + } + + next(); + } + }); } catch (error) { res.status(500).send({ statusCode: 500, diff --git a/src/api/database.js b/src/api/database.js index 566efdd0..522c76ff 100644 --- a/src/api/database.js +++ b/src/api/database.js @@ -138,7 +138,7 @@ const defaultCameraSettingsEntry = { labels: [], }, videoanalysis: { - sensitivity: 50, + sensitivity: 25, regions: [], }, }; diff --git a/src/common/ping.js b/src/common/ping.js index 31d50e8b..0a63f129 100644 --- a/src/common/ping.js +++ b/src/common/ping.js @@ -1,5 +1,6 @@ 'use-strict'; +const nodejsTcpPing = require('nodejs-tcp-ping'); const ping = require('ping'); const { URL } = require('url'); @@ -26,16 +27,33 @@ class Ping { const url = new URL(cameraSource); - log.debug(`Pinging ${url.hostname}`, camera.name); + log.debug(`Pinging ${url.hostname}:${url.port || 80}`, camera.name); - const response = await ping.promise.probe(url.hostname, { - timeout: timeout || 1, - extra: ['-i', '2'], - }); + let available = false; - let available = response && response.alive; + try { + const response = await nodejsTcpPing.tcpPing({ + attempts: 5, + host: url.hostname, + port: Number.parseInt(url.port) || 80, + timeout: (timeout || 1) * 1000, + }); - log.debug(`Pinging ${url.hostname} - ${available ? 'successful' : 'failed'}`, camera.name); + available = response.filter((result) => result.ping).length > 2; + } catch { + //ignore + } + + if (!available) { + const response = await ping.promise.probe(url.hostname, { + timeout: timeout || 1, + extra: ['-i', '2'], + }); + + available = response && response.alive; + } + + log.debug(`Pinging ${url.hostname}:${url.port || 80} - ${available ? 'successful' : 'failed'}`, camera.name); return available; } diff --git a/src/controller/camera/services/stream.service.js b/src/controller/camera/services/stream.service.js index 056aa538..3d9226a7 100644 --- a/src/controller/camera/services/stream.service.js +++ b/src/controller/camera/services/stream.service.js @@ -30,7 +30,6 @@ class StreamService { this.#prebufferService = prebufferService; this.cameraName = camera.name; - this.streamOptions = {}; } reconfigure(camera) { @@ -54,112 +53,86 @@ class StreamService { const cameraSetting = Settings.find((camera) => camera && camera.name === this.cameraName); const videoConfig = cameraUtils.generateVideoConfig(this.#camera.videoConfig); - const source = cameraUtils.generateInputSource(videoConfig); - - this.streamOptions = { - source: source, - ffmpegOptions: { - '-s': cameraSetting?.resolution ? cameraSetting.resolution : `${videoConfig.maxWidth}x${videoConfig.maxHeight}`, - '-b:v': `${videoConfig.maxBitrate}k`, - '-r': videoConfig.maxFPS, - '-bf': 0, - '-preset:v': 'ultrafast', - '-threads': '1', - }, - }; - - if (videoConfig.mapvideo) { - this.streamOptions.ffmpegOptions['-map'] = videoConfig.mapvideo; + let ffmpegInput = [...cameraUtils.generateInputSource(videoConfig).split(/\s+/)]; + let prebuffer = null; + + if (this.#camera.prebuffering && this.#prebufferService) { + try { + const containerInput = await this.#prebufferService.getVideo({ + container: 'mpegts', + }); + + ffmpegInput = prebuffer = containerInput; + } catch { + //ignore + } } - if (videoConfig.videoFilter) { - this.streamOptions.ffmpegOptions['-filter:v'] = videoConfig.videoFilter; + const videoArguments = ['-f', 'mpegts', '-vcodec', 'mpeg1video']; + + if (videoConfig.mapvideo && !prebuffer) { + videoArguments.unshift('-map', videoConfig.mapvideo); } + const audioArguments = []; + if (cameraSetting?.audio && this.#mediaService.codecs.audio.length > 0) { - delete this.streamOptions.ffmpegOptions['-an']; - - this.streamOptions.ffmpegOptions = { - ...this.streamOptions.ffmpegOptions, - '-codec:a': 'mp2', - '-ar': '44100', - '-ac': '1', - '-b:a': '128k', - }; - } else { - delete this.streamOptions.ffmpegOptions['-codec:a']; - delete this.streamOptions.ffmpegOptions['-ar']; - delete this.streamOptions.ffmpegOptions['-ac']; - delete this.streamOptions.ffmpegOptions['-b:a']; + audioArguments.push('-acodec', 'mp2', '-ac', '1', '-b:a', '128k'); + } - this.streamOptions.ffmpegOptions['-an'] = ''; + if (videoConfig.mapaudio && !prebuffer) { + audioArguments.unshift('-map', videoConfig.mapaudio); } + + const additionalFlags = [ + '-s', + cameraSetting?.resolution ? cameraSetting.resolution : `${videoConfig.maxWidth}x${videoConfig.maxHeight}`, + '-b:v', + `${videoConfig.maxBitrate}k`, + '-r', + videoConfig.maxFPS, + '-bf', + '0', + '-preset:v', + 'ultrafast', + '-threads', + '1', + '-q', + '1', + '-max_muxing_queue_size', + '1024', + ]; + + return { + ffmpegInput, + prebuffer, + audioArguments, + videoArguments, + additionalFlags, + }; } async start() { if (!this.streamSession) { - await this.configureStreamOptions(); + let { ffmpegInput, prebuffer, audioArguments, videoArguments, additionalFlags } = + await this.configureStreamOptions(); - const videoConfig = cameraUtils.generateVideoConfig(this.#camera.videoConfig); - - let input = this.streamOptions.source.split(/\s+/); - let prebuffer = null; - - if (this.#camera.prebuffering && this.#prebufferService) { - try { - const containerInput = await this.#prebufferService.getVideo({ - container: 'mpegts', - }); - - input = prebuffer = containerInput; - - delete this.streamOptions.ffmpegOptions['-map']; - delete this.streamOptions.ffmpegOptions['-filter:v']; - } catch { - //ignore - } - } - - if (!prebuffer) { + /*if (!prebuffer) { const allowStream = this.#sessionService.requestSession(); - - console.log('ALLOW???'); - console.log(allowStream); - - console.log(this.#sessionService); - if (!allowStream) { log.error('Not allowed to start stream. Session limit exceeded!', this.cameraName, 'streams'); return; } - } - - const additionalFlags = []; - - if (this.streamOptions.ffmpegOptions) { - for (const key of Object.keys(this.streamOptions.ffmpegOptions)) { - additionalFlags.push(key, this.streamOptions.ffmpegOptions[key]); - } - } - - if (videoConfig.mapaudio && !prebuffer) { - additionalFlags.push('-map', videoConfig.mapaudio); - } + }*/ const spawnOptions = [ '-hide_banner', '-loglevel', 'error', - ...input, - '-f', - 'mpegts', - '-codec:v', - 'mpeg1video', + ...ffmpegInput, + ...videoArguments, + ...audioArguments, ...additionalFlags, - '-q', - '1', - '-max_muxing_queue_size', - '1024', '-', ].filter((key) => key !== ''); @@ -214,26 +187,6 @@ class StreamService { this.start(); } } - - setStreamSource(source) { - if (source.inludes('-i')) { - this.streamOptions.source = source.split(/\s+/); - } else { - log.warn(`Source ${source} is not valid, skipping`, this.cameraName, 'streams'); - } - } - - setStreamOptions(options) { - for (const [key, value] of Object.entries(options)) { - this.streamOptions.ffmpegOptions[key] = value; - } - } - - delStreamOptions(options) { - for (const property of options) { - delete this.streamOptions.ffmpegOptions[property]; - } - } } exports.StreamService = StreamService; diff --git a/src/controller/camera/services/videoanalysis.service.js b/src/controller/camera/services/videoanalysis.service.js index 2948ddda..0a3560f6 100644 --- a/src/controller/camera/services/videoanalysis.service.js +++ b/src/controller/camera/services/videoanalysis.service.js @@ -23,7 +23,8 @@ const FFMPEG_MODE = 'rgba'; // gray, rgba, rgb24 const FFMPEG_RESOLUTION = '640:360'; const FFMPEG_FPS = '2'; const DIFFERENCE = 9; -const GRAYSCALE = 'luminosity'; +//const GRAYSCALE = 'luminosity'; +const DWELL_TIME = 2 * 60 * 1000; class VideoAnalysisService { #camera; @@ -35,8 +36,8 @@ class VideoAnalysisService { cameraState = true; restartTimer = null; watchdog = null; - - motionTriggered = false; + motionEventTimeout = null; + forceCloseTimeout = null; constructor(camera, prebufferService, socket) { //log.debug('Initializing video analysis', camera.name); @@ -62,18 +63,16 @@ class VideoAnalysisService { } } - changeSensitivity(sensitivity) { - if (sensitivity >= 0 && sensitivity <= 100 && this.videoanalysisSession?.pamDiff) { - this.videoanalysisSession.pamDiff.setDifference(DIFFERENCE); - this.videoanalysisSession.pamDiff.setPercent(100 - sensitivity); - } - } - changeZone(regions = [], sensitivity) { if (this.videoanalysisSession?.pamDiff) { this.videoanalysisSession.pamDiff.resetCache(); - this.changeSensitivity(sensitivity); - const zones = this.#createRegions(regions, sensitivity); + this.videoanalysisSession.p2p.resetCache(); + + const percent = sensitivity >= 0 && sensitivity <= 100 ? sensitivity : 25; + const zones = this.#createRegions(regions, percent); + + this.videoanalysisSession.pamDiff.setDifference(DIFFERENCE); + this.videoanalysisSession.pamDiff.setPercent(100 - percent); this.videoanalysisSession.pamDiff.setRegions(zones.length > 0 ? zones : null); } } @@ -124,7 +123,8 @@ class VideoAnalysisService { this.cameraState = true; this.restartTimer = null; this.watchdog = null; - this.motionTriggered = false; + this.motionEventTimeout = null; + this.forceCloseTimeout = null; } stop(killed) { @@ -137,12 +137,25 @@ class VideoAnalysisService { clearTimeout(this.watchdog); } + if (this.motionEventTimeout) { + clearTimeout(this.motionEventTimeout); + this.motionEventTimeout = null; + + this.#triggerMotion(false); + } + + if (this.forceCloseTimeout) { + clearTimeout(this.forceCloseTimeout); + this.forceCloseTimeout = null; + } + if (this.restartTimer) { clearTimeout(this.restartTimer); this.restartTimer = null; } this.videoanalysisSession.cp?.kill('SIGKILL'); + this.videoanalysisSession.pamDiff?.resetCache(); this.videoanalysisSession = undefined; } } @@ -175,8 +188,10 @@ class VideoAnalysisService { } } + const videoArguments = ['-an', '-vcodec', 'pam']; + if (!prebufferInput && videoConfig.mapvideo) { - input.push('-map', videoConfig.mapvideo); + videoArguments.push('-map', videoConfig.mapvideo); } const ffmpegArguments = [ @@ -186,9 +201,7 @@ class VideoAnalysisService { '-hwaccel', 'auto', ...input, - '-an', - '-vcodec', - 'pam', + ...videoArguments, '-pix_fmt', FFMPEG_MODE, '-f', @@ -211,13 +224,13 @@ class VideoAnalysisService { const p2p = new P2P(); const pamDiff = new PamDiff({ //difference: settings?.videoanalysis?.difference || 9, - grayscale: GRAYSCALE, + //grayscale: GRAYSCALE, difference: DIFFERENCE, - percent: settings?.videoanalysis?.percentage || 5, + percent: 100 - (settings?.videoanalysis?.sensitivity || 25), regions: regions.length > 0 ? regions : null, - //response: 'percent', - response: 'bounds', - draw: true, + response: 'percent', + //response: 'bounds', + //draw: true, }); const restartWatchdog = () => { @@ -243,18 +256,27 @@ class VideoAnalysisService { // eslint-disable-next-line no-unused-vars pamDiff.on('diff', async (data) => { - if (!this.motionTriggered) { - this.motionTriggered = true; - - log.debug(`Motion detected via Videoanalysis: ${JSON.stringify(data.trigger)}`, this.cameraName); + if (!this.motionEventTimeout) { + if (this.forceCloseTimeout) { + clearTimeout(this.forceCloseTimeout); + this.forceCloseTimeout = null; + } - const result = await MotionController.handleMotion('motion', this.cameraName, true, 'videoanalysis', {}); - log.debug(`Received a new VIDEOANALYSIS message ${JSON.stringify(result)} (${this.cameraName})`); + log.debug(`Motion detected via Videoanalysis: ${JSON.stringify(data.trigger[0])}`, this.cameraName); + this.#triggerMotion(true); - setTimeout(() => { - this.motionTriggered = false; - }, 60000); + this.forceCloseTimeout = setTimeout(() => { + // forceClose after 3min + this.#triggerMotion(false); + }, 3 * 60 * 1000); } + + clearTimeout(this.motionEventTimeout); + + this.motionEventTimeout = setTimeout(async () => { + this.#triggerMotion(false); + this.motionEventTimeout = null; + }, DWELL_TIME); }); const cp = spawn(ConfigService.ui.options.videoProcessor, ffmpegArguments, { @@ -299,9 +321,20 @@ class VideoAnalysisService { }, cp, pamDiff, + p2p, }; } + async #triggerMotion(state) { + const result = await MotionController.handleMotion('motion', this.cameraName, state, 'videoanalysis', {}); + log.debug(`Received a new VIDEOANALYSIS message ${JSON.stringify(result)} (${this.cameraName})`); + + if (!state && this.forceCloseTimeout) { + clearTimeout(this.forceCloseTimeout); + this.forceCloseTimeout = null; + } + } + #millisUntilMidnight() { const midnight = new Date(); midnight.setHours(24); @@ -325,15 +358,15 @@ class VideoAnalysisService { } #createRegions(regions = [], sensitivity) { - sensitivity = sensitivity >= 0 && sensitivity <= 100 ? sensitivity : 50; + const percent = 100 - (sensitivity >= 0 && sensitivity <= 100 ? sensitivity : 25); const zones = regions ?.map((region, index) => { if (region.coords?.length > 2) { return { name: `region${index}`, - difference: 9, - percent: 100 - sensitivity, + difference: DIFFERENCE, + percent: percent, polygon: region.coords ?.map((coord) => { let x = coord[0] < 0 ? 0 : coord[0] > 100 ? 100 : coord[0]; diff --git a/src/controller/event/event.controller.js b/src/controller/event/event.controller.js index eb92ef98..53651c77 100644 --- a/src/controller/event/event.controller.js +++ b/src/controller/event/event.controller.js @@ -137,9 +137,9 @@ class EventController { let allowStream = true; - if (controller && !fileBuffer && recordingSettings.active && !Camera.prebuffering) { + /*if (controller && !fileBuffer && recordingSettings.active && !Camera.prebuffering) { allowStream = controller.session.requestSession(); - } + }*/ if (fileBuffer) { motionInfo.label = 'Custom'; diff --git a/src/controller/motion/motion.controller.js b/src/controller/motion/motion.controller.js index acf6d5b1..fac2b557 100644 --- a/src/controller/motion/motion.controller.js +++ b/src/controller/motion/motion.controller.js @@ -149,6 +149,8 @@ class MotionController { message: `Malformed URL ${request.url}`, }; + log.debug(request.url, 'HTTP'); + let cameraName; if (request.url) { @@ -226,6 +228,8 @@ class MotionController { message: `Malformed MQTT message ${data.toString()} (${topic})`, }; + log.debug(`${data.toString()} (${topic})`, 'MQTT'); + let cameraName; const cameraMqttConfig = ConfigService.ui.topics.get(topic); @@ -301,7 +305,11 @@ class MotionController { state = check(); if (state === undefined && triggerType === 'motion') { - state = check(cameraMqttConfig.motionResetMessage); + const resetted = check(cameraMqttConfig.motionResetMessage); + + if (resetted !== undefined) { + state = !resetted; + } } result = diff --git a/ui/src/components/footer.vue b/ui/src/components/footer.vue index 8eb488b4..acfe9a15 100644 --- a/ui/src/components/footer.vue +++ b/ui/src/components/footer.vue @@ -2,7 +2,7 @@ v-footer.footer.tw-text-xs.tw-z-0(padless app absolute) .footer-wrapper.tw-flex.tw-items-center.tw-justify-end a.footer-link(href="https://github.com/SeydX" target="blank") {{ new Date().getFullYear() }} - - strong seydx + strong (c) seydx