1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

temper: add new node-type for reading PCSensor / TEMPer USB sensors

This commit is contained in:
Steffen Vogel 2021-06-18 11:27:02 -04:00
parent b2aab0b823
commit edd84d79af
7 changed files with 930 additions and 7 deletions

View file

@ -118,6 +118,7 @@ pkg_check_modules(LIBULDAQ IMPORTED_TARGET libuldaq>=1.0.0)
pkg_check_modules(UUID IMPORTED_TARGET REQUIRED uuid>=2.23)
pkg_check_modules(CGRAPH IMPORTED_TARGET libcgraph>=2.30)
pkg_check_modules(GVC IMPORTED_TARGET libgvc>=2.30)
pkg_check_modules(LIBUSB IMPORTED_TARGET libusb-1.0>=1.0.23)
pkg_check_modules(NANOMSG IMPORTED_TARGET nanomsg)
if(NOT NANOMSG_FOUND)
pkg_check_modules(NANOMSG IMPORTED_TARGET libnanomsg>=1.0.0)
@ -150,15 +151,19 @@ cmake_dependent_option(WITH_DOC "Build documentation"
cmake_dependent_option(WITH_GRAPHVIZ "Build with Graphviz" ON "CGRAPH_FOUND; GVC_FOUND" OFF)
cmake_dependent_option(WITH_LUA "Build with Lua" ON "LUA_FOUND" OFF)
cmake_dependent_option(WITH_NODE_AMQP "Build with amqp node-type" ON "RABBITMQ_C_FOUND" OFF)
cmake_dependent_option(WITH_NODE_CAN "Build with can node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_COMEDI "Build with comedi node-type" ON "COMEDILIB_FOUND" OFF)
cmake_dependent_option(WITH_NODE_FILE "Build with file node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_ETHERCAT "Build with ethercat node-type" ON "ETHERLAB_FOUND" OFF)
cmake_dependent_option(WITH_NODE_EXAMPLE "Build with example node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_EXEC "Build with exec node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_FILE "Build with file node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_FPGA "Build with fpga node-type" ON "WITH_FPGA" OFF)
cmake_dependent_option(WITH_NODE_IEC61850 "Build with iec61850 node-types" ON "LIBIEC61850_FOUND" OFF)
cmake_dependent_option(WITH_NODE_INFINIBAND "Build with infiniband node-type" ON "IBVERBS_FOUND; RDMACM_FOUND" OFF)
cmake_dependent_option(WITH_NODE_INFLUXDB "Build with influxdb node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_KAFKA "Build with kafka node-type" ON "RDKAFKA_FOUND" OFF)
cmake_dependent_option(WITH_NODE_LOOPBACK "Build with loopback node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_MQTT "Build with mqtt node-type" ON "MOSQUITTO_FOUND" OFF)
cmake_dependent_option(WITH_NODE_KAFKA "Build with kafka node-type" ON "RDKAFKA_FOUND" OFF)
cmake_dependent_option(WITH_NODE_NANOMSG "Build with nanomsg node-type" ON "NANOMSG_FOUND" OFF)
cmake_dependent_option(WITH_NODE_NGSI "Build with ngsi node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_OPAL "Build with opal node-type" ON "Opal_FOUND" OFF)
@ -167,14 +172,11 @@ cmake_dependent_option(WITH_NODE_SHMEM "Build with shmem node-type"
cmake_dependent_option(WITH_NODE_SIGNAL "Build with signal node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_SOCKET "Build with socket node-type" ON "LIBNL3_ROUTE_FOUND" OFF)
cmake_dependent_option(WITH_NODE_STATS "Build with stats node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_TEMPER "Build with temper node-type" ON "LIBUSB_FOUND" OFF)
cmake_dependent_option(WITH_NODE_TEST_RTT "Build with test_rtt node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_ULDAQ "Build with uldaq node-type" ON "LIBULDAQ_FOUND" OFF)
cmake_dependent_option(WITH_NODE_WEBSOCKET "Build with websocket node-type" ON "WITH_WEB; LIBWEBSOCKETS_FOUND" OFF)
cmake_dependent_option(WITH_NODE_ZEROMQ "Build with zeromq node-type" ON "LIBZMQ_FOUND" OFF)
cmake_dependent_option(WITH_NODE_ETHERCAT "Build with ethercat node-type" ON "ETHERLAB_FOUND" OFF)
cmake_dependent_option(WITH_NODE_CAN "Build with can node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_EXAMPLE "Build with example node-type" ON "" OFF)
cmake_dependent_option(WITH_NODE_FPGA "Build with fpga node-type" ON "WITH_FPGA" OFF)
# Add more build configurations
include(cmake/config/Debug.cmake)
@ -246,9 +248,9 @@ add_feature_info(NODE_FILE WITH_NODE_FILE "Build with
add_feature_info(NODE_IEC61850 WITH_NODE_IEC61850 "Build with iec61850 node-types")
add_feature_info(NODE_INFINIBAND WITH_NODE_INFINIBAND "Build with infiniband node-type")
add_feature_info(NODE_INFLUXDB WITH_NODE_INFLUXDB "Build with influxdb node-type")
add_feature_info(NODE_KAFKA WITH_NODE_KAFKA "Build with kafka node-type")
add_feature_info(NODE_LOOPBACK WITH_NODE_LOOPBACK "Build with loopback node-type")
add_feature_info(NODE_MQTT WITH_NODE_MQTT "Build with mqtt node-type")
add_feature_info(NODE_KAFKA WITH_NODE_KAFKA "Build with kafka node-type")
add_feature_info(NODE_NANOMSG WITH_NODE_NANOMSG "Build with nanomsg node-type")
add_feature_info(NODE_NGSI WITH_NODE_NGSI "Build with ngsi node-type")
add_feature_info(NODE_OPAL WITH_NODE_OPAL "Build with opal node-type")
@ -257,6 +259,7 @@ add_feature_info(NODE_SHMEM WITH_NODE_SHMEM "Build with
add_feature_info(NODE_SIGNAL_GENERATOR WITH_NODE_SIGNAL "Build with signal node-type")
add_feature_info(NODE_SOCKET WITH_NODE_SOCKET "Build with socket node-type")
add_feature_info(NODE_STATS WITH_NODE_STATS "Build with stats node-type")
add_feature_info(NODE_TEMPER WITH_NODE_TEMPER "Build with temper node-type")
add_feature_info(NODE_TEST_RTT WITH_NODE_TEST_RTT "Build with test_rtt node-type")
add_feature_info(NODE_ULDAQ WITH_NODE_ULDAQ "Build with uldaq node-type")
add_feature_info(NODE_WEBSOCKET WITH_NODE_WEBSOCKET "Build with websocket node-type")

View file

@ -0,0 +1,181 @@
/** An temper get started with new implementations of new node-types
*
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
/**
* @addtogroup temper BSD temper Node Type
* @ingroup node
* @{
*/
#pragma once
#include <villas/node/config.h>
#include <villas/node.h>
#include <villas/format.hpp>
#include <villas/timing.h>
#include <villas/usb.hpp>
#include <villas/log.hpp>
class TEMPerDevice : public villas::usb::Device {
protected:
constexpr static unsigned char question_temperature[] = { 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00 };
float scale;
float offset;
int timeout;
villas::Logger logger;
virtual void decode(unsigned char *answer, float *temp) = 0;
public:
static TEMPerDevice * make(struct libusb_device *desc);
TEMPerDevice(struct libusb_device *dev);
virtual ~TEMPerDevice()
{ }
void open(bool reset = true);
void close();
virtual int getNumSensors() const
{ return 1; }
virtual bool hasHumiditySensor() const
{ return false; };
void read(struct sample *smp);
};
class TEMPer1Device : public TEMPerDevice {
protected:
virtual void decode(unsigned char *answer, float *temp);
using TEMPerDevice::TEMPerDevice;
public:
static bool match(struct libusb_device *dev);
static std::string getName()
{ return "TEMPer1"; }
};
class TEMPer2Device : public TEMPer1Device {
protected:
virtual void decode(unsigned char *answer, float *temp);
using TEMPer1Device::TEMPer1Device;
public:
static bool match(struct libusb_device *dev);
static std::string getName()
{ return "TEMPer2"; }
virtual int getNumSensors() const
{ return 2; }
};
class TEMPerHUMDevice : public TEMPerDevice {
protected:
virtual void decode(unsigned char *answer, float *temp);
using TEMPerDevice::TEMPerDevice;
public:
static bool match(struct libusb_device *dev);
static std::string getName()
{ return "TEMPerHUM"; }
virtual bool hasHumiditySensor() const
{ return true; }
};
struct temper {
struct {
double scale;
double offset;
} calibration;
struct villas::usb::Filter filter;
TEMPerDevice *device;
};
/** @see node_vtable::type_start */
int temper_type_start(villas::node::SuperNode *sn);
/** @see node_type::type_stop */
int temper_type_stop();
/** @see node_type::init */
int temper_init(struct vnode *n);
/** @see node_type::destroy */
int temper_destroy(struct vnode *n);
/** @see node_type::parse */
int temper_parse(struct vnode *n, json_t *json);
/** @see node_type::print */
char * temper_print(struct vnode *n);
/** @see node_type::check */
int temper_check();
/** @see node_type::prepare */
int temper_prepare();
/** @see node_type::start */
int temper_start(struct vnode *n);
/** @see node_type::stop */
int temper_stop(struct vnode *n);
/** @see node_type::pause */
int temper_pause(struct vnode *n);
/** @see node_type::resume */
int temper_resume(struct vnode *n);
/** @see node_type::write */
int temper_write(struct vnode *n, struct sample * const smps[], unsigned cnt);
/** @see node_type::read */
int temper_read(struct vnode *n, struct sample * const smps[], unsigned cnt);
/** @see node_type::reverse */
int temper_reverse(struct vnode *n);
/** @see node_type::poll_fds */
int temper_poll_fds(struct vnode *n, int fds[]);
/** @see node_type::netem_fds */
int temper_netem_fds(struct vnode *n, int fds[]);
/** @} */

126
include/villas/usb.hpp Normal file
View file

@ -0,0 +1,126 @@
/** Helpers for USB node-types
*
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2021, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <villas/exceptions.hpp>
#include <libusb-1.0/libusb.h>
namespace villas {
namespace usb {
struct Filter {
int bus;
int port;
int vendor_id;
int product_id;
};
class Error : public std::runtime_error {
protected:
enum libusb_error err;
char *msg;
public:
Error(enum libusb_error e, const std::string &what) :
std::runtime_error(what),
err(e)
{
asprintf(&msg, "%s: %s", std::runtime_error::what(), libusb_strerror(err));
}
template<typename... Args>
Error(enum libusb_error e, const std::string &what, Args&&... args) :
usb::Error(e, fmt::format(what, std::forward<Args>(args)...))
{ }
/* Same as above but with int */
Error(int e, const std::string &what) :
usb::Error((enum libusb_error) e, what)
{ }
template<typename... Args>
Error(int e, const std::string &what, Args&&... args) :
usb::Error((enum libusb_error) e, what, std::forward<Args>(args)...)
{ }
~Error()
{
if (msg)
free(msg);
}
virtual const char * what() const noexcept
{
return msg;
}
};
class Device {
protected:
struct libusb_device *device;
struct libusb_device_handle *handle;
struct libusb_device_descriptor desc;
std::string getStringDescriptor(uint8_t desc_id) const;
public:
Device(struct libusb_device *dev) :
device(dev),
handle(nullptr)
{ }
const struct libusb_device_descriptor & getDescriptor() const
{ return desc; }
int getBus() const
{ return libusb_get_bus_number(device); }
int getPort() const
{ return libusb_get_port_number(device); }
std::string getManufacturer() const
{ return getStringDescriptor(desc.iManufacturer); }
std::string getProduct() const
{ return getStringDescriptor(desc.iProduct); }
std::string getSerial() const
{ return getStringDescriptor(desc.iSerialNumber); }
bool match(const Filter *flt) const;
};
struct libusb_context * init();
void deinit_context(struct libusb_context *ctx);
struct libusb_context * get_context();
void detach(struct libusb_device_handle *hdl, int iface);
} /* namespace usb */
} /* namespace villas */

View file

@ -138,6 +138,11 @@ if(LIBNL3_ROUTE_FOUND)
list(APPEND LIBRARIES PkgConfig::LIBNL3_ROUTE)
endif()
if(LIBUSB_FOUND)
list(APPEND LIB_SRC usb.cpp)
list(APPEND LIBRARIES PkgConfig::LIBUSB)
endif()
if(WITH_FPGA)
list(APPEND LIBRARIES villas-fpga)
endif()

View file

@ -173,6 +173,12 @@ if(WITH_NODE_FPGA)
list(APPEND INCLUDE_DIRS $<TARGET_PROPERTY:villas-fpga,INCLUDE_DIRECTORIES>)
endif()
# Enable TEMPer node-type support
if(WITH_NODE_TEMPER)
list(APPEND NODE_SRC temper.cpp)
list(APPEND LIBRARIES PkgConfig::LIBUSB)
endif()
add_library(nodes STATIC ${NODE_SRC})
target_include_directories(nodes PUBLIC ${INCLUDE_DIRS})
target_link_libraries(nodes PUBLIC ${LIBRARIES})

443
lib/nodes/temper.cpp Normal file
View file

@ -0,0 +1,443 @@
/** PCSensor / TEMPer node-type
*
* Based on pcsensor.c by Juan Carlos Perez (c) 2011 (cray@isp-sl.com)
* Based on Temper.c by Robert Kavaler (c) 2009 (relavak.com)
*
* All rights reserved.
*
* 2011/08/30 Thanks to EdorFaus: bugfix to support negative temperatures
* 2017/08/30 Improved by K.Cima: changed libusb-0.1 -> libusb-1.0
* https://github.com/shakemid/pcsensor
*
* Temper driver for linux. This program can be compiled either as a library
* or as a standalone program (-DUNIT_TEST). The driver will work with some
* TEMPer usb devices from RDing (www.PCsensor.com).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY Juan Carlos Perez ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Robert kavaler BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <villas/nodes/temper.hpp>
#include <villas/exceptions.hpp>
#include <villas/plugin.h>
#include <villas/utils.hpp>
using namespace villas;
using namespace villas::utils;
using namespace villas::node;
static Logger logger;
static std::list<TEMPerDevice *> devices;
static struct libusb_context *context;
/* Forward declartions */
static struct plugin p;
TEMPerDevice::TEMPerDevice(struct libusb_device *dev) :
usb::Device(dev),
scale(1.0),
offset(0.0),
timeout(5000)
{
int ret = libusb_get_device_descriptor(dev, &desc);
if (ret != LIBUSB_SUCCESS)
throw RuntimeError("Could not get USB device descriptor: {}", libusb_strerror((enum libusb_error) ret));
}
void TEMPerDevice::open(bool reset)
{
int ret;
usb::detach(handle, 0x00);
usb::detach(handle, 0x01);
if (reset)
libusb_reset_device(handle);
ret = libusb_set_configuration(handle, 0x01);
if (ret < 0)
throw RuntimeError("Could not set configuration 1");
ret = libusb_claim_interface(handle, 0x00);
if (ret < 0)
throw RuntimeError("Could not claim interface. Error: {}", ret);
ret = libusb_claim_interface(handle, 0x01);
if (ret < 0)
throw RuntimeError("Could not claim interface. Error: {}", ret);
}
void TEMPerDevice::close()
{
libusb_release_interface(handle, 0x00);
libusb_release_interface(handle, 0x01);
libusb_close(handle);
}
void TEMPerDevice::read(struct sample *smp)
{
unsigned char answer[8];
float temp[2];
int i = 0, al, ret;
/* Read from device */
unsigned char question[sizeof(question_temperature)];
memcpy(question, question_temperature, sizeof(question_temperature));
ret = libusb_control_transfer(handle, 0x21, 0x09, 0x0200, 0x01, question, 8, timeout);
if (ret < 0)
throw SystemError("USB control write failed: {}", ret);
memset(answer, 0, 8);
ret = libusb_interrupt_transfer(handle, 0x82, answer, 8, &al, timeout);
if (al != 8)
throw SystemError("USB interrupt read failed: {}", ret);
decode(answer, temp);
/* Temperature 1 */
smp->data[i++].f = temp[0];
/* Temperature 2 */
if (getNumSensors() == 2)
smp->data[i++].f = temp[1];
/* Humidity */
if (hasHumiditySensor())
smp->data[i++].f = temp[1];
smp->flags = 0;
smp->length = i;
if (smp->length > 0)
smp->flags |= (int) SampleFlags::HAS_DATA;
}
/* Thanks to https://github.com/edorfaus/TEMPered */
void TEMPer1Device::decode(unsigned char *answer, float *temp)
{
int buf;
// Temperature Celsius internal
buf = ((signed char) answer[2] << 8) + (answer[3] & 0xFF);
temp[0] = buf * (125.0 / 32000.0);
temp[0] *= scale;
temp[0] += offset;
}
void TEMPer2Device::decode(unsigned char *answer, float *temp)
{
int buf;
TEMPer1Device::decode(answer, temp);
// Temperature Celsius external
buf = ((signed char) answer[4] << 8) + (answer[5] & 0xFF);
temp[1] = buf * (125.0 / 32000.0);
temp[1] *= scale;
temp[1] += offset;
}
void TEMPerHUMDevice::decode(unsigned char *answer, float *temp)
{
int buf;
// Temperature Celsius
buf = ((signed char) answer[2] << 8) + (answer[3] & 0xFF);
temp[0] = -39.7 + 0.01 * buf;
temp[0] *= scale;
temp[0] += offset;
// Relative Humidity
buf = ((signed char) answer[4] << 8) + (answer[5] & 0xFF);
temp[1] = -2.0468 + 0.0367 * buf - 1.5955e-6 * buf * buf;
temp[1] = (temp[0] - 25) * (0.01 + 0.00008 * buf) + temp[1];
if (temp[1] < 0)
temp[1] = 0;
if (temp[1] > 99)
temp[1] = 100;
}
TEMPerDevice * TEMPerDevice::make(struct libusb_device *dev)
{
if (TEMPerHUMDevice::match(dev))
return new TEMPerHUMDevice(dev);
else if (TEMPer2Device::match(dev))
return new TEMPer2Device(dev);
else if (TEMPer1Device::match(dev))
return new TEMPer1Device(dev);
else
return nullptr;
}
bool TEMPer1Device::match(struct libusb_device *dev)
{
struct libusb_device_descriptor desc;
int ret = libusb_get_device_descriptor(dev, &desc);
if (ret < 0) {
logging.get("node:temper")->warn("Could not get USB device descriptor: {}", libusb_strerror((enum libusb_error) ret));
return false;
}
return desc.idProduct == 0x7401 &&
desc.idVendor == 0x0c45;
}
bool TEMPer2Device::match(struct libusb_device *dev)
{
int ret;
struct libusb_device_handle *handle;
unsigned char product[256];
struct libusb_device_descriptor desc;
ret = libusb_get_device_descriptor(dev, &desc);
if (ret < 0) {
logging.get("node:temper")->warn("Could not get USB device descriptor: {}", libusb_strerror((enum libusb_error) ret));
return false;
}
ret = libusb_open(dev, &handle);
if (ret < 0) {
logging.get("node:temper")->warn("Failed to open USB device: {}", libusb_strerror((enum libusb_error) ret));
return false;
}
ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, product, sizeof(product));
if (ret < 0) {
logging.get("node:temper")->warn("Could not get USB string descriptor: {}", libusb_strerror((enum libusb_error) ret));
return false;
}
libusb_close(handle);
return TEMPer1Device::match(dev) && getName() == (char *) product;
}
bool TEMPerHUMDevice::match(struct libusb_device *dev)
{
struct libusb_device_descriptor desc;
int ret = libusb_get_device_descriptor(dev, &desc);
if (ret < 0) {
logging.get("node:temper")->warn("Could not get USB device descriptor: {}", libusb_strerror((enum libusb_error) ret));
return false;
}
return desc.idProduct == 0x7401 &&
desc.idVendor == 0x7402;
}
int temper_type_start(villas::node::SuperNode *sn)
{
context = usb::get_context();
logger = logging.get("node:temper");
/* Enumerate temper devices */
devices.clear();
struct libusb_device **devs;
int cnt = libusb_get_device_list(context, &devs);
for (int i = 0; i < cnt; i++) {
auto *dev = TEMPerDevice::make(devs[i]);
logger->debug("Found Temper device at bus={03d}, port={03d}, vendor_id={04x}, product_id={04x}, manufacturer={}, product={}, serial={}",
dev->getBus(), dev->getPort(),
dev->getDescriptor().idVendor, dev->getDescriptor().idProduct,
dev->getManufacturer(),
dev->getProduct(),
dev->getSerial());
devices.push_back(dev);
}
libusb_free_device_list(devs, 1);
return 0;
}
int temper_type_stop()
{
usb::deinit_context(context);
return 0;
}
int temper_init(struct vnode *n)
{
struct temper *t = (struct temper *) n->_vd;
t->calibration.scale = 1.0;
t->calibration.offset = 0.0;
t->filter.bus = -1;
t->filter.port = -1;
t->filter.vendor_id = -1;
t->filter.product_id = -1;
t->device = nullptr;
return 0;
}
int temper_destroy(struct vnode *n)
{
struct temper *t = (struct temper *) n->_vd;
if (t->device)
delete t->device;
return 0;
}
int temper_parse(struct vnode *n, json_t *json)
{
int ret;
struct temper *t = (struct temper *) n->_vd;
json_error_t err;
ret = json_unpack_ex(json, &err, 0, "{ s?: { s?: F, s?: F}, s?: i, s?: i }",
"calibration",
"scale", &t->calibration.scale,
"offset", &t->calibration.offset,
"bus", &t->filter.bus,
"port", &t->filter.port
);
if (ret)
throw ConfigError(json, err, "node-config-node-temper");
return 0;
}
char * temper_print(struct vnode *n)
{
struct temper *t = (struct temper *) n->_vd;
return strf("product=%s, manufacturer=%s, serial=%s humidity=%s, temperature=%d, usb.vendor_id=%#x, usb.product_id=%#x, calibration.scale=%f, calibration.offset=%f",
t->device->getProduct(), t->device->getManufacturer(), t->device->getSerial(),
t->device->hasHumiditySensor() ? "yes" : "no",
t->device->getNumSensors(),
t->device->getDescriptor().idVendor,
t->device->getDescriptor().idProduct,
t->calibration.scale,
t->calibration.offset
);
}
int temper_prepare(struct vnode *n)
{
struct temper *t = (struct temper *) n->_vd;
/* Find matching USB device */
t->device = nullptr;
for (auto *dev : devices) {
if (dev->match(&t->filter)) {
t->device = dev;
break;
}
}
if (t->device == nullptr)
throw RuntimeError("No matching TEMPer USB device found!");
/* Create signal list */
assert(vlist_length(&n->in.signals) == 0);
/* Temperature 1 */
auto *sig1 = signal_create(t->device->getNumSensors() == 2 ? "temp_int" : "temp", "°C", SignalType::FLOAT);
vlist_push(&n->in.signals, sig1);
/* Temperature 2 */
if (t->device->getNumSensors() == 2) {
auto *sig2 = signal_create(t->device->getNumSensors() == 2 ? "temp_int" : "temp", "°C", SignalType::FLOAT);
vlist_push(&n->in.signals, sig2);
}
/* Humidity */
if (t->device->hasHumiditySensor()) {
auto *sig3 = signal_create("humidity", "%", SignalType::FLOAT);
vlist_push(&n->in.signals, sig3);
}
return 0;
}
int temper_start(struct vnode *n)
{
struct temper *t = (struct temper *) n->_vd;
t->device->open();
return 0;
}
int temper_stop(struct vnode *n)
{
struct temper *t = (struct temper *) n->_vd;
t->device->close();
return 0;
}
int temper_read(struct vnode *n, struct sample * const smps[], unsigned cnt)
{
struct temper *t = (struct temper *) n->_vd;
assert(cnt == 1);
t->device->read(smps[0]);
return 1;
}
__attribute__((constructor(110)))
static void register_plugin() {
p.name = "temper";
p.description = "An temper for staring new node-type implementations";
p.type = PluginType::NODE;
p.node.instances.state = State::DESTROYED;
p.node.vectorize = 1;
p.node.flags = (int) NodeFlags::PROVIDES_SIGNALS;
p.node.size = sizeof(struct temper);
p.node.type.start = temper_type_start;
p.node.type.stop = temper_type_stop;
p.node.init = temper_init;
p.node.destroy = temper_destroy;
p.node.prepare = temper_prepare;
p.node.parse = temper_parse;
p.node.print = temper_print;
p.node.start = temper_start;
p.node.stop = temper_stop;
p.node.read = temper_read;
int ret = vlist_init(&p.node.instances);
if (!ret)
vlist_init_and_push(&plugins, &p);
}
__attribute__((destructor(110)))
static void deregister_plugin() {
vlist_remove_all(&plugins, &p);
}

159
lib/usb.cpp Normal file
View file

@ -0,0 +1,159 @@
/** Helpers for USB node-types
*
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2021, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <villas/usb.hpp>
#include <villas/log.hpp>
using namespace villas;
using namespace villas::usb;
static struct libusb_context *context = nullptr;
static int context_users = 0;
static Logger logger;
static enum libusb_log_level spdlog_to_libusb_log_level(Log::Level lvl)
{
switch (lvl) {
case Log::Level::trace:
case Log::Level::debug:
return LIBUSB_LOG_LEVEL_DEBUG;
case Log::Level::info:
return LIBUSB_LOG_LEVEL_INFO;
case Log::Level::warn:
return LIBUSB_LOG_LEVEL_WARNING;
case Log::Level::err:
case Log::Level::critical:
return LIBUSB_LOG_LEVEL_ERROR;
default:
case Log::Level::off:
return LIBUSB_LOG_LEVEL_NONE;
}
}
static Log::Level libusb_to_spdlog_log_level(enum libusb_log_level lvl)
{
switch (lvl) {
case LIBUSB_LOG_LEVEL_ERROR:
return Log::Level::err;
case LIBUSB_LOG_LEVEL_WARNING:
return Log::Level::warn;
case LIBUSB_LOG_LEVEL_INFO:
return Log::Level::info;
case LIBUSB_LOG_LEVEL_DEBUG:
return Log::Level::debug;
case LIBUSB_LOG_LEVEL_NONE:
default:
return Log::Level::off;
}
}
static void log_cb(struct libusb_context *ctx, enum libusb_log_level lvl, const char *str)
{
auto level = libusb_to_spdlog_log_level(lvl);
logger->log(level, str);
}
void villas::usb::detach(struct libusb_device_handle *handle, int iface)
{
int ret;
ret = libusb_detach_kernel_driver(handle, iface);
if (ret != LIBUSB_SUCCESS)
throw Error(ret, "Failed to detach USB device from kernel driver");
}
struct libusb_context * villas::usb::init()
{
int ret;
struct libusb_context *ctx;
logger = logging.get("usb");
ret = libusb_init(&ctx);
if (ret)
throw Error(ret, "Failed to initialize libusb context");
auto lvl = spdlog_to_libusb_log_level(logger->level());
#if LIBUSBX_API_VERSION < 0x01000106
libusb_set_debug(ctx, lvl);
#else
ret = libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, lvl);
if (ret)
throw Error(ret, "Failed to set libusb log level");
#endif
libusb_set_log_cb(ctx, log_cb, LIBUSB_LOG_CB_GLOBAL);
return ctx;
}
void villas::usb::deinit_context(struct libusb_context *ctx)
{
context_users--;
if (context_users == 0) {
libusb_exit(ctx);
context = nullptr;
}
}
struct libusb_context * villas::usb::get_context()
{
if (context == nullptr)
context = init();
context_users++;
return context;
}
bool Device::match(const Filter *flt) const
{
return (flt->bus < 0 || flt->bus == getBus()) &&
(flt->port < 0 || flt->port == getPort()) &&
(flt->vendor_id < 0 || flt->vendor_id == getDescriptor().idVendor) &&
(flt->product_id < 0 || flt->product_id == getDescriptor().idProduct);
}
std::string Device::getStringDescriptor(uint8_t desc_id) const
{
int ret;
unsigned char buf[256];
ret = libusb_get_string_descriptor_ascii(handle, desc_id, buf, sizeof(buf));
if (ret != LIBUSB_SUCCESS)
throw RuntimeError("Failed to get USB string descriptor");
return (char *) buf;
}