281 lines
7.9 KiB
C++
Executable File
281 lines
7.9 KiB
C++
Executable File
#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()
|
|
{
|
|
}
|
|
};
|