mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 09:18:00 +01:00
[remote_base] Add Nexus protocol
Signed-off-by: zry98 <dev@zry.io>
This commit is contained in:
parent
77bb46ff3b
commit
75e8f2d363
6 changed files with 290 additions and 0 deletions
|
@ -36,6 +36,10 @@ from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_BUTTON,
|
CONF_BUTTON,
|
||||||
CONF_CHECK,
|
CONF_CHECK,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_BATTERY_LEVEL,
|
||||||
|
CONF_FORCE_UPDATE,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||||
|
@ -1963,3 +1967,67 @@ async def mirage_action(var, config, args):
|
||||||
vec_ = cg.std_vector.template(cg.uint8)
|
vec_ = cg.std_vector.template(cg.uint8)
|
||||||
template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_)
|
template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_)
|
||||||
cg.add(var.set_code(template_))
|
cg.add(var.set_code(template_))
|
||||||
|
|
||||||
|
|
||||||
|
# Nexus
|
||||||
|
(
|
||||||
|
NexusData,
|
||||||
|
NexusBinarySensor,
|
||||||
|
NexusTrigger,
|
||||||
|
NexusAction,
|
||||||
|
NexusDumper,
|
||||||
|
) = declare_protocol("Nexus")
|
||||||
|
|
||||||
|
NEXUS_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_CHANNEL): cv.All(cv.uint8_t, cv.Range(min=1, max=4)),
|
||||||
|
cv.Required(CONF_ADDRESS): cv.All(cv.uint8_t, cv.Range(min=0, max=255)),
|
||||||
|
cv.Optional(CONF_TEMPERATURE, default="25.5"): cv.All(
|
||||||
|
cv.float_, cv.Range(min=-204.8, max=204.7)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HUMIDITY, default="42"): cv.All(
|
||||||
|
cv.uint8_t, cv.Range(min=0, max=255)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BATTERY_LEVEL, default="true"): cv.boolean,
|
||||||
|
cv.Optional(CONF_FORCE_UPDATE, default="false"): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_binary_sensor("nexus", NexusBinarySensor, NEXUS_SCHEMA)
|
||||||
|
def nexus_binary_sensor(var, config):
|
||||||
|
cg.add(
|
||||||
|
var.set_data(
|
||||||
|
cg.StructInitializer(
|
||||||
|
NexusData,
|
||||||
|
("channel", config[CONF_CHANNEL]),
|
||||||
|
("address", config[CONF_ADDRESS]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_trigger("nexus", NexusTrigger, NexusData)
|
||||||
|
def nexus_trigger(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_dumper("nexus", NexusDumper)
|
||||||
|
def nexus_dumper(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_action("nexus", NexusAction, NEXUS_SCHEMA)
|
||||||
|
async def nexus_action(var, config, args):
|
||||||
|
template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||||
|
cg.add(var.set_channel(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
|
||||||
|
cg.add(var.set_address(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
|
||||||
|
cg.add(var.set_temperature(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_HUMIDITY], args, cg.uint8)
|
||||||
|
cg.add(var.set_humidity(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_BATTERY_LEVEL], args, cg.bool_)
|
||||||
|
cg.add(var.set_battery_level(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_FORCE_UPDATE], args, cg.bool_)
|
||||||
|
cg.add(var.set_force_update(template_))
|
||||||
|
|
146
esphome/components/remote_base/nexus_protocol.cpp
Normal file
146
esphome/components/remote_base/nexus_protocol.cpp
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
#include "nexus_protocol.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace remote_base {
|
||||||
|
|
||||||
|
static const char *const TAG = "remote.nexus";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example Nexus (Digoo) temperature & humidity sensor packet (36 data bits each with 2 pulses, 72 pulses in total):
|
||||||
|
* 10000101 | 1 | 0 | 01 | 000010001010 | 1111 | 01000101
|
||||||
|
* address (sensor ID) | battery | force update | channel | temperature | - | humidity
|
||||||
|
*
|
||||||
|
* channels start from 0, temperature is 12 bit signed scaled by 10, `-`s are constant bits
|
||||||
|
*
|
||||||
|
* Ref: https://manual.pilight.org/protocols/433.92/weather/nexus.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const uint8_t NBITS = 72;
|
||||||
|
|
||||||
|
static const uint32_t HEADER_HIGH_US = 500;
|
||||||
|
static const uint32_t HEADER_LOW_US = 3900;
|
||||||
|
static const uint32_t BIT_HIGH_US = 500;
|
||||||
|
static const uint32_t BIT_ZERO_LOW_US = 980;
|
||||||
|
static const uint32_t BIT_ONE_LOW_US = 1950;
|
||||||
|
|
||||||
|
void NexusProtocol::encode(RemoteTransmitData *dst, const NexusData &data) {
|
||||||
|
// encode temperature
|
||||||
|
int16_t t = int16_t(data.temperature * 10.0f);
|
||||||
|
if (t < 0)
|
||||||
|
t &= 0x0FFF;
|
||||||
|
|
||||||
|
dst->reserve(NBITS + 2); // with header of 2 pulses
|
||||||
|
|
||||||
|
// send header
|
||||||
|
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
|
||||||
|
|
||||||
|
// send address
|
||||||
|
for (uint8_t mask = 1UL << 7; mask != 0; mask >>= 1) {
|
||||||
|
if (data.address & mask) {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
||||||
|
} else {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send battery level ok flag
|
||||||
|
if (data.battery_level) {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
||||||
|
} else {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send force update (forced transmission) flag
|
||||||
|
if (data.force_update) {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
||||||
|
} else {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send channel
|
||||||
|
for (uint8_t mask = 1UL << 1; mask != 0; mask >>= 1) {
|
||||||
|
if ((data.channel - 1) & mask) {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
||||||
|
} else {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// send temperature
|
||||||
|
for (uint16_t mask = 1UL << 11; mask != 0; mask >>= 1) {
|
||||||
|
if (t & mask) {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
||||||
|
} else {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send constant bits 0b1111
|
||||||
|
for (uint8_t i = 0; i < 4; i++) {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send humidity
|
||||||
|
for (uint8_t mask = 1UL << 7; mask != 0; mask >>= 1) {
|
||||||
|
if (data.humidity & mask) {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
||||||
|
} else {
|
||||||
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<NexusData> NexusProtocol::decode(RemoteReceiveData src) {
|
||||||
|
NexusData out{
|
||||||
|
.channel = 0,
|
||||||
|
.address = 0,
|
||||||
|
.temperature = 0,
|
||||||
|
.humidity = 0,
|
||||||
|
.battery_level = false,
|
||||||
|
.force_update = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// check packet size
|
||||||
|
if (src.size() < NBITS)
|
||||||
|
return {};
|
||||||
|
// check constant bits
|
||||||
|
if (!src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US, 24 * 2) || !src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US, 25 * 2) ||
|
||||||
|
!src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US, 26 * 2) || !src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US, 27 * 2))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
uint64_t packet = 0;
|
||||||
|
for (uint8_t i = 0; i < NBITS / 2; i++) {
|
||||||
|
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
|
||||||
|
packet = (packet << 1) | 1;
|
||||||
|
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
|
||||||
|
packet = (packet << 1) | 0;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check constant bits
|
||||||
|
if (((packet >> 8) & 0b1111) != 0b1111) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Nexus packet: 0x%09" PRIX64, packet);
|
||||||
|
|
||||||
|
out.channel = ((packet >> 24) & 0b11) + 1;
|
||||||
|
out.address = (packet >> 28) & 0xFF;
|
||||||
|
int16_t t = (packet >> 12) & 0x0FFF;
|
||||||
|
t = 0x0800 & t ? 0xF000 | t : t;
|
||||||
|
out.temperature = float(t) / 10.0f;
|
||||||
|
out.humidity = packet & 0xFF;
|
||||||
|
out.battery_level = (packet >> 27) & 0b1;
|
||||||
|
out.force_update = (packet >> 26) & 0b1;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NexusProtocol::dump(const NexusData &data) {
|
||||||
|
ESP_LOGI(TAG, "Received Nexus: ch=%u, address=%u, temp=%.1f, humi=%u, bat=%d, forced=%d", data.channel, data.address,
|
||||||
|
data.temperature, data.humidity, data.battery_level, data.force_update);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace remote_base
|
||||||
|
} // namespace esphome
|
56
esphome/components/remote_base/nexus_protocol.h
Normal file
56
esphome/components/remote_base/nexus_protocol.h
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "remote_base.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace remote_base {
|
||||||
|
|
||||||
|
struct NexusData {
|
||||||
|
uint8_t channel;
|
||||||
|
uint8_t address;
|
||||||
|
float temperature;
|
||||||
|
uint8_t humidity;
|
||||||
|
bool battery_level;
|
||||||
|
bool force_update;
|
||||||
|
|
||||||
|
bool operator==(const NexusData &rhs) const {
|
||||||
|
return channel == rhs.channel && address == rhs.address && temperature == rhs.temperature &&
|
||||||
|
humidity == rhs.humidity && battery_level == rhs.battery_level && force_update == rhs.force_update;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class NexusProtocol : public RemoteProtocol<NexusData> {
|
||||||
|
public:
|
||||||
|
void encode(RemoteTransmitData *dst, const NexusData &data) override;
|
||||||
|
optional<NexusData> decode(RemoteReceiveData src) override;
|
||||||
|
void dump(const NexusData &data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_REMOTE_PROTOCOL(Nexus)
|
||||||
|
|
||||||
|
template<typename... Ts> class NexusAction : public RemoteTransmitterActionBase<Ts...> {
|
||||||
|
public:
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, address)
|
||||||
|
TEMPLATABLE_VALUE(float, temperature)
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, humidity)
|
||||||
|
TEMPLATABLE_VALUE(bool, battery_level)
|
||||||
|
TEMPLATABLE_VALUE(bool, force_update)
|
||||||
|
|
||||||
|
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||||
|
NexusData data{};
|
||||||
|
data.channel = this->channel_.value(x...);
|
||||||
|
data.address = this->address_.value(x...);
|
||||||
|
data.temperature = this->temperature_.value(x...);
|
||||||
|
data.humidity = this->humidity_.value(x...);
|
||||||
|
data.battery_level = this->battery_level_.value(x...);
|
||||||
|
data.force_update = this->force_update_.value(x...);
|
||||||
|
NexusProtocol().encode(dst, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace remote_base
|
||||||
|
} // namespace esphome
|
|
@ -148,6 +148,11 @@ remote_receiver:
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
ESP_LOGD("mirage", "Mirage data: %s", format_hex(x.data).c_str());
|
ESP_LOGD("mirage", "Mirage data: %s", format_hex(x.data).c_str());
|
||||||
|
on_nexus:
|
||||||
|
then:
|
||||||
|
- logger.log:
|
||||||
|
format: "on_nexus: %u %u %.1f %u %d %d"
|
||||||
|
args: ["x.channel", "x.address", "x.temperature", "x.humidity", "x.battery_level", "x.force_update"]
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: remote_receiver
|
- platform: remote_receiver
|
||||||
|
|
|
@ -146,6 +146,11 @@ remote_receiver:
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
ESP_LOGD("mirage", "Mirage data: %s", format_hex(x.data).c_str());
|
ESP_LOGD("mirage", "Mirage data: %s", format_hex(x.data).c_str());
|
||||||
|
on_nexus:
|
||||||
|
then:
|
||||||
|
- logger.log:
|
||||||
|
format: "on_nexus: %u %u %.1f %u %d %d"
|
||||||
|
args: ["x.channel", "x.address", "x.temperature", "x.humidity", "x.battery_level", "x.force_update"]
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: remote_receiver
|
- platform: remote_receiver
|
||||||
|
|
|
@ -190,3 +190,13 @@ button:
|
||||||
channel: 1
|
channel: 1
|
||||||
button: 1
|
button: 1
|
||||||
check: 1
|
check: 1
|
||||||
|
- platform: template
|
||||||
|
name: Nexus
|
||||||
|
on_press:
|
||||||
|
remote_transmitter.transmit_nexus:
|
||||||
|
channel: 1
|
||||||
|
address: 42
|
||||||
|
temperature: 42.0
|
||||||
|
humidity: 69
|
||||||
|
battery_level: false
|
||||||
|
force_update: false
|
||||||
|
|
Loading…
Reference in a new issue