// ----------------------------------------------------------------------------- // Abstract sensor class (other sensor classes extend this class) // Copyright (C) 2017-2019 by Xose PĂ©rez // ----------------------------------------------------------------------------- #pragma once #include #include "../espurna.h" #include "../sensor.h" #include #include #include // TODO: const'ify accessors. since these are virtuals, *every* implementing class would be affected // (...so, it would be time for a pretty big changeset...) class BaseSensor { public: // Pin this to the resulting object (poor man's rtti) so we know what it can be static_cast'ed into struct ClassKind { ClassKind() : _value(_last) { ++_last; } int value() const { return _value; } bool operator==(const ClassKind& other) const { return _value == other._value; } private: static int _last; int _value; }; static const ClassKind Kind; // Generic way to pass the sensor instance to the isr struct InterruptablePin { InterruptablePin() = default; ~InterruptablePin() { detach(); } template void attach(T* instance, TypedCallback callback, int mode) { _attach(static_cast(instance), reinterpret_cast(callback), mode); } InterruptablePin& operator=(unsigned char pin) { _pin = pin; return *this; } bool operator==(unsigned char other) const { return _pin == other; } explicit operator String() const { return String(_pin); } void detach() { gpioUnlock(_current); ::detachInterrupt(_current); _current = GPIO_NONE; } void pin(unsigned char value) { _pin = value; } unsigned char pin() const { return _pin; } private: using VoidCallback = void(*)(void*); void _attach(void* instance, VoidCallback callback, int mode) { if (_current != _pin) { if (!gpioLock(_pin)) { return; } detach(); ::attachInterruptArg(_pin, callback, instance, mode); _current = _pin; } } unsigned char _current { GPIO_NONE }; unsigned char _pin { GPIO_NONE }; }; // Generic container for magnitude types used in the sensor struct Magnitude { unsigned char type; #if __cplusplus <= 201103L constexpr Magnitude(unsigned char type) : type(type) {} #endif }; template static void findMagnitudes(const T& container, unsigned char type, Callback&& callback) { auto begin = std::begin(container); auto end = std::end(container); for (auto it = begin; it != end; ++it) { if ((*it).type == type) { callback(std::distance(begin, it)); } } } // Make sure we are correctly implementing an abstract base class BaseSensor() = default; virtual ~BaseSensor() = default; // Can't copy as base, should not happen BaseSensor(const BaseSensor&) = delete; BaseSensor(BaseSensor&&) = delete; BaseSensor& operator=(const BaseSensor&) = delete; BaseSensor& operator=(BaseSensor&&) = delete; // Initialization method, must be idempotent virtual void begin() { } // Suspend / resume sensor operation virtual void suspend() { } virtual void resume() { } // Custom hook (usually to update sensor state outside of normal methods) virtual void notify() { } // Loop-like method, call it in your main loop virtual void tick() { } // Pre-read hook (usually to populate registers with up-to-date data) virtual void pre() { } // Post-read hook (usually to reset things) virtual void post() { } // Generic calibration virtual void calibrate() { } // Number of decimals for a unit (or -1 for default) virtual signed char decimals(espurna::sensor::Unit) const { return -1; } // Kind of sensor virtual ClassKind kind() const { return Kind; } // Sensor ID, must be unique virtual unsigned char id() const = 0; // Number of available value slots virtual unsigned char count() const = 0; // Descriptive name of the sensor virtual String description() const = 0; // Descriptive name of the slot # index virtual String description(unsigned char) const { return description(); } // Address of the sensor (it could be the GPIO or I2C address) virtual String address(unsigned char index) const = 0; // Type for slot # index virtual unsigned char type(unsigned char index) const = 0; // Unit of the slot # index virtual espurna::sensor::Unit units(unsigned char index) const { using namespace espurna::sensor; switch (type(index)) { case MAGNITUDE_TEMPERATURE: return Unit::Celcius; case MAGNITUDE_HUMIDITY: case MAGNITUDE_POWER_FACTOR: return Unit::Percentage; case MAGNITUDE_PRESSURE: return Unit::Hectopascal; case MAGNITUDE_CURRENT: return Unit::Ampere; case MAGNITUDE_VOLTAGE: return Unit::Volt; case MAGNITUDE_POWER_ACTIVE: return Unit::Watt; case MAGNITUDE_POWER_APPARENT: return Unit::Voltampere; case MAGNITUDE_POWER_REACTIVE: return Unit::VoltampereReactive; case MAGNITUDE_ENERGY_DELTA: return Unit::Joule; case MAGNITUDE_ENERGY: return Unit::KilowattHour; case MAGNITUDE_PM1DOT0: case MAGNITUDE_PM2DOT5: case MAGNITUDE_PM10: case MAGNITUDE_TVOC: case MAGNITUDE_CH2O: return Unit::MicrogrammPerCubicMeter; case MAGNITUDE_CO: case MAGNITUDE_CO2: case MAGNITUDE_NO2: case MAGNITUDE_VOC: return Unit::PartsPerMillion; case MAGNITUDE_LUX: return Unit::Lux; case MAGNITUDE_RESISTANCE: return Unit::Ohm; case MAGNITUDE_HCHO: return Unit::MilligrammPerCubicMeter; case MAGNITUDE_GEIGER_CPM: return Unit::CountsPerMinute; case MAGNITUDE_GEIGER_SIEVERT: return Unit::MicrosievertPerHour; case MAGNITUDE_DISTANCE: return Unit::Meter; case MAGNITUDE_FREQUENCY: return Unit::Hertz; case MAGNITUDE_PH: return Unit::Ph; default: break; } return Unit::None; } // Current value for slot # index virtual double value(unsigned char index) = 0; // Return ready status (true if ready to be read) bool ready() const { return _ready; } // Return sensor last internal error int error() const { return _error; } protected: int _error = SENSOR_ERROR_OK; bool _dirty = true; bool _ready = false; }; int BaseSensor::ClassKind::_last { 0 }; const BaseSensor::ClassKind BaseSensor::Kind;