mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
Remote magiquest protocol (#2963)
Co-authored-by: Aaron Hertz <aaron@rockforest.org> Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
parent
1a8f8adc2a
commit
f137cc10f4
5 changed files with 190 additions and 0 deletions
|
@ -27,6 +27,8 @@ from esphome.const import (
|
||||||
CONF_CARRIER_FREQUENCY,
|
CONF_CARRIER_FREQUENCY,
|
||||||
CONF_RC_CODE_1,
|
CONF_RC_CODE_1,
|
||||||
CONF_RC_CODE_2,
|
CONF_RC_CODE_2,
|
||||||
|
CONF_MAGNITUDE,
|
||||||
|
CONF_WAND_ID,
|
||||||
CONF_LEVEL,
|
CONF_LEVEL,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine
|
||||||
|
@ -391,6 +393,54 @@ async def lg_action(var, config, args):
|
||||||
cg.add(var.set_nbits(template_))
|
cg.add(var.set_nbits(template_))
|
||||||
|
|
||||||
|
|
||||||
|
# MagiQuest
|
||||||
|
(
|
||||||
|
MagiQuestData,
|
||||||
|
MagiQuestBinarySensor,
|
||||||
|
MagiQuestTrigger,
|
||||||
|
MagiQuestAction,
|
||||||
|
MagiQuestDumper,
|
||||||
|
) = declare_protocol("MagiQuest")
|
||||||
|
|
||||||
|
MAGIQUEST_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_WAND_ID): cv.hex_uint32_t,
|
||||||
|
cv.Optional(CONF_MAGNITUDE, default=0xFFFF): cv.hex_uint16_t,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_binary_sensor("magiquest", MagiQuestBinarySensor, MAGIQUEST_SCHEMA)
|
||||||
|
def magiquest_binary_sensor(var, config):
|
||||||
|
cg.add(
|
||||||
|
var.set_data(
|
||||||
|
cg.StructInitializer(
|
||||||
|
MagiQuestData,
|
||||||
|
("magnitude", config[CONF_MAGNITUDE]),
|
||||||
|
("wand_id", config[CONF_WAND_ID]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_trigger("magiquest", MagiQuestTrigger, MagiQuestData)
|
||||||
|
def magiquest_trigger(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_dumper("magiquest", MagiQuestDumper)
|
||||||
|
def magiquest_dumper(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_action("magiquest", MagiQuestAction, MAGIQUEST_SCHEMA)
|
||||||
|
async def magiquest_action(var, config, args):
|
||||||
|
template_ = await cg.templatable(config[CONF_WAND_ID], args, cg.uint32)
|
||||||
|
cg.add(var.set_wand_id(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_MAGNITUDE], args, cg.uint16)
|
||||||
|
cg.add(var.set_magnitude(template_))
|
||||||
|
|
||||||
|
|
||||||
# NEC
|
# NEC
|
||||||
NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC")
|
NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC")
|
||||||
NEC_SCHEMA = cv.Schema(
|
NEC_SCHEMA = cv.Schema(
|
||||||
|
|
83
esphome/components/remote_base/magiquest_protocol.cpp
Normal file
83
esphome/components/remote_base/magiquest_protocol.cpp
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#include "magiquest_protocol.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
/* Based on protocol analysis from
|
||||||
|
* https://arduino-irremote.github.io/Arduino-IRremote/ir__MagiQuest_8cpp_source.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace remote_base {
|
||||||
|
|
||||||
|
static const char *const TAG = "remote.magiquest";
|
||||||
|
|
||||||
|
static const uint32_t MAGIQUEST_UNIT = 288; // us
|
||||||
|
static const uint32_t MAGIQUEST_ONE_MARK = 2 * MAGIQUEST_UNIT;
|
||||||
|
static const uint32_t MAGIQUEST_ONE_SPACE = 2 * MAGIQUEST_UNIT;
|
||||||
|
static const uint32_t MAGIQUEST_ZERO_MARK = MAGIQUEST_UNIT;
|
||||||
|
static const uint32_t MAGIQUEST_ZERO_SPACE = 3 * MAGIQUEST_UNIT;
|
||||||
|
|
||||||
|
void MagiQuestProtocol::encode(RemoteTransmitData *dst, const MagiQuestData &data) {
|
||||||
|
dst->reserve(101); // 2 start bits, 48 data bits, 1 stop bit
|
||||||
|
dst->set_carrier_frequency(38000);
|
||||||
|
|
||||||
|
// 2 start bits
|
||||||
|
dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE);
|
||||||
|
dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE);
|
||||||
|
for (uint32_t mask = 1 << 31; mask; mask >>= 1) {
|
||||||
|
if (data.wand_id & mask) {
|
||||||
|
dst->item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE);
|
||||||
|
} else {
|
||||||
|
dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t mask = 1 << 15; mask; mask >>= 1) {
|
||||||
|
if (data.magnitude & mask) {
|
||||||
|
dst->item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE);
|
||||||
|
} else {
|
||||||
|
dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dst->mark(MAGIQUEST_UNIT);
|
||||||
|
}
|
||||||
|
optional<MagiQuestData> MagiQuestProtocol::decode(RemoteReceiveData src) {
|
||||||
|
MagiQuestData data{
|
||||||
|
.magnitude = 0,
|
||||||
|
.wand_id = 0,
|
||||||
|
};
|
||||||
|
// Two start bits
|
||||||
|
if (!src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE) ||
|
||||||
|
!src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t mask = 1 << 31; mask; mask >>= 1) {
|
||||||
|
if (src.expect_item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE)) {
|
||||||
|
data.wand_id |= mask;
|
||||||
|
} else if (src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) {
|
||||||
|
data.wand_id &= ~mask;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t mask = 1 << 15; mask; mask >>= 1) {
|
||||||
|
if (src.expect_item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE)) {
|
||||||
|
data.magnitude |= mask;
|
||||||
|
} else if (src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) {
|
||||||
|
data.magnitude &= ~mask;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src.expect_mark(MAGIQUEST_UNIT);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
void MagiQuestProtocol::dump(const MagiQuestData &data) {
|
||||||
|
ESP_LOGD(TAG, "Received MagiQuest: wand_id=0x%08X, magnitude=0x%04X", data.wand_id, data.magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace remote_base
|
||||||
|
} // namespace esphome
|
50
esphome/components/remote_base/magiquest_protocol.h
Normal file
50
esphome/components/remote_base/magiquest_protocol.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "remote_base.h"
|
||||||
|
|
||||||
|
/* Based on protocol analysis from
|
||||||
|
* https://arduino-irremote.github.io/Arduino-IRremote/ir__MagiQuest_8cpp_source.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace remote_base {
|
||||||
|
|
||||||
|
struct MagiQuestData {
|
||||||
|
uint16_t magnitude;
|
||||||
|
uint32_t wand_id;
|
||||||
|
|
||||||
|
bool operator==(const MagiQuestData &rhs) const {
|
||||||
|
// Treat 0xffff as a special, wildcard magnitude
|
||||||
|
// In testing, the wand never produces this value, and this allows us to match
|
||||||
|
// on just the wand_id if wanted.
|
||||||
|
if (rhs.wand_id != this->wand_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (this->wand_id == 0xffff || rhs.wand_id == 0xffff || this->wand_id == rhs.wand_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MagiQuestProtocol : public RemoteProtocol<MagiQuestData> {
|
||||||
|
public:
|
||||||
|
void encode(RemoteTransmitData *dst, const MagiQuestData &data) override;
|
||||||
|
optional<MagiQuestData> decode(RemoteReceiveData src) override;
|
||||||
|
void dump(const MagiQuestData &data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_REMOTE_PROTOCOL(MagiQuest)
|
||||||
|
|
||||||
|
template<typename... Ts> class MagiQuestAction : public RemoteTransmitterActionBase<Ts...> {
|
||||||
|
public:
|
||||||
|
TEMPLATABLE_VALUE(uint16_t, magnitude)
|
||||||
|
TEMPLATABLE_VALUE(uint32_t, wand_id)
|
||||||
|
|
||||||
|
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||||
|
MagiQuestData data{};
|
||||||
|
data.magnitude = this->magnitude_.value(x...);
|
||||||
|
data.wand_id = this->wand_id_.value(x...);
|
||||||
|
MagiQuestProtocol().encode(dst, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace remote_base
|
||||||
|
} // namespace esphome
|
|
@ -344,6 +344,7 @@ CONF_LOOP_TIME = "loop_time"
|
||||||
CONF_LOW = "low"
|
CONF_LOW = "low"
|
||||||
CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference"
|
CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference"
|
||||||
CONF_MAC_ADDRESS = "mac_address"
|
CONF_MAC_ADDRESS = "mac_address"
|
||||||
|
CONF_MAGNITUDE = "magnitude"
|
||||||
CONF_MAINS_FILTER = "mains_filter"
|
CONF_MAINS_FILTER = "mains_filter"
|
||||||
CONF_MAKE_ID = "make_id"
|
CONF_MAKE_ID = "make_id"
|
||||||
CONF_MANUAL_IP = "manual_ip"
|
CONF_MANUAL_IP = "manual_ip"
|
||||||
|
@ -735,6 +736,7 @@ CONF_VOLTAGE_DIVIDER = "voltage_divider"
|
||||||
CONF_WAIT_TIME = "wait_time"
|
CONF_WAIT_TIME = "wait_time"
|
||||||
CONF_WAIT_UNTIL = "wait_until"
|
CONF_WAIT_UNTIL = "wait_until"
|
||||||
CONF_WAKEUP_PIN = "wakeup_pin"
|
CONF_WAKEUP_PIN = "wakeup_pin"
|
||||||
|
CONF_WAND_ID = "wand_id"
|
||||||
CONF_WARM_WHITE = "warm_white"
|
CONF_WARM_WHITE = "warm_white"
|
||||||
CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature"
|
CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature"
|
||||||
CONF_WATCHDOG_THRESHOLD = "watchdog_threshold"
|
CONF_WATCHDOG_THRESHOLD = "watchdog_threshold"
|
||||||
|
|
|
@ -1861,6 +1861,11 @@ switch:
|
||||||
turn_on_action:
|
turn_on_action:
|
||||||
remote_transmitter.transmit_jvc:
|
remote_transmitter.transmit_jvc:
|
||||||
data: 0x10EF
|
data: 0x10EF
|
||||||
|
- platform: template
|
||||||
|
name: MagiQuest
|
||||||
|
turn_on_action:
|
||||||
|
remote_transmitter.transmit_magiquest:
|
||||||
|
wand_id: 0x01234567
|
||||||
- platform: template
|
- platform: template
|
||||||
name: NEC
|
name: NEC
|
||||||
id: living_room_lights_off
|
id: living_room_lights_off
|
||||||
|
|
Loading…
Reference in a new issue