diff --git a/.eslintrc.json b/.eslintrc.json index 46c19d4..61ed338 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,14 +1,14 @@ { - "extends": "airbnb-base", - "globals": { - "Module": true, - "Log": true, - "MM": true - }, - "rules": { - "comma-dangle": "off", - "object-shorthand": "off", - "func-names": "off", - "space-before-function-paren": "off" - } -} \ No newline at end of file + "extends": "airbnb-base", + "globals": { + "Module": true, + "Log": true, + "MM": true + }, + "rules": { + "comma-dangle": "off", + "object-shorthand": "off", + "func-names": "off", + "space-before-function-paren": "off" + } +} diff --git a/MMM-SynologySurveillance.js b/MMM-SynologySurveillance.js index 2c1a66d..a082c20 100644 --- a/MMM-SynologySurveillance.js +++ b/MMM-SynologySurveillance.js @@ -1,5 +1,4 @@ -Module.register('MMM-SynologySurveillance', { - +Module.register("MMM-SynologySurveillance", { defaults: { ds: [], order: null, @@ -17,341 +16,440 @@ Module.register('MMM-SynologySurveillance', { animationSpeed: 500, vertical: true, skipOnPrivilegeError: true, + updateDomOnShow: false }, /** * Apply any styles, if we have any. */ getStyles() { - if(this.config.vertical){ + if (this.config.vertical) { return ["synology-surveillance_v.css", "font-awesome.css"]; } else { return ["synology-surveillance_h.css", "font-awesome.css"]; } - }, start() { - this.dsStreamInfo = [] - this.dsPresetInfo = {} - this.dsPresetCurPosition = {} + this.dsStreamInfo = []; + this.dsPresetInfo = {}; + this.dsPresetCurPosition = {}; Log.info("Starting module: " + this.name); - this.order = [] - this.curBigIdx = 0 - this.currentProfile = '' - this.currentProfilePattern = new RegExp('.*') - - if(this.config.order !== null){ - var nameDsCamIdxMap = {} - for (var curDsIdx = 0; curDsIdx < this.config.ds.length; curDsIdx++){ - this.dsPresetInfo[curDsIdx] = {} - this.dsPresetCurPosition[curDsIdx] = {} - for(var curCamIdx = 0; curCamIdx < this.config.ds[curDsIdx].cams.length; curCamIdx++){ - this.dsPresetInfo[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] = {} - this.dsPresetCurPosition[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] = 0 - if(typeof this.config.ds[curDsIdx].cams[curCamIdx].alias !== "undefined"){ - var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].alias + this.order = []; + this.curBigIdx = 0; + this.currentProfile = ""; + this.currentProfilePattern = new RegExp(".*"); + + if (this.config.order !== null) { + var nameDsCamIdxMap = {}; + for (var curDsIdx = 0; curDsIdx < this.config.ds.length; curDsIdx++) { + this.dsPresetInfo[curDsIdx] = {}; + this.dsPresetCurPosition[curDsIdx] = {}; + for ( + var curCamIdx = 0; + curCamIdx < this.config.ds[curDsIdx].cams.length; + curCamIdx++ + ) { + this.dsPresetInfo[curDsIdx][ + this.config.ds[curDsIdx].cams[curCamIdx].name + ] = {}; + this.dsPresetCurPosition[curDsIdx][ + this.config.ds[curDsIdx].cams[curCamIdx].name + ] = 0; + if ( + typeof this.config.ds[curDsIdx].cams[curCamIdx].alias !== + "undefined" + ) { + var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].alias; } else { - var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name + var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name; } // console.log("Mapping cam name: "+curCamName+" to ds "+curDsIdx+" and cam id "+curCamIdx) - nameDsCamIdxMap[curCamName] = [curDsIdx,curCamIdx,this.config.ds[curDsIdx].cams[curCamIdx].name] + nameDsCamIdxMap[curCamName] = [ + curDsIdx, + curCamIdx, + this.config.ds[curDsIdx].cams[curCamIdx].name + ]; } } - for(var curOrderIdx = 0; curOrderIdx < this.config.order.length; curOrderIdx++){ - var curOrderName = this.config.order[curOrderIdx] - if(typeof nameDsCamIdxMap[curOrderName] !== "undefined"){ - var curRes = [nameDsCamIdxMap[curOrderName][0], nameDsCamIdxMap[curOrderName][1], curOrderName, nameDsCamIdxMap[curOrderName][2]] + for ( + var curOrderIdx = 0; + curOrderIdx < this.config.order.length; + curOrderIdx++ + ) { + var curOrderName = this.config.order[curOrderIdx]; + if (typeof nameDsCamIdxMap[curOrderName] !== "undefined") { + var curRes = [ + nameDsCamIdxMap[curOrderName][0], + nameDsCamIdxMap[curOrderName][1], + curOrderName, + nameDsCamIdxMap[curOrderName][2] + ]; // console.log("Pushing to order (special): "+JSON.stringify(curRes)) - this.order.push(curRes) - } + this.order.push(curRes); + } // else { // console.log("Skipping unknown cam: "+curOrderName) // } } } else { - for (var curDsIdx = 0; curDsIdx < this.config.ds.length; curDsIdx++){ - this.dsPresetInfo[curDsIdx] = {} - this.dsPresetCurPosition[curDsIdx] = {} - for(var curCamIdx = 0; curCamIdx < this.config.ds[curDsIdx].cams.length; curCamIdx++){ - this.dsPresetInfo[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] = {} - this.dsPresetCurPosition[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] = 0 - if(typeof this.config.ds[curDsIdx].cams[curCamIdx].alias !== "undefined"){ - var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].alias + for (var curDsIdx = 0; curDsIdx < this.config.ds.length; curDsIdx++) { + this.dsPresetInfo[curDsIdx] = {}; + this.dsPresetCurPosition[curDsIdx] = {}; + for ( + var curCamIdx = 0; + curCamIdx < this.config.ds[curDsIdx].cams.length; + curCamIdx++ + ) { + this.dsPresetInfo[curDsIdx][ + this.config.ds[curDsIdx].cams[curCamIdx].name + ] = {}; + this.dsPresetCurPosition[curDsIdx][ + this.config.ds[curDsIdx].cams[curCamIdx].name + ] = 0; + if ( + typeof this.config.ds[curDsIdx].cams[curCamIdx].alias !== + "undefined" + ) { + var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].alias; } else { - var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name + var curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name; } - var curRes = [curDsIdx, curCamIdx, curCamName] + var curRes = [curDsIdx, curCamIdx, curCamName]; // console.log("Pushing to order (regular): "+JSON.stringify(curRes)) - this.order.push([curDsIdx, curCamIdx, curCamName, this.config.ds[curDsIdx].cams[curCamIdx].name]) + this.order.push([ + curDsIdx, + curCamIdx, + curCamName, + this.config.ds[curDsIdx].cams[curCamIdx].name + ]); } } } - this.sendSocketNotification('CONFIG', this.config); - this.sendSocketNotification("INIT_DS") + this.sendSocketNotification("CONFIG", this.config); + this.sendSocketNotification("INIT_DS"); - setTimeout(()=>{ - this.sendRefreshUrlRequestAndResetTimer() - }, this.config.urlRefreshInterval * 1000) + setTimeout(() => { + this.sendRefreshUrlRequestAndResetTimer(); + }, this.config.urlRefreshInterval * 1000); }, - sendRefreshUrlRequestAndResetTimer(){ - this.sendSocketNotification("REFRESH_URLS") - setTimeout(()=>{ - this.sendRefreshUrlRequestAndResetTimer() - }, this.config.urlRefreshInterval * 1000) + sendRefreshUrlRequestAndResetTimer() { + this.sendSocketNotification("REFRESH_URLS"); + setTimeout(() => { + this.sendRefreshUrlRequestAndResetTimer(); + }, this.config.urlRefreshInterval * 1000); }, getDom() { - const self = this - const wrapper = document.createElement("div") - wrapper.className = "synology-surveillance" - - if(this.config.vertical && this.config.showOneBig){ - if(typeof this.order[this.curBigIdx] !== "undefined"){ - let curDsIdx = this.order[this.curBigIdx][0] - let curCamIdx = this.order[this.curBigIdx][1] - let curCamAlias = this.order[this.curBigIdx][2] - let curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name - let camWrapper = document.createElement("div") - camWrapper.className = "camWrapper big "+curDsIdx+"_"+curCamIdx+" "+curCamAlias - if(this.config.showBigCamName){ - let camNameWrapper = document.createElement("div") - camNameWrapper.className = "name" - camNameWrapper.innerHTML = curCamAlias + "
" - camWrapper.appendChild(camNameWrapper) - } + const self = this; + const wrapper = document.createElement("div"); + wrapper.className = "synology-surveillance"; - let innerCamWrapper = document.createElement("div") - let innerCamWrapperClassName = "innerCamWrapper big" - if((typeof this.dsStreamInfo[curDsIdx] !== "undefined") && - (typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined") - ){ - var cam = document.createElement("img") - cam.className = "cam" - cam.src = this.dsStreamInfo[curDsIdx][curCamName] - } else { - var cam = document.createElement("i") - cam.className = "cam nourl fa "+this.config.noUrlIcon - cam.addEventListener("click", ()=>{self.sendSocketNotification("REFRESH_URLS")}) - innerCamWrapperClassName += " nourl" - } - innerCamWrapper.className = innerCamWrapperClassName - innerCamWrapper.appendChild(cam) - camWrapper.appendChild(innerCamWrapper) - - if(self.config.showBigPositions){ - let innerPositionWrapper = document.createElement("div") - innerPositionWrapper.className = "innerPositionWrapper big" - //this.dsPresetInfo[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] - let curPosition = 0 - if((typeof this.dsPresetInfo[curDsIdx] !== "undefined") && - (typeof this.dsPresetInfo[curDsIdx][curCamName] !== "undefined") - ){ - for(var curPreset in this.dsPresetInfo[curDsIdx][curCamName]) { - let thisPosition = curPosition - console.log("CUR_POS: "+curPosition + " curActive: "+this.dsPresetCurPosition[curDsIdx][curCamName]) - let curPositionName = this.dsPresetInfo[curDsIdx][curCamName][curPreset].name - - var position = document.createElement("div") - //this.dsPresetCurPosition[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] - position.className = "position big" - if(this.dsPresetCurPosition[curDsIdx][curCamName] === thisPosition){ - var positionSelected = document.createElement("div") - positionSelected.className = "selected" - position.appendChild(positionSelected) - } + if (this.config.vertical && this.config.showOneBig) { + if (typeof this.order[this.curBigIdx] !== "undefined") { + let curDsIdx = this.order[this.curBigIdx][0]; + let curCamIdx = this.order[this.curBigIdx][1]; + let curCamAlias = this.order[this.curBigIdx][2]; + let curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name; + let camWrapper = document.createElement("div"); + camWrapper.className = + "camWrapper big " + curDsIdx + "_" + curCamIdx + " " + curCamAlias; + if (this.config.showBigCamName) { + let camNameWrapper = document.createElement("div"); + camNameWrapper.className = "name"; + camNameWrapper.innerHTML = curCamAlias + "
"; + camWrapper.appendChild(camNameWrapper); + } - position.addEventListener("click", ()=>{ - self.dsPresetCurPosition[curDsIdx][curCamName] = thisPosition - self.updateDom(self.config.animationSpeed) - self.sendSocketNotification("DS_CHANGE_POSITION", { - dsIdx: curDsIdx, - camName: curCamName, - position: thisPosition - } - )}) - innerPositionWrapper.appendChild(position) - curPosition += 1 - } + let innerCamWrapper = document.createElement("div"); + let innerCamWrapperClassName = "innerCamWrapper big"; + if ( + typeof this.dsStreamInfo[curDsIdx] !== "undefined" && + typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined" + ) { + var cam = document.createElement("img"); + cam.className = "cam"; + cam.src = this.dsStreamInfo[curDsIdx][curCamName]; + } else { + var cam = document.createElement("i"); + cam.className = "cam nourl fa " + this.config.noUrlIcon; + cam.addEventListener("click", () => { + self.sendSocketNotification("REFRESH_URLS"); + }); + innerCamWrapperClassName += " nourl"; + } + innerCamWrapper.className = innerCamWrapperClassName; + innerCamWrapper.appendChild(cam); + camWrapper.appendChild(innerCamWrapper); + + if (self.config.showBigPositions) { + let innerPositionWrapper = document.createElement("div"); + innerPositionWrapper.className = "innerPositionWrapper big"; + //this.dsPresetInfo[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] + let curPosition = 0; + if ( + typeof this.dsPresetInfo[curDsIdx] !== "undefined" && + typeof this.dsPresetInfo[curDsIdx][curCamName] !== "undefined" + ) { + for (var curPreset in this.dsPresetInfo[curDsIdx][curCamName]) { + let thisPosition = curPosition; + console.log( + "CUR_POS: " + + curPosition + + " curActive: " + + this.dsPresetCurPosition[curDsIdx][curCamName] + ); + let curPositionName = + this.dsPresetInfo[curDsIdx][curCamName][curPreset].name; + + var position = document.createElement("div"); + //this.dsPresetCurPosition[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] + position.className = "position big"; + if ( + this.dsPresetCurPosition[curDsIdx][curCamName] === thisPosition + ) { + var positionSelected = document.createElement("div"); + positionSelected.className = "selected"; + position.appendChild(positionSelected); } - camWrapper.appendChild(innerPositionWrapper) + + position.addEventListener("click", () => { + self.dsPresetCurPosition[curDsIdx][curCamName] = thisPosition; + self.updateDom(self.config.animationSpeed); + self.sendSocketNotification("DS_CHANGE_POSITION", { + dsIdx: curDsIdx, + camName: curCamName, + position: thisPosition + }); + }); + innerPositionWrapper.appendChild(position); + curPosition += 1; + } } - wrapper.appendChild(camWrapper) + camWrapper.appendChild(innerPositionWrapper); + } + wrapper.appendChild(camWrapper); } } - for(let curOrderIdx = 0; curOrderIdx < this.order.length; curOrderIdx++){ - let curDsIdx = this.order[curOrderIdx][0] - let curCamIdx = this.order[curOrderIdx][1] - let curCamAlias = this.order[curOrderIdx][2] - let curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name - - if( - (typeof this.config.ds[curDsIdx].cams[curCamIdx].profiles === "undefined") || - (this.currentProfilePattern.test(this.config.ds[curDsIdx].cams[curCamIdx].profiles)) - ){ - if( this.config.showUnreachableCams || - ((typeof this.dsStreamInfo[curDsIdx] !== "undefined") && - (typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined")) - ){ - if(!this.config.showOneBig || (curOrderIdx !== this.curBigIdx)){ - var camWrapper = document.createElement("div") - camWrapper.className = "camWrapper "+curDsIdx+"_"+curCamIdx+" "+curCamAlias - - if(this.config.showCamName){ - var camNameWrapper = document.createElement("div") - camNameWrapper.className = "name" - camNameWrapper.innerHTML = curCamAlias - if(this.config.showOneBig){ - camNameWrapper.addEventListener("click", ()=>{self.sendSocketNotification("SYNO_SS_CHANGE_CAM", {id: curOrderIdx})}) - } - - camWrapper.appendChild(camNameWrapper) - } + for (let curOrderIdx = 0; curOrderIdx < this.order.length; curOrderIdx++) { + let curDsIdx = this.order[curOrderIdx][0]; + let curCamIdx = this.order[curOrderIdx][1]; + let curCamAlias = this.order[curOrderIdx][2]; + let curCamName = this.config.ds[curDsIdx].cams[curCamIdx].name; - var innerCamWrapper = document.createElement("div") - var innerCamWrapperClassName = "innerCamWrapper" - if(this.config.showOneBig){ - innerCamWrapper.addEventListener("click", ()=>{self.sendSocketNotification("SYNO_SS_CHANGE_CAM", {id: curOrderIdx})}) - } - if((typeof this.dsStreamInfo[curDsIdx] !== "undefined") && - (typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined") - ){ - var cam = document.createElement("img") - cam.className = "cam" - cam.src = this.dsStreamInfo[curDsIdx][curCamName] - } else { - var cam = document.createElement("i") - cam.className = "cam nourl fa "+this.config.noUrlIcon - cam.addEventListener("click", ()=>{self.sendSocketNotification("REFRESH_URLS")}) - innerCamWrapperClassName += " nourl" + if ( + typeof this.config.ds[curDsIdx].cams[curCamIdx].profiles === + "undefined" || + this.currentProfilePattern.test( + this.config.ds[curDsIdx].cams[curCamIdx].profiles + ) + ) { + if ( + this.config.showUnreachableCams || + (typeof this.dsStreamInfo[curDsIdx] !== "undefined" && + typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined") + ) { + if (!this.config.showOneBig || curOrderIdx !== this.curBigIdx) { + var camWrapper = document.createElement("div"); + camWrapper.className = + "camWrapper " + curDsIdx + "_" + curCamIdx + " " + curCamAlias; + + if (this.config.showCamName) { + var camNameWrapper = document.createElement("div"); + camNameWrapper.className = "name"; + camNameWrapper.innerHTML = curCamAlias; + if (this.config.showOneBig) { + camNameWrapper.addEventListener("click", () => { + self.sendSocketNotification("SYNO_SS_CHANGE_CAM", { + id: curOrderIdx + }); + }); + } + + camWrapper.appendChild(camNameWrapper); + } + + var innerCamWrapper = document.createElement("div"); + var innerCamWrapperClassName = "innerCamWrapper"; + if (this.config.showOneBig) { + innerCamWrapper.addEventListener("click", () => { + self.sendSocketNotification("SYNO_SS_CHANGE_CAM", { + id: curOrderIdx + }); + }); + } + if ( + typeof this.dsStreamInfo[curDsIdx] !== "undefined" && + typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined" + ) { + var cam = document.createElement("img"); + cam.className = "cam"; + cam.src = this.dsStreamInfo[curDsIdx][curCamName]; + } else { + var cam = document.createElement("i"); + cam.className = "cam nourl fa " + this.config.noUrlIcon; + cam.addEventListener("click", () => { + self.sendSocketNotification("REFRESH_URLS"); + }); + innerCamWrapperClassName += " nourl"; + } + innerCamWrapper.className = innerCamWrapperClassName; + innerCamWrapper.appendChild(cam); + camWrapper.appendChild(innerCamWrapper); + + if (self.config.showPositions) { + let innerPositionWrapper = document.createElement("div"); + innerPositionWrapper.className = "innerPositionWrapper"; + let curPosition = 0; + if ( + typeof this.dsPresetInfo[curDsIdx] !== "undefined" && + typeof this.dsPresetInfo[curDsIdx][curCamName] !== "undefined" + ) { + for (var curPreset in this.dsPresetInfo[curDsIdx][curCamName]) { + let thisPosition = curPosition; + let curPositionName = + this.dsPresetInfo[curDsIdx][curCamName][curPreset].name; + + var position = document.createElement("div"); + position.className = "position"; + if ( + this.dsPresetCurPosition[curDsIdx][curCamName] === + curPosition + ) { + var positionSelected = document.createElement("div"); + positionSelected.className = "selected"; + position.appendChild(positionSelected); } - innerCamWrapper.className = innerCamWrapperClassName - innerCamWrapper.appendChild(cam) - camWrapper.appendChild(innerCamWrapper) - - if(self.config.showPositions){ - let innerPositionWrapper = document.createElement("div") - innerPositionWrapper.className = "innerPositionWrapper" - let curPosition = 0 - if((typeof this.dsPresetInfo[curDsIdx] !== "undefined") && - (typeof this.dsPresetInfo[curDsIdx][curCamName] !== "undefined") - ){ - for(var curPreset in this.dsPresetInfo[curDsIdx][curCamName]) { - let thisPosition = curPosition - let curPositionName = this.dsPresetInfo[curDsIdx][curCamName][curPreset].name - - var position = document.createElement("div") - position.className = "position" - if(this.dsPresetCurPosition[curDsIdx][curCamName] === curPosition){ - var positionSelected = document.createElement("div") - positionSelected.className = "selected" - position.appendChild(positionSelected) - } - position.addEventListener("click", ()=>{ - self.dsPresetCurPosition[curDsIdx][curCamName] = thisPosition - self.updateDom(self.config.animationSpeed) - self.sendSocketNotification("DS_CHANGE_POSITION", { - dsIdx: curDsIdx, - camName: curCamName, - position: thisPosition - } - )}) - innerPositionWrapper.appendChild(position) - curPosition += 1 - } - } - camWrapper.appendChild(innerPositionWrapper) + position.addEventListener("click", () => { + self.dsPresetCurPosition[curDsIdx][curCamName] = + thisPosition; + self.updateDom(self.config.animationSpeed); + self.sendSocketNotification("DS_CHANGE_POSITION", { + dsIdx: curDsIdx, + camName: curCamName, + position: thisPosition + }); + }); + innerPositionWrapper.appendChild(position); + curPosition += 1; } - wrapper.appendChild(camWrapper) + } + camWrapper.appendChild(innerPositionWrapper); + } + wrapper.appendChild(camWrapper); } else { - if(this.config.vertical && this.config.addBigToNormal){ - var camWrapper = document.createElement("div") - camWrapper.className = "camWrapper currentBig "+curDsIdx+"_"+curCamIdx+" "+curCamAlias - - if(this.config.showCamName){ - var camNameWrapper = document.createElement("div") - camNameWrapper.className = "name" - camNameWrapper.innerHTML = curCamAlias - camWrapper.appendChild(camNameWrapper) - } - - var innerCamWrapper = document.createElement("div") - innerCamWrapper.className = "innerCamWrapper currentBig" - var icon = document.createElement("i") - icon.className = "cam currentBig far "+this.config.currentBigIcon - innerCamWrapper.appendChild(icon) - camWrapper.appendChild(innerCamWrapper) - wrapper.appendChild(camWrapper) - } else if(!this.config.vertical){ - var camWrapper = document.createElement("div") - camWrapper.className = "camWrapper big "+curDsIdx+"_"+curCamIdx+" "+curCamAlias - if(this.config.showBigCamName){ - var camNameWrapper = document.createElement("div") - camNameWrapper.className = "name" - camNameWrapper.innerHTML = curCamAlias + "
" - camWrapper.appendChild(camNameWrapper) - } + if (this.config.vertical && this.config.addBigToNormal) { + var camWrapper = document.createElement("div"); + camWrapper.className = + "camWrapper currentBig " + + curDsIdx + + "_" + + curCamIdx + + " " + + curCamAlias; - var innerCamWrapper = document.createElement("div") - var innerCamWrapperClassName = "innerCamWrapper big" - if((typeof this.dsStreamInfo[curDsIdx] !== "undefined") && - (typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined") - ){ - var cam = document.createElement("img") - cam.className = "cam" - cam.src = this.dsStreamInfo[curDsIdx][curCamName] - } else { - var cam = document.createElement("i") - cam.className = "cam nourl fa "+this.config.noUrlIcon - cam.addEventListener("click", ()=>{self.sendSocketNotification("REFRESH_URLS")}) - innerCamWrapperClassName += " nourl" - } - innerCamWrapper.className = innerCamWrapperClassName - innerCamWrapper.appendChild(cam) - camWrapper.appendChild(innerCamWrapper) - - if(self.config.showBigPositions){ - let innerPositionWrapper = document.createElement("div") - innerPositionWrapper.className = "innerPositionWrapper big" - //this.dsPresetInfo[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] - let curPosition = 0 - if((typeof this.dsPresetInfo[curDsIdx] !== "undefined") && - (typeof this.dsPresetInfo[curDsIdx][curCamName] !== "undefined") - ){ - for(var curPreset in this.dsPresetInfo[curDsIdx][curCamName]) { - // let curPosition = this.dsPresetInfo[curDsIdx][curCamName][curPreset].position - // console.log("CUR_POS: "+curPosition + " curActive: "+this.dsPresetCurPosition[curDsIdx][curCamName]) - let curPositionName = this.dsPresetInfo[curDsIdx][curCamName][curPreset].name - - var position = document.createElement("div") - //this.dsPresetCurPosition[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] - position.className = "position big" - if(this.dsPresetCurPosition[curDsIdx][curCamName] === curPosition){ - var positionSelected = document.createElement("div") - positionSelected.className = "selected" - position.appendChild(positionSelected) - } - let thisPosition = curPosition - position.addEventListener("click", ()=>{ - self.dsPresetCurPosition[curDsIdx][curCamName] = thisPosition - self.updateDom(self.config.animationSpeed) - self.sendSocketNotification("DS_CHANGE_POSITION", { - dsIdx: curDsIdx, - camName: curCamName, - position: thisPosition - } - )}) - innerPositionWrapper.appendChild(position) - curPosition += 1 - } + if (this.config.showCamName) { + var camNameWrapper = document.createElement("div"); + camNameWrapper.className = "name"; + camNameWrapper.innerHTML = curCamAlias; + camWrapper.appendChild(camNameWrapper); + } + + var innerCamWrapper = document.createElement("div"); + innerCamWrapper.className = "innerCamWrapper currentBig"; + var icon = document.createElement("i"); + icon.className = + "cam currentBig far " + this.config.currentBigIcon; + innerCamWrapper.appendChild(icon); + camWrapper.appendChild(innerCamWrapper); + wrapper.appendChild(camWrapper); + } else if (!this.config.vertical) { + var camWrapper = document.createElement("div"); + camWrapper.className = + "camWrapper big " + + curDsIdx + + "_" + + curCamIdx + + " " + + curCamAlias; + if (this.config.showBigCamName) { + var camNameWrapper = document.createElement("div"); + camNameWrapper.className = "name"; + camNameWrapper.innerHTML = curCamAlias + "
"; + camWrapper.appendChild(camNameWrapper); + } + + var innerCamWrapper = document.createElement("div"); + var innerCamWrapperClassName = "innerCamWrapper big"; + if ( + typeof this.dsStreamInfo[curDsIdx] !== "undefined" && + typeof this.dsStreamInfo[curDsIdx][curCamName] !== "undefined" + ) { + var cam = document.createElement("img"); + cam.className = "cam"; + cam.src = this.dsStreamInfo[curDsIdx][curCamName]; + } else { + var cam = document.createElement("i"); + cam.className = "cam nourl fa " + this.config.noUrlIcon; + cam.addEventListener("click", () => { + self.sendSocketNotification("REFRESH_URLS"); + }); + innerCamWrapperClassName += " nourl"; + } + innerCamWrapper.className = innerCamWrapperClassName; + innerCamWrapper.appendChild(cam); + camWrapper.appendChild(innerCamWrapper); + + if (self.config.showBigPositions) { + let innerPositionWrapper = document.createElement("div"); + innerPositionWrapper.className = "innerPositionWrapper big"; + //this.dsPresetInfo[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] + let curPosition = 0; + if ( + typeof this.dsPresetInfo[curDsIdx] !== "undefined" && + typeof this.dsPresetInfo[curDsIdx][curCamName] !== "undefined" + ) { + for (var curPreset in this.dsPresetInfo[curDsIdx][ + curCamName + ]) { + // let curPosition = this.dsPresetInfo[curDsIdx][curCamName][curPreset].position + // console.log("CUR_POS: "+curPosition + " curActive: "+this.dsPresetCurPosition[curDsIdx][curCamName]) + let curPositionName = + this.dsPresetInfo[curDsIdx][curCamName][curPreset].name; + + var position = document.createElement("div"); + //this.dsPresetCurPosition[curDsIdx][this.config.ds[curDsIdx].cams[curCamIdx].name] + position.className = "position big"; + if ( + this.dsPresetCurPosition[curDsIdx][curCamName] === + curPosition + ) { + var positionSelected = document.createElement("div"); + positionSelected.className = "selected"; + position.appendChild(positionSelected); } - camWrapper.appendChild(innerPositionWrapper) + let thisPosition = curPosition; + position.addEventListener("click", () => { + self.dsPresetCurPosition[curDsIdx][curCamName] = + thisPosition; + self.updateDom(self.config.animationSpeed); + self.sendSocketNotification("DS_CHANGE_POSITION", { + dsIdx: curDsIdx, + camName: curCamName, + position: thisPosition + }); + }); + innerPositionWrapper.appendChild(position); + curPosition += 1; + } } - wrapper.appendChild(camWrapper) + camWrapper.appendChild(innerPositionWrapper); + } + wrapper.appendChild(camWrapper); } } } @@ -361,201 +459,248 @@ Module.register('MMM-SynologySurveillance', { return wrapper; }, - getNextCamId: function(curId, type=1){ - var nextCamId = curId - if(type === 1){ - for(var i = 0; i < this.order.length; i++){ - nextCamId = curId + 1 - if(nextCamId >= this.order.length){ - nextCamId = 0 + resume: function () { + const self = this + if (self.config.updateDomOnShow){ + self.updateDom(); + } + }, + + getNextCamId: function (curId, type = 1) { + var nextCamId = curId; + if (type === 1) { + for (var i = 0; i < this.order.length; i++) { + nextCamId = curId + 1; + if (nextCamId >= this.order.length) { + nextCamId = 0; } - var curDsIdx = this.order[nextCamId][0] - var curCamId = this.order[nextCamId][1] - if( - (typeof this.config.ds[curDsIdx].cams[curCamId].profiles === "undefined") || - (this.currentProfilePattern.test(this.config.ds[curDsIdx].cams[curCamId].profiles)) - ){ - return nextCamId + var curDsIdx = this.order[nextCamId][0]; + var curCamId = this.order[nextCamId][1]; + if ( + typeof this.config.ds[curDsIdx].cams[curCamId].profiles === + "undefined" || + this.currentProfilePattern.test( + this.config.ds[curDsIdx].cams[curCamId].profiles + ) + ) { + return nextCamId; } } - } else if(type === -1){ - for(var i = 0; i < this.order.length; i++){ - nextCamId = curId - 1 - if(nextCamId < 0){ - nextCamId = this.order.length - 1 + } else if (type === -1) { + for (var i = 0; i < this.order.length; i++) { + nextCamId = curId - 1; + if (nextCamId < 0) { + nextCamId = this.order.length - 1; } - var curDsIdx = this.order[nextCamId][0] - var curCamId = this.order[nextCamId][1] - if( - (typeof this.config.ds[curDsIdx].cams[curCamId].profiles === "undefined") || - (this.currentProfilePattern.test(this.config.ds[curDsIdx].cams[curCamId].profiles)) - ){ - return nextCamId + var curDsIdx = this.order[nextCamId][0]; + var curCamId = this.order[nextCamId][1]; + if ( + typeof this.config.ds[curDsIdx].cams[curCamId].profiles === + "undefined" || + this.currentProfilePattern.test( + this.config.ds[curDsIdx].cams[curCamId].profiles + ) + ) { + return nextCamId; } } - } else if(type === 0){ - var curDsIdx = this.order[curId][0] - var curCamId = this.order[curId][1] - if( - (typeof this.config.ds[curDsIdx].cams[curCamId].profiles === "undefined") || - (this.currentProfilePattern.test(this.config.ds[curDsIdx].cams[curCamId].profiles)) - ){ - return curId + } else if (type === 0) { + var curDsIdx = this.order[curId][0]; + var curCamId = this.order[curId][1]; + if ( + typeof this.config.ds[curDsIdx].cams[curCamId].profiles === + "undefined" || + this.currentProfilePattern.test( + this.config.ds[curDsIdx].cams[curCamId].profiles + ) + ) { + return curId; } else { - return this.getNextCamId(curId, 1) + return this.getNextCamId(curId, 1); } } - return nextCamId + return nextCamId; }, - getNextPositionIdx: function (dsIdx, camName, type=1) { - var nextPostion = this.dsPresetCurPosition[dsIdx][camName] - if((typeof this.dsPresetInfo[dsIdx] !== "undefined") && - (typeof this.dsPresetInfo[dsIdx][camName] !== "undefined") && - (Object.keys(this.dsPresetInfo[dsIdx][camName]).length > 0) - ){ - if(type === 1){ - nextPostion += 1 - if(nextPostion >= Object.keys(this.dsPresetInfo[dsIdx][camName]).length){ - nextPostion = 0 + getNextPositionIdx: function (dsIdx, camName, type = 1) { + var nextPostion = this.dsPresetCurPosition[dsIdx][camName]; + if ( + typeof this.dsPresetInfo[dsIdx] !== "undefined" && + typeof this.dsPresetInfo[dsIdx][camName] !== "undefined" && + Object.keys(this.dsPresetInfo[dsIdx][camName]).length > 0 + ) { + if (type === 1) { + nextPostion += 1; + if ( + nextPostion >= Object.keys(this.dsPresetInfo[dsIdx][camName]).length + ) { + nextPostion = 0; } - } else if(type === -1){ - nextPostion -= 1 - if(nextPostion < 0){ - nextPostion = Object.keys(this.dsPresetInfo[dsIdx][camName]).length - 1 + } else if (type === -1) { + nextPostion -= 1; + if (nextPostion < 0) { + nextPostion = + Object.keys(this.dsPresetInfo[dsIdx][camName]).length - 1; } } } - return nextPostion + return nextPostion; }, - - notificationReceived: function(notification,payload) { - if(notification === "SYNO_SS_NEXT_CAM"){ - this.curBigIdx = this.getNextCamId(this.curBigIdx, 1) - this.updateDom(this.config.animationSpeed) - } else if (notification === "SYNO_SS_PREVIOUS_CAM"){ - this.curBigIdx = this.getNextCamId(this.curBigIdx, -1) - this.updateDom(this.config.animationSpeed) - } else if (notification === "SYNO_SS_CHANGE_CAM"){ - console.log("Got notification to change cam to: "+payload.id) - if(typeof this.order[payload.id] !== "undefined"){ - this.curBigIdx = this.payload.id - this.updateDom(this.config.animationSpeed) + notificationReceived: function (notification, payload) { + if (notification === "SYNO_SS_NEXT_CAM") { + this.curBigIdx = this.getNextCamId(this.curBigIdx, 1); + this.updateDom(this.config.animationSpeed); + } else if (notification === "SYNO_SS_PREVIOUS_CAM") { + this.curBigIdx = this.getNextCamId(this.curBigIdx, -1); + this.updateDom(this.config.animationSpeed); + } else if (notification === "SYNO_SS_CHANGE_CAM") { + console.log("Got notification to change cam to: " + payload.id); + if (typeof this.order[payload.id] !== "undefined") { + this.curBigIdx = this.payload.id; + this.updateDom(this.config.animationSpeed); } - } else if(notification === "SYNO_SS_NEXT_POSITION"){ - if((typeof payload.dsIdx !== "undefined") && - (typeof payload.camName !== "undefined") - ){ - var dsIdx = payload.dsIdx - var camName = payload.camName + } else if (notification === "SYNO_SS_NEXT_POSITION") { + if ( + typeof payload.dsIdx !== "undefined" && + typeof payload.camName !== "undefined" + ) { + var dsIdx = payload.dsIdx; + var camName = payload.camName; } else { - var dsIdx = this.order[this.curBigIdx][0] - var camName = this.order[this.curBigIdx][3] + var dsIdx = this.order[this.curBigIdx][0]; + var camName = this.order[this.curBigIdx][3]; } - var position = this.getNextPositionIdx(dsIdx,camName,1) - this.dsPresetCurPosition[dsIdx][camName] = position + var position = this.getNextPositionIdx(dsIdx, camName, 1); + this.dsPresetCurPosition[dsIdx][camName] = position; this.sendSocketNotification("DS_CHANGE_POSITION", { dsIdx: dsIdx, camName: camName, - position: position, - }) - if(this.config.showBigPositions || this.config.showPositions){ - this.updateDom(this.config.animationSpeed) + position: position + }); + if (this.config.showBigPositions || this.config.showPositions) { + this.updateDom(this.config.animationSpeed); } - } else if(notification === "SYNO_SS_PREVIOUS_POSITION"){ - if((typeof payload.dsIdx !== "undefined") && - (typeof payload.camName !== "undefined") - ){ - var dsIdx = payload.dsIdx - var camName = payload.camName + } else if (notification === "SYNO_SS_PREVIOUS_POSITION") { + if ( + typeof payload.dsIdx !== "undefined" && + typeof payload.camName !== "undefined" + ) { + var dsIdx = payload.dsIdx; + var camName = payload.camName; } else { - var dsIdx = this.order[this.curBigIdx][0] - var camName = this.order[this.curBigIdx][3] + var dsIdx = this.order[this.curBigIdx][0]; + var camName = this.order[this.curBigIdx][3]; } - var position = this.getNextPositionIdx(dsIdx,camName,-1) - this.dsPresetCurPosition[dsIdx][camName] = position - + var position = this.getNextPositionIdx(dsIdx, camName, -1); + this.dsPresetCurPosition[dsIdx][camName] = position; + this.sendSocketNotification("DS_CHANGE_POSITION", { dsIdx: dsIdx, camName: camName, - position: position, - }) - if(this.config.showBigPositions || this.config.showPositions){ - this.updateDom(this.config.animationSpeed) + position: position + }); + if (this.config.showBigPositions || this.config.showPositions) { + this.updateDom(this.config.animationSpeed); } - } else if(notification === "SYNO_SS_CHANGE_POSITION"){ + } else if (notification === "SYNO_SS_CHANGE_POSITION") { this.sendSocketNotification("DS_CHANGE_POSITION", { dsIdx: payload.dsIdx, camName: payload.camName, - position: payload.position, - }) - this.dsPresetCurPosition[payload.dsIdx][payload.camName] = payload.position - if(this.config.showBigPositions || this.config.showPositions){ - this.updateDom(this.config.animationSpeed) + position: payload.position + }); + this.dsPresetCurPosition[payload.dsIdx][payload.camName] = + payload.position; + if (this.config.showBigPositions || this.config.showPositions) { + this.updateDom(this.config.animationSpeed); } - } else if (notification === 'SYNO_REFRESH_URLS'){ - this.sendSocketNotification("REFRESH_URLS") - } else if (notification === 'CHANGED_PROFILE'){ - if(typeof payload.to !== 'undefined'){ - this.currentProfile = payload.to - this.currentProfilePattern = new RegExp('\\b'+payload.to+'\\b') - this.curBigIdx = this.getNextCamId(this.curBigIdx, 0) - this.updateDom(this.config.animationSpeed) + } else if (notification === "SYNO_REFRESH_URLS") { + this.sendSocketNotification("REFRESH_URLS"); + } else if (notification === "CHANGED_PROFILE") { + if (typeof payload.to !== "undefined") { + this.currentProfile = payload.to; + this.currentProfilePattern = new RegExp("\\b" + payload.to + "\\b"); + this.curBigIdx = this.getNextCamId(this.curBigIdx, 0); + this.updateDom(this.config.animationSpeed); } } }, socketNotificationReceived: function (notification, payload) { - if(notification === "DS_STREAM_INFO"){ - console.log("Got new Stream info of ds with id: "+payload.dsIdx) + if (notification === "DS_STREAM_INFO") { + console.log("Got new Stream info of ds with id: " + payload.dsIdx); // console.log(JSON.stringify(payload, null, 3)) - if( - (typeof this.dsStreamInfo[payload.dsIdx] !== "undefined") && - (this.config.onlyRefreshIfUrlChanges) - ){ - if(JSON.stringify(this.dsStreamInfo[payload.dsIdx]) !== JSON.stringify(payload.camStreams)){ - this.dsStreamInfo[payload.dsIdx] = payload.camStreams - this.updateDom(this.config.animationSpeed) - console.log("Some urls of ds with id "+payload.dsIdx+" changed. Updating view!") + if ( + typeof this.dsStreamInfo[payload.dsIdx] !== "undefined" && + this.config.onlyRefreshIfUrlChanges + ) { + if ( + JSON.stringify(this.dsStreamInfo[payload.dsIdx]) !== + JSON.stringify(payload.camStreams) + ) { + this.dsStreamInfo[payload.dsIdx] = payload.camStreams; + this.updateDom(this.config.animationSpeed); + console.log( + "Some urls of ds with id " + + payload.dsIdx + + " changed. Updating view!" + ); } else { - console.log("No urls of ds with id "+payload.dsIdx+" changed. Skipping update of the view!") + console.log( + "No urls of ds with id " + + payload.dsIdx + + " changed. Skipping update of the view!" + ); } } else { - console.log("Did not have any url information of ds with id: "+payload.dsIdx+". Updating view!") - this.dsStreamInfo[payload.dsIdx] = payload.camStreams - this.updateDom(this.config.animationSpeed) + console.log( + "Did not have any url information of ds with id: " + + payload.dsIdx + + ". Updating view!" + ); + this.dsStreamInfo[payload.dsIdx] = payload.camStreams; + this.updateDom(this.config.animationSpeed); } - } else if (notification === "SYNO_SS_CHANGE_CAM"){ - console.log("Got notification to change cam to: "+payload.id) - if(typeof this.order[payload.id] !== "undefined"){ - this.curBigIdx = payload.id - this.updateDom(this.config.animationSpeed) + } else if (notification === "SYNO_SS_CHANGE_CAM") { + console.log("Got notification to change cam to: " + payload.id); + if (typeof this.order[payload.id] !== "undefined") { + this.curBigIdx = payload.id; + this.updateDom(this.config.animationSpeed); } - } else if (notification === "DS_PTZ_PRESET_INFO"){ - if(this.config.onlyRefreshIfUrlChanges){ - if(JSON.stringify(this.dsPresetInfo[payload.dsIdx][payload.camName]) !== JSON.stringify(payload.ptzData)){ - this.dsPresetInfo[payload.dsIdx][payload.camName] = payload.ptzData - if(this.config.showBigPositions || this.config.showPositions){ - this.updateDom(this.config.animationSpeed) + } else if (notification === "DS_PTZ_PRESET_INFO") { + if (this.config.onlyRefreshIfUrlChanges) { + if ( + JSON.stringify(this.dsPresetInfo[payload.dsIdx][payload.camName]) !== + JSON.stringify(payload.ptzData) + ) { + this.dsPresetInfo[payload.dsIdx][payload.camName] = payload.ptzData; + if (this.config.showBigPositions || this.config.showPositions) { + this.updateDom(this.config.animationSpeed); } } else { - console.log("Skipping position updates of ds with id: "+payload.dsIdx+" because no values changed!") + console.log( + "Skipping position updates of ds with id: " + + payload.dsIdx + + " because no values changed!" + ); } } else { - this.dsPresetInfo[payload.dsIdx][payload.camName] = payload.ptzData - if(this.config.showBigPositions || this.config.showPositions){ - this.updateDom(this.config.animationSpeed) + this.dsPresetInfo[payload.dsIdx][payload.camName] = payload.ptzData; + if (this.config.showBigPositions || this.config.showPositions) { + this.updateDom(this.config.animationSpeed); } } - } else if(notification === "DS_CHANGED_POSITION"){ - if(this.dsPresetCurPosition[payload.dsIdx][payload.camName] !== payload.position){ - this.dsPresetCurPosition[payload.dsIdx][payload.camName] = payload.position - this.updateDom(this.config.animationSpeed) + } else if (notification === "DS_CHANGED_POSITION") { + if ( + this.dsPresetCurPosition[payload.dsIdx][payload.camName] !== + payload.position + ) { + this.dsPresetCurPosition[payload.dsIdx][payload.camName] = + payload.position; + this.updateDom(this.config.animationSpeed); } } - }, - + } }); diff --git a/README.md b/README.md index b97bf1e..96b0921 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ +# MMM-SynologySurveillance -# MMM-SynologySurveillance # This module queries the "mjpeg" streams of surveillance cams connected to Synology disk stations and displays one or more cams in columns. One selected cam can be displayed in a big single view. The cam in the big view can either be switched by notification or by click/touch. @@ -12,24 +12,27 @@ Attention: The "mpjeg" streams provide worse quality than the rtsp streams but d Because the module uses Flexbox Layout instead of Tables there is a lot of css styling possiblity. -## Screenshots ## -### Horizontal Layout ### +## Screenshots + +### Horizontal Layout + ![alt text](https://github.com/Tom-Hirschberger/MMM-SynologySurveillance/raw/master/examples/screenshot-horizontal.png "Horizontal Layout") -### Vertical Layout ### +### Vertical Layout + ![alt text](https://github.com/Tom-Hirschberger/MMM-SynologySurveillance/raw/master/examples/screenshot-vertical.png "Vertical Layout") ![alt text](https://github.com/Tom-Hirschberger/MMM-SynologySurveillance/raw/master/examples/screenshot-vertical-positions.png "Vertical Layout with positions") - ## Installation - cd ~/MagicMirror/modules + + cd ~/MagicMirror/modules git clone https://github.com/Tom-Hirschberger/MMM-SynologySurveillance.git cd MMM-SynologySurveillance npm install +## Configuration -## Configuration ## ```json5 { module: "MMM-SynologySurveillance", @@ -63,49 +66,52 @@ Because the module uses Flexbox Layout instead of Tables there is a lot of css s }, ``` -### General ### -| Option | Description | Type | Default | -| ------- | --- | --- | --- | -| ds | The array containing the information about the discstations and cams | Array | [] | -| vertical | Should the vertical or horizontal layout be used? | Boolean | true | -| showOneBig | If this option is true an extra big view of the first cam is displayed at the beginning | Boolean | true | -| addBigToNormal | If this option is true an icon will be displayed in the small views while the cam is visible in the big view | Boolean | false | -| showBigCamName | Should the name of the cam that is displayed in the big view added to the big view | Boolean | false | -| showCamName | Should the name of each cam be added the the small view | Boolean | false | -| showUnreachableCams | Should cams we can not query the video url of being displayed | Boolean | true | -| order | An string containing the names or alias (if you use the same cam name in different stations use the alias) of the cams in the order they should be displayed. If no order is provided the order of the diskstations and cams in the configuration is used. | String | null | -| showPositions | If set to true saved positions for this cam will be added as buttons; You can either click them or send an notification to change to this position | Boolean | true | -| showBigPositions | If set to true the saved positions of the current big cam will be displayed as buttons; You can either click them or change the positions by notification | Boolean | true | -| urlRefreshInterval | The module connects periodically to the discstations to get the current urls (and refreshes the authentication cookie). This option controls the interval (seconds) | Integer | 60 | -| onlyRefreshIfUrlChanges | Only if some of the urls of the currently visable cams (also the unreachable ones) changed the view is being refreshed if this value is set to true. | Boolean | true | -| animationSpeed | The refresh of the view can be animated. This options controls the animation speed (milliseconds) | Integer | 500 | -| skipOnPrivilegeError | Sometimes the disk stations report a privilege error although the user does have valid rights to access the surveillance station. If activated the old urls of this station are kept valid and the module will try to get new urls during the next refresh. | Boolean | true | - -### DiskStations ### -| Option | Description | Type | -| ------- | --- | --- | -| host | The hostname or ip address of the diskstation to connect to | true | -| port | The port of the diskstation (not the survaillance redirect!) | true | -| user | The username to login to the diskstation | true | -| password | The password used for the login | true | -| cams | The array containing the information about the cams to query | true | -| replaceHostPart | If this option is set to true the host and protocol part in the stream url will be replaced with the values of the config file. I introduce this option because i access my cam with an public url (dynamic dns) but the disk station returns the private ip of the camera in the result. | Boolean | false | -| skipOnPrivilegeError | The Diskstation API throws privilage errors randomly. If this option is set to true this errors will be ignored and the last url or position info will be kept. | Boolean | true | - -### Cams ### -| Option | Description | Type | -| ------- | --- | --- | -| name | The name this camera is listed in the diskstation | true | -| alias | An alias to use in the module for this camera | false | -| profiles | An profile string to specify if this cam only should be displayed in specific profiles. If no profile string is provided the camera is visible in all profiles| false | - - -## Supported Notifications ## -| Notification | Payload | Description | -| ------------ | ------- | ----------- | -| SYNO_SS_NEXT_CAM | nothing | Switch to the next cam in the order in the big view | -| SYNO_SS_PREVIOUS_CAM | nothing | Switch to the previous cam in the order in the big view | -| SYNO_SS_CHANGE_CAM | id | Switch to the cam with the specific id. The id is either the one of the position in the order string or if no order string is used the position in the config (starting with 0) | -| SYNO_SS_NEXT_POSITION | nothing or dsIdx = index of the datastation in the configuration; camName = the name of the cam as in the configuration| The cam will with the specified id and name will be moved to the next position; If no information is provided the current big cam will be used | -| SYNO_SS_PREVIOUS_POSITION | nothing or dsIdx = index of the datastation in the configuration; camName = the name of the cam as in the configuration| The cam will with the specified id and name will be moved to the next position; If no information is provided the current big cam will be used | -| SYNO_SS_CHANGE_POSITION | dsIdx = index of the datastation in the configuration; camName = the name of the cam as in the configuration; position = the id of the position (starting with 0) | The cam will with the specified id and name will be moved to the specified position | \ No newline at end of file +### General + +| Option | Description | Type | Default | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------- | +| ds | The array containing the information about the discstations and cams | Array | [] | +| vertical | Should the vertical or horizontal layout be used? | Boolean | true | +| showOneBig | If this option is true an extra big view of the first cam is displayed at the beginning | Boolean | true | +| addBigToNormal | If this option is true an icon will be displayed in the small views while the cam is visible in the big view | Boolean | false | +| showBigCamName | Should the name of the cam that is displayed in the big view added to the big view | Boolean | false | +| showCamName | Should the name of each cam be added the the small view | Boolean | false | +| showUnreachableCams | Should cams we can not query the video url of being displayed | Boolean | true | +| order | An string containing the names or alias (if you use the same cam name in different stations use the alias) of the cams in the order they should be displayed. If no order is provided the order of the diskstations and cams in the configuration is used. | String | null | +| showPositions | If set to true saved positions for this cam will be added as buttons; You can either click them or send an notification to change to this position | Boolean | true | +| showBigPositions | If set to true the saved positions of the current big cam will be displayed as buttons; You can either click them or change the positions by notification | Boolean | true | +| urlRefreshInterval | The module connects periodically to the discstations to get the current urls (and refreshes the authentication cookie). This option controls the interval (seconds) | Integer | 60 | +| onlyRefreshIfUrlChanges | Only if some of the urls of the currently visable cams (also the unreachable ones) changed the view is being refreshed if this value is set to true. | Boolean | true | +| animationSpeed | The refresh of the view can be animated. This options controls the animation speed (milliseconds) | Integer | 500 | +| skipOnPrivilegeError | Sometimes the disk stations report a privilege error although the user does have valid rights to access the surveillance station. If activated the old urls of this station are kept valid and the module will try to get new urls during the next refresh. | Boolean | true | + +### DiskStations + +| Option | Description | Type | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | +| host | The hostname or ip address of the diskstation to connect to | true | +| port | The port of the diskstation (not the survaillance redirect!) | true | +| user | The username to login to the diskstation | true | +| password | The password used for the login | true | +| cams | The array containing the information about the cams to query | true | +| replaceHostPart | If this option is set to true the host and protocol part in the stream url will be replaced with the values of the config file. I introduce this option because i access my cam with an public url (dynamic dns) but the disk station returns the private ip of the camera in the result. | Boolean | false | +| skipOnPrivilegeError | The Diskstation API throws privilage errors randomly. If this option is set to true this errors will be ignored and the last url or position info will be kept. | Boolean | true | + +### Cams + +| Option | Description | Type | +| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | +| name | The name this camera is listed in the diskstation | true | +| alias | An alias to use in the module for this camera | false | +| profiles | An profile string to specify if this cam only should be displayed in specific profiles. If no profile string is provided the camera is visible in all profiles | false | + +## Supported Notifications + +| Notification | Payload | Description | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| SYNO_SS_NEXT_CAM | nothing | Switch to the next cam in the order in the big view | +| SYNO_SS_PREVIOUS_CAM | nothing | Switch to the previous cam in the order in the big view | +| SYNO_SS_CHANGE_CAM | id | Switch to the cam with the specific id. The id is either the one of the position in the order string or if no order string is used the position in the config (starting with 0) | +| SYNO_SS_NEXT_POSITION | nothing or dsIdx = index of the datastation in the configuration; camName = the name of the cam as in the configuration | The cam will with the specified id and name will be moved to the next position; If no information is provided the current big cam will be used | +| SYNO_SS_PREVIOUS_POSITION | nothing or dsIdx = index of the datastation in the configuration; camName = the name of the cam as in the configuration | The cam will with the specified id and name will be moved to the next position; If no information is provided the current big cam will be used | +| SYNO_SS_CHANGE_POSITION | dsIdx = index of the datastation in the configuration; camName = the name of the cam as in the configuration; position = the id of the position (starting with 0) | The cam will with the specified id and name will be moved to the specified position | diff --git a/node_helper.js b/node_helper.js index 3147595..23de7fe 100644 --- a/node_helper.js +++ b/node_helper.js @@ -4,264 +4,353 @@ * By Tom Hirschberger * MIT Licensed. */ -const NodeHelper = require("node_helper") -const Syno = require("syno") +const NodeHelper = require("node_helper"); +const Syno = require("syno"); module.exports = NodeHelper.create({ - start: function () { - this.started = false - this.urlUpdateInProgress = false - this.ds = {} + this.started = false; + this.urlUpdateInProgress = false; + this.ds = {}; }, - getStreamUrls: function(){ - this.urlUpdateInProgress = true - const self = this - let result = [] + getStreamUrls: function () { + this.urlUpdateInProgress = true; + const self = this; + let result = []; // console.log(self.name+": Creating "+self.config.ds.length+" DiskStation(s)") - for(let curDsIdx = 0; curDsIdx < self.config.ds.length; curDsIdx++) { - let curDs = self.config.ds[curDsIdx] - let curDsResult = {} + for (let curDsIdx = 0; curDsIdx < self.config.ds.length; curDsIdx++) { + let curDs = self.config.ds[curDsIdx]; + let curDsResult = {}; let syno = new Syno({ - protocol : curDs.protocol, + protocol: curDs.protocol, host: curDs.host, port: curDs.port, account: curDs.user, passwd: curDs.password, ignoreCertificateErrors: true - }) + }); - syno.dsIdx = curDsIdx + syno.dsIdx = curDsIdx; // console.log(self.name+": Created DS with id: "+curDsIdx+" and url: "+curDs.protocol+"://"+curDs.host+":"+curDs.port) - self.ds[curDsIdx] = {} - self.ds[curDsIdx].syno = syno + self.ds[curDsIdx] = {}; + self.ds[curDsIdx].syno = syno; - let validCamNames = {} - for (let i = 0; i setTimeout(resolve, milliseconds)); + return new Promise((resolve) => setTimeout(resolve, milliseconds)); }, - goPosition: async function(dsIdx, camName, position){ - const self = this - let curDsIdx = dsIdx - let curCamName = camName - while(self.urlUpdateInProgress){ - Sleep(1000) + goPosition: async function (dsIdx, camName, position) { + const self = this; + let curDsIdx = dsIdx; + let curCamName = camName; + while (self.urlUpdateInProgress) { + Sleep(1000); } - if((typeof self.ds[dsIdx] !== "undefined") && (typeof self.ds[dsIdx].idNameMap !== "undefined")){ - var camId = null - for(var curCamId in self.ds[dsIdx].idNameMap){ + if ( + typeof self.ds[dsIdx] !== "undefined" && + typeof self.ds[dsIdx].idNameMap !== "undefined" + ) { + var camId = null; + for (var curCamId in self.ds[dsIdx].idNameMap) { // console.log(self.name+": Checking if camdId "+curCamId+" with name: "+self.ds[dsIdx].idNameMap[curCamId]+" matches name "+camName) - if(self.ds[dsIdx].idNameMap[curCamId] === camName){ - console.log(self.name+": Found id of cam: "+camName) - camId = curCamId - break + if (self.ds[dsIdx].idNameMap[curCamId] === camName) { + console.log(self.name + ": Found id of cam: " + camName); + camId = curCamId; + break; } } - if(camId){ - if((typeof self.ds[dsIdx].ptzPresetInfo !== "undefined")&& (typeof self.ds[dsIdx].ptzPresetInfo[camId] !== "undefined")){ - if((position >= 0) && (position < Object.keys(self.ds[dsIdx].ptzPresetInfo[camId]).length)){ - console.log(self.name+": Changing position of cam") - var curRealPosition = self.ds[dsIdx].ptzPresetInfo[camId][position]["position"] - console.log(self.name+": New position with idx: "+position+" is "+curRealPosition) - self.ds[dsIdx].syno.ss.goPresetPtz({'cameraId':camId, 'position':curRealPosition}, function(goPtzError,goPtzData){ - self.sendSocketNotification("DS_CHANGED_POSITION", { - dsIdx: curDsIdx, - camName: curCamName, - position: position - }) - }) + if (camId) { + if ( + typeof self.ds[dsIdx].ptzPresetInfo !== "undefined" && + typeof self.ds[dsIdx].ptzPresetInfo[camId] !== "undefined" + ) { + if ( + position >= 0 && + position < Object.keys(self.ds[dsIdx].ptzPresetInfo[camId]).length + ) { + console.log(self.name + ": Changing position of cam"); + var curRealPosition = + self.ds[dsIdx].ptzPresetInfo[camId][position]["position"]; + console.log( + self.name + + ": New position with idx: " + + position + + " is " + + curRealPosition + ); + self.ds[dsIdx].syno.ss.goPresetPtz( + { cameraId: camId, position: curRealPosition }, + function (goPtzError, goPtzData) { + self.sendSocketNotification("DS_CHANGED_POSITION", { + dsIdx: curDsIdx, + camName: curCamName, + position: position + }); + } + ); } } else { - console.log(self.name+": Could not change position of cam: "+camName+" of ds: "+dsIdx+" because no ptz preset info is available!") + console.log( + self.name + + ": Could not change position of cam: " + + camName + + " of ds: " + + dsIdx + + " because no ptz preset info is available!" + ); } } else { - console.log(self.name+": Could not change postion of cam: "+camName+" because no id of this cam is known!") + console.log( + self.name + + ": Could not change postion of cam: " + + camName + + " because no id of this cam is known!" + ); } } else { - console.log(self.name+": Could not change position of cam: "+camName+" because no id name mapping is present for this ds ("+dsIdx+") at the moment.") + console.log( + self.name + + ": Could not change position of cam: " + + camName + + " because no id name mapping is present for this ds (" + + dsIdx + + ") at the moment." + ); } }, socketNotificationReceived: function (notification, payload) { - const self = this - console.log(self.name + ": Received notification "+notification) + const self = this; + console.log(self.name + ": Received notification " + notification); if (notification === "CONFIG" && self.started === false) { - self.config = payload - self.started = true - } else if (notification === "INIT_DS"){ - self.getStreamUrls() - } else if ((notification === "REFRESH_URLS") && self.started){ - console.log(self.name + ': Refreshing the urls!') - self.getStreamUrls() - } else if (notification === "SYNO_SS_CHANGE_CAM"){ - self.sendSocketNotification(notification,payload) - } else if (notification === "DS_CHANGE_POSITION"){ - console.log(self.name+": Changing position of cam: "+payload.camName+" of ds: "+payload.dsIdx+" to: "+payload.position) - self.goPosition(payload.dsIdx, payload.camName, payload.position) + self.config = payload; + self.started = true; + } else if (notification === "INIT_DS") { + self.getStreamUrls(); + } else if (notification === "REFRESH_URLS" && self.started) { + console.log(self.name + ": Refreshing the urls!"); + self.getStreamUrls(); + } else if (notification === "SYNO_SS_CHANGE_CAM") { + self.sendSocketNotification(notification, payload); + } else if (notification === "DS_CHANGE_POSITION") { + console.log( + self.name + + ": Changing position of cam: " + + payload.camName + + " of ds: " + + payload.dsIdx + + " to: " + + payload.position + ); + self.goPosition(payload.dsIdx, payload.camName, payload.position); } } -}) +}); diff --git a/synology-surveillance_h.css b/synology-surveillance_h.css index 697705e..3eda1da 100644 --- a/synology-surveillance_h.css +++ b/synology-surveillance_h.css @@ -1,108 +1,109 @@ .synology-surveillance { - --cam-bg-color: #2b2c30; - --cam-name-bg-color: #1b1b1c; - --postion-color: #5e5f63; - --position-selected-color: #1b1b1c; - --cam-wrapper-border-radius: 5px; - --cam-border-radius: 5px; - --name-border-radius: 5px; - --position-border-radius: 3px; - --padding-camWrapper: 5px; - --cam-width: 200px; - --cam-min-height: calc(var(--cam-width) /1.33); + --cam-bg-color: #2b2c30; + --cam-name-bg-color: #1b1b1c; + --postion-color: #5e5f63; + --position-selected-color: #1b1b1c; + --cam-wrapper-border-radius: 5px; + --cam-border-radius: 5px; + --name-border-radius: 5px; + --position-border-radius: 3px; + --padding-camWrapper: 5px; + --cam-width: 200px; + --cam-min-height: calc(var(--cam-width) / 1.33); - --cam-big-width: calc(var(--cam-width) * 2); - --cam-big-wrapper-width: var(--cam-big-width); - --cam-big-min-height: calc(var(--cam-big-width) /1.33); + --cam-big-width: calc(var(--cam-width) * 2); + --cam-big-wrapper-width: var(--cam-big-width); + --cam-big-min-height: calc(var(--cam-big-width) / 1.33); - --icon-size: 50px; - --icon-postion: calc(var(--cam-min-height)/2 - 25px); - --icon-big-position: calc(var(--cam-big-min-height)/2 - 25px); + --icon-size: 50px; + --icon-postion: calc(var(--cam-min-height) / 2 - 25px); + --icon-big-position: calc(var(--cam-big-min-height) / 2 - 25px); - --position-size: 10px; - --position-big-size: calc(var(--position-size) * 2); - --position-margin: 5px 5px 5px 5px; + --position-size: 10px; + --position-big-size: calc(var(--position-size) * 2); + --position-margin: 5px 5px 5px 5px; - justify-content: flex-start; - align-items: center; - display: flex; - flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + display: flex; + flex-wrap: wrap; } .innerPositionWrapper { - min-height: calc(var(--position-size) + 20px); - justify-content: center; - align-items: center; - display: flex; - flex-wrap: wrap;; + min-height: calc(var(--position-size) + 20px); + justify-content: center; + align-items: center; + display: flex; + flex-wrap: wrap; } .position { - margin: var(--position-margin); - padding: 5px; - height: var(--position-size); - width: var(--position-size); - background-color: var(--postion-color); - border-radius: var(--position-border-radius); + margin: var(--position-margin); + padding: 5px; + height: var(--position-size); + width: var(--position-size); + background-color: var(--postion-color); + border-radius: var(--position-border-radius); } .position.big { - height: var(--position-big-size); - width: var(--position-big-size); + height: var(--position-big-size); + width: var(--position-big-size); } .position .selected { - width: 100%; - min-width: 100%; - height: 100%; - min-height: 100%; - border-radius: var(--position-border-radius); - background-color: var(--position-selected-color); + width: 100%; + min-width: 100%; + height: 100%; + min-height: 100%; + border-radius: var(--position-border-radius); + background-color: var(--position-selected-color); } .camWrapper { - background-color: var(--cam-bg-color); - border-radius: var(--cam-wrapper-border-radius); - padding: var(--padding-camWrapper); - margin:5px; + background-color: var(--cam-bg-color); + border-radius: var(--cam-wrapper-border-radius); + padding: var(--padding-camWrapper); + margin: 5px; } .camWrapper .name { - width: 100%; - background-color: var(--cam-name-bg-color); - border-radius: var(--name-border-radius); - margin-bottom: 5px; + width: 100%; + background-color: var(--cam-name-bg-color); + border-radius: var(--name-border-radius); + margin-bottom: 5px; } .innerCamWrapper { - margin: auto; - display: block; - width: var(--cam-width); - min-width: var(--cam-width); - min-height: var(--cam-min-height); + margin: auto; + display: block; + width: var(--cam-width); + min-width: var(--cam-width); + min-height: var(--cam-min-height); } .innerCamWrapper.big { - margin: auto; - display: block; - min-width: var(--cam-big-wrapper-width); - min-height: var(--cam-big-min-height); + margin: auto; + display: block; + min-width: var(--cam-big-wrapper-width); + min-height: var(--cam-big-min-height); } .cam { - border-radius: var(--cam-border-radius); - max-width: 100%; - margin: auto; - display: block; + border-radius: var(--cam-border-radius); + max-width: 100%; + margin: auto; + display: block; } -.cam.nourl, .cam.currentBig{ - min-height: 100%; - font-size: var(--icon-size); - position:relative; - top:var(--icon-postion); +.cam.nourl, +.cam.currentBig { + min-height: 100%; + font-size: var(--icon-size); + position: relative; + top: var(--icon-postion); } .cam.nourl.big { - top:var(--icon-big-postion); + top: var(--icon-big-postion); } diff --git a/synology-surveillance_v.css b/synology-surveillance_v.css index f038730..d0972d5 100644 --- a/synology-surveillance_v.css +++ b/synology-surveillance_v.css @@ -1,113 +1,119 @@ .synology-surveillance { - --column-cnt: 2; - --cam-bg-color: #2b2c30; - --cam-name-bg-color: #1b1b1c; - --postion-color: #5e5f63; - --position-selected-color: #1b1b1c; - --cam-wrapper-border-radius: 5px; - --cam-border-radius: 5px; - --name-border-radius: 5px; - --position-border-radius: 3px; - --padding-camWrapper: 5px; - --cam-width: 200px; - --cam-min-height: calc(var(--cam-width) /1.33); - - --cam-big-width: calc(var(--cam-width)*var(--column-cnt)); - --cam-big-wrapper-width: calc(var(--cam-big-width) + calc(var(--padding-camWrapper)*var(--column-cnt))); - --cam-big-min-height: calc(var(--cam-big-width) /1.33); - - --icon-size: 50px; - --icon-postion: calc(var(--cam-min-height)/2 - 25px); - --icon-big-position: calc(calc(var(--cam-big-min-height)/2) - 25px); - - --position-size: 10px; - --position-big-size: calc(var(--position-size) * 2); - --position-margin: 5px 5px 5px 5px; - - --width-noSpace: calc(var(--cam-width) * var(--column-cnt)); - - width: calc(calc(calc(var(--column-cnt) * 3)*var(--padding-camWrapper)) + var(--width-noSpace)); - - justify-content: space-evenly; - align-items: center; - display: flex; - flex-wrap: wrap; + --column-cnt: 2; + --cam-bg-color: #2b2c30; + --cam-name-bg-color: #1b1b1c; + --postion-color: #5e5f63; + --position-selected-color: #1b1b1c; + --cam-wrapper-border-radius: 5px; + --cam-border-radius: 5px; + --name-border-radius: 5px; + --position-border-radius: 3px; + --padding-camWrapper: 5px; + --cam-width: 200px; + --cam-min-height: calc(var(--cam-width) / 1.33); + + --cam-big-width: calc(var(--cam-width) * var(--column-cnt)); + --cam-big-wrapper-width: calc( + var(--cam-big-width) + calc(var(--padding-camWrapper) * var(--column-cnt)) + ); + --cam-big-min-height: calc(var(--cam-big-width) / 1.33); + + --icon-size: 50px; + --icon-postion: calc(var(--cam-min-height) / 2 - 25px); + --icon-big-position: calc(calc(var(--cam-big-min-height) / 2) - 25px); + + --position-size: 10px; + --position-big-size: calc(var(--position-size) * 2); + --position-margin: 5px 5px 5px 5px; + + --width-noSpace: calc(var(--cam-width) * var(--column-cnt)); + + width: calc( + calc(calc(var(--column-cnt) * 3) * var(--padding-camWrapper)) + + var(--width-noSpace) + ); + + justify-content: space-evenly; + align-items: center; + display: flex; + flex-wrap: wrap; } .innerPositionWrapper { - min-height: calc(var(--position-size) + 20px); - justify-content: center; - align-items: center; - display: flex; - flex-wrap: wrap;; + min-height: calc(var(--position-size) + 20px); + justify-content: center; + align-items: center; + display: flex; + flex-wrap: wrap; } .position { - margin: var(--position-margin); - padding: 5px; - height: var(--position-size); - width: var(--position-size); - background-color: var(--postion-color); - border-radius: var(--position-border-radius); + margin: var(--position-margin); + padding: 5px; + height: var(--position-size); + width: var(--position-size); + background-color: var(--postion-color); + border-radius: var(--position-border-radius); } .position.big { - height: var(--position-big-size); - width: var(--position-big-size); + height: var(--position-big-size); + width: var(--position-big-size); } .position .selected { - width: 100%; - min-width: 100%; - height: 100%; - min-height: 100%; - border-radius: var(--position-border-radius); - background-color: var(--position-selected-color); + width: 100%; + min-width: 100%; + height: 100%; + min-height: 100%; + border-radius: var(--position-border-radius); + background-color: var(--position-selected-color); } .camWrapper { - background-color: var(--cam-bg-color); - border-radius: var(--cam-wrapper-border-radius); - padding: var(--padding-camWrapper); - margin-bottom:5px; + background-color: var(--cam-bg-color); + border-radius: var(--cam-wrapper-border-radius); + padding: var(--padding-camWrapper); + margin-bottom: 5px; } .camWrapper .name { - width: 100%; - background-color: var(--cam-name-bg-color); - border-radius: var(--name-border-radius); - margin-bottom: 5px; + width: 100%; + background-color: var(--cam-name-bg-color); + border-radius: var(--name-border-radius); + margin-bottom: 5px; } .innerCamWrapper { - margin: auto; - display: block; - width: var(--cam-width); - min-width: var(--cam-width); - min-height: var(--cam-min-height); + margin: auto; + display: block; + width: var(--cam-width); + min-width: var(--cam-width); + min-height: var(--cam-min-height); } .innerCamWrapper.big { - margin: auto; - display: block; - min-width: var(--cam-big-wrapper-width); - min-height: var(--cam-big-min-height); + margin: auto; + display: block; + min-width: var(--cam-big-wrapper-width); + min-height: var(--cam-big-min-height); } .cam { - border-radius: var(--cam-border-radius); - max-width: 100%; - margin: auto; - display: block; + border-radius: var(--cam-border-radius); + max-width: 100%; + margin: auto; + display: block; } -.cam.nourl, .cam.currentBig{ - min-height: 100%; - font-size: var(--icon-size); - position:relative; - top:var(--icon-postion); +.cam.nourl, +.cam.currentBig { + min-height: 100%; + font-size: var(--icon-size); + position: relative; + top: var(--icon-postion); } .cam.nourl.big { - top:var(--icon-big-postion); + top: var(--icon-big-postion); }