Browse Source

ifan: refactor into a separate module

mcspr-patch-1
Maxim Prokhorov 3 years ago
parent
commit
a40eca30ad
10 changed files with 402 additions and 78 deletions
  1. +9
    -0
      code/espurna/config/general.h
  2. +5
    -13
      code/espurna/config/hardware.h
  3. +364
    -0
      code/espurna/ifan.cpp
  4. +11
    -0
      code/espurna/ifan.h
  5. +4
    -0
      code/espurna/main.cpp
  6. +2
    -2
      code/espurna/mqtt.cpp
  7. +1
    -1
      code/espurna/mqtt.h
  8. +4
    -61
      code/espurna/relay.cpp
  9. +1
    -1
      code/espurna/tuya.cpp
  10. +1
    -0
      code/test/build/nondefault.h

+ 9
- 0
code/espurna/config/general.h View File

@ -1805,6 +1805,7 @@
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// TUYA switch & dimmer support // TUYA switch & dimmer support
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
#ifndef TUYA_SUPPORT #ifndef TUYA_SUPPORT
#define TUYA_SUPPORT 0 #define TUYA_SUPPORT 0
#endif #endif
@ -1837,6 +1838,14 @@
#define PROMETHEUS_SUPPORT 0 #define PROMETHEUS_SUPPORT 0
#endif #endif
//--------------------------------------------------------------------------------
// ITEAD iFan support
//--------------------------------------------------------------------------------
#ifndef IFAN_SUPPORT
#define IFAN_SUPPORT 0
#endif
// ============================================================================= // =============================================================================
// Configuration helpers // Configuration helpers
// ============================================================================= // =============================================================================


+ 5
- 13
code/espurna/config/hardware.h View File

@ -1010,25 +1010,17 @@
#define MANUFACTURER "ITEAD" #define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_IFAN02" #define DEVICE "SONOFF_IFAN02"
// These are virtual buttons triggered by the remote
// Base module
#define IFAN_SUPPORT 1
// These buttons are triggered by the remote
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define BUTTON2_PIN 9 #define BUTTON2_PIN 9
#define BUTTON3_PIN 10 #define BUTTON3_PIN 10
#define BUTTON4_PIN 14 #define BUTTON4_PIN 14
#define BUTTON1_CONFIG BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON2_CONFIG BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON3_CONFIG BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON4_CONFIG BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Relays
// Only one relay by default, controlling the ON / OFF
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY2_PIN 5
#define RELAY3_PIN 4
#define RELAY4_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_TYPE RELAY_TYPE_NORMAL
#define RELAY3_TYPE RELAY_TYPE_NORMAL
#define RELAY4_TYPE RELAY_TYPE_NORMAL
// LEDs // LEDs
#define LED1_PIN 13 #define LED1_PIN 13


+ 364
- 0
code/espurna/ifan.cpp View File

