Files
espurna/code/espurna/lightfox.cpp
Maxim Prokhorov bd3a588977 hardware: lightfox fixes & buttonAdd
Should be buildable once again

Restore the behaviour from #1468, where button clicks were triggered
Inject relay provider using the relayAdd, add buttonAdd to provide input handling

Remove extra button handling code from the WebUI

Still assuming that DUAL code really needs a syncronized way to handle
input & output with relays, so we can't treat it as a real button.
Without the device it is only guessing, and various issues / comments
are not really clear on that part :/
2021-06-09 11:20:07 +03:00

234 lines
5.5 KiB
C++

/*
LightFox module
Copyright (C) 2019 by Andrey F. Kupreychik <foxle@quickfox.ru>
*/
#include "espurna.h"
#ifdef FOXEL_LIGHTFOX_DUAL
#include "button.h"
#include "lightfox.h"
#include "relay.h"
#include "terminal.h"
#include "ws.h"
#include <bitset>
#include <vector>
static_assert(1 == (RELAY_SUPPORT), "");
static_assert(1 == (BUTTON_SUPPORT), "");
constexpr size_t _lightfoxBuildButtons() {
return LIGHTFOX_BUTTONS;
}
constexpr size_t _lightfoxBuildRelays() {
return LIGHTFOX_RELAYS;
}
// -----------------------------------------------------------------------------
// PROTOCOL
// -----------------------------------------------------------------------------
constexpr uint8_t CodeStart { 0xa0 };
constexpr uint8_t CodeLearn { 0xf1 };
constexpr uint8_t CodeClear { 0xf2 };
constexpr uint8_t CodeStop { 0xa1 };
void _lightfoxSend(uint8_t code) {
uint8_t data[6] {
CodeStart,
code,
0x00,
CodeStop,
static_cast<uint8_t>('\r'),
static_cast<uint8_t>('\n')
};
Serial.write(data, sizeof(data));
Serial.flush();
DEBUG_MSG_P(PSTR("[LIGHTFOX] Code %02X sent\n"), code);
}
void lightfoxLearn() {
_lightfoxSend(CodeLearn);
}
void lightfoxClear() {
_lightfoxSend(CodeClear);
}
class LightfoxProvider : public RelayProviderBase {
public:
LightfoxProvider() = delete;
explicit LightfoxProvider(size_t id) :
_id(id)
{
_instances.push_back(this);
}
~LightfoxProvider() {
_instances.erase(
std::remove(_instances.begin(), _instances.end(), this),
_instances.end());
}
const char* id() const override {
return "lightfox";
}
bool setup() override {
static bool once { false };
if (!once) {
once = true;
Serial.begin(SERIAL_BAUDRATE);
}
return true;
}
void change(bool) override {
static bool scheduled { false };
if (!scheduled) {
schedule_function([]() {
flush();
scheduled = false;
});
}
}
size_t relayId() const {
return _id;
}
static std::vector<LightfoxProvider*>& instances() {
return _instances;
}
static void flush() {
size_t mask { 0ul };
for (size_t index = 0; index < _instances.size(); ++index) {
bool status { relayStatus(_instances[index]->relayId()) };
mask |= (status ? 1ul : 0ul << index);
}
DEBUG_MSG_P(PSTR("[LIGHTFOX] Sending DUAL mask: 0x%02X\n"), mask);
uint8_t buffer[4] { 0xa0, 0x04, static_cast<uint8_t>(mask), 0xa1 };
Serial.write(buffer, sizeof(buffer));
Serial.flush();
}
private:
size_t _id;
static std::vector<LightfoxProvider*> _instances;
};
std::vector<LightfoxProvider*> LightfoxProvider::_instances;
size_t _lightfox_button_offset { 0 };
size_t _lightfox_buttons { 0 };
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
void _lightfoxWebSocketOnVisible(JsonObject& root) {
root["lightfoxVisible"] = 1;
}
void _lightfoxWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "lightfoxLearn") == 0) {
lightfoxLearn();
} else if (strcmp(action, "lightfoxClear") == 0) {
lightfoxClear();
}
}
#endif
// -----------------------------------------------------------------------------
// TERMINAL
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void _lightfoxInitCommands() {
terminalRegisterCommand(F("LIGHTFOX.LEARN"), [](const terminal::CommandContext& ctx) {
lightfoxLearn();
terminalOK(ctx);
});
terminalRegisterCommand(F("LIGHTFOX.CLEAR"), [](const terminal::CommandContext& ctx) {
lightfoxClear();
terminalOK(ctx);
});
}
#endif
// -----------------------------------------------------------------------------
// SETUP & LOOP
// -----------------------------------------------------------------------------
void _lightfoxInputLoop() {
if (Serial.available() < 4) {
return;
}
unsigned char bytes[4] = {0};
Serial.readBytes(bytes, 4);
if ((bytes[0] != 0xA0) && (bytes[1] != 0x04) && (bytes[3] != 0xA1)) {
return;
}
// Unlike DUAL, inputs may have different IDs than the outputs
// ref. https://github.com/foxel/esp-dual-rf-switch
constexpr unsigned long InputsMask { 0xf };
unsigned long mask { static_cast<unsigned long>(bytes[2]) & InputsMask };
unsigned long id { 0 };
for (size_t button = 0; id < _lightfox_buttons; ++button) {
if (mask & (1ul << button)) {
buttonEvent(button + _lightfox_button_offset, ButtonEvent::Click);
}
}
}
void lightfoxSetup() {
#if WEB_SUPPORT
wsRegister()
.onVisible(_lightfoxWebSocketOnVisible)
.onAction(_lightfoxWebSocketOnAction);
#endif
#if TERMINAL_SUPPORT
_lightfoxInitCommands();
#endif
for (size_t relay = 0; relay < _lightfoxBuildRelays(); ++relay) {
size_t relayId { relayCount() };
if (!relayAdd(std::make_unique<LightfoxProvider>(relayId))) {
break;
}
}
_lightfox_button_offset = buttonCount();
for (size_t index = 0; index < _lightfoxBuildButtons(); ++index) {
if (buttonAdd()) {
++_lightfox_buttons;
}
}
espurnaRegisterLoop(_lightfoxInputLoop);
}
#endif