// ===================================================================== // LD2453Parser.cpp // // See the big comment at the top of LD2453Parser.h before touching // this file. The acquisition/debug layer below is fully functional; // tryParseFrame() is intentionally stubbed pending the official // HLK-LD2453 protocol. // ===================================================================== #include "LD2453Parser.h" #if !defined(ARDUINO_ARCH_ESP32) #warning "LD2453Parser was written/tested against the ESP32 Arduino core. Verify HardwareSerial pin-remap behavior on other cores." #endif // --------------------------------------------------------------- // PLACEHOLDER PROTOCOL CONSTANTS // // None of the values below are confirmed. They exist only so the // state machine in tryParseFrame() has something concrete to compare // against once you fill them in from the real manual or from a // DEBUG_RADAR_RAW capture. Do not rely on these for real parsing. // --------------------------------------------------------------- // static const uint8_t FRAME_HEADER[] = { 0x00, 0x00, 0x00, 0x00 }; // TODO: confirm // static const uint8_t FRAME_TAIL[] = { 0x00, 0x00 }; // TODO: confirm // static const size_t TARGET_RECORD_SIZE = 0; // bytes per target, TODO: confirm // static const size_t MAX_TARGETS = 3; void LD2453Parser::begin(HardwareSerial &serial, int rxPin, int txPin, uint32_t baud, const char *nodeId, bool rawDebug) { _serial = &serial; _rawDebug = rawDebug; _nodeId = nodeId; _bufferLen = 0; _sequenceCounter = 0; _stats = LD2453Stats{}; _ppsWindowStart = millis(); _ppsWindowCount = 0; // HardwareSerial::begin(baud, config, rxPin, txPin) — ESP32 Arduino // core supports remapping RX/TX to any capable GPIO. 8N1 assumed; // confirm against the official manual. _serial->begin(baud, SERIAL_8N1, rxPin, txPin); } void LD2453Parser::dumpRawHex(const uint8_t *data, size_t len) { Serial.print(F("[RAW] ")); for (size_t i = 0; i < len; i++) { if (data[i] < 0x10) Serial.print('0'); Serial.print(data[i], HEX); Serial.print(' '); } Serial.print(F(" (len=")); Serial.print(len); Serial.println(')'); } void LD2453Parser::feedByte(uint8_t b) { if (_bufferLen >= RING_BUFFER_SIZE) { // Buffer overrun with no recognized frame boundary — drop the // oldest half so we can keep tolerating a stuck/garbage stream // without ever blocking or allocating. memmove(_ringBuffer, _ringBuffer + RING_BUFFER_SIZE / 2, RING_BUFFER_SIZE / 2); _bufferLen = RING_BUFFER_SIZE / 2; _stats.parseErrors++; } _ringBuffer[_bufferLen++] = b; } bool LD2453Parser::update() { if (!_serial) return false; bool gotFrame = false; // Drain whatever is available right now — non-blocking, no delay(). // While rawDebug is on, dump exactly the newly-arrived bytes as // hex so you can capture real traffic to reverse-engineer the // frame format, independent of whether tryParseFrame() (still a // stub) recognizes anything. size_t newBytesStart = _bufferLen; while (_serial->available() > 0) { uint8_t b = (uint8_t)_serial->read(); feedByte(b); _stats.bytesReceived++; } if (_rawDebug && _bufferLen > newBytesStart) { dumpRawHex(_ringBuffer + newBytesStart, _bufferLen - newBytesStart); } // Try to pull as many complete frames out of the buffer as // possible in this call (in case several arrived since the last // loop() iteration). while (tryParseFrame()) { gotFrame = true; _stats.framesParsed++; _ppsWindowCount++; } // Update packets-per-second once a second, non-blocking. uint32_t now = millis(); if (now - _ppsWindowStart >= 1000) { _stats.packetsPerSecond = _ppsWindowCount * 1000.0f / (now - _ppsWindowStart); _ppsWindowStart = now; _ppsWindowCount = 0; } return gotFrame; } bool LD2453Parser::tryParseFrame() { // ----------------------------------------------------------- // STUB: real frame sync/parse goes here once the official // HLK-LD2453 protocol is confirmed. Suggested shape, mirroring // the LD2410/LD2450 family's approach (adapt once verified): // // 1. Scan _ringBuffer for FRAME_HEADER. // 2. If found but fewer than (header + declared length + tail) // bytes are available yet, return false and wait for more // bytes next call (do NOT block). // 3. Verify FRAME_TAIL at the expected offset. // 4. If rawDebug, dumpRawHex() the whole candidate frame. // 5. Decode each target record into RadarTarget (x_mm, y_mm, // speed, and distance/angle only if the protocol actually // carries them). // 6. Fill _lastFrame (timestamp_ms = millis(), sequence_number // = ++_sequenceCounter, node_id = _nodeId, targetCount). // 7. memmove() the consumed bytes out of _ringBuffer. // 8. Return true. // // Until then, this always returns false so callers never receive // fabricated coordinates. // ----------------------------------------------------------- return false; }