@ -0,0 +1,364 @@
/*
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 DefaultRelayId { 0u };
constexpr unsigned char DefaultStateButton { 0u };
constexpr unsigned char DefaultLowButton { 1u };
constexpr unsigned char DefaultMediumButton { 2u };
constexpr unsigned char DefaultHighButton { 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}}
};
}
struct Config {
unsigned long save { DefaultSaveDelay };
unsigned char relayId { RELAY_NONE };
unsigned char buttonLowId { RELAY_NONE };
unsigned char buttonMediumId { RELAY_NONE };
unsigned char buttonHighId { RELAY_NONE };
Speed speed { Speed::Off };
StatePins state_pins;
};
Config readSettings() {
return {
getSetting("ifanSave", DefaultSaveDelay),
getSetting("ifanRelayId", DefaultRelayId),
getSetting("ifanBtnLowId", DefaultMediumButton),
getSetting("ifanBtnLowId", DefaultMediumButton),
getSetting("ifanBtnHighId", DefaultHighButton),
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 "";
}
void setSpeed(StatePins& pins, Speed speed) {
auto state = stateFromSpeed(speed);
DEBUG_MSG_P(PSTR("[IFAN] State mask: %s\n"), maskFromSpeed(speed));
for (size_t index = 0; index < pins.size(); ++ index) {
if (!pins[index].second) continue;
pins[index].second->digitalWrite(state[index]);
}
}
void setSpeed(Speed speed) {
setSpeed(config.state_pins, speed);
}
// Note that we use API speed endpoint strictly for the setting
// (which also allows to pre-set the speed without turning the relay ON)
void setSpeedFromPayload(const char* payload) {
auto speed = payloadToSpeed(payload);
switch (speed) {
case Speed::Low:
case Speed::Medium:
case Speed::High:
setSpeed(speed);
report(speed);
save(speed);
break;
case Speed::Off:
break;
}
}
void setSpeedFromPayload(const String& payload) {
setSpeedFromPayload(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)) {
setSpeedFromPayload(payload);
}
break;
}
}
}
#endif // MQTT_SUPPORT
void setSpeedFromStatus(bool status) {
setSpeed(status ? config.speed : Speed::Off);
}
void setup() {
config.state_pins = setupStatePins();
if (!config.state_pins.size()) {
return;
}
configure();
espurnaRegisterReload(configure);
#if BUTTON_SUPPORT
ButtonBroker::Register([](unsigned char id, button_event_t event) {
// TODO: add special 'custom' action for buttons, and trigger via basic callback?
// that way we don't depend on the event type and directly trigger with whatever cfg says
if (event != button_event_t::Click) {
return;
}
if (config.buttonLowId == id) {
setSpeed(Speed::Low);
} else if (config.buttonMediumId == id) {
setSpeed(Speed::Medium);
} else if (config.buttonHighId == id) {
setSpeed(Speed::High);
}
});
#endif
#if RELAY_SUPPORT
setSpeedFromStatus(relayStatus(config.relayId));
relaySetStatusChange([](unsigned char id, bool status) {
if (config.relayId == id) {
setSpeedFromStatus(status);
}
});
#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) {
setSpeedFromPayload(request.param(F("value")));
return true;
}
);
#endif
#if TERMINAL_SUPPORT
terminalRegisterCommand(F("SPEED"), [](const terminal::CommandContext& ctx) {
if (ctx.argc == 2) {
setSpeedFromPayload(ctx.argv[1]);
}
ctx.output.println(speedToPayload(config.speed));
terminalOK(ctx);
});
#endif
}
} // namespace ifan
void ifanSetup() {
ifan02::setup();
}
#endif // IFAN_SUPPORT

+ 11
- 0
code/espurna/ifan.h View File

@ -0,0 +1,11 @@
/*
iFan MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#pragma once
void ifanSetup();

+ 4
- 0
code/espurna/main.cpp View File

@ -34,6 +34,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "garland.h" #include "garland.h"
#include "i2c.h" #include "i2c.h"
#include "influxdb.h" #include "influxdb.h"
#include "ifan.h"
#include "ir.h" #include "ir.h"
#include "led.h" #include "led.h"
#include "light.h" #include "light.h"
@ -304,6 +305,9 @@ void setup() {
#if KINGART_CURTAIN_SUPPORT #if KINGART_CURTAIN_SUPPORT
kingartCurtainSetup(); kingartCurtainSetup();
#endif #endif
#if IFAN_SUPPORT
ifanSetup();
#endif
// 3rd party code hook // 3rd party code hook
#if USE_EXTRA #if USE_EXTRA


+ 2
- 2
code/espurna/mqtt.cpp View File

@ -523,7 +523,7 @@ void _mqttCallback(unsigned int type, const char * topic, const char * payload)
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Match topic // Match topic
String t = mqttMagnitude((char *) topic);
String t = mqttMagnitude(topic);
// Actions // Actions
if (t.equals(MQTT_TOPIC_ACTION)) { if (t.equals(MQTT_TOPIC_ACTION)) {
@ -646,7 +646,7 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
@param topic the full MQTT topic @param topic the full MQTT topic
@return String object with the magnitude part. @return String object with the magnitude part.
*/ */
String mqttMagnitude(char * topic) {
String mqttMagnitude(const char* topic) {
String pattern = _mqtt_topic + _mqtt_setter; String pattern = _mqtt_topic + _mqtt_setter;
int position = pattern.indexOf("#"); int position = pattern.indexOf("#");


+ 1
- 1
code/espurna/mqtt.h View File

@ -26,7 +26,7 @@ void mqttRegister(mqtt_callback_f callback);
String mqttTopic(const char * magnitude, bool is_set); String mqttTopic(const char * magnitude, bool is_set);
String mqttTopic(const char * magnitude, unsigned int index, bool is_set); String mqttTopic(const char * magnitude, unsigned int index, bool is_set);
String mqttMagnitude(char * topic);
String mqttMagnitude(const char* topic);
bool mqttSendRaw(const char * topic, const char * message, bool retain); bool mqttSendRaw(const char * topic, const char * message, bool retain);
bool mqttSendRaw(const char * topic, const char * message); bool mqttSendRaw(const char * topic, const char * message);


+ 4
- 61
code/espurna/relay.cpp View File

@ -739,34 +739,6 @@ void _relayProcess(bool mode) {
} }
} }
#if defined(ITEAD_SONOFF_IFAN02)
unsigned char _relay_ifan02_speeds[] = {0, 1, 3, 5};
unsigned char getSpeed() {
unsigned char speed =
(_relays[1].target_status ? 1 : 0) +
(_relays[2].target_status ? 2 : 0) +
(_relays[3].target_status ? 4 : 0);
for (unsigned char i=0; i<4; i++) {
if (_relay_ifan02_speeds[i] == speed) return i;
}
return 0;
}
void setSpeed(unsigned char speed) {
if ((0 <= speed) & (speed <= 3)) {
if (getSpeed() == speed) return;
unsigned char states = _relay_ifan02_speeds[speed];
for (unsigned char i=0; i<3; i++) {
relayStatus(i+1, states & 1 == 1);
states >>= 1;
}
}
}
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RELAY // RELAY
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1350,20 +1322,6 @@ void relaySetupAPI() {
} }
); );
#if defined(ITEAD_SONOFF_IFAN02)
apiRegister(F(MQTT_TOPIC_SPEED), {
[](ApiRequest& request) {
request.send(String(static_cast<int>(getSpeed())));
return true;
},
[](ApiRequest& request) {
setSpeed(atoi(request.param(F("value"))));
return true;
},
nullptr
});
#endif
} }
#endif // API_SUPPORT #endif // API_SUPPORT
@ -1432,13 +1390,6 @@ void relayMQTT(unsigned char id) {
_relayMQTTGroup(id); _relayMQTTGroup(id);
} }
// Send speed for IFAN02
#if defined (ITEAD_SONOFF_IFAN02)
char buffer[5];
snprintf(buffer, sizeof(buffer), "%u", getSpeed());
mqttSend(MQTT_TOPIC_SPEED, buffer);
#endif
} }
void relayMQTT() { void relayMQTT() {
@ -1473,6 +1424,10 @@ void relayStatusWrap(unsigned char id, PayloadStatus value, bool is_group_topic)
void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) { void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (!relayCount()) {
return;
}
if (type == MQTT_CONNECT_EVENT) { if (type == MQTT_CONNECT_EVENT) {
// Send status on connect // Send status on connect
@ -1490,10 +1445,6 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
snprintf_P(pulse_topic, sizeof(pulse_topic), PSTR("%s/+"), MQTT_TOPIC_PULSE); snprintf_P(pulse_topic, sizeof(pulse_topic), PSTR("%s/+"), MQTT_TOPIC_PULSE);
mqttSubscribe(pulse_topic); mqttSubscribe(pulse_topic);
#if defined(ITEAD_SONOFF_IFAN02)
mqttSubscribe(MQTT_TOPIC_SPEED);
#endif
// Subscribe to group topics // Subscribe to group topics
for (unsigned char i=0; i < _relays.size(); i++) { for (unsigned char i=0; i < _relays.size(); i++) {
const auto t = getSetting({"mqttGroup", i}); const auto t = getSetting({"mqttGroup", i});
@ -1547,13 +1498,6 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
} }
} }
// Itead Sonoff IFAN02
#if defined (ITEAD_SONOFF_IFAN02)
if (t.startsWith(MQTT_TOPIC_SPEED)) {
setSpeed(atoi(payload));
}
#endif
} }
// TODO: safeguard against network issues. this one has good intentions, but we may end up // TODO: safeguard against network issues. this one has good intentions, but we may end up
@ -1583,7 +1527,6 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
} }
void relaySetupMQTT() { void relaySetupMQTT() {
if (!relayCount()) return;
mqttRegister(relayMQTTCallback); mqttRegister(relayMQTTCallback);
} }


+ 1
- 1
code/espurna/tuya.cpp View File

@ -2,7 +2,7 @@
TUYA MODULE TUYA MODULE
Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/ */


+ 1
- 0
code/test/build/nondefault.h View File

@ -18,3 +18,4 @@
#define MCP23S08_SUPPORT 1 #define MCP23S08_SUPPORT 1
#define RELAY_PROVIDER_DUAL_SUPPORT 1 #define RELAY_PROVIDER_DUAL_SUPPORT 1
#define RELAY_PROVIDER_STM_SUPPORT 1 #define RELAY_PROVIDER_STM_SUPPORT 1
#define IFAN_SUPPORT 1

Loading…
Cancel
Save