periodic push
This commit is contained in:
352
esphome/include/bcm500ds.h
Executable file
352
esphome/include/bcm500ds.h
Executable file
@@ -0,0 +1,352 @@
|
||||
#include "esphome.h"
|
||||
|
||||
// protocol
|
||||
#define TUYA_COVER_MAX_LEN 640 // Max length of message value
|
||||
// #define TUYA_COVER_MAX_LEN 256 // Max length of message value
|
||||
#define TUYA_COVER_BUFFER_LEN 6 // Length of serial buffer for header + type + length
|
||||
#define TUYA_COVER_HEADER_LEN 2 // Length of fixed header
|
||||
|
||||
// enable/disable reversed motor direction
|
||||
// Normal = header (55AA) + (00060005) + 050100010011 "(55AA00060005050100010011)
|
||||
// Reversed = header (55AA) + (00060005) + 050100010112 "(55AA00060005050100010112)"
|
||||
#define TUYA_COVER_DISABLE_REVERSING { 0x69, 0x01, 0x00, 0x01, 0x00 } //dpid = 105, type = bool, len = 1, value = disable
|
||||
#define TUYA_COVER_ENABLE_REVERSING { 0x69, 0x01, 0x00, 0x01, 0x01 } //dpid = 105, type = bool, len = 1, value = enable
|
||||
// Curtain commands
|
||||
// Open = header (55AA) + (00060005) + 6604000100 "(55aa000600056604000100)"
|
||||
// Close = header (55AA) + (00060005) + 6604000101 "(55aa000600056604000101)"
|
||||
// Stop = header (55AA) + (00060005) + 6604000102 "(55AA000600056604000102)"
|
||||
#define TUYA_COVER_OPEN { 0x66, 0x04, 0x00, 0x01, 0x00 } //dpid = 101, type = enum, len = 1, value = OPEN
|
||||
#define TUYA_COVER_CLOSE { 0x66, 0x04, 0x00, 0x01, 0x01 } //dpid = 101, type = enum, len = 1, value = CLOSE
|
||||
#define TUYA_COVER_STOP { 0x66, 0x04, 0x00, 0x01, 0x02 } //dpid = 101, type = enum, len = 1, value = STOP
|
||||
|
||||
#define TUYA_COVER_SET_POSITION { 0x65, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00 } //"65020004000000" //dpid = 2, type = value, len = 4, value = 0x000000 + 1 byte (0x00-0x64)
|
||||
|
||||
static const char *TAG = "TUYACOVER";
|
||||
static const uint16_t TUYA_COVER_HEADER = 0x55AA;
|
||||
//static const uint16_t TUYA_COVER_VERSION = 0x03;
|
||||
static const uint16_t TUYA_COVER_VERSION = 0x00;
|
||||
|
||||
const uint8_t tuya_cover_enable_reversing[] = TUYA_COVER_ENABLE_REVERSING;
|
||||
const uint8_t tuya_cover_disable_reversing[] = TUYA_COVER_DISABLE_REVERSING;
|
||||
const uint8_t tuya_cover_open[] = TUYA_COVER_OPEN;
|
||||
const uint8_t tuya_cover_close[] = TUYA_COVER_CLOSE;
|
||||
const uint8_t tuya_cover_stop[] = TUYA_COVER_STOP;
|
||||
static uint8_t tuya_cover_pos[] = TUYA_COVER_SET_POSITION;
|
||||
|
||||
#define HEARTBEAT_INTERVAL_MS 10000
|
||||
unsigned long previousHeartbeatMillis = 0;
|
||||
|
||||
struct TUYACOVERCommand
|
||||
{
|
||||
uint16_t header;
|
||||
uint8_t version;
|
||||
uint8_t command;
|
||||
uint16_t length;
|
||||
uint8_t value[TUYA_COVER_MAX_LEN];
|
||||
uint8_t checksum;
|
||||
};
|
||||
|
||||
struct TUYACOVERMessage
|
||||
{
|
||||
uint8_t dpid;
|
||||
uint8_t type;
|
||||
uint16_t len;
|
||||
uint8_t value[TUYA_COVER_MAX_LEN - 4]; //Subtract dpid, type, len
|
||||
};
|
||||
|
||||
enum TUYACOVERCommandType
|
||||
{
|
||||
TUYA_COVER_HEARTBEAT = 0x00,
|
||||
TUYA_COVER_COMMAND = 0x06,
|
||||
TUYA_COVER_RESPONSE = 0x07,
|
||||
TUYA_COVER_QUERY_STATUS = 0x08
|
||||
};
|
||||
|
||||
enum TUYACOVERdpidType
|
||||
{
|
||||
TUYA_COVER_DPID_POSITION= 0x65,
|
||||
TUYA_COVER_DPID_DIRECTION = 0x64,
|
||||
TUYA_COVER_DPID_UNKNOWN = 0x67,
|
||||
TUYA_COVER_DPID_ERROR = 0x6E
|
||||
};
|
||||
|
||||
// Variables
|
||||
TUYACOVERCommand command_{TUYA_COVER_HEADER, TUYA_COVER_VERSION, 0, 0, {}, 0};
|
||||
uint8_t uart_buffer_[TUYA_COVER_BUFFER_LEN]{0};
|
||||
|
||||
// Forward declarations
|
||||
bool read_command();
|
||||
void write_command(TUYACOVERCommandType command, const uint8_t *value, uint16_t length);
|
||||
uint8_t checksum();
|
||||
|
||||
/*
|
||||
* Attempt to read an entire command from the serial UART into the command struct.
|
||||
* Will fail early if unable to find the two-byte header in the current
|
||||
* data stream. If the header is found, it will contine to read the complete
|
||||
* TLV+checksum sequence off the port. If the entire sequence can be read
|
||||
* and the checksum is valid, it will return true.
|
||||
*/
|
||||
bool read_command()
|
||||
{
|
||||
// Shift bytes through until we find a valid header
|
||||
bool valid_header = false;
|
||||
while (Serial.available() >= 1)
|
||||
{
|
||||
uart_buffer_[0] = uart_buffer_[1];
|
||||
uart_buffer_[1] = Serial.read();
|
||||
command_.header = (uart_buffer_[0] << 8) + uart_buffer_[1];
|
||||
if (command_.header == TUYA_COVER_HEADER)
|
||||
{
|
||||
valid_header = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the next 4 bytes (Version, Command, Data length)
|
||||
// Read n bytes (Data length)
|
||||
// Read the checksum byte
|
||||
if (valid_header)
|
||||
{
|
||||
Serial.readBytes(uart_buffer_ + TUYA_COVER_HEADER_LEN, TUYA_COVER_BUFFER_LEN - TUYA_COVER_HEADER_LEN);
|
||||
command_.version = uart_buffer_[2];
|
||||
command_.command = uart_buffer_[3];
|
||||
command_.length = (uart_buffer_[4] << 8) + uart_buffer_[5];
|
||||
ESP_LOGV(TAG, "RX: Header = 0x%04X, Version = 0x%02X, Command = 0x%02X, Data length = 0x%04X", command_.header, command_.version, command_.command, command_.length);
|
||||
|
||||
if (command_.length < TUYA_COVER_MAX_LEN)
|
||||
{
|
||||
Serial.readBytes(command_.value, command_.length);
|
||||
ESP_LOGV(TAG, "RX_RAW:");
|
||||
for (size_t i = 0; i < command_.length; i++)
|
||||
{
|
||||
ESP_LOGV(TAG, "%02d: 0x%02X", i, command_.value[i]);
|
||||
}
|
||||
while (Serial.available() == 0) // Dirty
|
||||
{
|
||||
//Wait
|
||||
}
|
||||
command_.checksum = Serial.read();
|
||||
|
||||
ESP_LOGV(TAG, "RX_CHK: 0x%02X", command_.checksum);
|
||||
uint8_t calc_checksum = checksum();
|
||||
if (calc_checksum == command_.checksum)
|
||||
{
|
||||
// Clear buffer contents to start with beginning of next command
|
||||
memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
|
||||
ESP_LOGE(TAG, "Checksum error: Read = 0x%02X != Calculated = 0x%02X", command_.checksum, calc_checksum);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
|
||||
ESP_LOGE(TAG, "Command length exceeds limit: %d >= %d", command_.length, TUYA_COVER_MAX_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
// Do not clear buffer to allow for resume in case of reading partway through header RX
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the given type, value, and length into the command struct and send
|
||||
* it out the serial port. Automatically calculates the checksum as well.
|
||||
*/
|
||||
void write_command(TUYACOVERCommandType command, const uint8_t *value, uint16_t length)
|
||||
{
|
||||
// Copy params into command struct
|
||||
command_.header = TUYA_COVER_HEADER;
|
||||
command_.version = TUYA_COVER_VERSION;
|
||||
command_.command = command;
|
||||
command_.length = length;
|
||||
ESP_LOGV(TAG, "TX: Header = 0x%04X, Version = 0x%02X, Command = 0x%02X, Data length = 0x%04X", command_.header, command_.version, command_.command, command_.length);
|
||||
memcpy(&command_.value, value, length);
|
||||
ESP_LOGV(TAG, "TX_RAW");
|
||||
for (size_t i = 0; i < command_.length; i++)
|
||||
{
|
||||
ESP_LOGV(TAG, "%02d: 0x%02X", i, command_.value[i]);
|
||||
}
|
||||
// Copy struct values into buffer, converting longs to big-endian
|
||||
uart_buffer_[0] = command_.header >> 8;
|
||||
uart_buffer_[1] = command_.header & 0xFF;
|
||||
uart_buffer_[2] = command_.version;
|
||||
uart_buffer_[3] = command_.command;
|
||||
uart_buffer_[4] = command_.length >> 8;
|
||||
uart_buffer_[5] = command_.length & 0xFF;
|
||||
command_.checksum = checksum();
|
||||
ESP_LOGV(TAG, "TX_CHK: 0x%02X", command_.checksum);
|
||||
// Send buffer out via UART
|
||||
Serial.write(uart_buffer_, TUYA_COVER_BUFFER_LEN);
|
||||
Serial.write(command_.value, command_.length);
|
||||
Serial.write(command_.checksum);
|
||||
// Clear buffer contents to avoid re-reading our own payload
|
||||
memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate checksum from current UART buffer (header+type+length) plus command value.
|
||||
*/
|
||||
uint8_t checksum()
|
||||
{
|
||||
uint8_t checksum = 0;
|
||||
for (size_t i = 0; i < TUYA_COVER_BUFFER_LEN; i++)
|
||||
{
|
||||
checksum += uart_buffer_[i];
|
||||
}
|
||||
for (size_t i = 0; i < command_.length; i++)
|
||||
{
|
||||
checksum += command_.value[i];
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
class CustomCurtain : public Component, public Cover
|
||||
{
|
||||
|
||||
protected:
|
||||
|
||||
public:
|
||||
void setup() override
|
||||
{
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = QUERY_STATUS");
|
||||
write_command(TUYA_COVER_QUERY_STATUS, 0, 0);
|
||||
}
|
||||
|
||||
CoverTraits get_traits() override
|
||||
{
|
||||
auto traits = CoverTraits();
|
||||
traits.set_is_assumed_state(false);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_tilt(false);
|
||||
return traits;
|
||||
}
|
||||
|
||||
void control(const CoverCall &call) override
|
||||
{
|
||||
if (call.get_position().has_value())
|
||||
{
|
||||
// Write pos (range 0-1) to cover
|
||||
// Cover pos (range 0x00-0x64) (closed - open)
|
||||
uint8_t pos = (100-(*call.get_position() * 100));
|
||||
ESP_LOGV(TAG, "POS = %d", pos);
|
||||
|
||||
switch (pos)
|
||||
{
|
||||
case 0:
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = CLOSE");
|
||||
write_command(TUYA_COVER_COMMAND, tuya_cover_close, sizeof(tuya_cover_close));
|
||||
break;
|
||||
case 100:
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = OPEN");
|
||||
write_command(TUYA_COVER_COMMAND, tuya_cover_open, sizeof(tuya_cover_open));
|
||||
break;
|
||||
default:
|
||||
tuya_cover_pos[7] = pos;
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = POS = %d%%", pos);
|
||||
write_command(TUYA_COVER_COMMAND, tuya_cover_pos, sizeof(tuya_cover_pos));
|
||||
break;
|
||||
}
|
||||
// publish_state only when position is confirmed in loop()
|
||||
}
|
||||
if (call.get_stop())
|
||||
{
|
||||
// User requested cover stop
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = STOP");
|
||||
write_command(TUYA_COVER_COMMAND, tuya_cover_stop, sizeof(tuya_cover_stop));
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override
|
||||
{
|
||||
unsigned long currentHeartbeatMillis = millis();
|
||||
if (currentHeartbeatMillis - previousHeartbeatMillis >= HEARTBEAT_INTERVAL_MS)
|
||||
{
|
||||
previousHeartbeatMillis += HEARTBEAT_INTERVAL_MS;
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = HEARTBEAT");
|
||||
write_command(TUYA_COVER_HEARTBEAT, 0, 0);
|
||||
}
|
||||
|
||||
bool have_message = read_command();
|
||||
|
||||
if(have_message && command_.command == TUYA_COVER_HEARTBEAT)
|
||||
{
|
||||
ESP_LOGI(TAG, "TUYA_COVER_RESPONSE = %s", (command_.value[0] == 0) ? "FIRST_HEARTBEAT" : "HEARTBEAT");
|
||||
}
|
||||
else if (have_message && command_.command == TUYA_COVER_RESPONSE)
|
||||
{
|
||||
switch (command_.value[0])
|
||||
{
|
||||
case TUYA_COVER_DPID_POSITION:
|
||||
ESP_LOGI(TAG, "TUYA_COVER_DPID_POSITION = %d%%", command_.value[7]);
|
||||
this->position = (1-((command_.value[7]) / 100.0f));
|
||||
this->publish_state();
|
||||
break;
|
||||
case TUYA_COVER_DPID_DIRECTION:
|
||||
ESP_LOGI(TAG, "TUYA_COVER_DPID_DIRECTION = 0x%02X", command_.value[4]);
|
||||
break;
|
||||
case TUYA_COVER_DPID_UNKNOWN:
|
||||
ESP_LOGI(TAG, "TUYA_COVER_DPID_UNKNOWN ENUM = 0x%02X", command_.value[4]);
|
||||
break;
|
||||
case TUYA_COVER_DPID_ERROR:
|
||||
ESP_LOGI(TAG, "TUYA_COVER_DPID_ERROR BITMAP = 0x%02X", command_.value[4]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class CustomAPI : public Component, public CustomAPIDevice
|
||||
{
|
||||
public:
|
||||
void setup() override
|
||||
{
|
||||
register_service(&CustomAPI::setStatusReport, "get_status_report");
|
||||
register_service(&CustomAPI::setMotorNormal, "set_motor_normal");
|
||||
register_service(&CustomAPI::setMotorReversed, "set_motor_reversed");
|
||||
register_service(&CustomAPI::sendCommand, "send_command", {"data"});
|
||||
}
|
||||
|
||||
void setStatusReport()
|
||||
{
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = QUERY_STATUS");
|
||||
write_command(TUYA_COVER_QUERY_STATUS, 0, 0);
|
||||
}
|
||||
|
||||
void setMotorNormal()
|
||||
{
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = ENABLE_REVERSING");
|
||||
write_command(TUYA_COVER_COMMAND, tuya_cover_enable_reversing, sizeof(tuya_cover_enable_reversing));
|
||||
}
|
||||
|
||||
void setMotorReversed()
|
||||
{
|
||||
ESP_LOGI(TAG, "TUYA_COVER_COMMAND = ENABLE_REVERSING");
|
||||
write_command(TUYA_COVER_COMMAND, tuya_cover_disable_reversing, sizeof(tuya_cover_disable_reversing));
|
||||
}
|
||||
|
||||
void sendCommand(std::string data)
|
||||
{
|
||||
int i = 0;
|
||||
uint8_t sum = 0;
|
||||
while (i < data.length())
|
||||
{
|
||||
const char hex[2] = {data[i++], data[i++]};
|
||||
uint8_t d = strtoul(hex, NULL, 16);
|
||||
sum += d;
|
||||
writeByte(d);
|
||||
}
|
||||
writeByte(sum);
|
||||
}
|
||||
|
||||
void writeByte(uint8_t data)
|
||||
{
|
||||
Serial.write(data);
|
||||
}
|
||||
};
|
||||
145
esphome/include/epaper75.h
Executable file
145
esphome/include/epaper75.h
Executable file
@@ -0,0 +1,145 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
#define ICON_stop "\U000F04DB"
|
||||
#define ICON_play "\U000F040A"
|
||||
#define ICON_pause "\U000F03E4"
|
||||
|
||||
std::string playbackStatusToIcon(bool playing, bool paused) {
|
||||
if (playing) return ICON_play;
|
||||
else if (paused) return ICON_pause;
|
||||
else return ICON_stop;
|
||||
}
|
||||
|
||||
#define ICON_moon_first_quarter "\U000F0F61"
|
||||
#define ICON_moon_full "\U000F0F62"
|
||||
#define ICON_moon_last_quarter "\U000F0F63"
|
||||
#define ICON_moon_new "\U000F0F64"
|
||||
#define ICON_moon_waning_crescent "\U000F0F65"
|
||||
#define ICON_moon_waning_gibbous "\U000F0F66"
|
||||
#define ICON_moon_waxing_crescent "\U000F0F67"
|
||||
#define ICON_moon_waxing_gibbous "\U000F0F68"
|
||||
|
||||
std::string moonToIcon(std::string moonPhase)
|
||||
{
|
||||
if (moonPhase == "new_moon") return ICON_moon_new;
|
||||
if (moonPhase == "waxing_crescent") return ICON_moon_waxing_crescent;
|
||||
if (moonPhase == "first_quarter") return ICON_moon_first_quarter;
|
||||
if (moonPhase == "waxing_gibbous") return ICON_moon_waxing_gibbous;
|
||||
if (moonPhase == "full_moon") return ICON_moon_full;
|
||||
if (moonPhase == "waning_gibbous") return ICON_moon_waning_gibbous;
|
||||
if (moonPhase == "last_quarter") return ICON_moon_last_quarter;
|
||||
if (moonPhase == "waning_crescent") return ICON_moon_waning_crescent;
|
||||
return "";
|
||||
}
|
||||
|
||||
// Map weather states to MDI characters.
|
||||
std::map<std::string, std::string> weather_icon_map
|
||||
{
|
||||
{"cloudy", "\U000F0590"},
|
||||
{"cloudy-alert", "\U000F0F2F"},
|
||||
{"cloudy-arrow-right", "\U000F0E6E"},
|
||||
{"fog", "\U000F0591"},
|
||||
{"hail", "\U000F0592"},
|
||||
{"hazy", "\U000F0F30"},
|
||||
{"hurricane", "\U000F0898"},
|
||||
{"lightning", "\U000F0593"},
|
||||
{"lightning-rainy", "\U000F067E"},
|
||||
{"night", "\U000F0594"},
|
||||
{"night-partly-cloudy", "\U000F0F31"},
|
||||
{"partlycloudy", "\U000F0595"},
|
||||
{"partly-lightning", "\U000F0F32"},
|
||||
{"partly-rainy", "\U000F0F33"},
|
||||
{"partly-snowy", "\U000F0F34"},
|
||||
{"partly-snowy-rainy", "\U000F0F35"},
|
||||
{"pouring", "\U000F0596"},
|
||||
{"rainy", "\U000F0597"},
|
||||
{"snowy", "\U000F0598"},
|
||||
{"snowy-heavy", "\U000F0F36"},
|
||||
{"snowy-rainy", "\U000F067F"},
|
||||
{"sunny", "\U000F0599"},
|
||||
{"sunny-alert", "\U000F0F37"},
|
||||
{"sunny-off", "\U000F14E4"},
|
||||
{"sunset", "\U000F059A"},
|
||||
{"sunset-down", "\U000F059B"},
|
||||
{"sunset-up", "\U000F059C"},
|
||||
{"tornado", "\U000F0F38"},
|
||||
{"windy", "\U000F059D"},
|
||||
{"windy-variant", "\U000F059E"},
|
||||
{"car", "\U000f010b"},
|
||||
{"trash", "\U000F0819"},
|
||||
};
|
||||
std::map<std::string, std::string> moon_icon_map
|
||||
{
|
||||
{ "mdi:moon-waxing-crescent", "\U000f0f67" },
|
||||
{ "mdi:moon-first-quarter", "\U000F0F61" },
|
||||
{ "mdi:moon-waxing-gibbous", "\U000F0F68" },
|
||||
{ "mdi:moon-full", "\U000F0F62" },
|
||||
{ "mdi:moon-waning-gibbous", "\U000F0F66" },
|
||||
{ "mdi:moon-last-quarter", "\U000F0F63" },
|
||||
{ "mdi:moon-waning-crescent", "\U000F0F65" },
|
||||
};
|
||||
|
||||
|
||||
#define ICON_w_clear_night "\U000F0594"
|
||||
#define ICON_w_cloudy "\U000F0590"
|
||||
#define ICON_w_fog "\U000F0591"
|
||||
#define ICON_w_hail "\U000F0592"
|
||||
#define ICON_w_lightning "\U000F0593"
|
||||
#define ICON_w_lightning_rainy "\U000F067E"
|
||||
#define ICON_w_night_partly_cloudy "\U000F0F31"
|
||||
#define ICON_w_partly_cloudy "\U000F0595"
|
||||
#define ICON_w_pouring "\U000F0596"
|
||||
#define ICON_w_rainy "\U000F0597"
|
||||
#define ICON_w_snowy "\U000F0F36"
|
||||
#define ICON_w_snowy_rainy "\U000F067F"
|
||||
#define ICON_w_sunny "\U000F0599"
|
||||
#define ICON_w_windy "\U000F059D"
|
||||
#define ICON_w_windy_variant "\U000F059E"
|
||||
#define ICON_w_exceptional "\U000F0F38"
|
||||
|
||||
std::string conditionToIcon(std::string condition, bool daytime)
|
||||
{
|
||||
if (condition == "clear") return ICON_w_clear_night;
|
||||
if (condition == "clear-night") return ICON_w_clear_night;
|
||||
if (condition == "cloudy") return ICON_w_cloudy;
|
||||
if (condition == "dust") return ICON_w_fog;
|
||||
if (condition == "dusty") return ICON_w_fog;
|
||||
if (condition == "fog") return ICON_w_fog;
|
||||
if (condition == "frost") return ICON_w_snowy;
|
||||
if (condition == "hail") return ICON_w_hail;
|
||||
if (condition == "haze") return ICON_w_fog;
|
||||
if (condition == "hazy") return ICON_w_fog;
|
||||
if (condition == "heavy_shower") return ICON_w_rainy;
|
||||
if (condition == "heavy_showers") return ICON_w_rainy;
|
||||
if (condition == "light_rain") return ICON_w_rainy;
|
||||
if (condition == "light_showers") return ICON_w_rainy;
|
||||
if (condition == "light_shower") return ICON_w_rainy;
|
||||
if (condition == "lightning") return ICON_w_lightning;
|
||||
if (condition == "lightning-rainy") return ICON_w_lightning_rainy;
|
||||
if (condition == "mostly_sunny") return ICON_w_sunny;
|
||||
if (condition == "night") return ICON_w_clear_night;
|
||||
if (condition == "partlycloudy" && !daytime) return ICON_w_night_partly_cloudy;
|
||||
if (condition == "partlycloudy" && daytime) return ICON_w_partly_cloudy;
|
||||
if (condition == "partly_cloudy" && !daytime) return ICON_w_night_partly_cloudy;
|
||||
if (condition == "partly_cloudy" && daytime) return ICON_w_partly_cloudy;
|
||||
if (condition == "pouring") return ICON_w_pouring;
|
||||
if (condition == "rain") return ICON_w_rainy;
|
||||
if (condition == "rainy") return ICON_w_rainy;
|
||||
if (condition == "shower") return ICON_w_rainy;
|
||||
if (condition == "showers") return ICON_w_rainy;
|
||||
if (condition == "snow") return ICON_w_snowy;
|
||||
if (condition == "snowy") return ICON_w_snowy;
|
||||
if (condition == "snowy-rainy") return ICON_w_snowy_rainy;
|
||||
if (condition == "storm") return ICON_w_lightning_rainy;
|
||||
if (condition == "storms") return ICON_w_lightning_rainy;
|
||||
if (condition == "sunny") return ICON_w_sunny;
|
||||
if (condition == "wind") return ICON_w_windy;
|
||||
if (condition == "windy") return ICON_w_windy;
|
||||
if (condition == "windy-variant") return ICON_w_windy_variant;
|
||||
if (condition == "exceptional") return ICON_w_exceptional;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
280
esphome/include/ld2410_uart.h
Executable file
280
esphome/include/ld2410_uart.h
Executable file
@@ -0,0 +1,280 @@
|
||||
#include "esphome.h"
|
||||
|
||||
#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1)
|
||||
|
||||
class LD2410 : public PollingComponent, public UARTDevice
|
||||
{
|
||||
public:
|
||||
LD2410(UARTComponent *parent) : UARTDevice(parent) {}
|
||||
|
||||
BinarySensor *hasTarget = new BinarySensor();
|
||||
BinarySensor *hasMovingTarget = new BinarySensor();
|
||||
BinarySensor *hasStillTarget = new BinarySensor();
|
||||
BinarySensor *lastCommandSuccess = new BinarySensor();
|
||||
Sensor *movingTargetDistance = new Sensor();
|
||||
Sensor *movingTargetEnergy = new Sensor();
|
||||
Sensor *stillTargetDistance = new Sensor();
|
||||
Sensor *stillTargetEnergy = new Sensor();
|
||||
Sensor *detectDistance = new Sensor();
|
||||
|
||||
Number *maxMovingDistanceRange;
|
||||
Number *maxStillDistanceRange;
|
||||
int movingSensitivities[9] = {0};
|
||||
int stillSensitivities[9] = {0};
|
||||
Number *noneDuration;
|
||||
|
||||
long lastPeriodicMillis = millis();
|
||||
|
||||
void setNumbers(Number *maxMovingDistanceRange_, Number *maxStillDistanceRange_, Number *noneDuration_){
|
||||
maxMovingDistanceRange = maxMovingDistanceRange_;
|
||||
maxStillDistanceRange = maxStillDistanceRange_;
|
||||
noneDuration = noneDuration_;
|
||||
}
|
||||
|
||||
void sendCommand(char *commandStr, char *commandValue, int commandValueLen)
|
||||
{
|
||||
lastCommandSuccess->publish_state(false);
|
||||
// frame start bytes
|
||||
write_byte(0xFD);
|
||||
write_byte(0xFC);
|
||||
write_byte(0xFB);
|
||||
write_byte(0xFA);
|
||||
// length bytes
|
||||
int len = 2;
|
||||
if (commandValue != nullptr)
|
||||
len += commandValueLen;
|
||||
write_byte(lowByte(len));
|
||||
write_byte(highByte(len));
|
||||
// command string bytes
|
||||
write_byte(commandStr[0]);
|
||||
write_byte(commandStr[1]);
|
||||
// command value bytes
|
||||
if (commandValue != nullptr)
|
||||
{
|
||||
for (int i = 0; i < commandValueLen; i++)
|
||||
{
|
||||
write_byte(commandValue[i]);
|
||||
}
|
||||
}
|
||||
// frame end bytes
|
||||
write_byte(0x04);
|
||||
write_byte(0x03);
|
||||
write_byte(0x02);
|
||||
write_byte(0x01);
|
||||
delay(50);
|
||||
}
|
||||
|
||||
int twoByteToInt(char firstByte, char secondByte)
|
||||
{
|
||||
return (int16_t)(secondByte << 8) + firstByte;
|
||||
}
|
||||
|
||||
void handlePeriodicData(char *buffer, int len)
|
||||
{
|
||||
if (len < 12)
|
||||
return; // 4 frame start bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame end bytes
|
||||
if (buffer[0] != 0xF4 || buffer[1] != 0xF3 || buffer[2] != 0xF2 || buffer[3] != 0xF1)
|
||||
return; // check 4 frame start bytes
|
||||
if (buffer[7] != 0xAA || buffer[len - 6] != 0x55 || buffer[len - 5] != 0x00)
|
||||
return; // data head=0xAA, data end=0x55, crc=0x00
|
||||
/*
|
||||
Data Type: 6th byte
|
||||
0x01: Engineering mode
|
||||
0x02: Normal mode
|
||||
*/
|
||||
char dataType = buffer[5];
|
||||
/*
|
||||
Target states: 9th byte
|
||||
0x00 = No target
|
||||
0x01 = Moving targets
|
||||
0x02 = Still targets
|
||||
0x03 = Moving+Still targets
|
||||
*/
|
||||
char stateByte = buffer[8];
|
||||
hasTarget->publish_state(stateByte != 0x00);
|
||||
/*
|
||||
Reduce data update rate to prevent home assistant database size glow fast
|
||||
*/
|
||||
long currentMillis = millis();
|
||||
if (currentMillis - lastPeriodicMillis < 1000)
|
||||
return;
|
||||
lastPeriodicMillis = currentMillis;
|
||||
|
||||
hasMovingTarget->publish_state(CHECK_BIT(stateByte, 0));
|
||||
hasStillTarget->publish_state(CHECK_BIT(stateByte, 1));
|
||||
|
||||
/*
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
int newMovingTargetDistance = twoByteToInt(buffer[9], buffer[10]);
|
||||
if (movingTargetDistance->get_state() != newMovingTargetDistance)
|
||||
movingTargetDistance->publish_state(newMovingTargetDistance);
|
||||
int newMovingTargetEnergy = buffer[11];
|
||||
if (movingTargetEnergy->get_state() != newMovingTargetEnergy)
|
||||
movingTargetEnergy->publish_state(newMovingTargetEnergy);
|
||||
int newStillTargetDistance = twoByteToInt(buffer[12], buffer[13]);
|
||||
if (stillTargetDistance->get_state() != newStillTargetDistance)
|
||||
stillTargetDistance->publish_state(newStillTargetDistance);
|
||||
int newStillTargetEnergy = buffer[14];
|
||||
if (stillTargetEnergy->get_state() != newStillTargetEnergy)
|
||||
stillTargetEnergy->publish_state(buffer[14]);
|
||||
int newDetectDistance = twoByteToInt(buffer[15], buffer[16]);
|
||||
if (detectDistance->get_state() != newDetectDistance)
|
||||
detectDistance->publish_state(newDetectDistance);
|
||||
if (dataType == 0x01)
|
||||
{ // engineering mode
|
||||
// todo: support engineering mode data
|
||||
}
|
||||
}
|
||||
|
||||
void handleACKData(char *buffer, int len)
|
||||
{
|
||||
if (len < 10)
|
||||
return;
|
||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA)
|
||||
return; // check 4 frame start bytes
|
||||
if (buffer[7] != 0x01)
|
||||
return;
|
||||
if (twoByteToInt(buffer[8], buffer[9]) != 0x00)
|
||||
{
|
||||
lastCommandSuccess->publish_state(false);
|
||||
return;
|
||||
}
|
||||
lastCommandSuccess->publish_state(true);
|
||||
switch (buffer[6])
|
||||
{
|
||||
case 0x61: // Query parameters response
|
||||
{
|
||||
if (buffer[10] != 0xAA)
|
||||
return; // value head=0xAA
|
||||
/*
|
||||
Moving distance range: 13th byte
|
||||
Still distance range: 14th byte
|
||||
*/
|
||||
maxMovingDistanceRange->publish_state(buffer[12]);
|
||||
maxStillDistanceRange->publish_state(buffer[13]);
|
||||
/*
|
||||
Moving Sensitivities: 15~23th bytes
|
||||
Still Sensitivities: 24~32th bytes
|
||||
*/
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
movingSensitivities[i] = buffer[14 + i];
|
||||
}
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
stillSensitivities[i] = buffer[23 + i];
|
||||
}
|
||||
/*
|
||||
None Duration: 33~34th bytes
|
||||
*/
|
||||
noneDuration->publish_state(twoByteToInt(buffer[32], buffer[33]));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void readline(int readch, char *buffer, int len)
|
||||
{
|
||||
static int pos = 0;
|
||||
|
||||
if (readch >= 0)
|
||||
{
|
||||
if (pos < len - 1)
|
||||
{
|
||||
buffer[pos++] = readch;
|
||||
buffer[pos] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
if (pos >= 4)
|
||||
{
|
||||
if (buffer[pos - 4] == 0xF8 && buffer[pos - 3] == 0xF7 && buffer[pos - 2] == 0xF6 && buffer[pos - 1] == 0xF5)
|
||||
{
|
||||
handlePeriodicData(buffer, pos);
|
||||
pos = 0; // Reset position index ready for next time
|
||||
}
|
||||
else if (buffer[pos - 4] == 0x04 && buffer[pos - 3] == 0x03 && buffer[pos - 2] == 0x02 && buffer[pos - 1] == 0x01)
|
||||
{
|
||||
handleACKData(buffer, pos);
|
||||
pos = 0; // Reset position index ready for next time
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void setConfigMode(bool enable)
|
||||
{
|
||||
char cmd[2] = {enable ? 0xFF : 0xFE, 0x00};
|
||||
char value[2] = {0x01, 0x00};
|
||||
sendCommand(cmd, enable ? value : nullptr, 2);
|
||||
}
|
||||
|
||||
void queryParameters()
|
||||
{
|
||||
char cmd_query[2] = {0x61, 0x00};
|
||||
sendCommand(cmd_query, nullptr, 0);
|
||||
}
|
||||
|
||||
void setup() override
|
||||
{
|
||||
set_update_interval(15000);
|
||||
}
|
||||
|
||||
void loop() override
|
||||
{
|
||||
const int max_line_length = 80;
|
||||
static char buffer[max_line_length];
|
||||
while (available())
|
||||
{
|
||||
readline(read(), buffer, max_line_length);
|
||||
}
|
||||
}
|
||||
|
||||
void setEngineeringMode(bool enable)
|
||||
{
|
||||
char cmd[2] = {enable ? 0x62 : 0x63, 0x00};
|
||||
sendCommand(cmd, nullptr, 0);
|
||||
}
|
||||
|
||||
void setMaxDistancesAndNoneDuration(int maxMovingDistanceRange, int maxStillDistanceRange, int noneDuration)
|
||||
{
|
||||
char cmd[2] = {0x60, 0x00};
|
||||
char value[18] = {0x00, 0x00, lowByte(maxMovingDistanceRange), highByte(maxMovingDistanceRange), 0x00, 0x00, 0x01, 0x00, lowByte(maxStillDistanceRange), highByte(maxStillDistanceRange), 0x00, 0x00, 0x02, 0x00, lowByte(noneDuration), highByte(noneDuration), 0x00, 0x00};
|
||||
sendCommand(cmd, value, 18);
|
||||
queryParameters();
|
||||
}
|
||||
|
||||
void factoryReset()
|
||||
{
|
||||
char cmd[2] = {0xA2, 0x00};
|
||||
sendCommand(cmd, nullptr, 0);
|
||||
}
|
||||
|
||||
void reboot()
|
||||
{
|
||||
char cmd[2] = {0xA3, 0x00};
|
||||
sendCommand(cmd, nullptr, 0);
|
||||
// not need to exit config mode because the ld2410 will reboot automatically
|
||||
}
|
||||
|
||||
void setBaudrate(int index)
|
||||
{
|
||||
char cmd[2] = {0xA1, 0x00};
|
||||
char value[2] = {index, 0x00};
|
||||
sendCommand(cmd, value, 2);
|
||||
}
|
||||
|
||||
void update()
|
||||
{
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user