// ----------------------------------------------------------------------------- // Digital Sensor (maps to a digitalRead) // Copyright (C) 2017-2019 by Xose PĂ©rez // ----------------------------------------------------------------------------- #if SENSOR_SUPPORT && DIGITAL_SUPPORT #pragma once #include "BaseSensor.h" namespace espurna { namespace sensor { namespace driver { namespace digital { enum class State { Low, High, Initial, }; enum Mode { Input, PullUp, PullDown, }; namespace { namespace build { constexpr size_t SensorsMax { 8 }; constexpr unsigned char pin(unsigned char index) { return (0 == index) ? (DIGITAL1_PIN) : (1 == index) ? (DIGITAL2_PIN) : (2 == index) ? (DIGITAL3_PIN) : (3 == index) ? (DIGITAL4_PIN) : (4 == index) ? (DIGITAL5_PIN) : (5 == index) ? (DIGITAL6_PIN) : (6 == index) ? (DIGITAL7_PIN) : (7 == index) ? (DIGITAL8_PIN) : (GPIO_NONE); } constexpr Mode mode_from_value(int value) { return (INPUT == value) ? Mode::Input : (INPUT_PULLUP == value) ? Mode::PullUp : (INPUT_PULLDOWN == value) ? Mode::PullDown : Mode::Input; } constexpr Mode mode(unsigned char index) { return mode_from_value( (0 == index) ? (DIGITAL1_PIN_MODE) : (1 == index) ? (DIGITAL2_PIN_MODE) : (2 == index) ? (DIGITAL3_PIN_MODE) : (3 == index) ? (DIGITAL4_PIN_MODE) : (4 == index) ? (DIGITAL5_PIN_MODE) : (5 == index) ? (DIGITAL6_PIN_MODE) : (6 == index) ? (DIGITAL7_PIN_MODE) : (7 == index) ? (DIGITAL8_PIN_MODE) : (INPUT_PULLUP)); } constexpr State state_from_value(int value) { return (HIGH == value) ? State::High : (LOW == value) ? State::Low : State::Initial; } constexpr State state(unsigned char index) { return state_from_value( (0 == index) ? (DIGITAL1_DEFAULT_STATE) : (1 == index) ? (DIGITAL2_DEFAULT_STATE) : (2 == index) ? (DIGITAL3_DEFAULT_STATE) : (3 == index) ? (DIGITAL4_DEFAULT_STATE) : (4 == index) ? (DIGITAL5_DEFAULT_STATE) : (5 == index) ? (DIGITAL6_DEFAULT_STATE) : (6 == index) ? (DIGITAL7_DEFAULT_STATE) : (7 == index) ? (DIGITAL8_DEFAULT_STATE) : (HIGH)); } } // namespace build namespace settings { namespace options { using espurna::settings::options::Enumeration; PROGMEM_STRING(Low, "low"); PROGMEM_STRING(High, "high"); PROGMEM_STRING(Initial, "initial"); static constexpr std::array, 3> State PROGMEM { {{digital::State::Low, Low}, {digital::State::High, High}, {digital::State::Initial, Initial}} }; PROGMEM_STRING(Input, "default"); PROGMEM_STRING(PullUp, "pull-up"); PROGMEM_STRING(PullDown, "pull-down"); static constexpr std::array, 3> Mode PROGMEM { {{digital::Mode::Input, Input}, {digital::Mode::PullUp, PullUp}, {digital::Mode::PullDown, PullDown}} }; } // namespace options } // namespace settings } // namespace } // namespace digital } // namespace driver } // namespace sensor namespace settings { namespace internal { using namespace espurna::sensor::driver; template<> digital::Mode convert(const String& value) { return convert(digital::settings::options::Mode, value, digital::Mode::PullUp); } String serialize(digital::Mode value) { return serialize(digital::settings::options::Mode, value); } template<> digital::State convert(const String& value) { return convert(digital::settings::options::State, value, digital::State::High); } String serialize(digital::State value) { return serialize(digital::settings::options::State, value); } } // namespace internal } // namespace settings namespace sensor { namespace driver { namespace digital { namespace { namespace settings { STRING_VIEW_INLINE(Prefix, "digital"); namespace keys { STRING_VIEW_INLINE(Pin, "digitalPin"); STRING_VIEW_INLINE(Mode, "digitalPinMode"); STRING_VIEW_INLINE(State, "digitalDefState"); } // namespace keys unsigned char pin(size_t index) { return getSetting(keys::Pin, build::pin(index)); } Mode mode(size_t index) { return getSetting(keys::Mode, build::mode(index)); } State state(size_t index) { return getSetting(keys::State, build::state(index)); } namespace query { #define ID_VALUE(NAME, FUNC)\ String NAME (size_t id) {\ return espurna::settings::internal::serialize(FUNC(id));\ } ID_VALUE(pin, settings::pin) ID_VALUE(mode, settings::mode) ID_VALUE(state, settings::state) #undef ID_VALUE static constexpr espurna::settings::query::IndexedSetting IndexedSettings[] PROGMEM { {keys::Pin, pin}, {keys::Mode, mode}, {keys::State, state}, }; bool checkSamePrefix(StringView key) { return key.startsWith(Prefix); } espurna::settings::query::Result findFrom(StringView key) { return espurna::settings::query::findFrom( build::SensorsMax, IndexedSettings, key); } void setup() { settingsRegisterQueryHandler({ .check = checkSamePrefix, .get = findFrom, }); } } // namespace query } // namespace settings struct Config { uint8_t pin; int pin_mode; State default_state; }; class Sensor : public BaseSensor { public: Sensor() = delete; explicit Sensor(Config config) : _pin(config.pin), _pin_mode(config.pin_mode), _default_state(config.default_state) {} // --------------------------------------------------------------------- // Sensor API // --------------------------------------------------------------------- unsigned char id() const override { return SENSOR_DIGITAL_ID; } unsigned char count() const override { return 1; } // Initialization method, must be idempotent void begin() override { if (!_ready) { pinMode(_pin, _pin_mode); switch (_default_state) { case State::Initial: _default = digitalRead(_pin); break; case State::High: _default = HIGH; break; case State::Low: _default = LOW; break; } } _ready = true; } // Descriptive name of the sensor String description() const override { char buffer[32]; snprintf_P(buffer, sizeof(buffer), PSTR("Digital @ GPIO%hhu"), _pin); return String(buffer); } // Address of the sensor (it could be the GPIO or I2C address) String address(unsigned char) const override { return String(_pin, 10); } // Type for slot # index unsigned char type(unsigned char index) const override { if (index == 0) { return MAGNITUDE_DIGITAL; } return MAGNITUDE_NONE; } void pre() override { _current = digitalRead(_pin); } // Current value for slot # index double value(unsigned char index) override { if (index != 0) { return 0; } return (_current != _default) ? 1.0 : 0.0; } private: unsigned char _pin; uint8_t _pin_mode; State _default_state; int _current { -1 }; int _default { LOW }; }; class Init : public sensor::PreInit { public: Init() = default; String description() const override { return STRING_VIEW("DigitalSensor").toString(); } Result find_sensors() override { return _find_sensors(); } private: Result _with_error(int error) { return Result{ .sensors = make_span(_sensors), .error = error, }; } Result _find_sensors() { std::array pins; pins.fill(GPIO_NONE); int err = SENSOR_ERROR_OK; size_t index = 0; for (; index < build::SensorsMax; ++index) { const auto pin = settings::pin(index); if (pin == GPIO_NONE) { break; } if (!gpioLock(pin)) { err = SENSOR_ERROR_GPIO_USED; break; } pins[index] = pin; } size_t until = index; if (!until) { err = SENSOR_ERROR_CONFIG; } for (size_t index = 0; index < until; ++index) { if (err != SENSOR_ERROR_OK) { gpioUnlock(pins[index]); } else { _sensors.push_back( new Sensor(Config{ .pin = pins[index], .pin_mode = settings::mode(index), .default_state = settings::state(index), })); } } return _with_error(err); } std::vector _sensors; }; inline void load() { settings::query::setup(); sensor::add_preinit(std::make_unique()); } } // namespace } // namespace digital } // namespace driver } // namespace sensor } // namespace espurna #endif // SENSOR_SUPPORT && DIGITAL_SUPPORT