REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
protocol_oob.cpp
Go to the documentation of this file.
1// Copyright (c) 2024 anabrid GmbH
2// Contact: https://www.anabrid.com/licensing/
3// SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
5#include <Arduino.h>
6#include <bitset>
7#include <daq/daq.h>
8#include <protocol/protocol_oob.h>
9#include <run/run.h>
10#include <utils/logging.h>
11#include <utils/streaming_json.h>
12
13FLASHMEM void client::RunStateChangeNotificationHandler::handle(const run::RunStateChange change,
14 const run::Run &run) {
15 envelope_out.clear();
16 envelope_out["type"] = "run_state_change";
17 auto msg = envelope_out.createNestedObject("msg");
18 msg["id"] = run.id;
19 msg["t"] = change.t;
20 msg["old"] = run::RunStateNames[static_cast<size_t>(change.old)];
21 msg["new"] = run::RunStateNames[static_cast<size_t>(change.new_)];
22 serializeJson(envelope_out, target);
23 target.write("\n"); // note EthernetClient::writeFully("\n") is probably
24 target.flush();
25}
26
27FLASHMEM client::RunDataNotificationHandler::RunDataNotificationHandler(carrier::Carrier &carrier,
28 Print &target)
29 : carrier(carrier), target(target) {}
30
31FLASHMEM void client::RunDataNotificationHandler::handle(volatile uint32_t *data, size_t outer_count,
32 size_t inner_count, const run::Run &run) {
33 // The buffer has an inner_count that is always a power of two.
34 // We may be interested in only part of each row of the buffer
35 size_t inner_count_actual = inner_count;
36 size_t inner_count_desired = run.daq_config.get_num_channels();
37
38 // Streaming happens in equal-sized chunk, except possibly for the last one.
39 // For the last one, we may have to move the MESSAGE_END and send out less data.
40 auto new_buffer_length = calculate_total_buffer_length(outer_count, inner_count_desired);
41 if (new_buffer_length < actual_buffer_length) {
42 // Calculate position after the last of the now fewer outer_count data packages.
43 auto new_end_of_data = calculate_outer_buffer_position(outer_count, inner_count_desired);
44 // Fill buffer behind with zeros
45 memset(str_buffer + new_end_of_data, '\0', actual_buffer_length - new_buffer_length);
46 // Copy MESSAGE_END after last data package
47 memcpy(str_buffer + new_end_of_data - sizeof(',' /* trailing comma */), MESSAGE_END,
48 sizeof(MESSAGE_END) - 1);
49 // Add a newline
50 *(str_buffer + new_end_of_data + sizeof(MESSAGE_END) - 2) = '\n';
51 // From now, only send out that much data
52 actual_buffer_length = new_buffer_length;
53 } else if (new_buffer_length > actual_buffer_length) {
54 // This should never happen, since we would need to call prepare() again,
55 // which we do not want.
56 LOG_ERROR("RunDataNotificationHandler::handle should not have to increase buffer size.")
57 return;
58 }
59
60 // digitalWriteFast(LED_BUILTIN, HIGH);
61 auto buffer = str_buffer + BUFFER_LENGTH_STATIC;
62 size_t inner_length = 3 + inner_count_desired * 7 - 1;
63 for (size_t outer_i = 0; outer_i < outer_count; outer_i++) {
64 for (size_t inner_i = 0; inner_i < inner_count_desired; inner_i++) {
65 const uint32_t number = data[outer_i * inner_count_actual + inner_i];
66 const char *float_repr = daq::raw_to_str(number);
67 char *dst = buffer + outer_i * inner_length + 1 + inner_i * 7;
68 constexpr size_t len = 6;
69 char float_repr_fallback[len + 2];
70 if (!float_repr) {
71 // Need to make sure string is exactly "len" long without NULL terminating byte
72 // so it fits into the "len" sized slot in the dst buffer.
73 snprintf(float_repr_fallback, len + 1, "% 1.3f",
74 daq::raw_to_float(number, daq::details::raw_zero_offsets[inner_i]));
75 float_repr = float_repr_fallback;
76 }
77 memcpy(dst, float_repr, len);
78 }
79 }
80 // digitalWriteFast(LED_BUILTIN, LOW);
81
82 // digitalWriteFast(18, HIGH);
83 target.write(str_buffer, actual_buffer_length);
84 target.flush();
85 // digitalWriteFast(18, LOW);
86}
87
88FLASHMEM void client::RunDataNotificationHandler::stream(volatile uint32_t *buffer, run::Run &run) {
89 // TODO: Remove
90}
91
92FLASHMEM void client::RunDataNotificationHandler::prepare(run::Run &run) {
93 memcpy(str_buffer, MESSAGE_START, strlen(MESSAGE_START));
94 memcpy(str_buffer + BUFFER_IDX_ENTITY_ID, carrier.get_entity_id().c_str(), BUFFER_LENGTH_ENTITY_ID);
95
96 // The buffer has an inner_count that is always a power of two.
97 // We may be interested in only part of each row of the buffer
98 size_t inner_count_actual = run.daq_config.get_num_channels_min_power_of_two();
99 size_t inner_count_desired = run.daq_config.get_num_channels();
100
101 // We always stream half the buffer
102 // TODO: Do not access daq::stream::details::BUFFER_SIZE directly, probably make it a template parameter.
103 size_t outer_count = daq::stream::details::BUFFER_SIZE / inner_count_actual / 2;
104
105 actual_buffer_length = calculate_total_buffer_length(outer_count, inner_count_desired);
106 memset(str_buffer + BUFFER_LENGTH_STATIC, '-', actual_buffer_length - BUFFER_LENGTH_STATIC);
107 memcpy(str_buffer + BUFFER_IDX_RUN_ID, run.id.c_str(), BUFFER_LENGTH_RUN_ID);
108 auto buffer = str_buffer + BUFFER_LENGTH_STATIC - 1;
109 for (size_t outer_i = 0; outer_i < outer_count; outer_i++) {
110 *(++buffer) = '[';
111 for (size_t inner_i = 0; inner_i < inner_count_desired; inner_i++) {
112 buffer += 7;
113 *buffer = ',';
114 }
115 *buffer = ']';
116 *(++buffer) = ',';
117 }
118 memcpy(buffer, MESSAGE_END, sizeof(MESSAGE_END) - 1);
119 str_buffer[actual_buffer_length - 1] = '\n';
120}
121
122FLASHMEM size_t client::RunDataNotificationHandler::calculate_inner_buffer_length(size_t inner_count) {
123 // For each inner array, we need '[],', inner_count*6 for 'sD.FFF' and (inner_count-1) bytes for all ','.
124 return 3 + inner_count * 7 - 1;
125}
126
127FLASHMEM size_t client::RunDataNotificationHandler::calculate_outer_buffer_position(size_t outer_count,
128 size_t inner_count) {
129 // Returns the position of the N-th outer data package
130 auto inner_length = calculate_inner_buffer_length(inner_count);
131 return BUFFER_LENGTH_STATIC + outer_count * inner_length;
132}
133
134FLASHMEM size_t client::RunDataNotificationHandler::calculate_total_buffer_length(size_t outer_count,
135 size_t inner_count) {
136 auto inner_length = calculate_inner_buffer_length(inner_count);
137 // For message end, we need a few more bytes.
138 return BUFFER_LENGTH_STATIC + outer_count * inner_length - sizeof(',' /* trailing comma */) +
139 sizeof(MESSAGE_END) - sizeof('\0' /* null byte in MESSAGE_END */) + sizeof('\n' /* newline */);
140}
141
142FLASHMEM void client::StreamingRunDataNotificationHandler::handle(uint16_t *data, size_t outer_count,
143 size_t inner_count, const run::Run &run) {
144 utils::StreamingJson doc(target);
145 doc.begin_dict(); // envelope
146 doc.kv("type", "run_data");
147 doc.key("msg");
148 doc.begin_dict(); // msg
149 doc.kv("id", run.id);
150
151 doc.key("entity");
152 doc.begin_list();
153 doc.val(carrier.get_entity_id());
154 doc.val("0"); // cluster
155 doc.end_list();
156
157 doc.key("data");
158 doc.begin_list(); // data
159 for (size_t outer = 0; outer < outer_count; outer++) {
160 doc.begin_list(); // outer
161 for (size_t inner = 0; inner < inner_count; inner++) {
162 // doc.val(data[outer * inner_count + inner]);
163 const uint32_t number = data[outer * inner_count + inner];
164 const char *float_repr = nullptr; // daq::BaseDAQ::raw_to_str(number); //<- is just buggy! cf #168
165 if (float_repr) {
166 doc.json(float_repr);
167 } else {
168 doc.check_comma();
169
170 target.printf("% 1.3f", daq::raw_to_float(number, daq::details::raw_zero_offsets[inner]));
171 doc.needs_comma();
172 }
173 }
174 doc.end_list(); // outer
175 if (outer != outer_count - 1)
176 doc.check_comma();
177 }
178 doc.end_list(); // data
179
180 doc.end_dict(); // msg
181 doc.end_dict(); // envelope
182 doc.endln();
183}
uint32_t
Definition flasher.cpp:195
for(;;)
Definition flasher.cpp:197
Definition daq.h:14