mirror of
https://github.com/esphome/esphome.git
synced 2024-11-23 15:38:11 +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_BUTTON,
|
||||
CONF_CHECK,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_FORCE_UPDATE,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
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)
|
||||
template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_)
|
||||
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:
|
||||
- lambda: |-
|
||||
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:
|
||||
- platform: remote_receiver
|
||||
|
|
|
@ -146,6 +146,11 @@ remote_receiver:
|
|||
then:
|
||||
- lambda: |-
|
||||
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:
|
||||
- platform: remote_receiver
|
||||
|
|
|
@ -190,3 +190,13 @@ button:
|
|||
channel: 1
|
||||
button: 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