From f629ec038b8922690cf64e438cbb480329415038 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Sat, 21 Sep 2019 11:33:38 -0700 Subject: [PATCH] add sensor framework, tests (#929) * add framework, tests * move oil pressure to new way * add init logic * brackets on the same line * spaces -> tabs * spaces -> tabs for tests * bracket on same line * hook up sensor mocking * add nan check * fix nan check * I wrote an essay * casing * only init if we have a sensor to init * style, actually call init * format * fix casing * typo * implement linear sensor * wire up producer * smarter limiting * setup comments * add reporting * doxyfile * oops * add adc subscription * clarity * fix logic * multiply voltage * test styling * test guards * remove dependencies * linear sensor test * remove unused * fix merge * format, implicit convert op * explicit * format tests * fix merge --- firmware/Doxyfile | 1 + firmware/Makefile | 3 + firmware/console/status_loop.cpp | 3 +- firmware/controllers/algo/engine2.cpp | 1 - firmware/controllers/algo/engine_parts.h | 5 - firmware/controllers/algo/obd_error_codes.h | 1 + firmware/controllers/algo/rusefi_types.h | 1 + firmware/controllers/engine_controller.cpp | 5 + firmware/controllers/sensors/allsensors.h | 1 - .../controllers/sensors/converter_sensor.h | 60 ++++++++ .../sensors/function_pointer_sensor.h | 34 +++++ .../controllers/sensors/linear_sensor.cpp | 20 +++ firmware/controllers/sensors/linear_sensor.h | 23 +++ firmware/controllers/sensors/oil_pressure.cpp | 28 ---- firmware/controllers/sensors/oil_pressure.h | 15 -- firmware/controllers/sensors/sensor.cpp | 116 +++++++++++++++ firmware/controllers/sensors/sensor.h | 133 ++++++++++++++++++ firmware/controllers/sensors/sensor_reader.h | 63 +++++++++ firmware/controllers/sensors/sensor_type.h | 33 +++++ firmware/controllers/sensors/sensors.mk | 3 +- .../controllers/sensors/stored_value_sensor.h | 70 +++++++++ firmware/hw_layer/adc_inputs.cpp | 3 + firmware/hw_layer/adc_subscription.cpp | 56 ++++++++ firmware/hw_layer/adc_subscription.h | 8 ++ firmware/hw_layer/hw_layer.mk | 3 +- firmware/init/init.h | 3 + firmware/init/init.mk | 3 + firmware/init/sensor/init_oil_pressure.cpp | 42 ++++++ firmware/init/sensor/init_sensors.cpp | 20 +++ firmware/util/cli_registry.cpp | 33 ++++- firmware/util/cli_registry.h | 3 + unit_tests/tests/sensor/basic_sensor.cpp | 81 +++++++++++ unit_tests/tests/sensor/converter_sensor.cpp | 68 +++++++++ .../tests/sensor/function_pointer_sensor.cpp | 27 ++++ unit_tests/tests/sensor/lin_sensor.cpp | 45 ++++++ unit_tests/tests/sensor/mock/mock_sensor.h | 20 +++ unit_tests/tests/sensor/mock_sensor.cpp | 92 ++++++++++++ unit_tests/tests/sensor/sensor_reader.cpp | 85 +++++++++++ unit_tests/tests/tests.mk | 9 +- 39 files changed, 1164 insertions(+), 56 deletions(-) create mode 100644 firmware/controllers/sensors/converter_sensor.h create mode 100644 firmware/controllers/sensors/function_pointer_sensor.h create mode 100644 firmware/controllers/sensors/linear_sensor.cpp create mode 100644 firmware/controllers/sensors/linear_sensor.h delete mode 100644 firmware/controllers/sensors/oil_pressure.cpp delete mode 100644 firmware/controllers/sensors/oil_pressure.h create mode 100644 firmware/controllers/sensors/sensor.cpp create mode 100644 firmware/controllers/sensors/sensor.h create mode 100644 firmware/controllers/sensors/sensor_reader.h create mode 100644 firmware/controllers/sensors/sensor_type.h create mode 100644 firmware/controllers/sensors/stored_value_sensor.h create mode 100644 firmware/hw_layer/adc_subscription.cpp create mode 100644 firmware/hw_layer/adc_subscription.h create mode 100644 firmware/init/init.h create mode 100644 firmware/init/init.mk create mode 100644 firmware/init/sensor/init_oil_pressure.cpp create mode 100644 firmware/init/sensor/init_sensors.cpp create mode 100644 unit_tests/tests/sensor/basic_sensor.cpp create mode 100644 unit_tests/tests/sensor/converter_sensor.cpp create mode 100644 unit_tests/tests/sensor/function_pointer_sensor.cpp create mode 100644 unit_tests/tests/sensor/lin_sensor.cpp create mode 100644 unit_tests/tests/sensor/mock/mock_sensor.h create mode 100644 unit_tests/tests/sensor/mock_sensor.cpp create mode 100644 unit_tests/tests/sensor/sensor_reader.cpp diff --git a/firmware/Doxyfile b/firmware/Doxyfile index c8226ec154..663109fe00 100644 --- a/firmware/Doxyfile +++ b/firmware/Doxyfile @@ -742,6 +742,7 @@ INPUT = . \ util \ console \ controllers \ + init \ emulation \ hw_layer diff --git a/firmware/Makefile b/firmware/Makefile index bd33614086..9aa9d0e970 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -164,6 +164,7 @@ include $(PROJECT_DIR)/controllers/sensors/sensors.mk include $(PROJECT_DIR)/controllers/system/system.mk include $(PROJECT_DIR)/controllers/trigger/trigger.mk include $(PROJECT_DIR)/console/console.mk +include $(PROJECT_DIR)/init/init.mk ifeq ($(BOOTLOADERINC),) # include default bootloader code @@ -229,6 +230,7 @@ CPPSRC = $(CHCPPSRC) \ $(UTILSRC_CPP) \ $(CONTROLLERS_CORE_SRC_CPP) \ $(CONTROLLERS_MATH_SRC_CPP) \ + $(INIT_SRC_CPP) \ rusefi.cpp \ main.cpp @@ -297,6 +299,7 @@ INCDIR = $(CHIBIOS)/os/license \ $(HW_INC) \ $(HW_LAYER_DRIVERS_INC) \ $(UTIL_INC) \ + init \ development \ development/hw_layer \ development/test \ diff --git a/firmware/console/status_loop.cpp b/firmware/console/status_loop.cpp index 760e2b3adc..d6254be5cb 100644 --- a/firmware/console/status_loop.cpp +++ b/firmware/console/status_loop.cpp @@ -36,6 +36,7 @@ #include "trigger_central.h" #include "allsensors.h" +#include "sensor_reader.h" #include "io_pins.h" #include "efi_gpio.h" #include "mmc_card.h" @@ -782,8 +783,6 @@ void updateTunerStudioState(TunerStudioOutputChannels *tsOutputChannels DECLARE_ tsOutputChannels->accelerationX = engine->sensors.accelerometer.x; // 278 tsOutputChannels->accelerationY = engine->sensors.accelerometer.y; - // 280 - tsOutputChannels->oilPressure = engine->sensors.oilPressure; // 288 tsOutputChannels->injectionOffset = engine->engineState.injectionOffset; diff --git a/firmware/controllers/algo/engine2.cpp b/firmware/controllers/algo/engine2.cpp index 331f564a7e..f14d2fcf6b 100644 --- a/firmware/controllers/algo/engine2.cpp +++ b/firmware/controllers/algo/engine2.cpp @@ -139,7 +139,6 @@ void EngineState::updateSlowSensors(DECLARE_ENGINE_PARAMETER_SIGNATURE) { engine->sensors.clt = engine->sensors.mockClt; } #endif - engine->sensors.oilPressure = getOilPressure(PASS_ENGINE_PARAMETER_SIGNATURE); } void EngineState::periodicFastCallback(DECLARE_ENGINE_PARAMETER_SIGNATURE) { diff --git a/firmware/controllers/algo/engine_parts.h b/firmware/controllers/algo/engine_parts.h index e93e973abb..846efd63e6 100644 --- a/firmware/controllers/algo/engine_parts.h +++ b/firmware/controllers/algo/engine_parts.h @@ -63,11 +63,6 @@ public: float auxTemp1 = NAN; float auxTemp2 = NAN; - /** - * Oil pressure in kPa - */ - float oilPressure; - Accelerometer accelerometer; float vBatt = 0; diff --git a/firmware/controllers/algo/obd_error_codes.h b/firmware/controllers/algo/obd_error_codes.h index 0310b44830..8451f87fa5 100644 --- a/firmware/controllers/algo/obd_error_codes.h +++ b/firmware/controllers/algo/obd_error_codes.h @@ -550,6 +550,7 @@ typedef enum { //P0518 Idle Air Control Circuit Intermittent //P0519 Idle Air Control System Performance //P0520 Engine Oil Pressure Sensor/Switch Circuit Malfunction + OBD_Oil_Pressure_Sensor_Malfunction = 520, //P0521 Engine Oil Pressure Sensor/Switch Circuit Range/Performance //P0522 Engine Oil Pressure Sensor/Switch Circuit Low Voltage //P0523 Engine Oil Pressure Sensor/Switch Circuit High Voltage diff --git a/firmware/controllers/algo/rusefi_types.h b/firmware/controllers/algo/rusefi_types.h index 578bc0bd83..b2f791ace5 100644 --- a/firmware/controllers/algo/rusefi_types.h +++ b/firmware/controllers/algo/rusefi_types.h @@ -120,6 +120,7 @@ typedef void (*VoidFloatFloat)(float, float); typedef void (*VoidFloatFloatVoidPtr)(float, float, void*); typedef void (*VoidIntInt)(int, int); typedef void (*VoidIntIntVoidPtr)(int, int, void*); +typedef void (*VoidIntFloat)(int, float); typedef void (*VoidCharPtr)(const char *); typedef void (*VoidCharPtrVoidPtr)(const char *, void*); diff --git a/firmware/controllers/engine_controller.cpp b/firmware/controllers/engine_controller.cpp index c09ac902b7..45c6203660 100644 --- a/firmware/controllers/engine_controller.cpp +++ b/firmware/controllers/engine_controller.cpp @@ -76,6 +76,7 @@ #if EFI_PROD_CODE #include "pwm_generator.h" #include "adc_inputs.h" +#include "init.h" #include "pwm_tester.h" #include "pwm_generator.h" @@ -90,6 +91,10 @@ // this method is used by real firmware and simulator and unit test void mostCommonInitEngineController(Logging *sharedLogger DECLARE_ENGINE_PARAMETER_SUFFIX) { +#if !EFI_UNIT_TEST + initSensors(); +#endif + initSensors(sharedLogger PASS_ENGINE_PARAMETER_SUFFIX); initAccelEnrichment(sharedLogger PASS_ENGINE_PARAMETER_SUFFIX); diff --git a/firmware/controllers/sensors/allsensors.h b/firmware/controllers/sensors/allsensors.h index 61412718d4..673a5c4b3d 100644 --- a/firmware/controllers/sensors/allsensors.h +++ b/firmware/controllers/sensors/allsensors.h @@ -19,7 +19,6 @@ #include "ego.h" #include "voltage.h" #include "thermistors.h" -#include "oil_pressure.h" #include "adc_inputs.h" #include "adc_inputs.h" diff --git a/firmware/controllers/sensors/converter_sensor.h b/firmware/controllers/sensors/converter_sensor.h new file mode 100644 index 0000000000..f3e86b5644 --- /dev/null +++ b/firmware/controllers/sensors/converter_sensor.h @@ -0,0 +1,60 @@ +/** + * @file converter_sensor.h + * + * @date September 12, 2019 + * @author Matthew Kennedy, (c) 2019 + */ + +#pragma once + +#include "stored_value_sensor.h" + +/** + * @brief Base class for sensors that convert from some raw floating point + * value (ex: voltage, frequency, pulse width) to a sensor reading. + * + * To use this base class, inherit it and implement ConvertFromInputValue(float input). + * Perform any conversion work necessary to convert from the raw value to a sensor + * reading, and return it. Register an instance of the new class with an interface + * that provides and posts raw values so the sensor can update. + */ +class ConvertedSensor : public StoredValueSensor { +public: + void postRawValue(float inputValue) { + // Report the raw value + float *rawReportLocation = m_rawReportingLocation; + if (rawReportLocation) { + *rawReportLocation = inputValue; + } + + auto r = convertFromInputValue(inputValue); + + // This has to happen so that we set the valid bit after + // the value is stored, to prevent the data race of reading + // an old invalid value + if (r.Valid) { + setValidValue(r.Value); + } else { + invalidate(); + } + } + + void setRawReportingLocation(float *rawReportingLocation) { + m_rawReportingLocation = rawReportingLocation; + } + +protected: + explicit ConvertedSensor(SensorType type) + : StoredValueSensor(type) {} + + /** + * @brief Convert from the "raw" input value to a sensor reading (or invalid). + * + * For example, this function might convert from a voltage to the pressure + * represented by that voltage. + */ + virtual SensorResult convertFromInputValue(float inputValue) = 0; + +private: + float *m_rawReportingLocation = nullptr; +}; diff --git a/firmware/controllers/sensors/function_pointer_sensor.h b/firmware/controllers/sensors/function_pointer_sensor.h new file mode 100644 index 0000000000..716fd0ef05 --- /dev/null +++ b/firmware/controllers/sensors/function_pointer_sensor.h @@ -0,0 +1,34 @@ +/** + * @file function_pointer_sensor.h + * @brief A sensor to provide a bridge from old getX()-style functions to the new sensor registry. + * + * @date September 12, 2019 + * @author Matthew Kennedy, (c) 2019 + */ + +#pragma once + +#include "sensor.h" + +/* This class is intended as a bridge to bridge from old getMySensor() functions + * to the new system. This way, producers and consumers can be independently + * updated to the new system, with sensors being usable either way for some time. + */ +class FunctionPointerSensor final : public Sensor { +public: + FunctionPointerSensor(SensorType type, float (*func)()) + : Sensor(type) + , m_func(func) {} + + SensorResult get() const final { + float result = m_func(); + + // check for NaN + bool valid = !(result != result); + + return {valid, result}; + } + +private: + float (*m_func)(); +}; diff --git a/firmware/controllers/sensors/linear_sensor.cpp b/firmware/controllers/sensors/linear_sensor.cpp new file mode 100644 index 0000000000..d219eb38d3 --- /dev/null +++ b/firmware/controllers/sensors/linear_sensor.cpp @@ -0,0 +1,20 @@ +#include "linear_sensor.h" + +#include "interpolation.h" + +void LinearSensor::configure(float in1, float out1, float in2, float out2, float minOutput, float maxOutput) { + m_minOutput = minOutput; + m_maxOutput = maxOutput; + + m_a = INTERPOLATION_A(in1, out1, in2, out2); + m_b = out1 - m_a * in1; +} + +SensorResult LinearSensor::convertFromInputValue(float inputValue) { + float result = m_a * inputValue + m_b; + + // Bounds check + bool isValid = result <= m_maxOutput && result >= m_minOutput; + + return {isValid, result}; +} diff --git a/firmware/controllers/sensors/linear_sensor.h b/firmware/controllers/sensors/linear_sensor.h new file mode 100644 index 0000000000..373c746df2 --- /dev/null +++ b/firmware/controllers/sensors/linear_sensor.h @@ -0,0 +1,23 @@ +#pragma once + +#include "converter_sensor.h" + +class LinearSensor final : public ConvertedSensor { +public: + explicit LinearSensor(SensorType type) + : ConvertedSensor(type) {} + + void configure(float in1, float out1, float in2, float out2, float minOutput, float maxOutput); + +protected: + SensorResult convertFromInputValue(float inputValue) override; + +private: + // Linear equation parameters for equation of form + // y = ax + b + float m_a = 1; + float m_b = 0; + + float m_minOutput = 0; + float m_maxOutput = 0; +}; diff --git a/firmware/controllers/sensors/oil_pressure.cpp b/firmware/controllers/sensors/oil_pressure.cpp deleted file mode 100644 index 5299e54dbe..0000000000 --- a/firmware/controllers/sensors/oil_pressure.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @author Matthew Kennedy, (c) 2017 - */ -#include "global.h" -#include "os_access.h" -#include "oil_pressure.h" -#include "interpolation.h" -#include "adc_inputs.h" -#include "engine.h" - -EXTERN_ENGINE; - -bool hasOilPressureSensor(DECLARE_ENGINE_PARAMETER_SIGNATURE) { - return engineConfiguration->oilPressure.hwChannel != EFI_ADC_NONE; -} - -float getOilPressure(DECLARE_ENGINE_PARAMETER_SIGNATURE) { - // If there's no sensor, return 0 pressure. - if(!hasOilPressureSensor(PASS_ENGINE_PARAMETER_SIGNATURE)) { - return 0.0f; - } - - oil_pressure_config_s* sensor = &CONFIG(oilPressure); - - float volts = getVoltageDivided("oilp", sensor->hwChannel); - - return interpolateMsg("oil", sensor->v1, sensor->value1, sensor->v2, sensor->value2, volts); -} diff --git a/firmware/controllers/sensors/oil_pressure.h b/firmware/controllers/sensors/oil_pressure.h deleted file mode 100644 index d3d3693095..0000000000 --- a/firmware/controllers/sensors/oil_pressure.h +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @date Nov 9, 2017 - * @author Matthew Kennedy, (c) 2017 - */ - -#ifndef OIL_PRESSURE_H_ -#define OIL_PRESSURE_H_ - -#include "global.h" -#include "engine_configuration.h" - -float getOilPressure(DECLARE_ENGINE_PARAMETER_SIGNATURE); -bool hasOilPressureSensor(DECLARE_ENGINE_PARAMETER_SIGNATURE); - -#endif diff --git a/firmware/controllers/sensors/sensor.cpp b/firmware/controllers/sensors/sensor.cpp new file mode 100644 index 0000000000..2406ee5d26 --- /dev/null +++ b/firmware/controllers/sensors/sensor.cpp @@ -0,0 +1,116 @@ +#include "sensor.h" + +// This struct represents one sensor in the registry. +// It stores whether the sensor should use a mock value, +// the value to use, and if not a pointer to the sensor that +// can provide a real value. +struct SensorRegistryEntry { + bool useMock; + float mockValue; + Sensor *sensor; +}; + +static SensorRegistryEntry s_sensorRegistry[static_cast(SensorType::PlaceholderLast)] = {}; + +bool Sensor::Register() { + // Get a ref to where we should be + auto &entry = s_sensorRegistry[getIndex()]; + + // If there's somebody already here - a consumer tried to double-register a sensor + if (entry.sensor) { + // This sensor has already been registered. Don't re-register it. + return false; + } else { + // put ourselves in the registry + s_sensorRegistry[getIndex()].sensor = this; + return true; + } +} + +/*static*/ void Sensor::resetRegistry() { + constexpr size_t len = sizeof(s_sensorRegistry) / sizeof(s_sensorRegistry[0]); + + // Clear all entries + for (size_t i = 0; i < len; i++) { + auto &entry = s_sensorRegistry[i]; + + entry.sensor = nullptr; + entry.mockValue = 0.0f; + } +} + +/*static*/ SensorRegistryEntry *Sensor::getEntryForType(SensorType type) { + size_t index = getIndex(type); + // Check that we didn't get garbage + if (index >= getIndex(SensorType::PlaceholderLast)) { + return nullptr; + } + + return &s_sensorRegistry[index]; +} + +/*static*/ const Sensor *Sensor::getSensorOfType(SensorType type) { + auto entry = getEntryForType(type); + return entry ? entry->sensor : nullptr; +} + +/*static*/ SensorResult Sensor::get(SensorType type) { + const auto entry = getEntryForType(type); + + // Check if this is a valid sensor entry + if (!entry) { + return {false, 0.0f}; + } + + // Next check for mock + if (entry->useMock) { + return {true, entry->mockValue}; + } + + // Get the sensor out of the entry + const Sensor *s = entry->sensor; + if (s) { + // If we found the sensor, ask it for a result. + return s->get(); + } + + // We've exhausted all valid ways to return something - sensor not found. + return {false, 0}; +} + +/*static*/ void Sensor::setMockValue(SensorType type, float value) { + auto entry = getEntryForType(type); + + if (entry) { + entry->mockValue = value; + entry->useMock = true; + } +} + +/*static*/ void Sensor::setMockValue(int type, float value) { + // bounds check + if (type <= 0 || type >= static_cast(SensorType::PlaceholderLast)) { + return; + } + + setMockValue(static_cast(type), value); +} + +/*static*/ void Sensor::resetMockValue(SensorType type) { + auto entry = getEntryForType(type); + + if (entry) { + entry->useMock = false; + } +} + +/*static*/ void Sensor::resetAllMocks() { + constexpr size_t len = sizeof(s_sensorRegistry) / sizeof(s_sensorRegistry[0]); + + // Reset all mocks + for (size_t i = 0; i < len; i++) { + auto &entry = s_sensorRegistry[i]; + + entry.useMock = false; + } +} diff --git a/firmware/controllers/sensors/sensor.h b/firmware/controllers/sensors/sensor.h new file mode 100644 index 0000000000..bdc4608ab6 --- /dev/null +++ b/firmware/controllers/sensors/sensor.h @@ -0,0 +1,133 @@ +/** + * @file sensor.h + * @brief Base class for sensors. Inherit this class to implement a new type of sensor. + * + * This file defines the basis for all sensor inputs to the ECU, and provides a registry + * so that consumers may be agnostic to how each sensor may work. + * + * HOW TO ADD A NEW SENSOR TYPE: + * + * 1. Add an entry to the enum in sensor_type.h. Be sure to add it ABOVE the placeholder + * at the end of the list. + * + * 2. In the init/sensor folder, create/modify logic to create an instance of the new sensor, + * configure it if necessary, and call its Register() function if it should be enabled. + * See init_oil_pressure.cpp for a minimal example. + * + * 3. Consume the new sensor with instance(s) of SensorConsumer + * + * Consumers: + * + * tl;dr: Use a SensorConsumer. See sensor_consumer.h + * + * All a consumer does is look up whether a particular sensor is present in the table, + * and if so, asks it for the current reading. This could synchronously perform sensor + * acquisition and conversion (not recommended), or use a previously stored value (recommended!). + * This functionality is implemented in sensor_consumer.h, and sensor.cpp. + * + * Providers: + * Instantiate a subclass of Sensor, and implement the Get() function. + * Call Register() to install the new sensor in the registry, preparing it for use. + * + * Mocking: + * The sensor table supports mocking each sensors value. Call Sensor::SetMockValue to + * set a mock value for a particular sensor, and Sensor::ResetMockValue or + * Sensor::ResetAllMocks to reset one or all stored mock values. + * + * Composite Sensors: + * Some sensors may be implemented as composite sensors, ie sensors that depend on other + * sensors to determine their reading. For example, the throttle pedal may have a pair of + * potentiometers that provide redundancy for the pedal's position. Each one may be its + * own sensor, then with one "master" sensors that combines the results of the others, and + * provides validation of whether the readings agree. + * + * @date September 12, 2019 + * @author Matthew Kennedy, (c) 2019 + */ + +#pragma once + +#include "sensor_type.h" + +#include + +struct SensorResult { + const bool Valid; + const float Value; +}; + +// Fwd declare - nobody outside of Sensor.cpp needs to see inside this type +struct SensorRegistryEntry; + +class Sensor { +public: + // Register this sensor in the sensor registry. + // Returns true if registration succeeded, or false if + // another sensor of the same type is already registered. + // The return value should not be ignored: no error handling/reporting is + // done internally! + [[nodiscard]] bool Register(); + + // Remove all sensors from the sensor registry - tread carefully if you use this outside of a unit test + static void resetRegistry(); + + /* + * Static helper for sensor lookup + */ + static const Sensor *getSensorOfType(SensorType type); + + /* + * Get a reading from the specified sensor. + */ + static SensorResult get(SensorType type); + + /* + * Mock a value for a particular sensor. + */ + static void setMockValue(SensorType type, float value); + + /* + * Mock a value for a particular sensor. + */ + static void setMockValue(int type, float value); + + /* + * Reset mock for a particular sensor. + */ + static void resetMockValue(SensorType type); + + /* + * Reset mocking for all sensors. + */ + static void resetAllMocks(); + +protected: + // Protected constructor - only subclasses call this + explicit Sensor(SensorType type) + : m_type(type) {} + +private: + // Retrieve the current reading from the sensor. + // + // Override this in a particular sensor's implementation. As reading sensors is in many hot paths, + // it is unwise to synchronously read the sensor or do anything otherwise costly here. At the most, + // this should be field lookup and simple math. + virtual SensorResult get() const = 0; + + SensorType m_type; + + // Get this sensor's index in the list + constexpr size_t getIndex() { + return getIndex(m_type); + } + + // Get the index in the list for a sensor of particular type + static constexpr size_t getIndex(SensorType type) { + return static_cast(type); + } + + /* + * Static helper for sensor lookup + */ + static SensorRegistryEntry *getEntryForType(SensorType type); +}; diff --git a/firmware/controllers/sensors/sensor_reader.h b/firmware/controllers/sensors/sensor_reader.h new file mode 100644 index 0000000000..659ac48816 --- /dev/null +++ b/firmware/controllers/sensors/sensor_reader.h @@ -0,0 +1,63 @@ +/** + * @file sensor_reader.h + * @brief Declaration for SensorReader, the class used to acquire readings from a sensor. + * + * @date September 12, 2019 + * @author Matthew Kennedy, (c) 2019 + */ + +#pragma once + +#include "sensor.h" + +/** + * @brief Provides an interface for reading sensors. + * + * Example usage: + * + * // Create a sensor reader for type MySensor, and fallback value 10.0 + * SensorReader mySensorReader(10.0f); + * + * + * + * // Returns the sensor value if valid, or 10.0 if invalid. + * float currentSensorValue = mySensorReader.getOrDefault(); + * alternatively, because we have implicit conversion: + * float currentSensorValue = mySensorReader; + * + * Simply calling the get() method returns the full SensorResult, should + * more complex logic be required in case of an invalid sensor reading. + */ +template +class SensorReader final { +public: + using sensorValueType = float; + + explicit SensorReader(sensorValueType defaultValue) + : m_defaultValue(defaultValue) {} + + SensorResult get() const { + return Sensor::get(TSensorType); + } + + // Get the sensor's reading, or a default value. + // Inteded for applications where a default may be used silently, + // while elsewhere in the world the failed sensor is otherwise handled. + sensorValueType getOrDefault() const { + auto result = get(); + + if (result.Valid) { + return (sensorValueType)result.Value; + } else { + return m_defaultValue; + } + } + + // Implicit conversion operator + operator sensorValueType() const { + return getOrDefault(); + } + +private: + const sensorValueType m_defaultValue; +}; diff --git a/firmware/controllers/sensors/sensor_type.h b/firmware/controllers/sensors/sensor_type.h new file mode 100644 index 0000000000..29cadbaba9 --- /dev/null +++ b/firmware/controllers/sensors/sensor_type.h @@ -0,0 +1,33 @@ +/** + * @file sensor_type.h + * @brief Enumeration of sensors supported by the ECU. + * + * @date September 12, 2019 + * @author Matthew Kennedy, (c) 2019 + */ + +#pragma once + +/** + ************************************** + * SEE sensor.h ON HOW TO ADD NEW SENSOR TYPES + ************************************** + */ + +enum class SensorType : unsigned char { + Invalid = 0, + Clt, + Iat, + + OilPressure, + + // This is the "resolved" position, potentially composited out of the following two + Tps1, + // This is the first sensor + Tps1Primary, + // This is the second sensor + Tps1Secondary, + + // Leave me at the end! + PlaceholderLast +}; diff --git a/firmware/controllers/sensors/sensors.mk b/firmware/controllers/sensors/sensors.mk index d7f78ec0f2..b2ae71cdba 100644 --- a/firmware/controllers/sensors/sensors.mk +++ b/firmware/controllers/sensors/sensors.mk @@ -10,4 +10,5 @@ CONTROLLERS_SENSORS_SRC_CPP = $(PROJECT_DIR)/controllers/sensors/thermistors.cp $(PROJECT_DIR)/controllers/sensors/ego.cpp \ $(PROJECT_DIR)/controllers/sensors/maf2map.cpp \ $(PROJECT_DIR)/controllers/sensors/hip9011_lookup.cpp \ - $(PROJECT_DIR)/controllers/sensors/oil_pressure.cpp + $(PROJECT_DIR)/controllers/sensors/sensor.cpp \ + $(PROJECT_DIR)/controllers/sensors/linear_sensor.cpp diff --git a/firmware/controllers/sensors/stored_value_sensor.h b/firmware/controllers/sensors/stored_value_sensor.h new file mode 100644 index 0000000000..d8198567ee --- /dev/null +++ b/firmware/controllers/sensors/stored_value_sensor.h @@ -0,0 +1,70 @@ +/** + * @file stored_value_sensor.h + * @brief Base class for a sensor that has its value asynchronously + * set, then later retrieved by a consumer. + * + * @date September 12, 2019 + * @author Matthew Kennedy, (c) 2019 + */ + +#pragma once + +#include "sensor.h" + +/** + * @brief Base class for sensors that compute a value on one thread, and want + * to make it available to consumers asynchronously. + * + * Common examples include sensors that have to do heavy lifting to produce + * a reading, and don't want to perform that conversion at the time of + * consumption. + * + * To use this class, create a class for your sensor that inherits StoredValueSensor, + * and call Invalidate() and SetValidValue(float) as appropriate when readings are available + * (or known to be invalid) for your sensor. + * + * Consumers will retrieve the last set (or invalidated) value. + */ +class StoredValueSensor : public Sensor { +public: + SensorResult get() const final { + bool valid = m_isValid; + float value = m_value; + + return {valid, value}; + } + + void setReportingLocation(float *reportLocation) { + m_reportingLocation = reportLocation; + } + +protected: + explicit StoredValueSensor(SensorType type) + : Sensor(type) + //, m_reportingLocation(reportingLocation) + {} + + // Invalidate the stored value. + void invalidate() { + m_isValid = false; + } + + // A new reading is available: set and validate a new value for the sensor. + void setValidValue(float value) { + // Set value before valid - so we don't briefly have the valid bit set on an invalid value + m_value = value; + m_isValid = true; + + // Report value + float *reportLoc = m_reportingLocation; + if (reportLoc) { + *reportLoc = value; + } + } + +private: + bool m_isValid = false; + float m_value = 0.0f; + + float *m_reportingLocation = nullptr; +}; diff --git a/firmware/hw_layer/adc_inputs.cpp b/firmware/hw_layer/adc_inputs.cpp index 74c694c451..e83ba1b616 100644 --- a/firmware/hw_layer/adc_inputs.cpp +++ b/firmware/hw_layer/adc_inputs.cpp @@ -27,6 +27,7 @@ #include "engine.h" #include "adc_inputs.h" +#include "adc_subscription.h" #include "AdcConfiguration.h" #include "mpu_util.h" @@ -467,6 +468,8 @@ static void adc_callback_slow(ADCDriver *adcp, adcsample_t *buffer, size_t n) { } slowAdcCounter++; } + + AdcSubscription::UpdateSubscribers(); } static char errorMsgBuff[10]; diff --git a/firmware/hw_layer/adc_subscription.cpp b/firmware/hw_layer/adc_subscription.cpp new file mode 100644 index 0000000000..f156003ce1 --- /dev/null +++ b/firmware/hw_layer/adc_subscription.cpp @@ -0,0 +1,56 @@ +#include "adc_subscription.h" +#include "adc_inputs.h" +#include "engine.h" + +#include + +EXTERN_ENGINE; + +#if !EFI_UNIT_TEST + +struct AdcSubscriptionEntry { + ConvertedSensor* Sensor; + float VoltsPerAdcVolt; + adc_channel_e Channel; +}; + +static size_t s_nextEntry = 0; +static AdcSubscriptionEntry s_entries[8]; + +void AdcSubscription::SubscribeSensor(ConvertedSensor& sensor, adc_channel_e channel, float voltsPerAdcVolt /*= 0.0f*/) { + // Don't subscribe null channels + if (channel == EFI_ADC_NONE) { + return; + } + + // bounds check + if (s_nextEntry >= std::size(s_entries)) { + return; + } + + // if 0, default to the board's divider coefficient + if (voltsPerAdcVolt == 0) { + voltsPerAdcVolt = engineConfiguration->analogInputDividerCoefficient; + } + + // Populate the entry + auto& entry = s_entries[s_nextEntry]; + entry.Sensor = &sensor; + entry.VoltsPerAdcVolt = voltsPerAdcVolt; + entry.Channel = channel; + + s_nextEntry++; +} + +void AdcSubscription::UpdateSubscribers() { + for (size_t i = 0; i < s_nextEntry; i++) { + auto& entry = s_entries[i]; + + float mcuVolts = getVoltage("sensor", entry.Channel); + float sensorVolts = mcuVolts * entry.VoltsPerAdcVolt; + + entry.Sensor->postRawValue(sensorVolts); + } +} + +#endif// !EFI_UNIT_TEST diff --git a/firmware/hw_layer/adc_subscription.h b/firmware/hw_layer/adc_subscription.h new file mode 100644 index 0000000000..ab53c19c97 --- /dev/null +++ b/firmware/hw_layer/adc_subscription.h @@ -0,0 +1,8 @@ +#include "converter_sensor.h" +#include "rusefi_hw_enums.h" + +class AdcSubscription { +public: + static void SubscribeSensor(ConvertedSensor& sensor, adc_channel_e channel, float voltsPerAdcVolt = 0.0f); + static void UpdateSubscribers(); +}; diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk index e960caf304..04c70ac206 100644 --- a/firmware/hw_layer/hw_layer.mk +++ b/firmware/hw_layer/hw_layer.mk @@ -20,7 +20,8 @@ HW_LAYER_EMS_CPP = $(HW_LAYER_EGT_CPP) \ $(PROJECT_DIR)/hw_layer/neo6m.cpp \ $(PROJECT_DIR)/hw_layer/mmc_card.cpp \ $(PROJECT_DIR)/hw_layer/lcd/lcd_HD44780.cpp \ - $(PROJECT_DIR)/hw_layer/adc_inputs.cpp \ + $(PROJECT_DIR)/hw_layer/adc_inputs.cpp \ + $(PROJECT_DIR)/hw_layer/adc_subscription.cpp \ $(PROJECT_DIR)/hw_layer/pwm_generator.cpp \ $(PROJECT_DIR)/hw_layer/trigger_input.cpp \ $(PROJECT_DIR)/hw_layer/hip9011.cpp \ diff --git a/firmware/init/init.h b/firmware/init/init.h new file mode 100644 index 0000000000..783ef8baa9 --- /dev/null +++ b/firmware/init/init.h @@ -0,0 +1,3 @@ +#pragma once + +void initSensors(); diff --git a/firmware/init/init.mk b/firmware/init/init.mk new file mode 100644 index 0000000000..84f9d96b8f --- /dev/null +++ b/firmware/init/init.mk @@ -0,0 +1,3 @@ + +INIT_SRC_CPP = $(PROJECT_DIR)/init/sensor/init_sensors.cpp \ + $(PROJECT_DIR)/init/sensor/init_oil_pressure.cpp diff --git a/firmware/init/sensor/init_oil_pressure.cpp b/firmware/init/sensor/init_oil_pressure.cpp new file mode 100644 index 0000000000..7d15aeafea --- /dev/null +++ b/firmware/init/sensor/init_oil_pressure.cpp @@ -0,0 +1,42 @@ +#include "adc_subscription.h" +#include "engine.h" +#include "error_handling.h" +#include "global.h" +#include "linear_sensor.h" +#include "tunerstudio_configuration.h" + +EXTERN_ENGINE; + +extern TunerStudioOutputChannels tsOutputChannels; + +LinearSensor oilpSensor(SensorType::OilPressure); + +void initOilPressure() { + // Only register if we have a sensor + auto channel = engineConfiguration->oilPressure.hwChannel; + if (channel == EFI_ADC_NONE) { + return; + } + + oil_pressure_config_s *sensorCfg = &CONFIG(oilPressure); + + float val1 = sensorCfg->value1; + float val2 = sensorCfg->value2; + + // Limit to max given pressure - val1 or val2 could be larger + // (sensor may be backwards, high voltage = low pressure) + float greaterOutput = val1 > val2 ? val1 : val2; + + // Allow slightly negative output (-5kpa) so as to not fail the sensor when engine is off + oilpSensor.configure(sensorCfg->v1, val1, sensorCfg->v2, val2, /*minOutput*/ -5, greaterOutput); + + // Tell it to report to its output channel + oilpSensor.setReportingLocation(&tsOutputChannels.oilPressure); + + // Subscribe the sensor to the ADC + AdcSubscription::SubscribeSensor(oilpSensor, channel); + + if (!oilpSensor.Register()) { + warning(OBD_Oil_Pressure_Sensor_Malfunction, "Duplicate oilp sensor registration, ignoring"); + } +} diff --git a/firmware/init/sensor/init_sensors.cpp b/firmware/init/sensor/init_sensors.cpp new file mode 100644 index 0000000000..83356b678c --- /dev/null +++ b/firmware/init/sensor/init_sensors.cpp @@ -0,0 +1,20 @@ +#include "cli_registry.h" +#include "init.h" +#include "sensor.h" + +void initOilPressure(); + +void initSensorCli(); + +void initSensors() { + // aux sensors + initOilPressure(); + + // Init CLI functionality for sensors (mocking) + initSensorCli(); +} + +void initSensorCli() { + addConsoleActionIF("set_sensor_mock", Sensor::setMockValue); + addConsoleAction("reset_sensor_mocks", Sensor::resetAllMocks); +} diff --git a/firmware/util/cli_registry.cpp b/firmware/util/cli_registry.cpp index a825789185..a60bc9a1f1 100644 --- a/firmware/util/cli_registry.cpp +++ b/firmware/util/cli_registry.cpp @@ -97,6 +97,10 @@ void addConsoleActionIIP(const char *token, VoidIntIntVoidPtr callback, void *pa doAddAction(token, TWO_INTS_PARAMETER_P, (Void) callback, param); } +void addConsoleActionIF(const char *token, VoidIntFloat callback) { + doAddAction(token, INT_FLOAT_PARAMETER, (Void) callback, NULL); +} + void addConsoleActionS(const char *token, VoidCharPtr callback) { doAddAction(token, STRING_PARAMETER, (Void) callback, NULL); } @@ -149,6 +153,7 @@ static int getParameterCount(action_type_e parameterType) { case STRING2_PARAMETER_P: case TWO_INTS_PARAMETER: case TWO_INTS_PARAMETER_P: + case INT_FLOAT_PARAMETER: return 2; case STRING3_PARAMETER: return 3; @@ -394,7 +399,7 @@ void handleActionWithParameter(TokenCallback *current, char *parameter) { print("invalid float [%s]\r\n", parameter); return; } -if (current->parameterType == FLOAT_FLOAT_PARAMETER) { + if (current->parameterType == FLOAT_FLOAT_PARAMETER) { VoidFloatFloat callbackS = (VoidFloatFloat) current->callback; (*callbackS)(value1, value2); } else { @@ -404,6 +409,32 @@ if (current->parameterType == FLOAT_FLOAT_PARAMETER) { return; } + if (current->parameterType == INT_FLOAT_PARAMETER) { + int spaceIndex = findEndOfToken(parameter); + if (spaceIndex == -1) + return; + REPLACE_SPACES_WITH_ZERO; + int value1 = atoi(parameter); + if (absI(value1) == ERROR_CODE) { +#if EFI_PROD_CODE || EFI_SIMULATOR + scheduleMsg(logging, "not an integer [%s]", parameter); +#endif + return; + } + parameter += spaceIndex + 1; + float value2 = atoff(parameter); + + if (cisnan(value2)) { + print("invalid float [%s]\r\n", parameter); + return; + } + + VoidIntFloat callback = (VoidIntFloat) current->callback; + callback(value1, value2); + + return; + } + int value = atoi(parameter); if (absI(value) == ERROR_CODE) { print("invalid integer [%s]\r\n", parameter); diff --git a/firmware/util/cli_registry.h b/firmware/util/cli_registry.h index 6ed26c308c..e5c6287907 100644 --- a/firmware/util/cli_registry.h +++ b/firmware/util/cli_registry.h @@ -30,6 +30,7 @@ typedef enum { TWO_INTS_PARAMETER_P, FLOAT_FLOAT_PARAMETER, FLOAT_FLOAT_PARAMETER_P, + INT_FLOAT_PARAMETER, } action_type_e; typedef struct { @@ -61,6 +62,8 @@ void addConsoleActionP(const char *token, VoidPtr callback, void *param); void addConsoleActionI(const char *token, VoidInt callback); void addConsoleActionIP(const char *token, VoidIntVoidPtr callback, void *param); +void addConsoleActionIF(const char* token, VoidIntFloat callback); + void addConsoleActionII(const char *token, VoidIntInt callback); void addConsoleActionIIP(const char *token, VoidIntIntVoidPtr callback, void *param); diff --git a/unit_tests/tests/sensor/basic_sensor.cpp b/unit_tests/tests/sensor/basic_sensor.cpp new file mode 100644 index 0000000000..4982a1376b --- /dev/null +++ b/unit_tests/tests/sensor/basic_sensor.cpp @@ -0,0 +1,81 @@ +#include "mock/mock_sensor.h" +#include "stored_value_sensor.h" + +#include + +class SensorBasic : public ::testing::Test { +protected: + void SetUp() override { + Sensor::resetRegistry(); + } + + void TearDown() override { + Sensor::resetRegistry(); + } +}; + +TEST_F(SensorBasic, Registry) { + // Create a sensor - but don't register it + MockSensor dut(SensorType::Tps1); + + // Expect not to find it + { + auto s = Sensor::getSensorOfType(SensorType::Tps1); + EXPECT_FALSE(s); + } + + // Register the sensor now + EXPECT_TRUE(dut.Register()); + + // It should now get us back our sensor + { + auto s = Sensor::getSensorOfType(SensorType::Tps1); + EXPECT_EQ(s, &dut); + } + + // Reset - it should now be gone! + Sensor::resetRegistry(); + + { + auto s = Sensor::getSensorOfType(SensorType::Tps1); + EXPECT_FALSE(s); + } +} + +TEST_F(SensorBasic, DoubleRegister) { + // Create a sensor, register it + MockSensor dut(SensorType::Tps1); + ASSERT_TRUE(dut.Register()); + + // And then do it again! + MockSensor dut2(SensorType::Tps1); + EXPECT_FALSE(dut2.Register()); + + // Make sure that we get the first DUT back - not the second + auto shouldBeDut = Sensor::getSensorOfType(SensorType::Tps1); + EXPECT_EQ(shouldBeDut, &dut); + EXPECT_NE(shouldBeDut, &dut2); +} + +TEST_F(SensorBasic, SensorNotInitialized) { + auto result = Sensor::get(SensorType::Clt); + + EXPECT_FALSE(result.Valid); +} + +TEST_F(SensorBasic, SensorInitialized) { + MockSensor dut(SensorType::Clt); + ASSERT_TRUE(dut.Register()); + + // Check before init - make sure it isn't set yet + auto result = Sensor::get(SensorType::Clt); + ASSERT_FALSE(result.Valid); + + // Set a value + dut.set(75); + + // Make sure now it's set + auto result2 = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result2.Valid); + EXPECT_FLOAT_EQ(result2.Value, 75); +} diff --git a/unit_tests/tests/sensor/converter_sensor.cpp b/unit_tests/tests/sensor/converter_sensor.cpp new file mode 100644 index 0000000000..0434ae8331 --- /dev/null +++ b/unit_tests/tests/sensor/converter_sensor.cpp @@ -0,0 +1,68 @@ +#include "converter_sensor.h" + +#include + +class SensorConverted : public ::testing::Test { +protected: + void SetUp() override { + Sensor::resetRegistry(); + } + + void TearDown() override { + Sensor::resetRegistry(); + } +}; + +class DoublerConverterSensor final : public ConvertedSensor { +public: + DoublerConverterSensor() + : ConvertedSensor(SensorType::Clt) {} + +protected: + SensorResult convertFromInputValue(float input) { + bool valid = input > 0; + float value = input * 2; + + return {valid, value}; + } +}; + +TEST_F(SensorConverted, TestValid) { + DoublerConverterSensor dut; + ASSERT_TRUE(dut.Register()); + + // Should be invalid - not set yet + { + auto s = Sensor::get(SensorType::Clt); + EXPECT_FALSE(s.Valid); + } + + dut.postRawValue(25); + + // Should be valid, with a value of 25*2 = 50 + { + auto s = Sensor::get(SensorType::Clt); + EXPECT_TRUE(s.Valid); + EXPECT_FLOAT_EQ(s.Value, 50); + } +} + +TEST_F(SensorConverted, TestInvalid) { + DoublerConverterSensor dut; + ASSERT_TRUE(dut.Register()); + + // Should be invalid - not set yet + { + auto s = Sensor::get(SensorType::Clt); + EXPECT_FALSE(s.Valid); + } + + dut.postRawValue(-25); + + // Should be invalid, with a value of -25*2 = 0 + { + auto s = Sensor::get(SensorType::Clt); + EXPECT_FALSE(s.Valid); + EXPECT_FLOAT_EQ(s.Value, 0); + } +} diff --git a/unit_tests/tests/sensor/function_pointer_sensor.cpp b/unit_tests/tests/sensor/function_pointer_sensor.cpp new file mode 100644 index 0000000000..0d01c00510 --- /dev/null +++ b/unit_tests/tests/sensor/function_pointer_sensor.cpp @@ -0,0 +1,27 @@ +#include "function_pointer_sensor.h" + +#include + +class SensorFunctionPointer : public ::testing::Test { +protected: + void SetUp() override { + Sensor::resetRegistry(); + } + + void TearDown() override { + Sensor::resetRegistry(); + } +}; + +float testFunc() { + return 23; +} + +TEST_F(SensorFunctionPointer, TestValue) { + FunctionPointerSensor dut(SensorType::Clt, testFunc); + ASSERT_TRUE(dut.Register()); + + auto result = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 23); +} diff --git a/unit_tests/tests/sensor/lin_sensor.cpp b/unit_tests/tests/sensor/lin_sensor.cpp new file mode 100644 index 0000000000..847cf0e972 --- /dev/null +++ b/unit_tests/tests/sensor/lin_sensor.cpp @@ -0,0 +1,45 @@ +#include "linear_sensor.h" + +#include + +class SensorLinear : public ::testing::Test { +protected: + // Maps (1, 4) -> (100, -100) + LinearSensor dut; + + SensorLinear() + : dut(SensorType::Clt) {} + + void SetUp() override { + dut.configure(1, 100, 4, -100, -110, 110); + } +}; + +#define test_point(in, out) \ + { \ + dut.postRawValue(in); \ + auto result = dut.get(); \ + \ + EXPECT_TRUE(result.Valid); \ + EXPECT_FLOAT_EQ(result.Value, (out)); \ + } + +#define test_point_invalid(in) \ + { \ + dut.postRawValue(in); \ + EXPECT_FALSE(dut.get().Valid); \ + } + +TEST_F(SensorLinear, TestInRange) { + test_point(2.5, 0); + test_point(1, 100); + test_point(4, -100); +} + +TEST_F(SensorLinear, TestOutOfRange) { + test_point(1, 100); + test_point_invalid(0.5); + + test_point(4, -100); + test_point_invalid(4.5); +} diff --git a/unit_tests/tests/sensor/mock/mock_sensor.h b/unit_tests/tests/sensor/mock/mock_sensor.h new file mode 100644 index 0000000000..4c082daf76 --- /dev/null +++ b/unit_tests/tests/sensor/mock/mock_sensor.h @@ -0,0 +1,20 @@ +#pragma once + +#include "stored_value_sensor.h" + +struct MockSensor final : public StoredValueSensor +{ + MockSensor(SensorType type) : StoredValueSensor(type) + { + } + + void set(float value) + { + setValidValue(value); + } + + void invalidate() + { + StoredValueSensor::invalidate(); + } +}; diff --git a/unit_tests/tests/sensor/mock_sensor.cpp b/unit_tests/tests/sensor/mock_sensor.cpp new file mode 100644 index 0000000000..789241732a --- /dev/null +++ b/unit_tests/tests/sensor/mock_sensor.cpp @@ -0,0 +1,92 @@ +#include "mock/mock_sensor.h" + +#include "sensor.h" + +#include + +class SensorMocking : public ::testing::Test { +protected: + MockSensor realSensor; + + SensorMocking() + : realSensor(SensorType::Clt) {} + + void SetUp() override { + Sensor::resetRegistry(); + ASSERT_TRUE(realSensor.Register()); + } + + void TearDown() override { + Sensor::resetRegistry(); + } +}; + +TEST_F(SensorMocking, MockSensor) { + // Set a value on the "real" sensor + realSensor.set(25.0f); + + // And expect to see it + { + auto result = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 25.0f); + } + + // Now set a mock value + Sensor::setMockValue(SensorType::Clt, 37.0f); + + // And expect to see that + { + auto result = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 37.0f); + } +} + +TEST_F(SensorMocking, ResetOne) { + // Set a value on the "real" sensor + realSensor.set(13.0f); + + // Now set a mock value + Sensor::setMockValue(SensorType::Clt, 21.0f); + + // Expect to see the mock value + { + auto result = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 21.0f); + } + + // Reset that mock - it should go to the real value + Sensor::resetMockValue(SensorType::Clt); + + { + auto result = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 13.0f); + } +} + +TEST_F(SensorMocking, ResetAll) { + // Set a value on the "real" sensor + realSensor.set(46.0f); + + // Now set a mock value + Sensor::setMockValue(SensorType::Clt, 33.0f); + + // Expect to see the mock value + { + auto result = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 33.0f); + } + + // Reset all mocks - it should go to the real value + Sensor::resetAllMocks(); + + { + auto result = Sensor::get(SensorType::Clt); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 46.0f); + } +} diff --git a/unit_tests/tests/sensor/sensor_reader.cpp b/unit_tests/tests/sensor/sensor_reader.cpp new file mode 100644 index 0000000000..454504a863 --- /dev/null +++ b/unit_tests/tests/sensor/sensor_reader.cpp @@ -0,0 +1,85 @@ +#include "sensor_reader.h" + +#include "mock/mock_sensor.h" + +#include + +class SensorBasicReader : public ::testing::Test { +protected: + void SetUp() override { + Sensor::resetRegistry(); + } + + void TearDown() override { + Sensor::resetRegistry(); + } +}; + +TEST_F(SensorBasicReader, Value) { + // Create a sensor - but don't register it + MockSensor dut(SensorType::Tps1); + + // Create a reader for the same sensor type - fallback value = 123 + SensorReader dutReader(123.0f); + + // Expect not to find it + { + auto result = dutReader.get(); + EXPECT_FALSE(result.Valid); + } + + // Register the sensor now + EXPECT_TRUE(dut.Register()); + + // Still expect invalid - no value has been set yet + { + auto result = dutReader.get(); + EXPECT_FALSE(result.Valid); + } + + dut.set(47.0f); + + // Expect valid - with the value 47 we just set + { + auto result = dutReader.get(); + EXPECT_TRUE(result.Valid); + EXPECT_FLOAT_EQ(result.Value, 47.0f); + } +} + +TEST_F(SensorBasicReader, FallbackValue) { + // Create a sensor - but don't register it + MockSensor dut(SensorType::Tps1); + + // Create a reader for the same sensor type - fallback value = 123 + SensorReader dutReader(123.0f); + + // Expect not to find it + { + auto result = dutReader.get(); + EXPECT_FALSE(result.Valid); + } + + // Register the sensor now + EXPECT_TRUE(dut.Register()); + + // Still expect invalid - no value has been set yet + { + auto result = dutReader.get(); + EXPECT_FALSE(result.Valid); + } + + // However - we should be able to get the fallback value + { + auto result = dutReader.getOrDefault(); + EXPECT_FLOAT_EQ(result, 123.0f); + } + + // Now set the value for real, and ensure we get that instead + dut.set(63); + + { + auto result = dutReader.getOrDefault(); + EXPECT_FLOAT_EQ(result, 63.0f); + } +} diff --git a/unit_tests/tests/tests.mk b/unit_tests/tests/tests.mk index d5e0d60fa2..b911bd1f66 100644 --- a/unit_tests/tests/tests.mk +++ b/unit_tests/tests/tests.mk @@ -26,4 +26,11 @@ TESTS_SRC_CPP = \ tests/test_sensors.cpp \ tests/test_pid_auto.cpp \ tests/test_accel_enrichment.cpp \ - tests/test_gpiochip.cpp + tests/test_gpiochip.cpp \ +\ + tests/sensor/basic_sensor.cpp \ + tests/sensor/converter_sensor.cpp \ + tests/sensor/function_pointer_sensor.cpp \ + tests/sensor/mock_sensor.cpp \ + tests/sensor/sensor_reader.cpp \ + tests/sensor/lin_sensor.cpp