Port Frame object to C++

This commit is contained in:
Allen Hill
2026-07-05 19:35:58 -07:00
parent 66bbf8639e
commit e878a61313
14 changed files with 224 additions and 250 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ set(AVCLAN_TARGET avr-attiny3216 CACHE STRING "Hardware target (port) to build")
# <avr/...>, no register access). The selected target's port sources and flags
# are contributed by its subdirectory.
add_library(avclan STATIC
src/avclan/avclan_frame.c
src/avclan/frame.cc
src/avclan/cdchanger.cc
src/avclan/bus.cc
)
-22
View File
@@ -11,11 +11,6 @@
#include <stdint.h>
#define MAXMSGLEN 32
#define DEVICE_ADDR 0x360 // CD Changer address
#define HU_ADDR 0x190 // Head-unit address
typedef enum : uint8_t {
dev_LAN = 0x00,
dev_COMM_CTRL = 0x01,
@@ -109,23 +104,6 @@ typedef enum : uint8_t {
Report_TOC = 0xf9,
} actions;
typedef struct print_struct {
bool print : 1; // print at all
bool binary : 1; // when also printing, format as binary instead of text
bool verbose : 1; // include extra context in error reports
} log_t;
typedef struct AVCLAN_frame_struct {
uint8_t reaction;
uint8_t owning_device;
bool is_unicast;
uint16_t controller_addr; // formerly "master"
uint16_t peripheral_addr; // formerly "slave"
uint8_t control;
uint8_t length;
uint8_t data[MAXMSGLEN];
} AVCLAN_frame_t;
// A single bus symbol. bit_zero/bit_one carry data (and double as parity
// values); bit_start marks a frame start bit.
typedef enum avclan_bit : uint8_t {
-114
View File
@@ -1,114 +0,0 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <stdint.h>
#include <string.h>
#include "avclan_frame.h"
#include "avclan_phy.h" // bus symbol I/O + transaction guard (target-provided)
#include "com232.h" // error logging
void AVCLAN_printframe(const AVCLAN_frame_t *frame, bool binary) {
if (binary) {
uint8_t buffer[8];
buffer[0] = 0x10; // Data Link Escape, signaling binary data forthcoming
buffer[1] = frame->is_unicast;
// Send addresses in big-endian order
buffer[2] = (uint8_t)(frame->controller_addr >> 8);
buffer[3] = (uint8_t)frame->controller_addr;
buffer[4] = (uint8_t)(frame->peripheral_addr >> 8);
buffer[5] = (uint8_t)frame->peripheral_addr;
buffer[6] = frame->control;
buffer[7] = frame->length;
RS232_sendbytes((uint8_t *)&buffer, 8);
RS232_sendbytes(frame->data, frame->length);
buffer[0] = 0x17; // End of transmission block
buffer[1] = 0x0D; // \r
buffer[2] = 0x0A; // \n
RS232_sendbytes((uint8_t *)&buffer, 3);
} else {
RS232_PrintHex4(frame->is_unicast);
RS232_Print(" 0x");
RS232_PrintHex12(frame->controller_addr);
RS232_Print(" 0x");
RS232_PrintHex12(frame->peripheral_addr);
RS232_Print(" 0x");
RS232_PrintHex4(frame->control);
RS232_Print(" 0x");
RS232_PrintHex4(frame->length);
for (uint8_t i = 0; i < frame->length; i++) {
RS232_Print(" 0x");
RS232_PrintHex8(frame->data[i]);
}
RS232_Print("\n");
}
}
uint8_t AVCLAN_parseframe(const uint8_t *bytes, uint8_t len,
AVCLAN_frame_t *frame) {
struct errtype {
enum : uint8_t {
TOO_SHORT = 0x01,
MISMATCH_LENGTH,
LENGTH_TOO_BIG,
} errno;
uint8_t val;
} err = {0};
if (len < sizeof(AVCLAN_frame_t)) {
err.errno = TOO_SHORT;
goto handle_err;
}
const uint8_t *last = bytes + len;
frame->is_unicast = *bytes++;
frame->controller_addr = bytes[0] | ((uint16_t)bytes[1] << 8);
bytes += 2;
frame->peripheral_addr = bytes[0] | ((uint16_t)bytes[1] << 8);
bytes += 2;
frame->control = *bytes++;
frame->length = *bytes++;
if (frame->length > MAXMSGLEN) {
err.errno = LENGTH_TOO_BIG;
err.val = frame->length;
goto handle_err;
}
if ((bytes + frame->length) <= last) {
memcpy(frame->data, bytes, frame->length);
} else {
err.errno = MISMATCH_LENGTH;
goto handle_err;
}
if (false) {
handle_err:;
RS232_Print("ERR(parse): ");
switch (err.errno) {
case TOO_SHORT:
RS232_Print("not enough bytes too fill AVCLAN frame");
break;
case MISMATCH_LENGTH:
RS232_Print("frame->length is longer than remaining data");
break;
case LENGTH_TOO_BIG:
RS232_Print("frame->length exceeds MAXMSGLEN: 0x");
RS232_PrintHex8(err.val);
break;
default: break;
}
RS232_Print("\n");
}
return err.errno;
}
-48
View File
@@ -1,48 +0,0 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
/*
AVC LAN Frame Format
│ Bits │ Description
────────────────────────────────────────
| 1 │ Start bit
| 1 │ Direct/broadcast
| 12 │ Controller address
| 1 │ Parity
| 12 │ Peripheral address
| 1 │ Parity
| 1 │ *Acknowledge* (read below)
| 4 │ Control
| 1 │ Parity
| 1 │ *Acknowledge*
| 8 │ Message length (n)
| 1 │ Parity
| 1 │ *Acknowledge*
────────
| 8 │ Data
| 1 │ Parity
| 1 │ *Acknowledge*
*repeat `n` times*
No acknowledge bits are sent for broadcast frames.
*/
#pragma once
#include <stdint.h>
#include "avclan_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
void AVCLAN_printframe(const AVCLAN_frame_t *frame, bool binary);
uint8_t AVCLAN_parseframe(const uint8_t *bytes, uint8_t len,
AVCLAN_frame_t *frame);
#ifdef __cplusplus
}
#endif
-1
View File
@@ -9,7 +9,6 @@
#pragma once
#include "avclan_defs.h"
#include "avclan_frame.h"
#include "avclan_phy.h"
#include "mediacontrol.h"
#include "statustimer.h"
+6 -7
View File
@@ -32,9 +32,9 @@
#include "bus.hpp"
#include "avclan.hpp"
#include "avclan_defs.h"
#include "avclan_frame.h"
#include "avclan_phy.h" // bridge until phy has been ported
#include "com232.h"
#include "frame.hpp"
namespace {
constexpr int ADDR_WIDTH = 12;
@@ -45,8 +45,7 @@ void Bus::init() { AVCLAN_busInit(); };
void Bus::mute(bool mute) { AVCLAN_muteDevice(mute); };
bool Bus::is_muted() const { return AVCLAN_ismuted(); };
auto Bus::read(uint16_t address, AVCLAN_frame_t *in, log_t print)
-> Error::Read {
auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Error::Read {
struct errtype {
Error::Read errno;
union {
@@ -113,7 +112,7 @@ auto Bus::read(uint16_t address, AVCLAN_frame_t *in, log_t print)
goto handle_err;
}
if (in->length == 0 || in->length > MAXMSGLEN) {
if (in->length == 0 || in->length > Frame::MAXLENGTH) {
err.errno = BAD_LENGTH_RANGE;
err.val = in->length;
goto handle_err;
@@ -169,13 +168,13 @@ auto Bus::read(uint16_t address, AVCLAN_frame_t *in, log_t print)
if (print.print && (err.errno < STARTBIT_TOO_SHORT)) {
if (err.errno > BAD_DATA_PARITY)
in->length = 0;
AVCLAN_printframe(in, print.binary);
in->print(print);
}
return err.errno;
}
auto Bus::send(const AVCLAN_frame_t *out, log_t print) -> Error::Send {
auto Bus::send(const Frame *out, Frame::Print print) -> Error::Send {
struct errtype {
// Error enum is ordered such that a lower numeric value corresponds to
// more success
@@ -267,7 +266,7 @@ auto Bus::send(const AVCLAN_frame_t *out, log_t print) -> Error::Send {
}
if (print.print)
AVCLAN_printframe(out, print.binary);
out->print(print);
return err.errno;
}
+3 -2
View File
@@ -52,6 +52,7 @@
#include "avclan.hpp"
#include "avclan_defs.h"
#include "avclan_phy.h" // bridge until phy has been ported
#include "frame.hpp"
namespace avclan {
class Bus {
@@ -63,8 +64,8 @@ public:
void mute(bool mute);
bool is_muted() const;
Error::Read read(uint16_t address, AVCLAN_frame_t *in, log_t print);
Error::Send send(const AVCLAN_frame_t *out, log_t print);
Error::Read read(uint16_t address, Frame *in, Frame::Print print);
Error::Send send(const Frame *out, Frame::Print print);
static Handle get();
};
+6 -5
View File
@@ -8,6 +8,7 @@
#include "avclan_defs.h"
#include "cdchanger.hpp"
#include "frame.hpp"
#include "mediacontrol.h"
#include "statustimer.h"
@@ -53,7 +54,7 @@ void CDChanger::init() {
statustimer_init(this, &incrementTime_callback, &isPlaying_callback);
}
void CDChanger::handle(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
void CDChanger::handle(const Frame *in, Frame *out) {
const uint8_t *data = &in->data[1];
const auto from = static_cast<devices>(*data++);
/* const auto to = */ data++;
@@ -244,7 +245,7 @@ void CDChanger::handle(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
}
}
void CDChanger::react(AVCLAN_frame_t *out, detail::Error::Send err) {
void CDChanger::react(Frame *out, detail::Error::Send err) {
auto resp = static_cast<reaction_t>(out->reaction);
out->reaction = r_Nothing;
switch (resp) {
@@ -302,7 +303,7 @@ void CDChanger::react(AVCLAN_frame_t *out, detail::Error::Send err) {
}
}
void CDChanger::enable(AVCLAN_frame_t *out) {
void CDChanger::enable(Frame *out) {
if (!isPlaying()) {
if (mins > TWODIGIT_MAX)
mins = 0;
@@ -318,7 +319,7 @@ void CDChanger::enable(AVCLAN_frame_t *out) {
bool CDChanger::pending() { return statustimer_tickPending(); }
void CDChanger::resolvepending() { statustimer_clearTick(); }
void CDChanger::emit(AVCLAN_frame_t *out) {
void CDChanger::emit(Frame *out) {
generateStatus(out, true, dev_STATUS);
out->reaction = r_StateReport;
}
@@ -377,7 +378,7 @@ void CDChanger::incrementTime() {
}
// Used for changed status messages
void CDChanger::generateStatus(AVCLAN_frame_t *status, bool is_unicast,
void CDChanger::generateStatus(Frame *status, bool is_unicast,
devices to) const {
status->is_unicast = is_unicast;
if (!is_unicast)
+7 -7
View File
@@ -9,6 +9,7 @@
#include "avclan.hpp"
#include "avclan_defs.h"
#include "frame.hpp"
namespace avclan {
@@ -59,13 +60,13 @@ public:
static constexpr uint8_t id = dev_CD_CHANGER;
void init();
void handle(const AVCLAN_frame_t *in, AVCLAN_frame_t *out);
void react(AVCLAN_frame_t *out, detail::Error::Send err);
void enable(AVCLAN_frame_t *out);
void disable(AVCLAN_frame_t *out);
void handle(const Frame *in, Frame *out);
void react(Frame *out, detail::Error::Send err);
void enable(Frame *out);
void disable(Frame *out);
static bool pending();
static void resolvepending();
void emit(AVCLAN_frame_t *out);
void emit(Frame *out);
void incrementTime();
bool isPlaying() const;
@@ -74,8 +75,7 @@ private:
void stopPlaying();
void serialize(uint8_t *dst) const;
void setTime(uint8_t mins, uint8_t secs);
void generateStatus(AVCLAN_frame_t *status, bool is_unicast,
devices to) const;
void generateStatus(Frame *status, bool is_unicast, devices to) const;
void normalizeState();
bool playing = false;
+12 -12
View File
@@ -4,22 +4,22 @@
#pragma once
#include "avclan.hpp"
#include "avclan_defs.h"
#include "frame.hpp"
#include <concepts>
#include <cstdint>
namespace avclan {
template <class T>
concept Device = requires { std::integral_constant<uint8_t, T::id>{}; } &&
requires(T dev, const AVCLAN_frame_t *in, AVCLAN_frame_t *out,
detail::Error::Send err) {
dev.init();
dev.handle(in, out);
dev.enable(out);
dev.react(out, err);
{ dev.pending() } -> std::convertible_to<bool>;
dev.resolvepending();
dev.emit(out);
};
concept Device = requires {
std::integral_constant<uint8_t, T::id>{};
} && requires(T dev, const Frame *in, Frame *out, detail::Error::Send err) {
dev.init();
dev.handle(in, out);
dev.enable(out);
dev.react(out, err);
{ dev.pending() } -> std::convertible_to<bool>;
dev.resolvepending();
dev.emit(out);
};
} // namespace avclan
+118
View File
@@ -0,0 +1,118 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstdint>
#include <cstring>
#include "com232.h" // error logging
#include "frame.hpp"
namespace {
using Error = avclan::Frame::Error;
using enum Error::Parse;
} // namespace
namespace avclan {
void Frame::print(Frame::Print print) const {
if (print.binary) {
uint8_t buffer[8];
uint8_t *bptr = buffer;
*bptr++ = 0x10; // Data Link Escape, signaling binary data forthcoming
*bptr++ = static_cast<uint8_t>(is_unicast);
// Send addresses in big-endian order
*bptr++ = static_cast<uint8_t>(controller_addr >> 8);
*bptr++ = static_cast<uint8_t>(controller_addr);
*bptr++ = static_cast<uint8_t>(peripheral_addr >> 8);
*bptr++ = static_cast<uint8_t>(peripheral_addr);
*bptr++ = control;
*bptr++ = length;
RS232_sendbytes(buffer, 8);
RS232_sendbytes(data, length);
bptr = buffer;
*bptr++ = 0x17; // End of transmission block
*bptr++ = 0x0D; // \r
*bptr++ = 0x0A; // \n
RS232_sendbytes(buffer, 3);
} else {
RS232_PrintHex4(static_cast<uint8_t>(is_unicast));
RS232_Print(" 0x");
RS232_PrintHex12(controller_addr);
RS232_Print(" 0x");
RS232_PrintHex12(peripheral_addr);
RS232_Print(" 0x");
RS232_PrintHex4(control);
RS232_Print(" 0x");
RS232_PrintHex4(length);
for (uint8_t i = 0; i < length; i++) {
RS232_Print(" 0x");
RS232_PrintHex8(data[i]);
}
RS232_Print("\n");
}
}
Error::Parse Frame::parse(const uint8_t *bytes, uint8_t len) {
struct errtype {
Error::Parse errno;
uint8_t val;
} err = {};
const uint8_t *last = bytes + len;
if (len < sizeof(avclan::Frame)) {
err.errno = TOO_SHORT;
goto handle_err;
}
is_unicast = (*bytes++ != 0U);
controller_addr = bytes[0] | ((uint16_t)bytes[1] << 8);
bytes += 2;
peripheral_addr = bytes[0] | ((uint16_t)bytes[1] << 8);
bytes += 2;
control = *bytes++;
length = *bytes++;
if (length > MAXLENGTH) {
err.errno = LENGTH_TOO_BIG;
err.val = length;
goto handle_err;
}
if ((bytes + length) <= last) {
memcpy(data, bytes, length);
} else {
err.errno = MISMATCH_LENGTH;
goto handle_err;
}
if (false) { // NOLINT(readability-simplify-boolean-expr)
handle_err:;
RS232_Print("ERR(parse): ");
switch (err.errno) {
case TOO_SHORT:
RS232_Print("not enough bytes too fill AVCLAN frame");
break;
case MISMATCH_LENGTH:
RS232_Print("frame->length is longer than remaining data");
break;
case LENGTH_TOO_BIG:
RS232_Print("frame->length exceeds MAXLENGTH: 0x");
RS232_PrintHex8(err.val);
break;
default: break;
}
RS232_Print("\n");
}
return err.errno;
}
} // namespace avclan
+38
View File
@@ -0,0 +1,38 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
namespace avclan {
struct Frame {
struct Print {
bool print : 1 = false; // print at all
bool binary : 1 = false; // if printing, format as binary instead of text
bool verbose : 1 = false; // include extra context in error reports
};
struct Error {
enum class Parse : uint8_t {
TOO_SHORT = 0x01,
MISMATCH_LENGTH,
LENGTH_TOO_BIG,
};
};
static constexpr int MAXLENGTH = 32;
Error::Parse parse(const uint8_t *bytes, uint8_t len);
void print(Print print) const;
uint8_t reaction;
uint8_t owning_device;
bool is_unicast;
uint16_t controller_addr; // formerly "master"
uint16_t peripheral_addr; // formerly "slave"
uint8_t control = 0xF;
uint8_t length;
uint8_t data[MAXLENGTH];
};
} // namespace avclan
+6 -5
View File
@@ -7,6 +7,7 @@
#include "avclan_defs.h"
#include "bus.hpp"
#include "device.hpp"
#include "frame.hpp"
#include <cstdint>
#include <cstring>
@@ -24,16 +25,16 @@ public:
uint16_t address() const { return address_; };
Error::Read read(AVCLAN_frame_t *in, log_t print) {
Error::Read read(Frame *in, Frame::Print print) {
return bus.read(address_, in, print);
};
Error::Send send(const AVCLAN_frame_t *out, log_t print) {
Error::Send send(const Frame *out, Frame::Print print) {
return bus.send(out, print);
};
#define PACK3(a, b, c) (((uint32_t)(a) << 16) | ((uint32_t)(b) << 8) | (c))
void route(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
void route(const Frame *in, Frame *out) {
out->reaction = 0;
if (AVCLAN_ismuted() || in->length < 3)
@@ -119,7 +120,7 @@ public:
}
}
void react(AVCLAN_frame_t *out, Error::Send err) {
void react(Frame *out, Error::Send err) {
if (((Devs::id == out->owning_device) || ...))
((Devs::id == out->owning_device
? std::get<Devs>(devices_).react(out, err),
@@ -141,7 +142,7 @@ private:
dev.resolvepending();
}
template <Device Dev>
void device_preroute(Dev dev, const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
void device_preroute(Dev dev, const Frame *in, Frame *out) {
out->owning_device = Dev::id;
dev.handle(in, out);
}
+27 -26
View File
@@ -11,6 +11,7 @@
#include "board.h"
#include "cdchanger.hpp"
#include "com232.h"
#include "frame.hpp"
#include "peripheral.hpp"
#include "queue.hpp"
@@ -18,8 +19,10 @@ const char *const offon[] = {"OFF", "ON"};
constexpr uint8_t CACHE_SIZE = 32;
using namespace avclan;
namespace {
AVCLAN_frame_t frames[CACHE_SIZE];
Frame frames[CACHE_SIZE];
constinit Queue cache(frames);
constinit Queue incoming = cache;
@@ -60,17 +63,13 @@ int main() {
// Binary-mode REPL includes the full wire preamble (broadcast + 2*addr +
// control + length), so size for the worst case.
uint8_t data_tmp[MAXMSGLEN + sizeof(AVCLAN_frame_t)];
uint8_t data_tmp[Frame::MAXLENGTH + sizeof(Frame)];
uint8_t seqIdx = 0; // current index in data_tmp
// Temporary, direct access is questionable since cache has ownership
for (auto &frame : frames) {
frame.control = 0x0f;
}
avclan::Bus phy;
avclan::Peripheral<avclan::CDChanger> cd_changer(phy, 0x360);
using Error = decltype(cd_changer)::Error;
Bus phy;
Peripheral<CDChanger> peripheral(phy, 0x360);
using Error = decltype(peripheral)::Error;
using Print = Frame::Print;
Setup();
print_help();
@@ -78,9 +77,9 @@ int main() {
while (true) {
if (AVCLAN_busActive()) {
if (auto msg = cache.pop()) {
auto err = cd_changer.read(msg.get(), (log_t){.print = printAllFrames,
.binary = printBinary,
.verbose = verbose});
auto err = peripheral.read(msg.get(), Print{.print = printAllFrames,
.binary = printBinary,
.verbose = verbose});
if (err == Error::Read{0x00})
incoming.push(std::move(msg));
} else {
@@ -90,7 +89,7 @@ int main() {
if (const auto *in = incoming.peek()) {
if (auto out = cache.pop()) {
cd_changer.route(in, out.get());
peripheral.route(in, out.get());
incoming.pop();
if (out->reaction > 0)
@@ -100,7 +99,7 @@ int main() {
}
}
cd_changer.poll_devices([&](auto dev) {
peripheral.poll_devices([&](auto dev) {
if (auto status = cache.pop()) {
dev.emit(status.get());
outgoing.push(std::move(status));
@@ -110,9 +109,9 @@ int main() {
});
if (auto out = outgoing.pop()) {
auto err = cd_changer.send(
out.get(), (log_t){.print = printAllFrames, .binary = printBinary});
cd_changer.react(out.get(), err);
auto err = peripheral.send(
out.get(), Print{.print = printAllFrames, .binary = printBinary});
peripheral.react(out.get(), err);
if (out->reaction > 0)
outgoing.push(std::move(out));
}
@@ -139,8 +138,8 @@ int main() {
case 'E': // Beep
if (auto out = cache.pop()) {
out->is_unicast = true;
out->controller_addr = cd_changer.address();
out->peripheral_addr = HU_ADDR;
out->controller_addr = peripheral.address();
out->peripheral_addr = peripheral.controller();
{
const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS,
0x60, 0x01};
@@ -154,15 +153,15 @@ int main() {
case 'P':
if (auto out = cache.pop()) {
out->is_unicast = true;
out->controller_addr = cd_changer.address();
out->peripheral_addr = HU_ADDR;
out->controller_addr = peripheral.address();
out->peripheral_addr = peripheral.controller();
{
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
Ejection, dev_CD_CHANGER, 0x01};
out->length = sizeof(play);
memcpy(out->data, play, sizeof(play));
}
out->reaction = avclan::CDChanger::reaction_t::r_Ejection;
out->reaction = CDChanger::reaction_t::r_Ejection;
outgoing.push(std::move(out));
}
break;
@@ -224,7 +223,8 @@ int main() {
if (readBinary) {
if (data_tmp[seqIdx] == 0x17) {
if (auto out = cache.pop()) {
if (!AVCLAN_parseframe(data_tmp, --seqIdx, out.get())) {
if (out->parse(data_tmp, --seqIdx) ==
Frame::Error::Parse{0}) {
out->reaction = 1;
outgoing.push(std::move(out));
}
@@ -236,8 +236,9 @@ int main() {
} else {
if (auto out = cache.pop()) {
out->is_unicast = seqIsUnicast;
out->controller_addr = cd_changer.address();
out->peripheral_addr = seqIsUnicast ? HU_ADDR : 0x1FF;
out->controller_addr = peripheral.address();
out->peripheral_addr =
seqIsUnicast ? peripheral.controller() : 0x1FF;
out->length = seqIdx;
memcpy(out->data, data_tmp, seqIdx);
out->reaction = 1;