diff --git a/examples/c++/CMakeLists.txt b/examples/c++/CMakeLists.txt index 999db868..2abe742a 100644 --- a/examples/c++/CMakeLists.txt +++ b/examples/c++/CMakeLists.txt @@ -234,6 +234,7 @@ if (MODBUS_FOUND) include_directories(${MODBUS_INCLUDE_DIRS}) add_example (t3311) add_example (hwxpxx) + add_example (h803x) endif() add_example (hdxxvxta) add_example (rhusb) diff --git a/examples/c++/h803x.cxx b/examples/c++/h803x.cxx new file mode 100644 index 00000000..cd935012 --- /dev/null +++ b/examples/c++/h803x.cxx @@ -0,0 +1,149 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "h803x.h" + +using namespace std; + +bool shouldRun = true; + +void sig_handler(int signo) +{ + if (signo == SIGINT) + shouldRun = false; +} + +int main(int argc, char **argv) +{ + signal(SIGINT, sig_handler); + +//! [Interesting] + + string defaultDev = "/dev/ttyUSB0"; + + // if an argument was specified, use it as the device instead + if (argc > 1) + defaultDev = string(argv[1]); + + cout << "Using device " << defaultDev << endl; + cout << "Initializing..." << endl; + + // Instantiate an H803X instance, using MODBUS slave address 1, and + // default comm parameters (9600, 8, N, 2) + upm::H803X *sensor = new upm::H803X(defaultDev, 1); + + // output the Slave ID string + cout << "Slave ID: " << sensor->getSlaveID() << endl; + cout << endl; + + // update and print available values every second + while (shouldRun) + { + // update our values from the sensor + sensor->update(); + + // H8035 / H8036 + cout << "Consumption (kWh): " << sensor->getConsumption() << endl; + cout << "Real Power (kW): " << sensor->getRealPower() << endl; + + if (sensor->isH8036()) + { + // The H8036 has much more data available... + + cout << "Reactive Power (kVAR): " << sensor->getReactivePower() + << endl; + cout << "Apparent Power (kVA): " << sensor->getApparentPower() + << endl; + cout << "Power Factor: " << sensor->getPowerFactor() + << endl; + cout << "Volts Line to Line: " << sensor->getVoltsLineToLine() + << endl; + cout << "Volts Line to Neutral: " << sensor->getVoltsLineToNeutral() + << endl; + + cout << "Current: " << sensor->getCurrent() + << endl; + + cout << "Real Power Phase A (kW): " << sensor->getRealPowerPhaseA() + << endl; + cout << "Real Power Phase B (kW): " << sensor->getRealPowerPhaseB() + << endl; + cout << "Real Power Phase C (kW): " << sensor->getRealPowerPhaseC() + << endl; + + cout << "Power Factor Phase A: " << sensor->getPowerFactorPhaseA() + << endl; + cout << "Power Factor Phase B: " << sensor->getPowerFactorPhaseB() + << endl; + cout << "Power Factor Phase C: " << sensor->getPowerFactorPhaseC() + << endl; + + cout << "Volts Phase A to B: " << sensor->getVoltsPhaseAToB() + << endl; + cout << "Volts Phase B to C: " << sensor->getVoltsPhaseBToC() + << endl; + cout << "Volts Phase A to C: " << sensor->getVoltsPhaseAToC() + << endl; + cout << "Volts Phase A to Neutral: " + << sensor->getVoltsPhaseAToNeutral() + << endl; + cout << "Volts Phase B to Neutral: " + << sensor->getVoltsPhaseBToNeutral() + << endl; + cout << "Volts Phase C to Neutral: " + << sensor->getVoltsPhaseCToNeutral() + << endl; + + cout << "Current Phase A: " << sensor->getCurrentPhaseA() + << endl; + cout << "Current Phase B: " << sensor->getCurrentPhaseB() + << endl; + cout << "Current Phase C: " << sensor->getCurrentPhaseC() + << endl; + + cout << "Avg Real Power (kW): " << sensor->getAvgRealPower() + << endl; + cout << "Min Real Power (kW): " << sensor->getMinRealPower() + << endl; + cout << "Max Real Power (kW): " << sensor->getMaxRealPower() + << endl; + } + + cout << endl; + + sleep(2); + } + + cout << "Exiting..." << endl; + + delete sensor; + +//! [Interesting] + + return 0; +} diff --git a/examples/java/CMakeLists.txt b/examples/java/CMakeLists.txt index 548abb25..73036cd9 100644 --- a/examples/java/CMakeLists.txt +++ b/examples/java/CMakeLists.txt @@ -110,6 +110,9 @@ add_example(TEAMS_Example teams) add_example(APA102Sample apa102) add_example(TEX00_Example tex00) add_example(BMI160_Example bmi160) +if (MODBUS_FOUND) + add_example(H803X_Example h803x) +endif() add_example_with_path(Jhd1313m1_lcdSample lcd/upm_i2clcd.jar) add_example_with_path(Jhd1313m1Sample lcd/upm_i2clcd.jar) diff --git a/examples/java/H803X_Example.java b/examples/java/H803X_Example.java new file mode 100644 index 00000000..5d4828ec --- /dev/null +++ b/examples/java/H803X_Example.java @@ -0,0 +1,123 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import upm_h803x.H803X; + +public class H803X_Example +{ + private static String defaultDev = "/dev/ttyUSB0"; + + public static void main(String[] args) throws InterruptedException + { +// ! [Interesting] + if (args.length > 0) + defaultDev = args[0]; + System.out.println("Using device " + defaultDev); + System.out.println("Initializing..."); + + // Instantiate an H803X instance, using MODBUS slave address 1, and + // default comm parameters (9600, 8, N, 2) + H803X sensor = new H803X(defaultDev, 1); + + // output the Slave ID (manufacturer, model, serno) + System.out.println("Slave ID: " + sensor.getSlaveID()); + System.out.println(); + + while (true) + { + // update our values from the sensor + sensor.update(); + + // H8035 / H8036 + System.out.println("Consumption (kWh): " + + sensor.getConsumption()); + System.out.println("Real Power (kW): " + + sensor.getRealPower()); + + if (sensor.isH8036()) + { + // The H8036 has much more data available... + + System.out.println("Reactive Power (kVAR): " + + sensor.getReactivePower()); + System.out.println("Apparent Power (kVA): " + + sensor.getApparentPower()); + System.out.println("Power Factor: " + + sensor.getPowerFactor()); + System.out.println("Volts Line to Line: " + + sensor.getVoltsLineToLine()); + System.out.println("Volts Line to Neutral: " + + sensor.getVoltsLineToNeutral()); + + System.out.println("Current: " + sensor.getCurrent()); + + System.out.println("Real Power Phase A (kW): " + + sensor.getRealPowerPhaseA()); + System.out.println("Real Power Phase B (kW): " + + sensor.getRealPowerPhaseB()); + System.out.println("Real Power Phase C (kW): " + + sensor.getRealPowerPhaseC()); + + System.out.println("Power Factor Phase A: " + + sensor.getPowerFactorPhaseA()); + System.out.println("Power Factor Phase B: " + + sensor.getPowerFactorPhaseB()); + System.out.println("Power Factor Phase C: " + + sensor.getPowerFactorPhaseC()); + + System.out.println("Volts Phase A to B: " + + sensor.getVoltsPhaseAToB()); + System.out.println("Volts Phase B to C: " + + sensor.getVoltsPhaseBToC()); + System.out.println("Volts Phase A to C: " + + sensor.getVoltsPhaseAToC()); + System.out.println("Volts Phase A to Neutral: " + + sensor.getVoltsPhaseAToNeutral()); + System.out.println("Volts Phase B to Neutral: " + + sensor.getVoltsPhaseBToNeutral()); + System.out.println("Volts Phase C to Neutral: " + + sensor.getVoltsPhaseCToNeutral()); + + System.out.println("Current Phase A: " + + sensor.getCurrentPhaseA()); + System.out.println("Current Phase B: " + + sensor.getCurrentPhaseB()); + System.out.println("Current Phase C: " + + sensor.getCurrentPhaseC()); + + System.out.println("Avg Real Power (kW): " + + sensor.getAvgRealPower()); + System.out.println("Min Real Power (kW): " + + sensor.getMinRealPower()); + System.out.println("Max Real Power (kW): " + + sensor.getMaxRealPower()); + } + + System.out.println(); + Thread.sleep(2000); + } + +// ! [Interesting] + } +} diff --git a/examples/javascript/h803x.js b/examples/javascript/h803x.js new file mode 100644 index 00000000..23fe5d8a --- /dev/null +++ b/examples/javascript/h803x.js @@ -0,0 +1,115 @@ +/*jslint node:true, vars:true, bitwise:true, unparam:true */ +/*jshint unused:true */ + +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +var sensorObj = require('jsupm_h803x'); + + +/************** Main code **************/ + +var defaultDev = "/dev/ttyUSB0"; + +// if an argument was specified, use it as the device instead +if (process.argv.length > 2) +{ + defaultDev = process.argv[2]; +} + +console.log("Using device " + defaultDev); +console.log("Initializing..."); + +// Instantiate an H803X instance, using MODBUS slave address 1, and +// default comm parameters (9600, 8, N, 2) +var sensor = new sensorObj.H803X(defaultDev, 1); + +// output the Slave ID (manufacturer, model, serno) +console.log("Slave ID:", sensor.getSlaveID()); + +console.log(""); + +// update and print available values every second +setInterval(function() +{ + // update our values from the sensor + sensor.update(); + + // H8035 / H8036 + console.log("Consumption (kWh):", sensor.getConsumption()); + console.log("Real Power (kW):", sensor.getRealPower()); + + if (sensor.isH8036()) + { + // The H8036 has much more data available... + + console.log("Reactive Power (kVAR):", sensor.getReactivePower()); + console.log("Apparent Power (kVA):", sensor.getApparentPower()); + console.log("Power Factor:", sensor.getPowerFactor()); + console.log("Volts Line to Line:", sensor.getVoltsLineToLine()); + console.log("Volts Line to Neutral:", sensor.getVoltsLineToNeutral()); + + console.log("Current:", sensor.getCurrent()); + + console.log("Real Power Phase A (kW):", sensor.getRealPowerPhaseA()); + console.log("Real Power Phase B (kW):", sensor.getRealPowerPhaseB()); + console.log("Real Power Phase C (kW):", sensor.getRealPowerPhaseC()); + + console.log("Power Factor Phase A:", sensor.getPowerFactorPhaseA()); + console.log("Power Factor Phase B:", sensor.getPowerFactorPhaseB()); + console.log("Power Factor Phase C:", sensor.getPowerFactorPhaseC()); + + console.log("Volts Phase A to B:", sensor.getVoltsPhaseAToB()); + console.log("Volts Phase B to C:", sensor.getVoltsPhaseBToC()); + console.log("Volts Phase A to C:", sensor.getVoltsPhaseAToC()); + console.log("Volts Phase A to Neutral: ", + sensor.getVoltsPhaseAToNeutral()); + console.log("Volts Phase B to Neutral: ", + sensor.getVoltsPhaseBToNeutral()); + console.log("Volts Phase C to Neutral: ", + sensor.getVoltsPhaseCToNeutral()); + + console.log("Current Phase A:", sensor.getCurrentPhaseA()); + console.log("Current Phase B:", sensor.getCurrentPhaseB()); + console.log("Current Phase C:", sensor.getCurrentPhaseC()); + + console.log("Avg Real Power (kW):", sensor.getAvgRealPower()); + console.log("Min Real Power (kW):", sensor.getMinRealPower()); + console.log("Max Real Power (kW):", sensor.getMaxRealPower()); + } + + console.log(""); + +}, 2000); + + +process.on('SIGINT', function() +{ + sensor = null; + sensorObj.cleanUp(); + sensorObj = null; + console.log("Exiting..."); + process.exit(0); +}); diff --git a/examples/python/h803x.py b/examples/python/h803x.py new file mode 100644 index 00000000..4e950e0d --- /dev/null +++ b/examples/python/h803x.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# Author: Jon Trulson +# Copyright (c) 2016 Intel Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import time, sys, signal, atexit +import pyupm_h803x as sensorObj + +## Exit handlers ## +# This function stops python from printing a stacktrace when you hit control-C +def SIGINTHandler(signum, frame): + raise SystemExit + +# This function lets you run code on exit +def exitHandler(): + print "Exiting..." + sys.exit(0) + +# Register exit handlers +atexit.register(exitHandler) +signal.signal(signal.SIGINT, SIGINTHandler) + +defaultDev = "/dev/ttyUSB0" + +# if an argument was specified, use it as the device instead +if (len(sys.argv) > 1): + defaultDev = sys.argv[1] + +print "Using device", defaultDev +print "Initializing..." + +# Instantiate an H803X instance, using MODBUS slave address 1, and +# default comm parameters (9600, 8, N, 2) +sensor = sensorObj.H803X(defaultDev, 1) + +# output the serial number and firmware revision +print "Slave ID:", sensor.getSlaveID() + +print + +# update and print available values every second +while (1): + # update our values from the sensor + sensor.update() + + # H8035 / H8036 + print "Consumption (kWh):", sensor.getConsumption() + print "Real Power (kW):", sensor.getRealPower() + + if (sensor.isH8036()): + # The H8036 has much more data available... + + print "Reactive Power (kVAR):", sensor.getReactivePower() + print "Apparent Power (kVA):", sensor.getApparentPower() + print "Power Factor:", sensor.getPowerFactor() + print "Volts Line to Line:", sensor.getVoltsLineToLine() + print "Volts Line to Neutral:", sensor.getVoltsLineToNeutral() + + print "Current:", sensor.getCurrent() + + print "Real Power Phase A (kW):", sensor.getRealPowerPhaseA() + print "Real Power Phase B (kW):", sensor.getRealPowerPhaseB() + print "Real Power Phase C (kW):", sensor.getRealPowerPhaseC() + + print "Power Factor Phase A:", sensor.getPowerFactorPhaseA() + print "Power Factor Phase B:", sensor.getPowerFactorPhaseB() + print "Power Factor Phase C:", sensor.getPowerFactorPhaseC() + + print "Volts Phase A to B:", sensor.getVoltsPhaseAToB() + print "Volts Phase B to C:", sensor.getVoltsPhaseBToC() + print "Volts Phase A to C:", sensor.getVoltsPhaseAToC() + print "Volts Phase A to Neutral: ", + print sensor.getVoltsPhaseAToNeutral() + print "Volts Phase B to Neutral: ", + print sensor.getVoltsPhaseBToNeutral() + print "Volts Phase C to Neutral: ", + print sensor.getVoltsPhaseCToNeutral() + + print "Current Phase A:", sensor.getCurrentPhaseA() + print "Current Phase B:", sensor.getCurrentPhaseB() + print "Current Phase C:", sensor.getCurrentPhaseC() + + print "Avg Real Power (kW):", sensor.getAvgRealPower() + print "Min Real Power (kW):", sensor.getMinRealPower() + print "Max Real Power (kW):", sensor.getMaxRealPower() + + print + time.sleep(2) diff --git a/src/h803x/CMakeLists.txt b/src/h803x/CMakeLists.txt new file mode 100644 index 00000000..a99f85cd --- /dev/null +++ b/src/h803x/CMakeLists.txt @@ -0,0 +1,24 @@ +set (libname "h803x") +set (libdescription "upm module for the Veris H803X (H8035/H8036)") +set (module_src ${libname}.cxx) +set (module_h ${libname}.h) + +pkg_search_module(MODBUS libmodbus) +if (MODBUS_FOUND) + set (reqlibname "libmodbus") + include_directories(${MODBUS_INCLUDE_DIRS}) + upm_module_init() + add_dependencies(${libname} ${MODBUS_LIBRARIES}) + target_link_libraries(${libname} ${MODBUS_LIBRARIES}) + if (BUILDSWIG) + if (BUILDSWIGNODE) + swig_link_libraries (jsupm_${libname} ${MODBUS_LIBRARIES} ${MRAA_LIBRARIES} ${NODE_LIBRARIES}) + endif() + if (BUILDSWIGPYTHON) + swig_link_libraries (pyupm_${libname} ${MODBUS_LIBRARIES} ${PYTHON_LIBRARIES} ${MRAA_LIBRARIES}) + endif() + if (BUILDSWIGJAVA) + swig_link_libraries (javaupm_${libname} ${MODBUS_LIBRARIES} ${MRAAJAVA_LDFLAGS} ${JAVA_LDFLAGS}) + endif() + endif() +endif () diff --git a/src/h803x/h803x.cxx b/src/h803x/h803x.cxx new file mode 100644 index 00000000..187159e5 --- /dev/null +++ b/src/h803x/h803x.cxx @@ -0,0 +1,356 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "h803x.h" + +using namespace upm; +using namespace std; + +// We can't use the modbus float conversion functions since they +// assume the first word is the LSW. On this device, the first word +// is MSW. In addition, the data is already IEEE 754 formatted, which +// won't work with just the bit shuffling modbus_get_float*() does. +static float regs2float(uint16_t h, uint16_t l) +{ + // this function will fail horribly if the following isn't true + assert(sizeof(float) == sizeof(uint32_t)); + + // we can't use a cast here, since the data is already IEEE 754 + // formatted, so use a union instead. + union { + uint32_t i; + float f; + } converter; + + converter.i = ((uint32_t)h << 16) | l; + return converter.f; +} + + +H803X::H803X(std::string device, int address, int baud, int bits, char parity, + int stopBits) : + m_mbContext(0) +{ + // check some of the parameters + if (!(bits == 7 || bits == 8)) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": bits must be 7 or 8"); + } + + if (!(parity == 'N' || parity == 'E' || parity == 'O')) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": parity must be 'N', 'O', or 'E'"); + } + + if (!(stopBits == 1 || stopBits == 2)) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": stopBits must be 1 or 2"); + } + + // now, open/init the device and modbus context + + if (!(m_mbContext = modbus_new_rtu(device.c_str(), baud, parity, bits, + stopBits))) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_new_rtu() failed"); + } + + // set the slave address of the device we want to talk to + + // addresses are only 8bits wide + address &= 0xff; + if (modbus_set_slave(m_mbContext, address)) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_set_slave() failed"); + } + + // set the serial mode + modbus_rtu_set_serial_mode(m_mbContext, MODBUS_RTU_RS232); + + // now connect.. + if (modbus_connect(m_mbContext)) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_connect() failed"); + } + + // will set m_isH8036 appropriately + testH8036(); + + clearData(); + + // turn off debugging + setDebug(false); +} + +H803X::~H803X() +{ + if (m_mbContext) + { + modbus_close(m_mbContext); + modbus_free(m_mbContext); + } +} + +int H803X::readHoldingRegs(HOLDING_REGS_T reg, int len, uint16_t *buf) +{ + int rv; + int retries = 5; + + // Sometimes it seems the device goes to sleep, and therefore a read + // will timeout, so we will retry up to 5 times. + + while (retries >= 0) + { + if ((rv = modbus_read_registers(m_mbContext, reg, len, buf)) < 0) + { + if (errno == ETIMEDOUT) + { + // timeout + retries--; + sleep(1); + } + else if (errno == EMBXILADD) + { + // invalid registers will return a EMBXILADD (modbus) + // error. We want to detect these as a way to determine + // whether we are dealing with an H8035 or H8036. + return -1; + } + else + { + // anything else is a failure. + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_read_registers() failed: " + + modbus_strerror(errno)); + } + } + else + return rv; // success + } + + // if we're here, then all the retries were exhausted + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_read_registers() timed out after " + + "5 retries"); +} + +void H803X::writeHoldingReg(HOLDING_REGS_T reg, int value) +{ + if (modbus_write_register(m_mbContext, reg, value) != 1) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_write_register() failed: " + + modbus_strerror(errno)); + } +} + +void H803X::update() +{ + static const int h8035NumRegs = 4; // 2 regs * 2 + static const int h8036NumRegs = 52; // 26 regs * 2 + + int numRegs = (isH8036() ? h8036NumRegs : h8035NumRegs); + + uint16_t buf[numRegs]; + + // This should only fail (return -1) if we got isH8036() wrong + if (readHoldingRegs(HOLDING_CONSUMPTION_KWH, numRegs, buf) < 0) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": readHoldingRegs() failed: " + + modbus_strerror(errno)); + } + + // And so it begins... + + // H8035 / H8036 + m_consumptionkWh = regs2float(buf[0], buf[1]); + m_realPowerkW = regs2float(buf[2], buf[3]); + + // H8036 only + if (isH8036()) + { + m_reactivePowerkVAR = regs2float(buf[4], buf[5]); + m_apparentPowerkVA = regs2float(buf[6], buf[7]); + m_powerFactor = regs2float(buf[8], buf[9]); + + m_voltsLineToLine = regs2float(buf[10], buf[11]); + m_voltsLineToNeutral = regs2float(buf[12], buf[13]); + + m_current = regs2float(buf[14], buf[15]); + + m_realPowerPhaseAkW = regs2float(buf[16], buf[17]); + m_realPowerPhaseBkW = regs2float(buf[18], buf[19]); + m_realPowerPhaseCkW = regs2float(buf[20], buf[21]); + + m_powerFactorPhaseA = regs2float(buf[22], buf[23]); + m_powerFactorPhaseB = regs2float(buf[24], buf[25]); + m_powerFactorPhaseC = regs2float(buf[26], buf[27]); + + m_voltsPhaseAB = regs2float(buf[28], buf[29]); + m_voltsPhaseBC = regs2float(buf[30], buf[31]); + m_voltsPhaseAC = regs2float(buf[32], buf[33]); + m_voltsPhaseAN = regs2float(buf[34], buf[35]); + m_voltsPhaseBN = regs2float(buf[36], buf[37]); + m_voltsPhaseCN = regs2float(buf[38], buf[39]); + + m_currentPhaseA = regs2float(buf[40], buf[41]); + m_currentPhaseB = regs2float(buf[42], buf[43]); + m_currentPhaseC = regs2float(buf[44], buf[45]); + + m_avgRealPowerkW = regs2float(buf[46], buf[47]); + m_minRealPowerkW = regs2float(buf[48], buf[49]); + m_maxRealPowerkW = regs2float(buf[50], buf[51]); + } +} + +string H803X::getSlaveID() +{ + uint8_t id[MODBUS_MAX_PDU_LENGTH]; + int rv; + + if ((rv = modbus_report_slave_id(m_mbContext, MODBUS_MAX_PDU_LENGTH, id)) < 0) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_report_slave_id() failed: " + + modbus_strerror(errno)); + + } + + // the first byte is the number of bytes in the response, the second + // byte is the active indicator (00 = off, ff = on), and the rest + // are ascii identification (company, model, and serial number) data. + + if (rv > 2) + { + string retID((char *)&id[2], rv - 2); + return retID; + } + else + return ""; +} + +void H803X::setSlaveAddress(int addr) +{ + // addresses are only 8bits wide + addr &= 0xff; + + if (modbus_set_slave(m_mbContext, addr)) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": modbus_set_slave() failed: " + + modbus_strerror(errno)); + } + + // retest H8036 + testH8036(); + + // clear out any previously stored data + clearData(); +} + +void H803X::setDebug(bool enable) +{ + m_debugging = enable; + + if (enable) + modbus_set_debug(m_mbContext, 1); + else + modbus_set_debug(m_mbContext, 0); +} + +void H803X::clearData() +{ + // H8035 + m_consumptionkWh = 0.0; + m_realPowerkW = 0.0; + + // H8036 + m_reactivePowerkVAR = 0.0; + m_apparentPowerkVA = 0.0; + m_powerFactor = 0.0; + m_voltsLineToLine = 0.0; + m_voltsLineToNeutral = 0.0; + m_current = 0.0; + m_realPowerPhaseAkW = 0.0; + m_realPowerPhaseBkW = 0.0; + m_realPowerPhaseCkW = 0.0; + m_powerFactorPhaseA = 0.0; + m_powerFactorPhaseB = 0.0; + m_powerFactorPhaseC = 0.0; + m_voltsPhaseAB = 0.0; + m_voltsPhaseBC = 0.0; + m_voltsPhaseAC = 0.0; + m_voltsPhaseAN = 0.0; + m_voltsPhaseBN = 0.0; + m_voltsPhaseCN = 0.0; + m_currentPhaseA = 0.0; + m_currentPhaseB = 0.0; + m_currentPhaseC = 0.0; + m_avgRealPowerkW = 0.0; + m_minRealPowerkW = 0.0; + m_maxRealPowerkW = 0.0; +} + +void H803X::testH8036() +{ + // here we test a register read to see if we are on an H8036 device, + // which can provide much more information. + + uint16_t regs[2]; + + // here, we'll read 2 registers that only exist on the H8036. Any + // failure other than a illegal data access will generate an + // exception. A valid request will return >0, and an illegal + // register read will return -1. + if (readHoldingRegs(HOLDING_REACTIVE_POWER_KVAR, 2, regs) == -1) + m_isH8036 = false; + else + m_isH8036 = true; +} + +void H803X::presetConsumption(float value, MULTIPLIERS_T multiplier) +{ + uint32_t i = uint32_t(value * float(multiplier)); + + uint16_t h = uint16_t(i >> 16); + uint16_t l = uint16_t(i & 0xffff); + + // always write the LSW first + writeHoldingReg(HOLDING_CONSUMPTION_KWH_INT_L, l); + writeHoldingReg(HOLDING_CONSUMPTION_KWH_INT_H, h); +} diff --git a/src/h803x/h803x.h b/src/h803x/h803x.h new file mode 100644 index 00000000..472f5e18 --- /dev/null +++ b/src/h803x/h803x.h @@ -0,0 +1,547 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#pragma once + +#include + +#include + +namespace upm { + + /** + * @brief H803X Energy Meter + * @defgroup h803x libupm-h803x + * @ingroup uart electric + */ + + /** + * @library h803x + * @sensor h803x + * @comname UPM API for the Veris H803X Energy Meter + * @type electic + * @man veris + * @con uart + * @web http://www.veris.com/Item/H8035-0100-2.aspx + * + * @brief UPM API for the Veris H803X Energy Meter + * + * This module implements support for the Veris H8035 and H8036 + * Energy Meters. + * + * The H8036 is similar to the H8035, but provides much more data. + * + * The Enercept H8035/H8036 is an innovative three-phase networked + * (Modbus RTU) power transducer that combines electronics and high + * accuracy industrial grade CTs in a single package. The need for + * external electrical enclosures is eliminated, greatly reducing + * installation time and cost. Color-coordination between voltage + * leads and CTs makes phase matching easy. Additionally, these + * transducers automatically detect and compensate for phase + * reversal, eliminating the concern of CT load orientation. Up to + * 63 Transducers can be daisy-chained on a single RS-485 network. + * + * This module was developed using libmodbus 3.1.2, and the H8035. + * The H8036 has not been tested. libmodbus 3.1.2 must be present + * for this module to build. + * + * It was developed using an RS232->RS485 interface. You cannot use + * the built in MCU TTL UART pins for accessing this device -- you + * must use a full Serial RS232->RS485 or USB-RS485 interface + * connected via USB. + * + * @snippet h803x.cxx Interesting + */ + + class H803X { + public: + + // MODBUS holding registers. These offsets are for the MSW only. + // The LSW always follows, though they are not enumerated here. + // These are all 2 register (32-bit total (16b HSW + 16b LSW)) + // quantities, in IEEE 754 floating point format. + typedef enum { + // these two registers are used only for presetConsumption() + HOLDING_CONSUMPTION_KWH_INT_L = 0, // preset use only + HOLDING_CONSUMPTION_KWH_INT_H = 1, // preset use only + + // H8035/H8036 + HOLDING_CONSUMPTION_KWH = 258, // floating point data + + HOLDING_REAL_POWER_KW = 260, + + // H8036 only + HOLDING_REACTIVE_POWER_KVAR = 262, + HOLDING_APPARENT_POWER_KVA = 264, + HOLDING_POWER_FACTOR = 266, + HOLDING_VOLTS_LINE_TO_LINE = 268, + HOLDING_VOLTS_LINE_TO_NEUTRAL = 270, + HOLDING_CURRENT = 272, + HOLDING_REAL_POWER_PHASE_A_KWH = 274, + HOLDING_REAL_POWER_PHASE_B_KWH = 276, + HOLDING_REAL_POWER_PHASE_C_KWH = 278, + HOLDING_POWER_FACTOR_PHASE_A = 280, + HOLDING_POWER_FACTOR_PHASE_B = 282, + HOLDING_POWER_FACTOR_PHASE_C = 284, + HOLDING_VOLTS_PHASE_AB = 286, + HOLDING_VOLTS_PHASE_BC = 288, + HOLDING_VOLTS_PHASE_AC = 290, + HOLDING_VOLTS_PHASE_AN = 292, + HOLDING_VOLTS_PHASE_BN = 294, + HOLDING_VOLTS_PHASE_CN = 296, + HOLDING_CURRENT_PHASE_A = 298, + HOLDING_CURRENT_PHASE_B = 300, + HOLDING_CURRENT_PHASE_C = 302, + HOLDING_AVG_REAL_POWER_KW = 304, + HOLDING_MIN_REAL_POWER_KW = 306, + HOLDING_MAX_REAL_POWER_KW = 308 + } HOLDING_REGS_T; + + // these enums are used by presetConsumption() to scale the value + // properly depending on the devices' current capacity. + typedef enum { + MULT_100A = 128, // 100A devices + MULT_300A_400A = 32, + MULT_800A = 16, + MULT_1600A = 8, + MULT_2400A = 4 + } MULTIPLIERS_T; + + /** + * H803X constructor + * + * @param device Path to the serial device + * @param address The MODBUS slave address + * @param baud The baudrate of the device. Default: 9600 + * @param bits The number of bits per byte. Default: 8 + * @param parity The parity of the connection, 'N' for None, 'E' + * for Even, 'O' for Odd. Default: 'N' + * @param stopBits The number of stop bits. Default: 2 + */ + H803X(std::string device, int address, int baud=9600, int bits=8, + char parity='N', int stopBits=2); + + /** + * H803X Destructor + */ + ~H803X(); + + /** + * Read current values from the sensor and update internal stored + * values. This method must be called prior to querying any + * values. + */ + void update(); + + /** + * Return a string corresponding the the device's MODBUS slave ID. + * + * @return string represnting the MODBUS slave ID + */ + std::string getSlaveID(); + + /** + * Set a new MODBUS slave address. This is useful if you have + * multiple H803X devices on a single bus. When this method is + * called, the current stored data is cleared, and a new attempt + * is made to determine whether the target device is an H8035 or + * H8036. + * + * @param addr The new slave address to set + */ + void setSlaveAddress(int addr); + + /** + * Preset the kWh accumulated Consumption registers to a + * predefined value. This is generally not advised, but is + * provided for those installations that might require it. The + * multiplier depends on the current range of your device. Be + * sure to select the right multiplier for your devices' + * supported current capacity. + * + * @param value The desired value for the consumption accumulator + * registers in kWh. + * @param multiplier The correct MULTIPLIERS_T value for your device. + */ + void presetConsumption(float value, MULTIPLIERS_T multiplier); + + /** + * Return the accumulated consumption value, in kWh. update() must + * have been called prior to calling this method. + * + * @return The accumulated consumption. + */ + float getConsumption() + { + return m_consumptionkWh; + }; + + /** + * Return the real power value in kW. update() must have been + * called prior to calling this method. + * + * @return The real power value in kW. + */ + float getRealPower() + { + return m_realPowerkW; + }; + + /** + * Return the reactive power value in kVAR (kilo-volt Amperes + * Reactive). update() must have been called prior to calling this + * method. + * + * @return The reactive power value in kVAR. + */ + float getReactivePower() + { + return m_reactivePowerkVAR; + }; + + /** + * Return the apparent power value in kVA. update() must have been + * called prior to calling this method. + * + * @return The apparent power value in kVA. + */ + float getApparentPower() + { + return m_apparentPowerkVA; + }; + + /** + * Return the power factor value. update() must have been called + * prior to calling this method. + * + * @return The power factor. + */ + float getPowerFactor() + { + return m_powerFactor; + }; + + /** + * Return the voltage line to line value. update() must have been + * called prior to calling this method. + * + * @return The voltage, line to line. + */ + float getVoltsLineToLine() + { + return m_voltsLineToLine; + }; + + /** + * Return the voltage line to neutral. update() must have been + * called prior to calling this method. + * + * @return The voltage, line to neutral. + */ + float getVoltsLineToNeutral() + { + return m_voltsLineToNeutral; + }; + + /** + * Return the current value in amps. update() must have been + * called prior to calling this method. + * + * @return The current value in amps. + */ + float getCurrent() + { + return m_current; + }; + + /** + * Return the real power for phase A. update() must have been + * called prior to calling this method. + * + * @return The real power for phase A. + */ + float getRealPowerPhaseA() + { + return m_realPowerPhaseAkW; + }; + + /** + * Return the real power for phase B. update() must have been + * called prior to calling this method. + * + * @return The real power for phase B. + */ + float getRealPowerPhaseB() + { + return m_realPowerPhaseBkW; + }; + + /** + * Return the real power for phase C. update() must have been + * called prior to calling this method. + * + * @return The real power for phase C. + */ + float getRealPowerPhaseC() + { + return m_realPowerPhaseCkW; + }; + + /** + * Return the power factor for phase A. update() must have been + * called prior to calling this method. + * + * @return The power factor for phase A. + */ + float getPowerFactorPhaseA() + { + return m_powerFactorPhaseA; + }; + + /** + * Return the power factor for phase B. update() must have been + * called prior to calling this method. + * + * @return The power factor for phase B. + */ + float getPowerFactorPhaseB() + { + return m_powerFactorPhaseB; + }; + + /** + * Return the power factor for phase C. update() must have been + * called prior to calling this method. + * + * @return The power factor for phase C. + */ + float getPowerFactorPhaseC() + { + return m_powerFactorPhaseC; + }; + + /** + * Return the voltage for phase A to B. update() must have been + * called prior to calling this method. + * + * @return The voltage for phase A to B. + */ + float getVoltsPhaseAToB() + { + return m_voltsPhaseAB; + }; + + /** + * Return the voltage for phase B to C. update() must have been + * called prior to calling this method. + * + * @return The voltage for phase B to C. + */ + float getVoltsPhaseBToC() + { + return m_voltsPhaseBC; + }; + + /** + * Return the voltage for phase A to B. update() must have been + * called prior to calling this method. + * + * @return The voltage for phase A to B. + */ + float getVoltsPhaseAToC() + { + return m_voltsPhaseAC; + }; + + /** + * Return the voltage for phase A to neutral. update() must have + * been called prior to calling this method. + * + * @return The voltage for phase A to neutral. + */ + float getVoltsPhaseAToNeutral() + { + return m_voltsPhaseAN; + }; + + /** + * Return the voltage for phase B to neutral. update() must have + * been called prior to calling this method. + * + * @return The voltage for phase B to neutral. + */ + float getVoltsPhaseBToNeutral() + { + return m_voltsPhaseBN; + }; + + /** + * Return the voltage for phase C to neutral. update() must have + * been called prior to calling this method. + * + * @return The voltage for phase C to neutral. + */ + float getVoltsPhaseCToNeutral() + { + return m_voltsPhaseCN; + }; + + /** + * Return the current for phase A. update() must have been called + * prior to calling this method. + * + * @return The current for phase A. + */ + float getCurrentPhaseA() + { + return m_currentPhaseA; + }; + + /** + * Return the current for phase B. update() must have been called + * prior to calling this method. + * + * @return The current for phase B. + */ + float getCurrentPhaseB() + { + return m_currentPhaseB; + }; + + /** + * Return the current for phase C. update() must have been called + * prior to calling this method. + * + * @return The current for phase C. + */ + float getCurrentPhaseC() + { + return m_currentPhaseC; + }; + + /** + * Return the average real power. update() must have been called + * prior to calling this method. + * + * @return The average real power. + */ + float getAvgRealPower() + { + return m_avgRealPowerkW; + }; + + /** + * Return the minimum real power. update() must have been called + * prior to calling this method. + * + * @return The minimum real power. + */ + float getMinRealPower() + { + return m_minRealPowerkW; + }; + + /** + * Return the maximum real power. update() must have been called + * prior to calling this method. + * + * @return The maximum real power. + */ + float getMaxRealPower() + { + return m_maxRealPowerkW; + }; + + /** + * Enable or disable debugging output. This primarily enables and + * disables libmodbus debugging output. + * + * @param enable true to enable debugging, false otherwise + */ + void setDebug(bool enable); + + /** + * Indicate whether the connected device is an H8035 or an H8036. + * The H8036 provides many more data registers. + * + * @return true if we are using an H8036, false otherwise. + */ + bool isH8036() + { + return m_isH8036; + }; + + protected: + // holding registers + int readHoldingRegs(HOLDING_REGS_T reg, int len, uint16_t *buf); + void writeHoldingReg(HOLDING_REGS_T reg, int value); + + // clear out all stored data + void clearData(); + + // MODBUS context + modbus_t *m_mbContext; + + // test to see if the connected device is an H8036, and set + // m_isH8036 appropriately + void testH8036(); + + // Is this an H8036 (has extended registers) + bool m_isH8036; + + private: + bool m_debugging; + + // data + + // H8035 / H8036 + float m_consumptionkWh; + float m_realPowerkW; + + // H8036 only + float m_reactivePowerkVAR; + float m_apparentPowerkVA; + float m_powerFactor; + float m_voltsLineToLine; + float m_voltsLineToNeutral; + float m_current; // in amps + float m_realPowerPhaseAkW; + float m_realPowerPhaseBkW; + float m_realPowerPhaseCkW; + float m_powerFactorPhaseA; + float m_powerFactorPhaseB; + float m_powerFactorPhaseC; + float m_voltsPhaseAB; + float m_voltsPhaseBC; + float m_voltsPhaseAC; + float m_voltsPhaseAN; + float m_voltsPhaseBN; + float m_voltsPhaseCN; + float m_currentPhaseA; + float m_currentPhaseB; + float m_currentPhaseC; + float m_avgRealPowerkW; + float m_minRealPowerkW; + float m_maxRealPowerkW; + }; +} diff --git a/src/h803x/javaupm_h803x.i b/src/h803x/javaupm_h803x.i new file mode 100644 index 00000000..95df7069 --- /dev/null +++ b/src/h803x/javaupm_h803x.i @@ -0,0 +1,20 @@ +%module javaupm_h803x +%include "../upm.i" +%include "typemaps.i" + +%{ + #include "h803x.h" +%} + +%include "h803x.h" + +%pragma(java) jniclasscode=%{ + static { + try { + System.loadLibrary("javaupm_h803x"); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load. \n" + e); + System.exit(1); + } + } +%} diff --git a/src/h803x/jsupm_h803x.i b/src/h803x/jsupm_h803x.i new file mode 100644 index 00000000..b26156fe --- /dev/null +++ b/src/h803x/jsupm_h803x.i @@ -0,0 +1,8 @@ +%module jsupm_h803x +%include "../upm.i" +%include "stdint.i" + +%include "h803x.h" +%{ + #include "h803x.h" +%} diff --git a/src/h803x/pyupm_h803x.i b/src/h803x/pyupm_h803x.i new file mode 100644 index 00000000..b31387c5 --- /dev/null +++ b/src/h803x/pyupm_h803x.i @@ -0,0 +1,12 @@ +// Include doxygen-generated documentation +%include "pyupm_doxy2swig.i" +%module pyupm_h803x +%include "../upm.i" +%include "stdint.i" + +%feature("autodoc", "3"); + +%include "h803x.h" +%{ + #include "h803x.h" +%}