commit 48689f7ed87ee17853f1f3733afed27f3532e56b Author: Patrick McDonagh Date: Wed Jan 13 10:02:05 2016 -0600 Initial Commit diff --git a/python/solar_ww.py b/python/solar_ww.py new file mode 100644 index 0000000..b811260 --- /dev/null +++ b/python/solar_ww.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +''' +Created on Dec 8, 2015 + +@author: Patrick McDonagh +''' + + +from datetime import datetime +import sys +from random import randint +import time +import MySQLdb +import tuxeip + + +#TUXEIP Connection to PLC +from tuxeip import TuxEIP, LGX, LGX_REAL + + +def main(): + + db = MySQLdb.connect(host="127.0.0.1",user="website",passwd="henrypump",db="SolarData") + cur = db.cursor() + query = "SELECT * FROM SolarData.tags WHERE deleted = 0;" + cur.execute(query) + tags = cur.fetchall() + # ((1L, 'DC_Bus_Voltage', datetime.datetime(2015, 12, 8, 16, 2, 32), 'V', 0L), (2L, 'Output_Frequency', datetime.datetime(2015, 12, 8, 16, 31, 12), 'Hz', 0L)) + db.commit() + db.close() + + PLC_IP_ADDRESS = "192.168.1.13" # MAKE THIS A db VALUE + + tagList = []; + if len(tags) > 0: + for t in tags: + tagList.append({"id":int(t[0]), "name":t[1], "val":None, "lastVal":None}); + + try: + tux = TuxEIP(libpath="/usr/lib/libtuxeip.so") + sess = tux.OpenSession(PLC_IP_ADDRESS) + reg = tux.RegisterSession(sess) + conn = tux.ConnectPLCOverCNET(sess, LGX, 1, 100, 123, randint(0,9999), 123, 321, 100, 5000, 1, '01') + + while True: + for r in tagList: + r["val"] = tux.ReadLGXDataAsFloat(sess, conn, r['name'], 1)[0] + print("{0} - {1}".format(r["name"], r["val"])) + if not r["val"] == r["lastVal"]: + db = MySQLdb.connect(host="127.0.0.1",user="website",passwd="henrypump",db="SolarData") + cur = db.cursor() + aQuery = """INSERT INTO SolarData.values (tagID, val) VALUES ('%d', '%f');"""%(r["id"], float(r["val"])) + print(aQuery) + storeVal = cur.execute(aQuery) + db.commit() + db.close() + r["lastVal"] = r["val"] + + time.sleep(10) + except Exception as err: + print err + pass + + +if __name__ == '__main__': + main() diff --git a/python/tuxeip.py b/python/tuxeip.py new file mode 100644 index 0000000..1c39e40 --- /dev/null +++ b/python/tuxeip.py @@ -0,0 +1,276 @@ +#! /usr/bin/env python + +# Copyright (C) 2014 Gayner Technical Services Pty Ltd + +from ctypes import * + +# PLC TYPES +Unknow=0 +PLC=1 +SLC500=2 +LGX=3 + +# EIP DATA TYPES +PLC_BIT=1 +PLC_BIT_STRING=2 +PLC_BYTE_STRING=3 +PLC_INTEGER=4 +PLC_TIMER=5 +PLC_COUNTER=6 +PLC_CONTROL=7 +PLC_FLOATING=8 +PLC_ARRAY=9 +PLC_ADRESS=15 +PLC_BCD=16 + +# LOGIX DATA TYPES +LGX_BOOL=0xC1 +LGX_BITARRAY=0xD3 +LGX_SINT=0xC2 +LGX_INT=0xC3 +LGX_DINT=0xC4 +LGX_REAL=0xCA + +class Eip_Session(Structure): + _fields_ = [ + ('sock',c_int), + ('Session_Handle', c_uint), + ('Sender_ContextL',c_int), + ('Sender_ContextH',c_int), + ('timeout', c_int), + ('references', c_int), + ('Data', c_void_p), + ] + +class Eip_Connection(Structure): + _fields_ = [ + ('Eip_Session', Eip_Session), + ('references', c_int), + ('Data', c_void_p), + ('ConnectionSerialNumber', c_uint), + ('OriginatorVendorID', c_uint), + ('OriginatorSerialNumber', c_int), + ('OT_ConnID', c_int), + ('TO_ConnID', c_int), + ('packet', c_short), + ('Path_size', c_byte) + ] + +class Eip_PLC_Read(Structure): + _fields_ = [ + ('type', c_int), + ('Varcount', c_int), + ('totalise', c_int), + ('elementsize', c_int), + ('mask', c_uint), + ] + +class TuxEIPException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +class TuxEIP: + + def __init__(self, **kwargs): + self.__libpath = kwargs.get("libpath", "libtuxeip.dylib") + self.__tuxeip = CDLL(self.__libpath) + self.__tuxeip._cip_err_msg.restype = c_char_p + + def __del__(self): + del self.__tuxeip + + def OpenSession(self, slaveip_, slaveport_=44818, slavetimeout_=1000): + self.__tuxeip._OpenSession.restype = POINTER(Eip_Session) + + # Convert params to C types + slaveip = c_char_p(slaveip_) + slaveport = c_int(slaveport_) + slavetimeout = c_int(slavetimeout_) + + session = self.__tuxeip._OpenSession(slaveip, slaveport, slavetimeout) + + #print self.__tuxeip._cip_err_msg, self.__tuxeip._cip_errno, self.__tuxeip._cip_ext_errno + + if bool(session) == False: + raise TuxEIPException("Could not open session to " + str(slaveip) + ":" + str(slaveport)) + + return session + + def RegisterSession(self, sess_): + self.__tuxeip._RegisterSession.restype = c_int + reg = self.__tuxeip._RegisterSession(sess_) + + if reg != False: + raise TuxEIPException("Could not register session") + + return reg + + def ConnectPLCOverCNET(self, sess_, plctype_, priority_, timeoutticks_, connid_, conserial_, + vendorid_, serialnum_, timeoutmult_, rpi_, transport_, slavepath_): + # Convert params to C types + priority = c_byte(priority_) + timeoutticks = c_byte(timeoutticks_) + connid = c_uint(connid_) + conserial = c_ushort(conserial_) + vendorid = c_ushort(vendorid_) + serialnum = c_uint(serialnum_) + timeutmult = c_byte(timeoutmult_) + rpi = c_uint(rpi_) + transport = c_byte(transport_) + slavepath = c_char_p(slavepath_) + pathlength = len(slavepath_) + + self.__tuxeip._ConnectPLCOverCNET.restype = POINTER(Eip_Connection) + + connection = self.__tuxeip._ConnectPLCOverCNET( + sess_, + plctype_, + priority, + timeoutticks, + connid, + conserial, + vendorid, + serialnum, + timeutmult, + rpi, + transport, + slavepath, + pathlength + ) + + if bool(connection) == False: + raise TuxEIPException("Could not connect to CPU") + + return connection + + def ReadLgxData(self, sess_, conn_, var_, num_): + self.__tuxeip._ReadLgxData.restype = POINTER(Eip_PLC_Read) + readdata = self.__tuxeip._ReadLgxData(sess_, conn_, var_, num_) + + if bool(readdata) == False: + raise TuxEIPException("Read data failed") + + return readdata + + def WriteLGXData(self, sess_, conn_, address_, datatype_, data_, num_ ): + if datatype_ == LGX_INT or datatype_ == LGX_BOOL or datatype_ == LGX_DINT or datatype_ == LGX_SINT: + data = c_int(data_) + elif datatype_ == LGX_REAL: + data = c_float(data_) + else: + raise TuxEIPException("Write data failed") + + data = self.__tuxeip._WriteLgxData(sess_, conn_, address_, datatype_, byref(data), num_) + + return data + + def ReadLGXDataAsFloat(self, sess_, conn_, var_, num_): + data = self.ReadLgxData(sess_, conn_, var_, num_) + d = self.GetLGXValueAsFloat(data) + self.FreePLCRead(data) + return d + + def ReadLGXDataAsInteger(self, sess_, conn_, var_, num_): + data = self.ReadLgxData(sess_, conn_, var_, num_) + d = self.GetLGXValueAsInteger(data) + self.FreePLCRead(data) + return d + + def ReadPLCDataAsFloat(self, sess_, conn_, dhp_, routepath_, routesize_, plctype_, tns_, address_, number_): + data = self.ReadPLCData(sess_, conn_, dhp_, routepath_, routesize_, plctype_, tns_, address_, number_) + d = self.PCCC_GetValueAsFloat(data) + self.FreePLCRead(data) + return d + + def ReadPLCDataAsInteger(self, sess_, conn_, dhp_, routepath_, routesize_, plctype_, tns_, address_, number_): + data = self.ReadPLCData(sess_, conn_, dhp_, routepath_, routesize_, plctype_, tns_, address_, number_) + d = self.PCCC_GetValueAsInteger(data) + self.FreePLCRead(data) + return d + + def ReadPLCData(self, sess_, conn_, dhp_, routepath_, routesize_, plctype_, tns_, address_, number_): + self.__tuxeip._ReadPLCData.restype = POINTER(Eip_PLC_Read) + readdata = self.__tuxeip._ReadPLCData(sess_, conn_, dhp_, routepath_, routesize_, plctype_, + tns_, address_, number_) + + if bool(readdata) == False: + raise TuxEIPException("Read data failed") + + return readdata + + def GetLGXValueAsFloat(self, readdata_): + if bool(readdata_) == False: + return None + + self.__tuxeip._GetLGXValueAsFloat.restype = c_float + values = [] + for i in range(0, readdata_.contents.Varcount): + v = self.__tuxeip._GetLGXValueAsFloat(readdata_, i) + values.append(v) + + return values + + def GetLGXValueAsInteger(self, readdata_): + if bool(readdata_) == False: + return None + + self.__tuxeip._GetLGXValueAsInteger.restype = c_int + values = [] + for i in range(0, readdata_.contents.Varcount): + v = self.__tuxeip._GetLGXValueAsInteger(readdata_, i) + values.append(v) + + return values + + def PCCC_GetValueAsFloat(self, readdata_): + if bool(readdata_) == False: + return None + + self.__tuxeip._PCCC_GetValueAsFloat.restype = c_float + values = [] + for i in range(0, readdata_.contents.Varcount): + v = self.__tuxeip._PCCC_GetValueAsFloat(readdata_, i) + values.append(v) + + return values + + def PCCC_GetValueAsInteger(self, readdata_): + if bool(readdata_) == False: + return None + + self.__tuxeip._PCCC_GetValueAsInteger.restype = c_int + values = [] + for i in range(0, readdata_.contents.Varcount): + v = self.__tuxeip._PCCC_GetValueAsInteger(readdata_, i) + values.append(v) + + return values + + def WritePLCData(self, sess_, conn_, dhp_, routepath_, routesize_, plctype_, tns_, address_, datatype_, data_, number_): + + if datatype_ == PLC_INTEGER: + data = c_int(data_) + elif datatype_ == PLC_FLOATING: + data = c_float(data_) + else: + raise TuxEIPException("Variable type not supported" + str(datatype_)) + + result = self.__tuxeip._WritePLCData(sess_, conn_, dhp_, routepath_, routesize_, plctype_, + tns_, address_, datatype_, byref(data), number_) + + return result + + def Forward_Close(self, conn_): + self.__tuxeip._Forward_Close(conn_) + + def UnRegisterSession(self, sess_): + self.__tuxeip._UnRegisterSession(sess_) + + def CloseSession(self, sess_): + self.__tuxeip.CloseSession(sess_) + + def FreePLCRead(self, data_): + self.__tuxeip._FreePLCRead(data_) diff --git a/python/tuxeip.pyc b/python/tuxeip.pyc new file mode 100644 index 0000000..92f8848 Binary files /dev/null and b/python/tuxeip.pyc differ diff --git a/www/app.js b/www/app.js new file mode 100644 index 0000000..293322f --- /dev/null +++ b/www/app.js @@ -0,0 +1,78 @@ +var express = require('express'), + path = require('path'), + fs = require('fs'), + logger = require('morgan'), + methodOverride = require('method-override'), + bodyParser = require('body-parser'), + errorHandler = require('errorhandler'); +var app = express(); + +/** +* Configuration +*/ +var mysql = require('mysql'); +var db_config = { + host: 'localhost', + user: 'website', + password: 'henrypump' +}; +var handleDisconnect = function () { + console.log("Handling db disconnect gracefully"); + app.locals.db = mysql.createConnection(db_config); + app.locals.db.connect(function (err) { + if (err) { + console.log('error when connecting to db:', err); + setTimeout(handleDisconnect, 2000); + } + }); + app.locals.db.on('error', function (err) { + console.log('db error', err); + if (err.code === 'PROTOCOL_CONNECTION_LOST') { + handleDisconnect(); + } else { + throw err; + } + }); +}; +handleDisconnect(); + +app.set('port', process.env.PORT || 80); +app.set('views', path.join(__dirname, 'views')); +app.engine('.html', require('ejs').renderFile); +app.set('view engine', 'html'); +//app.use(favicon(__dirname + '/public/img/favicon.ico')); +app.use(logger('dev')); +app.use(methodOverride()); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({extended: true})); + +//app.use(express["static"](path.join(__dirname, 'public'))); +app.use(express.static(path.join(__dirname, 'public'))); + +/** +* Routes +*/ +var fns = require('./functions.js'); +var angular = function(req, res) { + res.render('angularIndex'); +}; + +app.get('/json/add/:tagName/:units', fns.addTag); // Adds a tag to the scan list +app.get('/json/remove/:tag', fns.removeTag); // Removes a tag from the scan list +app.get('/json/val/:tag', fns.latestTagValue); // Gets the latest value of a single tag +app.get('/json/series/:tag/:hours', fns.seriesTagValues); // Gets all the values of a tag for the last X hours +app.get('/json/tags', fns.allTags); // Lists all tags in the scan list +app.get('/json/all', fns.allValues); // Gets the latest values of all tags in the scan list + +app.get('*', angular); + +/** +* Start Server +*/ +connectionsArray = []; +s_port = 3000; +var server = app.listen(s_port, function () { + var host = server.address().address; + var port = server.address().port; + console.log('POConsole listening at http://%s:%s', host, port); +}); diff --git a/www/dbcreate.sql b/www/dbcreate.sql new file mode 100644 index 0000000..4cdb6cb --- /dev/null +++ b/www/dbcreate.sql @@ -0,0 +1,15 @@ +CREATE DATABASE SolarData; +CREATE TABLE `SolarData`.`tags` ( + `id` INT NOT NULL AUTO_INCREMENT, + `tagName` VARCHAR(128) NULL, + `dateAdded` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `units` VARCHAR(16) NULL, + `deleted` INT NULL DEFAULT 0, + PRIMARY KEY (`id`)); + +CREATE TABLE `SolarData`.`values` ( + `id` INT NOT NULL AUTO_INCREMENT, + `tagID` INT NULL, + `val` FLOAT NULL, + `dateAdded` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`)); \ No newline at end of file diff --git a/www/functions.js b/www/functions.js new file mode 100644 index 0000000..79dfa95 --- /dev/null +++ b/www/functions.js @@ -0,0 +1,68 @@ +// app.get('/json/add/:tag', fns.addTag); // Adds a tag to the scan list +// app.get('/json/remove/:tag', fns.removeTag); // Removes a tag from the scan list +// app.get('/json/val/:tag', fns.latestTagValue); // Gets the latest value of a single tag +// app.get('/json/series/:tag/:hours', fns.seriesTagValues); // Gets all the values of a tag for the last X hours +// app.get('/json/tags', fns.allTags); // Lists all tags in the scan list +// app.get('/json/all', fns.allValues); // Gets the latest values of all tags in the scan list + + +var getScanList = function(sl){ + var query = "SELECT * FROM SolarData.tags WHERE deleted = 0;"; + +}; + +exports.addTag = function(req, res){ + var vals = { + tagName: req.params.tagName, + units: req.params.units, + }; + var query = "INSERT INTO SolarData.tags SET ?"; + req.app.locals.db.query(query, vals, function(err, rows, fields) { + if (err) { + res.json({status:"error", message:err}); + } else { + res.json({status:"success"}); + } + }); +}; + +exports.removeTag = function(req, res){ + var query = "UPDATE SolarData.tags SET deleted = 1 WHERE id = " + parseInt(req.params.tag) + ";"; + req.app.locals.db.query(query, function(err, rows, fields) { + if (err) { + res.json({status:"error", message:err}); + } else { + res.json({status:"success"}); + } + }); +}; + +exports.latestTagValue = function(req, res){ + var query = "SELECT * FROM SolarData.values WHERE id = (SELECT MAX(id) FROM SolarData.values WHERE tagID = (SELECT id FROM SolarData.tags WHERE tagName = '" + req.params.tag + "'));"; + req.app.locals.db.query(query, function(err, rows, fields) { + if (err) { + res.json({status:"error", message:err}); + } else { + res.json({tag_val:rows[0]}); + } + }); +}; + +exports.seriesTagValues = function(req, res){ + res.json("not implemented"); +}; + +exports.allTags = function(req, res){ + var query = "SELECT * FROM SolarData.tags WHERE deleted = 0;"; + req.app.locals.db.query(query, function(err, rows, fields) { + if (err) { + res.json({status:"error", message:err}); + } else { + res.json({tags:rows}); + } + }); +}; + +exports.allValues = function(req, res){ + res.json("not implemented"); +}; diff --git a/www/package.json b/www/package.json new file mode 100644 index 0000000..d3c49aa --- /dev/null +++ b/www/package.json @@ -0,0 +1,25 @@ +{ + "name": "WaterWell DataLogger", + "version": "1.0.0", + "description": "Interface to Henry Pump Water Well Solar Panel", + "main": "app.js", + "dependencies": { + "body-parser": "*", + "ejs": "*", + "errorhandler": "*", + "express": "*", + "method-override": "*", + "morgan": "*", + "mysql": "*", + "serve-favicon": "*", + "ya-csv": "*", + "is-running":"*", + "async":"*" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Patrick McDonagh", + "license": "ISC" +} diff --git a/www/views/angularIndex.html b/www/views/angularIndex.html new file mode 100644 index 0000000..1dc4328 --- /dev/null +++ b/www/views/angularIndex.html @@ -0,0 +1,66 @@ + + + + POConsole: {{ Page.title() }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+