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
This commit is contained in:
Matthew Kennedy 2019-09-21 11:33:38 -07:00 committed by rusefi
parent f3c82eec0c
commit f629ec038b
39 changed files with 1164 additions and 56 deletions

View File

@ -742,6 +742,7 @@ INPUT = . \
util \
console \
controllers \
init \
emulation \
hw_layer

View File

@ -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 \

View File

@ -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;

View File

@ -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) {

View File

@ -63,11 +63,6 @@ public:
float auxTemp1 = NAN;
float auxTemp2 = NAN;
/**
* Oil pressure in kPa
*/
float oilPressure;
Accelerometer accelerometer;
float vBatt = 0;

View File

@ -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

View File

@ -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*);

View File

@ -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);

View File

@ -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"

View File

@ -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;
};

View File

@ -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)();
};

View File

@ -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};
}

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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

View File

@ -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<size_t>(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<int>(SensorType::PlaceholderLast)) {
return;
}
setMockValue(static_cast<SensorType>(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;
}
}

View File

@ -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<SensorType::MyNewSensor>
*
* 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 <cstddef>
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<size_t>(type);
}
/*
* Static helper for sensor lookup
*/
static SensorRegistryEntry *getEntryForType(SensorType type);
};

View File

@ -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<SensorType::MySensor> mySensorReader(10.0f);
*
* <later>
*
* // 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 <SensorType TSensorType>
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;
};

View File

@ -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
};

View File

@ -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

View File

@ -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;
};

View File

@ -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];

View File

@ -0,0 +1,56 @@
#include "adc_subscription.h"
#include "adc_inputs.h"
#include "engine.h"
#include <iterator>
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

View File

@ -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();
};

View File

@ -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 \

3
firmware/init/init.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
void initSensors();

3
firmware/init/init.mk Normal file
View File

@ -0,0 +1,3 @@
INIT_SRC_CPP = $(PROJECT_DIR)/init/sensor/init_sensors.cpp \
$(PROJECT_DIR)/init/sensor/init_oil_pressure.cpp

View File

@ -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");
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,81 @@
#include "mock/mock_sensor.h"
#include "stored_value_sensor.h"
#include <gtest/gtest.h>
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);
}

View File

@ -0,0 +1,68 @@
#include "converter_sensor.h"
#include <gtest/gtest.h>
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);
}
}

View File

@ -0,0 +1,27 @@
#include "function_pointer_sensor.h"
#include <gtest/gtest.h>
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);
}

View File

@ -0,0 +1,45 @@
#include "linear_sensor.h"
#include <gtest/gtest.h>
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);
}

View File

@ -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();
}
};

View File

@ -0,0 +1,92 @@
#include "mock/mock_sensor.h"
#include "sensor.h"
#include <gtest/gtest.h>
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);
}
}

View File

@ -0,0 +1,85 @@
#include "sensor_reader.h"
#include "mock/mock_sensor.h"
#include <gtest/gtest.h>
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<SensorType::Tps1> 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<SensorType::Tps1> 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);
}
}

View File

@ -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