[remote_base] Add Nexus protocol

Signed-off-by: zry98 <dev@zry.io>
This commit is contained in:
zry98 2024-11-02 21:44:46 +01:00
parent 77bb46ff3b
commit 75e8f2d363
No known key found for this signature in database
GPG key ID: D239A8A2222B2691
6 changed files with 290 additions and 0 deletions

View file

@ -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_))

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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