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:
parent
b2aab0b823
commit
edd84d79af
7 changed files with 930 additions and 7 deletions
|
@ -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")
|
||||
|
|
181
include/villas/nodes/temper.hpp
Normal file
181
include/villas/nodes/temper.hpp
Normal 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
126
include/villas/usb.hpp
Normal 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 */
|
|
@ -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()
|
||||
|
|
|
@ -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
443
lib/nodes/temper.cpp
Normal 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
159
lib/usb.cpp
Normal 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;
|
||||
}
|
Loading…
Add table
Reference in a new issue