// -----------------------------------------------------------------------------
|
|
// Digital Sensor (maps to a digitalRead)
|
|
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#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<Enumeration<digital::State>, 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<Enumeration<digital::Mode>, 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<uint8_t, build::SensorsMax> 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<BaseSensor*> _sensors;
|
|
};
|
|
|
|
inline void load() {
|
|
settings::query::setup();
|
|
sensor::add_preinit(std::make_unique<Init>());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace digital
|
|
} // namespace driver
|
|
} // namespace sensor
|
|
} // namespace espurna
|
|
|
|
#endif // SENSOR_SUPPORT && DIGITAL_SUPPORT
|