|
|
- /*
-
- iFan02 MODULE
-
- Copyright (C) 2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
-
- Original implementation via RELAY module
- Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
-
- */
-
- #include "espurna.h"
-
- #if IFAN_SUPPORT
-
- #include "api.h"
- #include "button.h"
- #include "mqtt.h"
- #include "relay.h"
- #include "terminal.h"
-
- #include <array>
- #include <utility>
-
- namespace ifan02 {
-
- enum class Speed {
- Off,
- Low,
- Medium,
- High
- };
-
- const char* speedToPayload(Speed value) {
- switch (value) {
- case Speed::Off:
- return "off";
- case Speed::Low:
- return "low";
- case Speed::Medium:
- return "medium";
- case Speed::High:
- return "high";
- }
-
- return "";
- }
-
- Speed payloadToSpeed(const char* payload) {
- auto len = strlen(payload);
- if (len == 1) {
- switch (payload[0]) {
- case '0':
- return Speed::Off;
- case '1':
- return Speed::Low;
- case '2':
- return Speed::Medium;
- case '3':
- return Speed::High;
- }
- } else if (len > 1) {
- String cmp(payload);
- if (cmp == "off") {
- return Speed::Off;
- } else if (cmp == "low") {
- return Speed::Low;
- } else if (cmp == "medium") {
- return Speed::Medium;
- } else if (cmp == "high") {
- return Speed::High;
- }
- }
-
- return Speed::Off;
- }
-
- Speed payloadToSpeed(const String& string) {
- return payloadToSpeed(string.c_str());
- }
-
- } // namespace ifan02
-
- namespace settings {
- namespace internal {
-
- template <>
- ifan02::Speed convert(const String& value) {
- return ifan02::payloadToSpeed(value);
- }
-
- } // namespace internal
- } // namespace settings
-
- namespace ifan02 {
-
- constexpr unsigned long DefaultSaveDelay { 1000ul };
-
- // Remote presses trigger GPIO pushbutton events
- // Attach to a specific ID to trigger an action
-
- constexpr unsigned char DefaultLowButtonId { 1u };
- constexpr unsigned char DefaultMediumButtonId { 2u };
- constexpr unsigned char DefaultHighButtonId { 3u };
-
- // We expect to write a specific 'mask' via GPIO LOW & HIGH to set the speed
- // Sync up with the relay and write it on ON / OFF status events
-
- constexpr size_t Gpios { 3ul };
-
- using State = std::array<int8_t, Gpios>;
-
- using Pin = std::pair<int, BasePinPtr>;
- using StatePins = std::array<Pin, Gpios>;
-
- // XXX: while these are hard-coded, we don't really benefit from having these in the hardware cfg
-
- StatePins statePins() {
- return {
- {{5, nullptr},
- {4, nullptr},
- {15, nullptr}}
- };
- }
-
- constexpr int controlPin() {
- return 12;
- }
-
- struct Config {
- Config() = default;
- explicit Config(unsigned long save_, unsigned char buttonLowId_,
- unsigned char buttonMediumId_, unsigned char buttonHighId_, Speed speed_) :
- save(save_),
- buttonLowId(buttonLowId_),
- buttonMediumId(buttonMediumId_),
- buttonHighId(buttonHighId_),
- speed(speed_)
- {}
-
- unsigned long save { DefaultSaveDelay };
- unsigned char buttonLowId { DefaultLowButtonId };
- unsigned char buttonMediumId { DefaultMediumButtonId };
- unsigned char buttonHighId { DefaultHighButtonId };
- Speed speed { Speed::Off };
- StatePins state_pins;
- };
-
- Config readSettings() {
- return Config(
- getSetting("ifanSave", DefaultSaveDelay),
- getSetting("ifanBtnLowId", DefaultLowButtonId),
- getSetting("ifanBtnMediumId", DefaultMediumButtonId),
- getSetting("ifanBtnHighId", DefaultHighButtonId),
- getSetting("ifanSpeed", Speed::Medium)
- );
- }
-
- Config config;
-
- void configure() {
- config = readSettings();
- }
-
- void report(Speed speed [[gnu::unused]]) {
- #if MQTT_SUPPORT
- mqttSend(MQTT_TOPIC_SPEED, speedToPayload(speed));
- #endif
- }
-
- void save(Speed speed) {
- static Ticker ticker;
- config.speed = speed;
- ticker.once_ms(config.save, []() {
- const char* value = speedToPayload(config.speed);
- setSetting("ifanSpeed", value);
- DEBUG_MSG_P(PSTR("[IFAN] Saved speed setting \"%s\"\n"), value);
- });
- }
-
- void cleanupPins(StatePins& pins) {
- for (auto& pin : pins) {
- if (!pin.second) continue;
- gpioUnlock(pin.second->pin());
- pin.second.reset(nullptr);
- }
- }
-
- StatePins setupStatePins() {
- StatePins pins = statePins();
-
- for (auto& pair : pins) {
- auto ptr = gpioRegister(pair.first);
- if (!ptr) {
- DEBUG_MSG_P(PSTR("[IFAN] Could not set up GPIO%d\n"), pair.first);
- cleanupPins(pins);
- return pins;
- }
- ptr->pinMode(OUTPUT);
- pair.second = std::move(ptr);
- }
-
- return pins;
- }
-
- State stateFromSpeed(Speed speed) {
- switch (speed) {
- case Speed::Low:
- return {HIGH, LOW, LOW};
- case Speed::Medium:
- return {HIGH, HIGH, LOW};
- case Speed::High:
- return {HIGH, LOW, HIGH};
- case Speed::Off:
- break;
- }
-
- return {LOW, LOW, LOW};
- }
-
- const char* maskFromSpeed(Speed speed) {
- switch (speed) {
- case Speed::Low:
- return "0b100";
- case Speed::Medium:
- return "0b110";
- case Speed::High:
- return "0b101";
- case Speed::Off:
- return "0b000";
- }
-
- return "";
- }
-
- // Note that we use API speed endpoint strictly for the setting
- // (which also allows to pre-set the speed without turning the relay ON)
-
- using FanSpeedUpdate = std::function<void(Speed)>;
-
- FanSpeedUpdate onSpeedUpdate = [](Speed) {
- };
-
- void updateSpeed(Config& config, Speed speed) {
- switch (speed) {
- case Speed::Low:
- case Speed::Medium:
- case Speed::High:
- save(speed);
- report(speed);
- onSpeedUpdate(speed);
- break;
- case Speed::Off:
- break;
- }
- }
-
- void updateSpeed(Speed speed) {
- updateSpeed(config, speed);
- }
-
- void updateSpeedFromPayload(const char* payload) {
- updateSpeed(payloadToSpeed(payload));
- }
-
- void updateSpeedFromPayload(const String& payload) {
- updateSpeedFromPayload(payload.c_str());
- }
-
- #if MQTT_SUPPORT
-
- void onMqttEvent(unsigned int type, const char* topic, const char* payload) {
- switch (type) {
-
- case MQTT_CONNECT_EVENT:
- mqttSubscribe(MQTT_TOPIC_SPEED);
- break;
-
- case MQTT_MESSAGE_EVENT: {
- auto parsed = mqttMagnitude(topic);
- if (parsed.startsWith(MQTT_TOPIC_SPEED)) {
- updateSpeedFromPayload(payload);
- }
- break;
- }
-
- }
- }
-
- #endif // MQTT_SUPPORT
-
- class FanProvider : public RelayProviderBase {
- public:
- explicit FanProvider(BasePinPtr&& pin, const Config& config, FanSpeedUpdate& callback) :
- _pin(std::move(pin)),
- _config(config)
- {
- callback = [this](Speed speed) {
- change(speed);
- };
- }
-
- const char* id() const override {
- return "fan";
- }
-
- void change(Speed speed) {
- _pin->digitalWrite((Speed::Off != speed) ? HIGH : LOW);
-
- auto state = stateFromSpeed(speed);
- DEBUG_MSG_P(PSTR("[IFAN] State mask: %s\n"), maskFromSpeed(speed));
-
- for (size_t index = 0; index < _config.state_pins.size(); ++index) {
- auto& pin = _config.state_pins[index].second;
- if (!pin) {
- continue;
- }
-
- pin->digitalWrite(state[index]);
- }
- }
-
- void change(bool status) override {
- change(status ? _config.speed : Speed::Off);
- }
-
- private:
- BasePinPtr _pin;
- const Config& _config;
- };
-
- void setup() {
-
- config.state_pins = setupStatePins();
- if (!config.state_pins.size()) {
- return;
- }
-
- configure();
-
- espurnaRegisterReload(configure);
-
- auto relay_pin = gpioRegister(controlPin());
- if (relay_pin) {
- auto provider = std::make_unique<FanProvider>(std::move(relay_pin), config, onSpeedUpdate);
- if (!relayAdd(std::move(provider))) {
- DEBUG_MSG_P(PSTR("[IFAN] Could not add relay provider for GPIO%d\n"), relay_pin->pin());
- gpioUnlock(relay_pin->pin());
- }
- }
-
- #if BUTTON_SUPPORT
- buttonSetCustomAction([](unsigned char id) {
- if (config.buttonLowId == id) {
- updateSpeed(Speed::Low);
- } else if (config.buttonMediumId == id) {
- updateSpeed(Speed::Medium);
- } else if (config.buttonHighId == id) {
- updateSpeed(Speed::High);
- }
- });
- #endif
-
- #if MQTT_SUPPORT
- mqttRegister(onMqttEvent);
- #endif
-
- #if API_SUPPORT
- apiRegister(F(MQTT_TOPIC_SPEED),
- [](ApiRequest& request) {
- request.send(speedToPayload(config.speed));
- return true;
- },
- [](ApiRequest& request) {
- updateSpeedFromPayload(request.param(F("value")));
- return true;
- }
- );
- #endif
-
- #if TERMINAL_SUPPORT
- terminalRegisterCommand(F("SPEED"), [](const terminal::CommandContext& ctx) {
- if (ctx.argc == 2) {
- updateSpeedFromPayload(ctx.argv[1]);
- }
-
- ctx.output.println(speedToPayload(config.speed));
- terminalOK(ctx);
- });
- #endif
-
- }
-
- } // namespace ifan
-
- void ifanSetup() {
- ifan02::setup();
- }
-
- #endif // IFAN_SUPPORT
|