UNPKG

webscada-client

Version:

webSocket SCADA communication client components

1 lines 14.2 kB
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Plc=void 0,require("./style.css"),Array.prototype.remove||(Array.prototype.remove=function(e){var t=this.indexOf(e);return t>=0&&this.splice(t,1),t>=0});class PLCTag{constructor(e,t,s,i,o){this.mactive=!1,this.mevents={},this.mhotPoints=[],void 0!==s&&(this.mvalue=s),this.mname=t,this.mplc=e,i&&(this.meqp=i),o&&(this.maddr=o)}notify(){AppHandler.mutationsCollector.stop(),this.mhotPoints.forEach(e=>{const{node:node,attributeName1:attributeName1,attributeName2:attributeName2,oldValue:oldValue,activate:activate}=e;let newValue;if(activate)if(newValue=oldValue.match(AppHandler.mutationsCollector.regFullProperty)?eval(oldValue.slice(2,-2)):oldValue.replace(AppHandler.mutationsCollector.regProperty,e=>{const rtn=eval(e.slice(2,-2));return rtn}),attributeName2)node[attributeName1][attributeName2]=newValue;else{const e=node[attributeName1];if("radio"===node.type&&newValue){const e=new CustomEvent("change");document.querySelectorAll(`input[name=${node.name}]`).forEach(t=>{t!=node&&"radio"===t.type&&(t.checked=!1,t.dispatchEvent(e))})}if(e==newValue)return;if(node[attributeName1]=newValue,"checked"==attributeName1||"value"==attributeName1){const e=new CustomEvent("change");node.dispatchEvent(e)}}}),AppHandler.mutationsCollector.start()}removeHotPoint(e){this.mhotPoints.remove(e)}set value(e){this.mprecision&&"number"==typeof e&&(e=+e.toFixed(this.mprecision)),this.mvalue!=e&&(this.maddr&&this.mactive&&this.meqp.writeTag(this.maddr,e),this.mvalue=e,this.mactive?this.fire("change"):console.log("Write Tag in unActive status. Tag:%s",this.mname))}get value(){return this.mvalue}set active(e){this.mactive!=e&&(this.mactive=e,e?(this.mevents.change&&this.mevents.change.forEach(e=>{if("object"!=typeof e.handler)return;let t=!0;for(let s of e.handler.tags){s.mactive||(t=!1);break}e.handler.active=t}),this.mhotPoints.forEach(e=>{let t=!0;for(let s of e.notifier)s.mactive||(t=!1);e.activate=t}),this.fire("change")):(this.mevents.change&&this.mevents.change.forEach(e=>{"object"==typeof e.handler&&(e.handler.active=!1)}),this.mhotPoints.forEach(e=>{e.activate=!1})))}on(e,t,s,i){this.mevents[e]||(this.mevents[e]=[]),"object"==typeof t&&(t.active=this.mactive&&t.active),this.mevents[e].push({handler:t,scope:s,once:i})}fire(e,t){if(!this.mactive)return;this.notify();const s=this.mevents[e];s&&((t=t||{}).type=e,t.sender=this,t.tagName=this.mname,t.tagValue=this.mvalue.valueOf(),this.maddr&&(t.tagAddr=this.maddr,t.devValue=this.mdevValue),s.forEach((e,s,i)=>{if(e.once&&i.splice(s,1),"function"==typeof e.handler)return void e.handler.call(e.scope,t);if("object"!=typeof e.handler||!e.handler.active)return;const o=e.handler,r=o.expression(this.mplc);r!=o.prestatus&&(o.prestatus=r,r&&o.onTrue?o.onTrueDelay?o.trueDelay=setTimeout(()=>{o.onTrueInterval?o.trueInterval=setInterval(()=>{o.onTrue.call(e.scope,t)},o.onTrueInterval):o.onTrue.call(e.scope,t)},o.onTrueDelay):o.onTrueInterval?o.trueInterval=setInterval(()=>{o.onTrue.call(e.scope,t)},o.onTrueInterval):o.onTrue.call(e.scope,t):o.onTrue&&(o.trueInterval&&(clearInterval(o.trueInterval),o.trueInterval=0),o.trueDelay&&(clearTimeout(o.trueDelay),o.trueDelay=0)),!r&&e.handler.onFalse?o.onFalseDelay?o.falseDelay=setTimeout(()=>{o.onFalseInterval?o.falseInterval=setInterval(()=>{o.onFalse.call(e.scope,t)},o.onFalseInterval):o.onFalse.call(e.scope,t)},o.onFalseDelay):o.onFalseInterval?o.falseInterval=setInterval(()=>{o.onFalse.call(e.scope,t)},o.onFalseInterval):o.onFalse.call(e.scope,t):o.onFalse&&(o.falseInterval&&(clearInterval(o.falseInterval),o.falseInterval=0),o.falseDelay&&(clearTimeout(o.falseDelay),o.falseDelay=0)))}))}}class Equipment{doOpen(){this.status="opened",this.mplc.mevents.open?this.mplc.fire("open",{equipmentName:this.mname,equipmentAddr:this.maddr}):console.log(`PLC(${this.maddr}) started successfully.`);for(let e in this.mtags){const t=this.mtags[e].mquality;t&&192==t&&(this.mtags[e].active=!0,this.mtags[e].fire("change"))}let e=!0;for(let t in this.mplc.mequipments)if("opened"!=this.mplc.mequipments[t].status){e=!1;break}e&&(document.body.style.visibility="visible")}onWSOpen(e){let t=[],s={cmd:"regist",pt:t},i=this.mWebSocket.onmessage;for(let e in this.mtags)t.push(e);if(0==t.length)return;let o=setTimeout(()=>{this.mWebSocket.onmessage=i;let e=t.join(",");t=[],this.mplc.mevents.error?this.mplc.fire("error",{equipmentAddr:this.maddr,equipmentName:this.mname,code:1e3,msg:`Regist PLC(${this.maddr}) point timeout, abnormal PLC points:${e}`}):console.log("Regist PLC point timeout, abnormal PLC points:"+e),this.doOpen()},30*t.length+500);this.mWebSocket.onmessage=e=>{let s=this.decodeMsg(e.data);for(let e of s){let s=-1;if(s=t.indexOf(e.name),s>=0&&(t.splice(s,1),0==t.length)){clearTimeout(o),this.mWebSocket.onmessage=i,this.doOpen();break}}},this.mWebSocket.send(JSON.stringify(s))}onWSError(e){for(let e in this.mtags)this.mtags[e].active=!1;const t=`An exception occurred while connecting to the OPC server(${this.maddr}). The connection will be retried in 1 second.`;this.mplc.mevents.error?this.mplc.fire("error",{equipmentAddr:this.maddr,equipmentName:this.mname,code:1001,msg:t}):console.log(t),this.status="connecting",document.body.style.visibility="visible",this.mWebSocket.onclose=null,this.mWebSocket.readyState<3&&this.mWebSocket.close(),setTimeout(()=>{this.open()},1e3)}onWSClose(e){this.status="closed",document.body.style.visibility="visible";for(let e in this.mtags)this.mtags[e].active=!1;const t=`The connection to the OPC server(${this.maddr}) was disconnected and will be retried in 1 second.`;this.mplc.mevents.error?this.mplc.fire("error",{code:1001,msg:t}):console.log(t),setTimeout(()=>{this.open()},1e3)}decodeMsg(e){const t=JSON.parse(e);return t.forEach(e=>{const t=this.mtags[e.name];t.mprecision&&"number"==typeof e.value&&(e.value=+e.value.toFixed(t.mprecision)),t.mdevValue=e.value,"opened"==this.status&&192==e.quality?t.active=!0:t.active=!1,t.mvalue!=e.value&&(t.mvalue=e.value,t.fire("change")),t.mquality=e.quality,t.mtimeStamp=new Date(24*(e.timeStamp-25569)*3600*1e3)}),t}registTags(e){for(const t in e){const s=e[t][0];if(!s)throw new Error("The OPC access address is not specified explicitly,The tag name in error is: "+t);const i=new PLCTag(this.mplc,t,void 0,this,s);if(this.mtags[s]=i,this.mplc.tags[t]=i,this.mplc.defineProperty(t),this.mplc.defineProperty(t+"_Quality"),this.mplc.defineProperty(t+"_TimeStamp"),"number"==typeof e[t][1])for(let e=0;e<32;e++)this.mplc.defineProperty(t+"_bit"+e);e[t][1]&&"number"==typeof e[t][1]&&(i.mprecision=e[t][1],this.mplc.defineProperty(t+"_Precision"))}}unload(){this.mWebSocket&&this.mWebSocket.readyState<3&&(this.mWebSocket.onclose=null,this.mWebSocket.close(),this.mWebSocket=void 0),window.removeEventListener("unload",this.bind_unload),this.bind_unload=void 0}open(){this.mWebSocket=new WebSocket(this.maddr),this.bind_unload||(this.bind_unload=this.unload.bind(this),window.addEventListener("unload",this.bind_unload)),this.mWebSocket.onmessage=e=>this.decodeMsg(e.data),this.mWebSocket.onopen=this.onWSOpen.bind(this),this.mWebSocket.onclose=this.onWSClose.bind(this),this.mWebSocket.onerror=this.onWSError.bind(this)}constructor(e,t,s,i){if(this.mtags={},this.status="closed",this.writeTag=(()=>{let e,t={};const s=this;return(i,o)=>{t[i]=o,e||(e=setTimeout(()=>{const i={cmd:"write",pt:t};s.mWebSocket.send(JSON.stringify(i)),t={},e=void 0},1))}})(),!t.addr)throw new Error(`Unknown device address(${s}:${t.addr}). Please specify the right device address.`);this.mplc=e,this.maddr=t.addr,this.mname=s,this.registTags(t.tags),i&&this.open()}}class AppHandler{open(){for(let e in this.mequipments)this.mequipments[e].open()}defineProperty(e){let t;if(e.endsWith("_Quality")){const t=e.substring(0,e.lastIndexOf("_"));Object.defineProperty(this,e,{enumerable:!0,configurable:!1,get(){return this.tags[t].mquality}})}else if(e.endsWith("_TimeStamp")){const t=e.substring(0,e.lastIndexOf("_"));Object.defineProperty(this,e,{enumerable:!0,configurable:!1,get(){return this.tags[t].mtimeStamp}})}else if(e.endsWith("_Precision")){const t=e.substring(0,e.lastIndexOf("_"));Object.defineProperty(this,e,{enumerable:!0,configurable:!1,get(){return this.tags[t].mprecision}})}else if(t=e.match(/(.*?)_bit([0-9]{1,2}$)/)){const s=t[1],i=1<<+t[2];Object.defineProperty(this,e,{enumerable:!0,configurable:!1,get(){return 0==(this.tags[s].value&i)?0:1},set(e){e>0?this.tags[s].value|=i:this.tags[s].value&=~i}})}else Object.defineProperty(this,e,{enumerable:!0,configurable:!1,get(){return this.tags[e].value},set(t){this.tags[e].value=t}})}constructor(e,t=!0){this.tags={},this.mevents={},this.mequipments={},window.plc=this;let s=0;for(let i in e)if(e[i].addr)s++,this.mequipments[i]=new Equipment(this,e[i],i,t);else{for(let t in e[i].tags){const s=new PLCTag(this,t,e[i].tags[t][1]);if(this.tags[t]=s,this.defineProperty(t),"number"==typeof s.mvalue)for(let e=0;e<32;e++)this.defineProperty(t+"_bit"+e);e[i].tags[t].length>2&&(s.mprecision=e[i].tags[t][2],this.defineProperty(t+"_Precision"))}setTimeout(()=>{for(let t in e[i].tags)this.tags[t].active=!0,this.tags[t].fire("change")},1)}s||(document.body.style.visibility="visible"),AppHandler.mutationsCollector.attachReciver(this),AppHandler.mutationsCollector.hotPoints.forEach(e=>{this.registrationHotpoint(e)})}on(e,t,s=!1,i){switch(i=i||this,typeof e){case"function":{const o=e.toString(),r=o.match(/([a-zA-Z_$][\w$]*?)\.([a-zA-Z_$][\w$]*)/g)||[],a=new Set;t.active=!0,t.expression=e,t.tags=a,r.forEach(e=>{const t=e.split(/[\._]/)[1],s=this.tags[t];s&&a.add(s)});for(let e of a)e.on("change",t,i,s);if(0==a.size)throw new Error(`There are no PLC_Tag to refer to. Error code at : on(${o},...)`)}break;case"string":{const o=this.tags[e];o?o.on("change",t,i,s):(this.mevents[e]||(this.mevents[e]=[]),this.mevents[e].push({callback:t,scope:i,once:s}))}break;case"object":if(Array.isArray(e)){e.forEach(e=>{this.tags[e].on("change",t,i,s)});break}throw new Error("Unknown event type.");default:throw new Error("Unknown event type.")}}fire(e,t){const s=this.mevents[e];s&&(t||(t={}),t.type=e,t.sender=this,s.forEach((e,s,i)=>{e.once&&i.splice(s,1),e.callback.call(e.scope,t)}))}registrationHotpoint(e){const{node:t,attributeName1:s,oldValue:i,notifier:o}=e,r=i.match(AppHandler.mutationsCollector.regProperty);if(!r)return;const a=["checkbox","radio"],n=["email","color","date","datetime-local","email","password","month","tel","text","url","search","time","week"],l=["number","range"];r.forEach(t=>{const s=t.match(/\b[a-zA-Z_]\w*?\b/g);if(!s)return;let i;s.forEach(t=>{t=t.split("_")[0],this.tags.hasOwnProperty(t)&&(i=this.tags[t]),i&&(o.has(i)||(e.activate=e.activate&&i.mactive,o.add(i),i.mhotPoints.push(e)))}),e.activate&&i&&i.notify()}),0!=o.size&&("INPUT"==t.tagName&&(a.includes(t.type)&&"checked"==s&&t.addEventListener("change",t=>{for(let s of e.notifier)s.value=t.target.checked}),n.includes(t.type)&&"value"==s&&t.addEventListener("change",t=>{for(let s of e.notifier)s.value=t.target.value}),l.includes(t.type)&&"value"==s&&t.addEventListener("change",t=>{for(let s of e.notifier)s.value=t.target.valueAsNumber})),["SELECT","TEXTAREA"].includes(t.tagName)&&t.addEventListener("change",t=>{for(let s of e.notifier)s.value=t.target.value}))}}class MutationCollector{get hotPoints(){return this.mhotPoints}removeHotPoint(e,t,s){this.mhotPoints.forEach((i,o,r)=>{e===i.node&&(!t||i.attributeName1===t&&i.attributeName2===s)&&(i.notifier.forEach(e=>{e.removeHotPoint(i)}),r.splice(o,1))})}addHotPoint(e){this.mhotPoints.push(e),this.mreciver&&this.mreciver.registrationHotpoint(e)}attachReciver(e){this.mreciver=e}praseProperty(e,t){let s;if(s=e.getAttribute?e.getAttribute(t):e[t],"class"===t&&(t="className"),s)switch(t){case"style":{let t;for(this.regStyle.lastIndex=0;t=this.regStyle.exec(s);){const s={node:e,attributeName1:"style",attributeName2:t[1].toLowerCase().replace(this.regStyleName,e=>e[1].toUpperCase()),oldValue:t[2],activate:!0,notifier:new Set};this.removeHotPoint(e,"style",s.attributeName2),this.addHotPoint(s)}}break;default:if(this.regProperty.test(s)){const i={node:e,attributeName1:t,attributeName2:void 0,notifier:new Set,activate:!0,oldValue:s};this.removeHotPoint(e,t),this.addHotPoint(i)}else this.removeHotPoint(e,t)}}start(){this.oldRecords&&this.documentChange(this.oldRecords),this.mobserver.observe(document,this.mobserverOption)}stop(){this.oldRecords=this.mobserver.takeRecords(),this.mobserver.disconnect()}recursiveAddNode(e){this.timer||(this.timer=setTimeout(()=>{this.processedNodes=[],clearTimeout(this.timer),this.timer=void 0},0));for(let t=e.childNodes.length-1;t>=0;t--)this.recursiveAddNode(e.childNodes[t]);e.getAttributeNames&&e.getAttributeNames().forEach(t=>{this.praseProperty(e,t)}),"#text"===e.nodeName&&this.praseProperty(e,"textContent"),this.processedNodes.push(e)}recursiveRemoveNode(e){this.timer||(this.timer=setTimeout(()=>{this.processedNodes=[],clearTimeout(this.timer),this.timer=void 0},0));for(let t=e.childNodes.length-1;t>=0;t--)this.recursiveRemoveNode(e.childNodes[t]);this.removeHotPoint(e),this.processedNodes.push(e)}documentChange(e){for(let t of e)switch(t.type){case"attributes":this.praseProperty(t.target,t.attributeName);break;case"characterData":this.praseProperty(t.target,"textContent");break;case"childList":t.addedNodes.forEach(e=>{e!==this.processedNodes[this.processedNodes.length-1]?(this.recursiveAddNode(e),this.processedNodes.pop()):this.processedNodes.pop()}),t.removedNodes.forEach(e=>{this.recursiveRemoveNode(e)})}}constructor(){this.regStyle=/([A-Za-z_][\w-]*?) ?: ?([^;]*@\(.+?\)@.*?)(?:(?:;)|(?:$))/g,this.regStyleName=/-[a-z]/g,this.regProperty=/@\(.+?\)@/g,this.regFullProperty=/^@\((?:(?!(?:@\()|(?:\)@)).)+\)@$/,this.mreciver=null,this.mhotPoints=[],this.processedNodes=[],this.mobserverOption={attributes:!0,attributeOldValue:!1,characterData:!0,characterDataOldValue:!1,childList:!0,subtree:!0},document.body&&this.recursiveAddNode(document.body),this.mobserver=new MutationObserver(this.documentChange.bind(this)),this.start()}}AppHandler.mutationsCollector=new MutationCollector;let Plc=AppHandler;exports.Plc=Plc;