UNPKG

vytronics.hmi

Version:

Vytronics HMI server. Core components Vytronics HMI - The 100% Free, Open-Source, SCADA/HMI Initiative

269 lines (216 loc) 9.2 kB
/* Copyright 2014 Charles Weissman This file is part of "Vytroncs HMI, the 100% Free, Open-Source SCADA/HMI Initiative" herein referred to as "Vytronics HMI". Vytronics HMI is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Vytronics HMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Vytronics HMI. If not, see <http://www.gnu.org/licenses/>. */ var util = require("util"); var path = require("path"); var events = require("events"); var vyutil = require('./vyutil'); var db = require("./db"); var sysdriver = require("./sysdriver"); var log = require("log4js").getLogger('driverdb'); log.setLevel(vyutil.getenv('VYTRONICS_DRIVERDB_LOG_LEVEL', 'warn')); exports.version = '0.0.0'; //List of loaded drivers var drivers = {}; //Emits drivervalue events var emitter = new events.EventEmitter(); //Load drivers from json file exports.load = function (json) { var builtin = [ {id:'sys', info:{uri:'./sysdriver'}}, {id:'sim', info:{uri:'./simdriver'}}, {id:'mem', info:{uri:'./memdriver'}} ]; //Instantiate built in drivers builtin.forEach( function(drv) { drivers[drv.id] = new Driver(drv.id, drv.info); }); log.debug("Loadings drivers."); for( var drvid in json ) { if( json.hasOwnProperty(drvid ) ) { //Do not let someone assign builtin driver id's or uris var reserved = false; builtin.forEach( function(drv) { if ( drv.info.uri === json[drvid].uri ) { log.error("driverdb.load error:" + drv.info.uri + " is loaded by default. Not loading driver:" + drvid, json[drvid]); reserved=true; } if ( drv.id === drvid ) { log.error("driverdb.load error:" + drvid + " is a reserved driver id. Not loading driver:" + drvid,json[drvid]); reserved=true; } }); if (reserved) continue; //TODO workaround - node require search path may not find drivers. It may try //and look relative to the install of the vytronics.hmi module but they are //really in the application package.json. So, for non built in //drivers look for and expect these in the exe's node_module folder json[drvid].uri = path.resolve(process.cwd(),'node_modules',json[drvid].uri); log.debug("Resolving custom driver to path " + json[drvid].uri); drivers[drvid] = new Driver(drvid,json[drvid]); } } //Now create driver sysObjects. TODO -maybe gotta rework the whole SysObject and sysdriver thing //Awkward coupling Object.getOwnPropertyNames(drivers).forEach( function(drv_id){ drivers[drv_id].started = sysdriver.create_sysObject('driver.' + drv_id + '.started', false); }); }; //Link a driver item to a tag exports.subscribe = function(tagid, driverInfo) { if ( !driverInfo.id) { log.error("driverdb driver missing id property:", driverInfo); return; } var driverid = driverInfo.id; if( ! drivers.hasOwnProperty(driverid) ) { log.error("DriverDB no such driver ID:" + driverid); return; } var driver = drivers[driverid]; //Link to driver data (i.e., register the item). The item string is driver specific. //The DriverDB does not really care what is inside. The driver will emit data each //time value associated with the item changes. var item=driverInfo.item; driver.driverObj.register(item); //Remember the linkage var itemsubs = driver.items[item]; if(!itemsubs) driver.items[item]=[]; driver.items[item].push(tagid); //Read current value of item on nextTick so subscriber gets current value initiazed //TODO - perhaps expose a Driver object for driver developers that has a register item method that //schedules emit itemvalue on nextTick and then calls custom drivers register method? process.nextTick( function() { var value = driver.driverObj.read_item(item); driver.procItemValues(item,value); }); }; //Get list of loaded drivers as an array of driver id's. exports.getDrivers = function() { var ids = []; for( var id in drivers ) { if ( drivers.hasOwnProperty(id) ) { ids.push(id); } } return ids; }; //Get list of loaded drivers as an array of driver info exports.getDriversInfo = function() { var info = []; for( var id in drivers ) { if ( drivers.hasOwnProperty(id) ) { info.push({id:id, uri:drivers[id].uri}); } } return info; }; //Start each driver. exports.start = function(id) { if (!id) { exports.getDrivers().forEach( function(id) { log.debug("Starting driver:" + id); drivers[id].driverObj.start(); drivers[id].started.set_value(true); }); } else { //Start the specified driver var driver = drivers[id]; var started = driver.started.get_value(); if( driver && (!started) ){ driver.driverObj.start(); driver.started.set_value(true); } } return true; //todo error codes }; exports.stop = function(id) { if (!id){ Object.getOwnPropertyNames(drivers).forEach( function(id) { log.debug("Stopping driver:" + id); drivers[id].driverObj.stop(); drivers[id].started.set_value(false); }); } else{ //Start the specified driver var driver = drivers[id]; if( driver && (driver.started.get_value()) ){ driver.driverObj.stop(); driver.started.set_value(false); } } return true; //todo error codes }; exports.emitter = emitter; //driverdb event emitter. TODO - why does exports.on = emitter.on not work? exports.write_item = function(driverinfo, value) { if ( !driverinfo ) return false; //TODO - error log var driver = drivers[driverinfo.id]; if (!driver) return false; //TODO - error log return driver.driverObj.write_item(driverinfo.item, value); }; //Create a driver from config info in json file function Driver(id,info) { log.debug('Creating driver id:'+id + ' info:',info); //To capture this var in closures var self = this; this.id = id; //Items and the tags that have subscribed to them for this driver. Each member is //an object with the name of an item and a list of tags that have subscribed to it. //This allows the driverdb to attach a list of tags to an emitted "drivervalue" message. //That is, if an "itemvalue" is received from a driver, a corresponding "drivervalue" message //will be emitted with the list of tags linked to the item. this.items = {}; //Example: //items = { // "item.1": [tag1, tag2...], // "item.another": [tagA], // ... //} //uri is required. var uri = info.uri; if ( undefined === uri ) { throw new Error("Driver missing 'uri' property."); } this.uri = uri; //Driver module loading. //TODO - What kind of sanitizing is needed? Maybe none since even when hosted //a project will execute in its own virtual machine. Shame on the designer //for loading an inappropriate module. //Let node use the standard module search hierarchy. Note that for developing a //driver set NODE_PATH env var to include the dev directory //A driver module must supply a create method that returns a driver object //with the following properties and methods //TODO - document the required interface here // // this.driverObj = require(uri).create(info.config); //Driver objects will emit "itemvalue" messages this.driverObj.on("itemvalue", function(item, value) { self.procItemValues(item,value); }); } //Callback function for "itemvalue" messages emitted by a driver object. //Send drivervalue event for the tag or tags that link to this driver item Driver.prototype.procItemValues = function(item,value) { var tags = this.items[item]; if(!tags) { log.warn("Driver id:" + this.id + " .Received item change for invalid item:" + item); return; } //Tell project that a list of tags have a new value. In most cases there is just one tag //but could be more if there are multiple tags that subscribe to the same driver and item (rare) //Item is only included for dev/debug and may be removed in future version. emitter.emit("drivervalue", this.id, tags, value, item); }