Arduino PulseWire Transceiver Library
Loading...
Searching...
No Matches
NRZCodec.h
1#pragma once
2#include "Codec.h"
3
4namespace pulsewire {
5
10class NRZCodec : public Codec {
11 public:
12 NRZCodec(uint8_t stopBits = 1) : _stopBits(stopBits) {}
13
15 : Codec(preambleDetector), _stopBits(stopBits) {}
16
17 CodecEnum getCodecType() const override { return CodecEnum::NRZ; }
18
19 size_t encode(uint8_t byte, Vector<OutputEdge>& output) override {
20 size_t edgeCount = 0;
21
22 // Start bit (LOW, since idle is HIGH)
24 start.level = false;
25 start.pulseUs = _bitPeriodUs;
26 output.push_back(start);
27 ++edgeCount;
28
29 // Data bits (LSB first)
30 for (int bit = 0; bit < 8; ++bit) {
31 OutputEdge data;
32 data.level = (byte & (1 << bit));
33 data.pulseUs = _bitPeriodUs;
34 output.push_back(data);
35 ++edgeCount;
36 }
37
38 // Stop bits (high)
39 for (uint8_t s = 0; s < _stopBits; ++s) {
41 stop.level = true;
42 stop.pulseUs = _bitPeriodUs;
43 output.push_back(stop);
44 ++edgeCount;
45 }
46
47 return edgeCount;
48 }
49
50 bool decodeEdge(uint32_t durationUs, bool level, uint8_t& result) override {
51 Logger::debug("[NRZCodec] decodeEdge: level=%s, duration=%d us",
52 level ? "HIGH" : "LOW ", durationUs);
53 // Filter idle gaps
54 if (level == getIdleLevel() && durationUs > getEndOfFrameDelayUs()) {
55 Logger::debug("Idle gap detected: %d us, resetting decoder", durationUs);
56 reset();
57 return false;
58 }
59
60 // No preamble means every edge is part of a frame
61 if (!_inFrame && _preamble->preambleLength() == 0) {
62 _inFrame = true;
63 }
64
65 if (_inFrame) {
66 if (durationUs < _bitPeriodUs * 1.5) {
67 // Short pulse, likely a single bit
68 return decodeEdgeInternal(durationUs, level, result);
69 }
70 // Split long pulses into individual bit-period edges
71 int count = (durationUs + _bitPeriodUs / 2) / _bitPeriodUs;
72 if (count < 1) count = 1;
73 Logger::debug("Long pulse detected: %d us, splitting into %d edges",
75
76 bool valid = false;
77 for (int i = 0; i < count; ++i) {
78 if (decodeEdgeInternal(_bitPeriodUs, level, result)) {
79 valid = true;
80 }
81 }
82 return valid;
83 } else {
84 return decodeEdgeInternal(durationUs, level, result);
85 }
86 }
87
88 bool decodeByte(Vector<OutputEdge>& edges, uint8_t& result) {
89 bool valid = true;
90 if (edges[0].level != false) {
91 Logger::error("Invalid start bit: expected LOW, got HIGH: duration=%d us",
92 edges[0].pulseUs);
93 return false; // Start bit
94 }
95
96 uint8_t byte = 0;
97 for (int i = 0; i < 8; ++i) {
98 if (edges[i + 1].level) {
99 byte |= (1 << i);
100 Logger::debug("Bit %d: 1", i);
101 } else {
102 Logger::debug("Bit %d: 0", i);
103 }
104 }
105
106 // Check stop bits
107 for (int s = 0; s < _stopBits; ++s) {
108 if (edges[9 + s].level != true) {
109 Logger::error("Stop bit %d: 0, duration=%d us", s, edges[9 + s].pulseUs);
110 //valid = false;
111 } else {
112 Logger::debug("Stop bit %d: 1, duration=%d us", s, edges[9 + s].pulseUs);
113 }
114 }
115 result = byte;
116 reset(); // Ready for next frame
117 return valid;
118 }
119
120 void reset() override {
121 Codec::reset();
122 _decodeEdgeStream.clear();
123 }
124
125 size_t getEdgeCount() const override { return 1 + 8 + _stopBits; }
126
127 int getEndOfFrameDelayUs() override {
128 return (getEdgeCount() + 2) * _bitPeriodUs;
129 }
130
131 bool getIdleLevel() const override { return true; }
132
133 void setStopBits(uint8_t stopBits) { _stopBits = stopBits; }
134
135 uint8_t getStopBits() const { return _stopBits; }
136
137 protected:
138 uint8_t _stopBits;
139
140 // Implementation of pure virtual function bitMatch
141 bool bitMatch(uint32_t duration, bool bit) const {
142 uint32_t expectedDuration = _bitPeriodUs;
143 return (duration >= expectedDuration - (_bitPeriodUs / 2)) &&
144 (duration <= expectedDuration + (_bitPeriodUs / 2));
145 }
146
147 virtual bool decodeEdgeInternal(uint32_t durationUs, bool level, uint8_t& result) {
148 // ensure that edges are allocated
149 assert(_decodeEdgeStream.capacity() > 0);
150
151 // Filter idle gaps
152 if (level == getIdleLevel() && durationUs > getEndOfFrameDelayUs()) {
153 Logger::debug("Idle gap detected: %d us, resetting decoder", durationUs);
154 reset();
155 return false;
156 }
157
158 OutputEdge newEdge{level, durationUs};
159 assert(_preamble != nullptr);
160 if (!_inFrame) {
161 if (_preamble->preambleLength() == 0) {
162 // no preamble, the edge is valid playload
163 _decodeEdgeStream.clear();
164 _decodeEdgeStream.push_back(newEdge);
165 _inFrame = true;
166 Logger::debug("No preamble, starting new frame");
167 } else if (_preamble->detect(newEdge)) {
168 // Detected preamble, start new frame
169 _inFrame = true;
170 _decodeEdgeStream.clear();
171 Logger::debug("Preamble detected, starting new frame");
172 }
173 } else {
174 _decodeEdgeStream.push_back(newEdge);
175 }
176
177 // Try to decode bytes if enough edges collected
178 if (_decodeEdgeStream.size() == getEdgeCount()) {
179 bool rc = decodeByte(_decodeEdgeStream, result);
180 Logger::debug("Decoded byte: 0x%x", result);
181 _decodeEdgeStream.clear();
182 return rc;
183 }
184 return false;
185 }
186
187};
188
189} // namespace pulsewire
Abstract base class for IR protocol encoding and decoding.
Definition Codec.h:53
virtual void reset()
Reset the internal state of the codec.
Definition Codec.h:84
static void debug(const char *format,...)
Log a debug message with formatting.
Definition Logger.h:82
static void error(const char *format,...)
Log an error message with formatting.
Definition Logger.h:37
NRZ (Non-Return-to-Zero) codec for serial-like encoding/decoding with start/stop bit framing.
Definition NRZCodec.h:10
int getEndOfFrameDelayUs() override
Definition NRZCodec.h:127
bool getIdleLevel() const override
Provides the initial ldle state (low or hith)
Definition NRZCodec.h:131
CodecEnum getCodecType() const override
Definition NRZCodec.h:17
size_t getEdgeCount() const override
Get the number of protocol symbols (bits, pulses, etc.) per encoded byte.
Definition NRZCodec.h:125
size_t encode(uint8_t byte, Vector< OutputEdge > &output) override
Fill output vector with protocol-specific OutputSpec(s) for a byte.
Definition NRZCodec.h:19
void reset() override
Reset the internal state of the codec.
Definition NRZCodec.h:120
bool decodeEdge(uint32_t durationUs, bool level, uint8_t &result) override
Edge-based decoding for protocol-agnostic RX drivers.
Definition NRZCodec.h:50
Abstract base class for preamble detection and generation.
Definition Preamble.h:40
virtual bool detect(const OutputEdge &edge)
Detects if the incoming edge matches the expected preamble pattern.
Definition Preamble.h:52
Small, header-only vector replacement for non-STL environments.
Definition Vector.h:29
size_t capacity() const
Current allocated capacity.
Definition Vector.h:167
void clear()
Remove all elements.
Definition Vector.h:120
void push_back(const T &value)
Add element to end of vector.
Definition Vector.h:92
size_t size() const
Number of elements in vector.
Definition Vector.h:116
Specifies a single IR signal segment for protocol-agnostic transmission.
Definition Preamble.h:23