From: Ramasamy Date: Fri, 2 Jan 2015 04:05:02 +0000 (+0530) Subject: Adding Rotation Vector virtual sensor X-Git-Tag: submit/tizen/20150113.012540~48 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7cd4ed8647a5a0b42fdb8eec6b3f289472b8a204;p=platform%2Fcore%2Fsystem%2Fsensord.git Adding Rotation Vector virtual sensor - Added 9-axis rotation vector based on sensor fusion - Added xml configuration support for virtual sensor - Added xml configuration for loading rotation vector library - Updated build and spec files Change-Id: I540b277019aa9a946e267b89c39a15e168dbe9f0 --- diff --git a/packaging/sensord.spec b/packaging/sensord.spec index 2e1fff7..1d305ff 100755 --- a/packaging/sensord.spec +++ b/packaging/sensord.spec @@ -18,6 +18,7 @@ Source2: sensord.socket %define orientation_state ON %define gravity_state ON %define linear_accel_state ON +%define rv_state ON %define build_test_suite OFF @@ -79,7 +80,7 @@ cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} -DACCEL=%{accel_state} \ -DGYRO=%{gyro_state} -DPROXI=%{proxi_state} -DLIGHT=%{light_state} \ -DGEO=%{geo_state} -DPRESSURE=%{pressure_state} -DTEMPERATURE=%{temperature_state} \ -DORIENTATION=%{orientation_state} -DGRAVITY=%{gravity_state} \ - -DLINEAR_ACCEL=%{linear_accel_state} \ + -DLINEAR_ACCEL=%{linear_accel_state} -DRV=%{rv_state} \ -DTEST_SUITE=%{build_test_suite} make %{?jobs:-j%jobs} diff --git a/sensor_plugins.xml.in b/sensor_plugins.xml.in index f40f79a..aca8a81 100755 --- a/sensor_plugins.xml.in +++ b/sensor_plugins.xml.in @@ -20,5 +20,6 @@ + diff --git a/src/libsensord/sensor_internal.h b/src/libsensord/sensor_internal.h index bec1487..3bc8311 100755 --- a/src/libsensord/sensor_internal.h +++ b/src/libsensord/sensor_internal.h @@ -49,6 +49,7 @@ extern "C" #include #include #include +#include #include diff --git a/src/libsensord/sensor_internal_deprecated.h b/src/libsensord/sensor_internal_deprecated.h index f0a1a74..dc4a8b3 100755 --- a/src/libsensord/sensor_internal_deprecated.h +++ b/src/libsensord/sensor_internal_deprecated.h @@ -48,6 +48,7 @@ extern "C" #include #include #include +#include typedef struct { condition_op_t cond_op; diff --git a/src/rotation_vector/CMakeLists.txt b/src/rotation_vector/CMakeLists.txt new file mode 100644 index 0000000..c111931 --- /dev/null +++ b/src/rotation_vector/CMakeLists.txt @@ -0,0 +1,4 @@ +IF("${RV}" STREQUAL "ON") +add_subdirectory(rv) +ENDIF() + diff --git a/src/rotation_vector/rv/CMakeLists.txt b/src/rotation_vector/rv/CMakeLists.txt new file mode 100755 index 0000000..33ac289 --- /dev/null +++ b/src/rotation_vector/rv/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 2.6) +project(rv CXX) + +# to install pkgconfig setup file. +SET(EXEC_PREFIX "\${prefix}") +SET(LIBDIR "\${prefix}/lib") +SET(INCLUDEDIR "\${prefix}/include") +SET(VERSION 1.0) + +SET(SENSOR_NAME rv_sensor) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/src/libsensord) +include_directories(${CMAKE_SOURCE_DIR}/src/sensor_fusion) + +include(FindPkgConfig) +pkg_check_modules(rpkgs REQUIRED vconf) +add_definitions(${rpkgs_CFLAGS} -DUSE_ONLY_ONE_MODULE -DUSE_LCD_TYPE_CHECK) + +set(PROJECT_MAJOR_VERSION "0") +set(PROJECT_MINOR_VERSION "0") +set(PROJECT_RELEASE_VERSION "1") +set(CMAKE_VERBOSE_MAKEFILE OFF) + + +FIND_PROGRAM(UNAME NAMES uname) +EXEC_PROGRAM("${UNAME}" ARGS "-m" OUTPUT_VARIABLE "ARCH") +IF("${ARCH}" MATCHES "^arm.*") + ADD_DEFINITIONS("-DTARGET") + MESSAGE("add -DTARGET") +ELSE("${ARCH}" MATCHES "^arm.*") + ADD_DEFINITIONS("-DSIMULATOR") + MESSAGE("add -DSIMULATOR") +ENDIF("${ARCH}" MATCHES "^arm.*") + +add_definitions(-Wall -O3 -omit-frame-pointer) +add_definitions(-DUSE_DLOG_LOG) +#add_definitions(-Wall -g -D_DEBUG) +add_definitions(-Iinclude) + +add_library(${SENSOR_NAME} SHARED + rv_sensor.cpp +) + +target_link_libraries(${SENSOR_NAME} ${rpkgs_LDFLAGS} ${GLES_LDFLAGS} "-lm") + +install(TARGETS ${SENSOR_NAME} DESTINATION lib/sensord) diff --git a/src/rotation_vector/rv/rv_sensor.cpp b/src/rotation_vector/rv/rv_sensor.cpp new file mode 100755 index 0000000..cee3a94 --- /dev/null +++ b/src/rotation_vector/rv/rv_sensor.cpp @@ -0,0 +1,390 @@ +/* + * sensord + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SENSOR_NAME "RV_SENSOR" +#define SENSOR_TYPE_RV "ROTATION_VECTOR" + +#define ACCELEROMETER_ENABLED 0x01 +#define GYROSCOPE_ENABLED 0x02 +#define GEOMAGNETIC_ENABLED 0x04 +#define ORIENTATION_ENABLED 7 + +#define INITIAL_VALUE -1 + +#define MS_TO_US 1000 + +#define ELEMENT_NAME "NAME" +#define ELEMENT_VENDOR "VENDOR" +#define ELEMENT_RAW_DATA_UNIT "RAW_DATA_UNIT" +#define ELEMENT_DEFAULT_SAMPLING_TIME "DEFAULT_SAMPLING_TIME" +#define ELEMENT_ACCEL_STATIC_BIAS "ACCEL_STATIC_BIAS" +#define ELEMENT_GYRO_STATIC_BIAS "GYRO_STATIC_BIAS" +#define ELEMENT_GEOMAGNETIC_STATIC_BIAS "GEOMAGNETIC_STATIC_BIAS" +#define ELEMENT_ACCEL_ROTATION_DIRECTION_COMPENSATION "ACCEL_ROTATION_DIRECTION_COMPENSATION" +#define ELEMENT_GYRO_ROTATION_DIRECTION_COMPENSATION "GYRO_ROTATION_DIRECTION_COMPENSATION" +#define ELEMENT_GEOMAGNETIC_ROTATION_DIRECTION_COMPENSATION "GEOMAGNETIC_ROTATION_DIRECTION_COMPENSATION" +#define ELEMENT_ACCEL_SCALE "ACCEL_SCALE" +#define ELEMENT_GYRO_SCALE "GYRO_SCALE" +#define ELEMENT_GEOMAGNETIC_SCALE "GEOMAGNETIC_SCALE" +#define ELEMENT_MAGNETIC_ALIGNMENT_FACTOR "MAGNETIC_ALIGNMENT_FACTOR" + +void pre_process_data(sensor_data &data_out, const float *data_in, float *bias, int *sign, float scale) +{ + data_out.m_data.m_vec[0] = sign[0] * (data_in[0] - bias[0]) / scale; + data_out.m_data.m_vec[1] = sign[1] * (data_in[1] - bias[1]) / scale; + data_out.m_data.m_vec[2] = sign[2] * (data_in[2] - bias[2]) / scale; +} + +rv_sensor::rv_sensor() +: m_accel_sensor(NULL) +, m_gyro_sensor(NULL) +, m_magnetic_sensor(NULL) +, m_x(-1) +, m_y(-1) +, m_z(-1) +, m_w(-1) +, m_accuracy(-1) +, m_time(0) +{ + cvirtual_sensor_config &config = cvirtual_sensor_config::get_instance(); + + m_name = string(SENSOR_NAME); + register_supported_event(ROTATION_VECTOR_EVENT_RAW_DATA_REPORT_ON_TIME); + m_enable_orientation = 0; + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_VENDOR, m_vendor)) { + ERR("[VENDOR] is empty\n"); + throw ENXIO; + } + + INFO("m_vendor = %s", m_vendor.c_str()); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_DEFAULT_SAMPLING_TIME, &m_default_sampling_time)) { + ERR("[DEFAULT_SAMPLING_TIME] is empty\n"); + throw ENXIO; + } + + INFO("m_default_sampling_time = %d", m_default_sampling_time); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_ACCEL_STATIC_BIAS, m_accel_static_bias, 3)) { + ERR("[ACCEL_STATIC_BIAS] is empty\n"); + throw ENXIO; + } + + INFO("m_accel_static_bias = (%f, %f, %f)", m_accel_static_bias[0], m_accel_static_bias[1], m_accel_static_bias[2]); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_GYRO_STATIC_BIAS, m_gyro_static_bias,3)) { + ERR("[GYRO_STATIC_BIAS] is empty\n"); + throw ENXIO; + } + + INFO("m_gyro_static_bias = (%f, %f, %f)", m_gyro_static_bias[0], m_gyro_static_bias[1], m_gyro_static_bias[2]); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_GEOMAGNETIC_STATIC_BIAS, m_geomagnetic_static_bias, 3)) { + ERR("[GEOMAGNETIC_STATIC_BIAS] is empty\n"); + throw ENXIO; + } + + INFO("m_geomagnetic_static_bias = (%f, %f, %f)", m_geomagnetic_static_bias[0], m_geomagnetic_static_bias[1], m_geomagnetic_static_bias[2]); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_ACCEL_ROTATION_DIRECTION_COMPENSATION, m_accel_rotation_direction_compensation, 3)) { + ERR("[ACCEL_ROTATION_DIRECTION_COMPENSATION] is empty\n"); + throw ENXIO; + } + + INFO("m_accel_rotation_direction_compensation = (%d, %d, %d)", m_accel_rotation_direction_compensation[0], m_accel_rotation_direction_compensation[1], m_accel_rotation_direction_compensation[2]); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_GYRO_ROTATION_DIRECTION_COMPENSATION, m_gyro_rotation_direction_compensation, 3)) { + ERR("[GYRO_ROTATION_DIRECTION_COMPENSATION] is empty\n"); + throw ENXIO; + } + + INFO("m_gyro_rotation_direction_compensation = (%d, %d, %d)", m_gyro_rotation_direction_compensation[0], m_gyro_rotation_direction_compensation[1], m_gyro_rotation_direction_compensation[2]); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_GEOMAGNETIC_ROTATION_DIRECTION_COMPENSATION, m_geomagnetic_rotation_direction_compensation, 3)) { + ERR("[GEOMAGNETIC_ROTATION_DIRECTION_COMPENSATION] is empty\n"); + throw ENXIO; + } + + INFO("m_geomagnetic_rotation_direction_compensation = (%d, %d, %d)", m_geomagnetic_rotation_direction_compensation[0], m_geomagnetic_rotation_direction_compensation[1], m_geomagnetic_rotation_direction_compensation[2]); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_ACCEL_SCALE, &m_accel_scale)) { + ERR("[ACCEL_SCALE] is empty\n"); + throw ENXIO; + } + + INFO("m_accel_scale = %f", m_accel_scale); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_GYRO_SCALE, &m_gyro_scale)) { + ERR("[GYRO_SCALE] is empty\n"); + throw ENXIO; + } + + INFO("m_gyro_scale = %f", m_gyro_scale); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_GEOMAGNETIC_SCALE, &m_geomagnetic_scale)) { + ERR("[GEOMAGNETIC_SCALE] is empty\n"); + throw ENXIO; + } + + INFO("m_geomagnetic_scale = %f", m_geomagnetic_scale); + + if (!config.get(SENSOR_TYPE_RV, ELEMENT_MAGNETIC_ALIGNMENT_FACTOR, &m_magnetic_alignment_factor)) { + ERR("[MAGNETIC_ALIGNMENT_FACTOR] is empty\n"); + throw ENXIO; + } + + INFO("m_magnetic_alignment_factor = %d", m_magnetic_alignment_factor); + + m_interval = m_default_sampling_time * MS_TO_US; + +} + +rv_sensor::~rv_sensor() +{ + INFO("rv_sensor is destroyed!\n"); +} + +bool rv_sensor::init() +{ + m_accel_sensor = sensor_plugin_loader::get_instance().get_sensor(ACCELEROMETER_SENSOR); + m_gyro_sensor = sensor_plugin_loader::get_instance().get_sensor(GYROSCOPE_SENSOR); + m_magnetic_sensor = sensor_plugin_loader::get_instance().get_sensor(GEOMAGNETIC_SENSOR); + + if (!m_accel_sensor || !m_gyro_sensor || !m_magnetic_sensor) { + ERR("Failed to load sensors, accel: 0x%x, gyro: 0x%x, mag: 0x%x", + m_accel_sensor, m_gyro_sensor, m_magnetic_sensor); + return false; + } + + INFO("%s is created!\n", sensor_base::get_name()); + + return true; +} + +sensor_type_t rv_sensor::get_type(void) +{ + return ROTATION_VECTOR_SENSOR; +} + +bool rv_sensor::on_start(void) +{ + AUTOLOCK(m_mutex); + + m_accel_sensor->add_client(ACCELEROMETER_EVENT_RAW_DATA_REPORT_ON_TIME); + m_accel_sensor->add_interval((int)this, (m_interval/MS_TO_US), false); + m_accel_sensor->start(); + m_gyro_sensor->add_client(GYROSCOPE_EVENT_RAW_DATA_REPORT_ON_TIME); + m_gyro_sensor->add_interval((int)this, (m_interval/MS_TO_US), false); + m_gyro_sensor->start(); + m_magnetic_sensor->add_client(GEOMAGNETIC_EVENT_RAW_DATA_REPORT_ON_TIME); + m_magnetic_sensor->add_interval((int)this, (m_interval/MS_TO_US), false); + m_magnetic_sensor->start(); + + activate(); + return true; +} + +bool rv_sensor::on_stop(void) +{ + AUTOLOCK(m_mutex); + + m_accel_sensor->delete_client(ACCELEROMETER_EVENT_RAW_DATA_REPORT_ON_TIME); + m_accel_sensor->delete_interval((int)this, false); + m_accel_sensor->stop(); + m_gyro_sensor->delete_client(GYROSCOPE_EVENT_RAW_DATA_REPORT_ON_TIME); + m_gyro_sensor->delete_interval((int)this, false); + m_gyro_sensor->stop(); + m_magnetic_sensor->delete_client(GEOMAGNETIC_EVENT_RAW_DATA_REPORT_ON_TIME); + m_magnetic_sensor->delete_interval((int)this, false); + m_magnetic_sensor->stop(); + + deactivate(); + return true; +} + +bool rv_sensor::add_interval(int client_id, unsigned int interval) +{ + AUTOLOCK(m_mutex); + + m_accel_sensor->add_interval(client_id, interval, false); + m_gyro_sensor->add_interval(client_id, interval, false); + m_magnetic_sensor->add_interval(client_id, interval, false); + + return sensor_base::add_interval(client_id, interval, false); +} + +bool rv_sensor::delete_interval(int client_id) +{ + AUTOLOCK(m_mutex); + + m_accel_sensor->delete_interval(client_id, false); + m_gyro_sensor->delete_interval(client_id, false); + m_magnetic_sensor->delete_interval(client_id, false); + + return sensor_base::delete_interval(client_id, false); +} + +void rv_sensor::synthesize(const sensor_event_t& event, vector &outs) +{ + const float MIN_DELIVERY_DIFF_FACTOR = 0.75f; + unsigned long long diff_time; + + sensor_event_t rv_event; + quaternion quaternion_orientation; + + if (event.event_type == ACCELEROMETER_EVENT_RAW_DATA_REPORT_ON_TIME) { + diff_time = event.data.timestamp - m_time; + + if (m_time && (diff_time < m_interval * MIN_DELIVERY_DIFF_FACTOR)) + return; + + pre_process_data(m_accel, event.data.values, m_accel_static_bias, m_accel_rotation_direction_compensation, m_accel_scale); + + m_accel.m_time_stamp = event.data.timestamp; + + m_enable_orientation |= ACCELEROMETER_ENABLED; + } + else if (event.event_type == GYROSCOPE_EVENT_RAW_DATA_REPORT_ON_TIME) { + diff_time = event.data.timestamp - m_time; + + if (m_time && (diff_time < m_interval * MIN_DELIVERY_DIFF_FACTOR)) + return; + + pre_process_data(m_gyro, event.data.values, m_gyro_static_bias, m_gyro_rotation_direction_compensation, m_gyro_scale); + + m_gyro.m_time_stamp = event.data.timestamp; + + m_enable_orientation |= GYROSCOPE_ENABLED; + } + else if (event.event_type == GEOMAGNETIC_EVENT_RAW_DATA_REPORT_ON_TIME) { + diff_time = event.data.timestamp - m_time; + + if (m_time && (diff_time < m_interval * MIN_DELIVERY_DIFF_FACTOR)) + return; + + pre_process_data(m_magnetic, event.data.values, m_geomagnetic_static_bias, m_geomagnetic_rotation_direction_compensation, m_geomagnetic_scale); + + m_magnetic.m_time_stamp = event.data.timestamp; + + m_enable_orientation |= GEOMAGNETIC_ENABLED; + } + + if (m_enable_orientation == ORIENTATION_ENABLED) { + m_enable_orientation = 0; + + m_orientation.m_pitch_phase_compensation = m_pitch_rotation_compensation; + m_orientation.m_roll_phase_compensation = m_roll_rotation_compensation; + m_orientation.m_azimuth_phase_compensation = m_azimuth_rotation_compensation; + m_orientation.m_magnetic_alignment_factor = m_magnetic_alignment_factor; + + { + AUTOLOCK(m_fusion_mutex); + quaternion_orientation = m_orientation.get_quaternion(m_accel, m_gyro, m_magnetic); + } + + rv_event.sensor_id = get_id(); + rv_event.event_type = ROTATION_VECTOR_EVENT_RAW_DATA_REPORT_ON_TIME; + rv_event.data.accuracy = SENSOR_ACCURACY_GOOD; + rv_event.data.timestamp = get_timestamp(); + rv_event.data.value_count = 4; + rv_event.data.values[0] = quaternion_orientation.m_quat.m_vec[1]; + rv_event.data.values[1] = quaternion_orientation.m_quat.m_vec[2]; + rv_event.data.values[2] = quaternion_orientation.m_quat.m_vec[3]; + rv_event.data.values[3] = quaternion_orientation.m_quat.m_vec[0]; + + push(rv_event); + + { + AUTOLOCK(m_value_mutex); + m_time = rv_event.data.value_count; + m_x = rv_event.data.values[0]; + m_y = rv_event.data.values[1]; + m_z = rv_event.data.values[2]; + m_w = rv_event.data.values[3]; + } + } + + return; +} + +int rv_sensor::get_sensor_data(unsigned int data_id, sensor_data_t &data) +{ + if (data_id != ROTATION_VECTOR_BASE_DATA_SET) + return -1; + + data.accuracy = SENSOR_ACCURACY_GOOD; + + AUTOLOCK(m_value_mutex); + data.timestamp = m_time; + data.values[0] = m_x; + data.values[1] = m_y; + data.values[2] = m_z; + data.values[3] = m_w; + data.value_count = 4; + + return 0; +} + +bool rv_sensor::get_properties(sensor_properties_t &properties) +{ + properties.vendor = m_vendor; + properties.name = SENSOR_NAME; + properties.min_range = 0; + properties.max_range = 1; + properties.resolution = 0.000001; + properties.fifo_count = 0; + properties.max_batch_count = 0; + properties.min_interval = 1; + + return true; +} + +extern "C" sensor_module* create(void) +{ + rv_sensor *sensor; + + try { + sensor = new(std::nothrow) rv_sensor; + } catch (int err) { + ERR("Failed to create module, err: %d, cause: %s", err, strerror(err)); + return NULL; + } + + sensor_module *module = new(std::nothrow) sensor_module; + retvm_if(!module || !sensor, NULL, "Failed to allocate memory"); + + module->sensors.push_back(sensor); + return module; +} diff --git a/src/rotation_vector/rv/rv_sensor.h b/src/rotation_vector/rv/rv_sensor.h new file mode 100755 index 0000000..3d84a80 --- /dev/null +++ b/src/rotation_vector/rv/rv_sensor.h @@ -0,0 +1,87 @@ +/* + * sensord + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef _RV_SENSOR_H_ +#define _RV_SENSOR_H_ + +#include +#include +#include + +class rv_sensor : public virtual_sensor { +public: + rv_sensor(); + virtual ~rv_sensor(); + + bool init(void); + + void synthesize(const sensor_event_t &event, vector &outs); + + bool add_interval(int client_id, unsigned int interval); + bool delete_interval(int client_id); + bool get_properties(sensor_properties_t &properties); + sensor_type_t get_type(void); + + int get_sensor_data(const unsigned int data_id, sensor_data_t &data); + +private: + sensor_base *m_accel_sensor; + sensor_base *m_gyro_sensor; + sensor_base *m_magnetic_sensor; + + sensor_data m_accel; + sensor_data m_gyro; + sensor_data m_magnetic; + + cmutex m_value_mutex; + + orientation_filter m_orientation; + + unsigned int m_enable_orientation; + + float m_x; + float m_y; + float m_z; + float m_w; + int m_accuracy; + unsigned long long m_time; + unsigned int m_interval; + + string m_vendor; + string m_raw_data_unit; + int m_default_sampling_time; + float m_accel_static_bias[3]; + float m_gyro_static_bias[3]; + float m_geomagnetic_static_bias[3]; + int m_accel_rotation_direction_compensation[3]; + int m_gyro_rotation_direction_compensation[3]; + int m_geomagnetic_rotation_direction_compensation[3]; + float m_accel_scale; + float m_gyro_scale; + float m_geomagnetic_scale; + int m_magnetic_alignment_factor; + int m_azimuth_rotation_compensation; + int m_pitch_rotation_compensation; + int m_roll_rotation_compensation; + + bool on_start(void); + bool on_stop(void); +}; + +#endif /*_RV_SENSOR_H_*/ diff --git a/virtual_sensors.xml.in b/virtual_sensors.xml.in index 42c9d72..680745e 100644 --- a/virtual_sensors.xml.in +++ b/virtual_sensors.xml.in @@ -38,4 +38,20 @@ + + + + + + + + + + + + + + + +