|
|
@ -3,170 +3,377 @@ |
|
|
|
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com> |
|
|
|
// ----------------------------------------------------------------------------- |
|
|
|
|
|
|
|
#define SENSOR_SUPPORT 1 |
|
|
|
#define DIGITAL_SUPPORT 1 |
|
|
|
|
|
|
|
#if SENSOR_SUPPORT && DIGITAL_SUPPORT |
|
|
|
|
|
|
|
#pragma once |
|
|
|
|
|
|
|
#include "BaseSensor.h" |
|
|
|
|
|
|
|
class DigitalSensor : public BaseSensor { |
|
|
|
|
|
|
|
public: |
|
|
|
// --------------------------------------------------------------------- |
|
|
|
|
|
|
|
static unsigned char defaultPin(unsigned char index) { |
|
|
|
switch (index) { |
|
|
|
case 0: |
|
|
|
return DIGITAL1_PIN; |
|
|
|
case 1: |
|
|
|
return DIGITAL2_PIN; |
|
|
|
case 2: |
|
|
|
return DIGITAL3_PIN; |
|
|
|
case 3: |
|
|
|
return DIGITAL4_PIN; |
|
|
|
case 4: |
|
|
|
return DIGITAL5_PIN; |
|
|
|
case 5: |
|
|
|
return DIGITAL6_PIN; |
|
|
|
case 6: |
|
|
|
return DIGITAL7_PIN; |
|
|
|
case 7: |
|
|
|
return DIGITAL8_PIN; |
|
|
|
} |
|
|
|
namespace espurna { |
|
|
|
namespace sensor { |
|
|
|
namespace driver { |
|
|
|
namespace digital { |
|
|
|
|
|
|
|
return GPIO_NONE; |
|
|
|
} |
|
|
|
enum class State { |
|
|
|
Low, |
|
|
|
High, |
|
|
|
Initial, |
|
|
|
}; |
|
|
|
|
|
|
|
static uint8_t defaultPinMode(unsigned char index) { |
|
|
|
switch (index) { |
|
|
|
case 0: |
|
|
|
return DIGITAL1_PIN_MODE; |
|
|
|
case 1: |
|
|
|
return DIGITAL2_PIN_MODE; |
|
|
|
case 2: |
|
|
|
return DIGITAL3_PIN_MODE; |
|
|
|
case 3: |
|
|
|
return DIGITAL4_PIN_MODE; |
|
|
|
case 4: |
|
|
|
return DIGITAL5_PIN_MODE; |
|
|
|
case 5: |
|
|
|
return DIGITAL6_PIN_MODE; |
|
|
|
case 6: |
|
|
|
return DIGITAL7_PIN_MODE; |
|
|
|
case 7: |
|
|
|
return DIGITAL8_PIN_MODE; |
|
|
|
} |
|
|
|
enum Mode { |
|
|
|
Input, |
|
|
|
PullUp, |
|
|
|
PullDown, |
|
|
|
}; |
|
|
|
|
|
|
|
return INPUT_PULLUP; |
|
|
|
} |
|
|
|
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}} |
|
|
|
}; |
|
|
|
|
|
|
|
static int defaultState(unsigned char index) { |
|
|
|
switch (index) { |
|
|
|
case 0: |
|
|
|
return DIGITAL1_DEFAULT_STATE; |
|
|
|
case 1: |
|
|
|
return DIGITAL2_DEFAULT_STATE; |
|
|
|
case 2: |
|
|
|
return DIGITAL3_DEFAULT_STATE; |
|
|
|
case 3: |
|
|
|
return DIGITAL4_DEFAULT_STATE; |
|
|
|
case 4: |
|
|
|
return DIGITAL5_DEFAULT_STATE; |
|
|
|
case 5: |
|
|
|
return DIGITAL6_DEFAULT_STATE; |
|
|
|
case 6: |
|
|
|
return DIGITAL7_DEFAULT_STATE; |
|
|
|
case 7: |
|
|
|
return DIGITAL8_DEFAULT_STATE; |
|
|
|
} |
|
|
|
PROGMEM_STRING(Input, "default"); |
|
|
|
PROGMEM_STRING(PullUp, "pull-up"); |
|
|
|
PROGMEM_STRING(PullDown, "pull-down"); |
|
|
|
|
|
|
|
return HIGH; |
|
|
|
} |
|
|
|
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 |
|
|
|
|
|
|
|
void setPin(unsigned char pin) { |
|
|
|
_pin = pin; |
|
|
|
} |
|
|
|
} // namespace digital |
|
|
|
} // namespace driver |
|
|
|
} // namespace sensor |
|
|
|
|
|
|
|
void setPinMode(uint8_t mode) { |
|
|
|
_pin_mode = mode; |
|
|
|
} |
|
|
|
namespace settings { |
|
|
|
namespace internal { |
|
|
|
|
|
|
|
void setDefault(int value) { |
|
|
|
_default = value; |
|
|
|
} |
|
|
|
using namespace espurna::sensor::driver; |
|
|
|
|
|
|
|
// --------------------------------------------------------------------- |
|
|
|
template<> |
|
|
|
digital::Mode convert(const String& value) { |
|
|
|
return convert(digital::settings::options::Mode, value, digital::Mode::PullUp); |
|
|
|
} |
|
|
|
|
|
|
|
unsigned char getPin() { |
|
|
|
return _pin; |
|
|
|
} |
|
|
|
String serialize(digital::Mode value) { |
|
|
|
return serialize(digital::settings::options::Mode, value); |
|
|
|
} |
|
|
|
|
|
|
|
unsigned char getPinMode() { |
|
|
|
return _pin_mode; |
|
|
|
} |
|
|
|
template<> |
|
|
|
digital::State convert(const String& value) { |
|
|
|
return convert(digital::settings::options::State, value, digital::State::High); |
|
|
|
} |
|
|
|
|
|
|
|
int getDefault() { |
|
|
|
return _default; |
|
|
|
} |
|
|
|
String serialize(digital::State value) { |
|
|
|
return serialize(digital::settings::options::State, value); |
|
|
|
} |
|
|
|
|
|
|
|
// --------------------------------------------------------------------- |
|
|
|
// Sensor API |
|
|
|
// --------------------------------------------------------------------- |
|
|
|
} // namespace internal |
|
|
|
} // namespace settings |
|
|
|
|
|
|
|
unsigned char id() const override { |
|
|
|
return SENSOR_DIGITAL_ID; |
|
|
|
} |
|
|
|
namespace sensor { |
|
|
|
namespace driver { |
|
|
|
namespace digital { |
|
|
|
namespace { |
|
|
|
|
|
|
|
unsigned char count() const override { |
|
|
|
return 1; |
|
|
|
} |
|
|
|
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)); |
|
|
|
} |
|
|
|
|
|
|
|
// Initialization method, must be idempotent |
|
|
|
void begin() override { |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
String findValueFrom(StringView key) { |
|
|
|
return espurna::settings::query::IndexedSetting::findValueFrom( |
|
|
|
build::SensorsMax, IndexedSettings, key); |
|
|
|
} |
|
|
|
|
|
|
|
void setup() { |
|
|
|
settingsRegisterQueryHandler({ |
|
|
|
.check = checkSamePrefix, |
|
|
|
.get = findValueFrom, |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
} // 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); |
|
|
|
_ready = true; |
|
|
|
|
|
|
|
switch (_default_state) { |
|
|
|
case State::Initial: |
|
|
|
_default = digitalRead(_pin); |
|
|
|
break; |
|
|
|
case State::High: |
|
|
|
_default = HIGH; |
|
|
|
break; |
|
|
|
case State::Low: |
|
|
|
_default = LOW; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 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); |
|
|
|
_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; |
|
|
|
} |
|
|
|
|
|
|
|
// Address of the sensor (it could be the GPIO or I2C address) |
|
|
|
String address(unsigned char) const override { |
|
|
|
return String(_pin, 10); |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
// Type for slot # index |
|
|
|
unsigned char type(unsigned char index) const override { |
|
|
|
if (index == 0) { |
|
|
|
return MAGNITUDE_DIGITAL; |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
return MAGNITUDE_NONE; |
|
|
|
pins[index] = pin; |
|
|
|
} |
|
|
|
|
|
|
|
// Current value for slot # index |
|
|
|
double value(unsigned char index) { |
|
|
|
if (index == 0) { |
|
|
|
if (digitalRead(_pin) != _default) { |
|
|
|
return 1; |
|
|
|
} |
|
|
|
} |
|
|
|
size_t until = index; |
|
|
|
if (!until) { |
|
|
|
err = SENSOR_ERROR_CONFIG; |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
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), |
|
|
|
})); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private: |
|
|
|
unsigned char _pin; |
|
|
|
uint8_t _pin_mode; |
|
|
|
int _default = LOW; |
|
|
|
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 |