diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a170f8cc..67144d185 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/include/villas/nodes/temper.hpp b/include/villas/nodes/temper.hpp new file mode 100644 index 000000000..e7e293b85 --- /dev/null +++ b/include/villas/nodes/temper.hpp @@ -0,0 +1,181 @@ +/** An temper get started with new implementations of new node-types + * + * @file + * @author Steffen Vogel + * @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 . + *********************************************************************************/ + +/** + * @addtogroup temper BSD temper Node Type + * @ingroup node + * @{ + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +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[]); + +/** @} */ diff --git a/include/villas/usb.hpp b/include/villas/usb.hpp new file mode 100644 index 000000000..b059383bd --- /dev/null +++ b/include/villas/usb.hpp @@ -0,0 +1,126 @@ +/** Helpers for USB node-types + * + * @file + * @author Steffen Vogel + * @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 . + *********************************************************************************/ + +#pragma once + +#include + +#include + +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 + Error(enum libusb_error e, const std::string &what, Args&&... args) : + usb::Error(e, fmt::format(what, std::forward(args)...)) + { } + + /* Same as above but with int */ + Error(int e, const std::string &what) : + usb::Error((enum libusb_error) e, what) + { } + + template + Error(int e, const std::string &what, Args&&... args) : + usb::Error((enum libusb_error) e, what, std::forward(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 */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0590856b7..dbb40c7a9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -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() diff --git a/lib/nodes/CMakeLists.txt b/lib/nodes/CMakeLists.txt index 3b80dabb4..71367f106 100644 --- a/lib/nodes/CMakeLists.txt +++ b/lib/nodes/CMakeLists.txt @@ -173,6 +173,12 @@ if(WITH_NODE_FPGA) list(APPEND INCLUDE_DIRS $) 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}) diff --git a/lib/nodes/temper.cpp b/lib/nodes/temper.cpp new file mode 100644 index 000000000..f5013ab47 --- /dev/null +++ b/lib/nodes/temper.cpp @@ -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 +#include +#include +#include + +using namespace villas; +using namespace villas::utils; +using namespace villas::node; + +static Logger logger; + +static std::list 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); +} diff --git a/lib/usb.cpp b/lib/usb.cpp new file mode 100644 index 000000000..75d8fcc13 --- /dev/null +++ b/lib/usb.cpp @@ -0,0 +1,159 @@ +/** Helpers for USB node-types + * + * @file + * @author Steffen Vogel + * @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 . + *********************************************************************************/ + +#include +#include + +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; +}