M5Unit-RS485 0.1.0 git rev:8a0af48
Loading...
Searching...
No Matches
rs485_component.hpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
3 *
4 * SPDX-License-Identifier: MIT
5 */
18#ifndef M5_UNIT_RS485_RS485_COMPONENT_HPP
19#define M5_UNIT_RS485_RS485_COMPONENT_HPP
20
21#include <M5UnitComponent.hpp>
22#include <m5_utility/stl/byteswap.hpp>
23#include <m5_utility/stl/endianness.hpp>
24#include <cstring>
25#include <memory>
26#include <type_traits>
27
28namespace m5 {
29namespace unit {
30
31class AdapterUART; // forward decl: framework-agnostic UART adapter
32
37class RS485Component : public Component {
38public:
40 struct ISerial {
41 virtual ~ISerial() = default;
42 virtual int available() = 0;
43 virtual int availableForWrite() = 0;
44 virtual int peek() = 0;
45 virtual int read() = 0;
46 virtual size_t read(uint8_t *buf, const size_t len) = 0;
47 virtual size_t readBytes(uint8_t *buf, const size_t len) = 0;
48 virtual void flush() = 0;
49 virtual void flush(const bool txOnly) = 0;
50 virtual size_t write(const uint8_t *buf, const size_t len) = 0;
51 virtual size_t write(uint8_t b) = 0;
52 virtual uint32_t baudRate() const = 0;
53 };
55
57 virtual ~RS485Component() = default;
58
63 virtual bool begin() override;
64
69 struct config_t {
70 bool flushRX{true};
71 };
72
75
80 {
81 return _cfg;
82 }
87 inline void config(const config_t &cfg)
88 {
89 _cfg = cfg;
90 }
92
97 inline operator bool() const
98 {
99 return isRegistered();
100 }
105 inline int available()
106 {
107 return _serial->available();
108 }
113 inline int availableForWrite()
114 {
115 return _serial->availableForWrite();
116 }
121 inline int peek()
122 {
123 return _serial->peek();
124 }
129 inline int read()
130 {
131 return _serial->read();
132 }
139 inline size_t read(uint8_t *buffer, const size_t size)
140 {
141 return _serial->read(buffer, size);
142 }
149 inline size_t read(char *buffer, const size_t size)
150 {
151 return read(reinterpret_cast<uint8_t *>(buffer), size);
152 }
159 size_t readBytes(uint8_t *buffer, const size_t length)
160 {
161 return _serial->readBytes(buffer, length);
162 }
169 inline size_t readBytes(char *buffer, const size_t length)
170 {
171 return readBytes(reinterpret_cast<uint8_t *>(buffer), length);
172 }
176 inline void flush()
177 {
178 _serial->flush();
179 }
184 inline void flush(const bool txOnly)
185 {
186 _serial->flush(txOnly);
187 }
194 size_t write(const uint8_t d, const bool flush = true);
202 size_t write(const uint8_t *buffer, const size_t size, const bool flush = true);
210 size_t write(const char *buffer, const size_t size, const bool flush = true);
217 size_t write(const char *s, const bool flush = true);
219
222
235 template <typename T, bool LittleEndian = true>
236 bool readValue(T &value)
237 {
238 static_assert(std::is_trivially_copyable<T>::value && std::is_standard_layout<T>::value,
239 "readValue requires trivially copyable, standard-layout types only");
240 value = T{};
241 uint8_t buf[sizeof(T)]{};
242 size_t got = _serial->readBytes(buf, sizeof(T));
243 if (got != sizeof(T)) {
244 return false;
245 }
246 std::memcpy(&value, buf, sizeof(T));
247 if (LittleEndian != m5::endian::little) {
248 value = byteswap_if_needed(value);
249 }
250 return true;
251 }
265 template <typename T, bool LittleEndian = true>
266 bool writeValue(const T value)
267 {
268 static_assert(std::is_trivially_copyable<T>::value && std::is_standard_layout<T>::value,
269 "writeValue requires trivially copyable, standard-layout types only");
270 T tmp = value;
271 if (LittleEndian != m5::endian::little) {
272 tmp = byteswap_if_needed(tmp);
273 }
274 uint8_t buf[sizeof(T)]{};
275 std::memcpy(buf, &tmp, sizeof(T));
276 return write(buf, sizeof(T), true) == sizeof(T);
277 }
279
280protected:
282 explicit RS485Component(const uint8_t addr);
283
284 inline ISerial *iserial()
285 {
286 return _serial.get();
287 }
288
298 virtual std::unique_ptr<ISerial> make_serial(AdapterUART *ad);
299
300 // 3-way byteswap dispatch tags: integral/enum, floating-point, other (aggregate).
302 };
304 };
306 };
307
308 template <typename T>
310 using type =
311 typename std::conditional<std::is_integral<T>::value || std::is_enum<T>::value, byteswap_int_tag,
312 typename std::conditional<std::is_floating_point<T>::value, byteswap_float_tag,
313 byteswap_none_tag>::type>::type;
314 };
315
316 template <typename T>
317 static T byteswap_if_needed(T v)
318 {
319 return byteswap_if_needed_impl(v, typename byteswap_tag_of<T>::type{});
320 }
321
322 // Integral / enum: delegated to m5::stl::byteswap.
323 template <typename T>
324 static T byteswap_if_needed_impl(T v, byteswap_int_tag)
325 {
326 return m5::stl::byteswap(v);
327 }
328
329 // Floating-point: bit-cast to same-size unsigned integer, byteswap, bit-cast back.
330 template <typename T>
331 static T byteswap_if_needed_impl(T v, byteswap_float_tag)
332 {
333 static_assert(sizeof(T) == 4 || sizeof(T) == 8,
334 "byteswap_if_needed: only 32-bit / 64-bit floating-point supported");
335 typename std::conditional<sizeof(T) == 4, uint32_t, uint64_t>::type u{};
336 std::memcpy(&u, &v, sizeof(T));
337 u = m5::stl::byteswap(u);
338 T out{};
339 std::memcpy(&out, &u, sizeof(T));
340 return out;
341 }
342
343 // Aggregate / struct: byte-reverse of the whole object is not meaningful; return as-is.
344 template <typename T>
345 static T byteswap_if_needed_impl(T v, byteswap_none_tag)
346 {
347 return v;
348 }
349
350 std::unique_ptr<ISerial> _serial{};
351 config_t _cfg{};
352};
353
354} // namespace unit
355} // namespace m5
356#endif
Non-instantiable base class for RS-485 transceiver units.
Definition rs485_component.hpp:37
int available()
Returns the number of available bytes to read.
Definition rs485_component.hpp:105
size_t readBytes(uint8_t *buffer, const size_t length)
Reads bytes with timeout defined by underlying Serial.
Definition rs485_component.hpp:159
virtual ~RS485Component()=default
Destructor.
size_t read(uint8_t *buffer, const size_t size)
Reads up to size bytes into buffer.
Definition rs485_component.hpp:139
bool readValue(T &value)
Read a trivially copyable value.
Definition rs485_component.hpp:236
int peek()
Peeks the next incoming byte without removing it.
Definition rs485_component.hpp:121
bool writeValue(const T value)
Write a trivially copyable value.
Definition rs485_component.hpp:266
size_t readBytes(char *buffer, const size_t length)
Reads bytes with timeout defined by underlying Serial.
Definition rs485_component.hpp:169
int read()
Reads one byte.
Definition rs485_component.hpp:129
virtual bool begin() override
Begin using the registered UART adapter.
Definition rs485_component.cpp:142
size_t read(char *buffer, const size_t size)
Reads up to size bytes into buffer.
Definition rs485_component.hpp:149
void flush()
Flushes the serial port.
Definition rs485_component.hpp:176
int availableForWrite()
Returns the number of bytes that can be written without blocking.
Definition rs485_component.hpp:113
void config(const config_t &cfg)
Sets the configuration.
Definition rs485_component.hpp:87
size_t write(const uint8_t d, const bool flush=true)
Writes one byte.
Definition rs485_component.cpp:169
void flush(const bool txOnly)
Flushes the serial port.
Definition rs485_component.hpp:184
config_t config()
Gets the configuration.
Definition rs485_component.hpp:79
virtual std::unique_ptr< ISerial > make_serial(AdapterUART *ad)
Factory hook: build the concrete ISerial to be used by begin().
Definition rs485_component.cpp:131
Top level namespace of M5Stack.
Unit-related namespace.
Definition rs485_component.hpp:303
Definition rs485_component.hpp:301
Definition rs485_component.hpp:305
Definition rs485_component.hpp:309
Settings for begin.
Definition rs485_component.hpp:69
bool flushRX
Flush RX buffer on begin?
Definition rs485_component.hpp:70