REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
iblock.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 <block/iblock.h>
6#include <utils/is_number.h>
7#include <utils/logging.h>
8
9blocks::IBlock::IBlock(IBlockHAL *hardware) : FunctionBlock("I", hardware), hardware(hardware), outputs{0} {
10 classifier.class_enum = CLASS_;
11}
12
13blocks::IBlock::IBlock() : IBlock(new IBlockHALDummy(bus::NULL_ADDRESS)) {}
14
15FLASHMEM metadata::eui_t blocks::IBlock::get_entity_eui() const {
16 if (hardware)
17 return hardware->get_entity_eui();
18 return {};
19}
20
21FLASHMEM utils::status blocks::IBlock::write_to_hardware() {
22 return utils::status(hardware->write_upscaling(scaling_factors) and hardware->write_outputs(outputs));
23}
24
25FLASHMEM bool blocks::IBlock::init() {
26 LOG(ANABRID_DEBUG_INIT, __PRETTY_FUNCTION__);
27 if (!FunctionBlock::init()) {
28 return false;
29 };
30 // I-Block matrix is not reset on power-cycle, apparently.
31 if (!write_to_hardware())
32 return false;
33 return true;
34}
35
36FLASHMEM bool blocks::IBlock::_is_connected(uint8_t input, uint8_t output) const {
37 return outputs[output] & INPUT_BITMASK(input);
38}
39
40FLASHMEM bool blocks::IBlock::_is_output_connected(uint8_t output) const { return outputs[output]; }
41
42FLASHMEM bool blocks::IBlock::is_connected(uint8_t input, uint8_t output) const {
43 if (output >= NUM_OUTPUTS or input >= NUM_INPUTS)
44 return false;
45 return _is_connected(input, output);
46}
47
48FLASHMEM bool blocks::IBlock::is_anything_connected() const {
49 for (auto &output : outputs)
50 if (_is_output_connected(output))
51 return true;
52 return false;
53}
54
55FLASHMEM bool blocks::IBlock::connect(uint8_t input, uint8_t output, bool exclusive,
56 bool allow_input_splitting) {
57 if (output >= NUM_OUTPUTS or input >= NUM_INPUTS)
58 return false;
59
60 // Usually, we don't want to connect one input to multiple outputs, so check for other connections
61 if (!allow_input_splitting) {
62 for (size_t other_output_idx = 0; other_output_idx < outputs.size(); other_output_idx++) {
63 if (output == other_output_idx)
64 continue;
65 if (is_connected(input, other_output_idx)) {
66 return false;
67 }
68 }
69 }
70
71 if (exclusive)
72 outputs[output] = INPUT_BITMASK(input);
73 else
74 outputs[output] |= INPUT_BITMASK(input);
75 return true;
76}
77
78FLASHMEM void blocks::IBlock::reset_outputs() {
79 for (auto &output : outputs) {
80 output = 0;
81 }
82}
83
84FLASHMEM void blocks::IBlock::reset(entities::ResetAction action) {
85 FunctionBlock::reset(action);
86
87 if (action.has(entities::ResetAction::CIRCUIT_RESET)) {
88 reset_outputs();
89 reset_upscaling();
90 }
91}
92
93FLASHMEM utils::status blocks::IBlock::config_self_from_json(JsonObjectConst cfg) {
94#ifdef ANABRID_DEBUG_ENTITY_CONFIG
95 Serial.println(__PRETTY_FUNCTION__);
96#endif
97 for (auto cfgItr = cfg.begin(); cfgItr != cfg.end(); ++cfgItr) {
98 if (cfgItr->key() == "outputs") {
99 auto res = _config_outputs_from_json(cfgItr->value());
100 if (!res)
101 return res;
102 } else if (cfgItr->key() == "upscaling") {
103 auto res = _config_upscaling_from_json(cfgItr->value());
104 if (!res)
105 return res;
106 } else {
107 return utils::status("IBlock: Unknown configuration key");
108 }
109 }
110 return utils::status::success();
111}
112
113FLASHMEM utils::status blocks::IBlock::_config_outputs_from_json(const JsonVariantConst &cfg) {
114 // Handle a mapping of output to list of inputs
115 // This only overrides outputs that are specifically mentioned
116 if (cfg.is<JsonObjectConst>()) {
117 for (JsonPairConst keyval : cfg.as<JsonObjectConst>()) {
118 // Key defines output
119 // TODO: Check conversion from string to number
120
121 std::string output_str = keyval.key().c_str();
122 if (!utils::is_number(output_str.begin(), output_str.end()))
123 return utils::status("IBlock: Expected number but key is '%s'", output_str.c_str());
124
125 auto output = std::stoul(output_str);
126 // Disconnect also sanity checks output index for us
127 if (!disconnect(output))
128 return utils::status("IBlock: Could not disconnect output '%d', probably out of range", output);
129 // Input may be given as list or as a single number
130 auto res = _connect_from_json(keyval.value(), output);
131 if (!res)
132 return res;
133 }
134 return utils::status::success();
135 }
136 // Handle a list of outputs
137 // This must overwrite all outputs, so we clear all of them
138 else if (cfg.is<JsonArrayConst>()) {
139 auto outputs_json = cfg.as<JsonArrayConst>();
140 if (outputs_json.size() != NUM_OUTPUTS)
141 return utils::status("IBlock: Given array size %d does not meet expected %d", outputs_json.size(),
142 NUM_OUTPUTS);
143 reset_outputs();
144 uint8_t idx = 0;
145 for (JsonVariantConst input_spec : outputs_json) {
146 // Input may be given as list or as a single number
147 auto res = _connect_from_json(input_spec, idx++);
148 if (!res)
149 return res;
150 }
151 return utils::status::success();
152 }
153 return utils::status("IBlock: Expected either array or object as configuration");
154}
155
156FLASHMEM utils::status blocks::IBlock::_config_upscaling_from_json(const JsonVariantConst &cfg) {
157 if (cfg.is<JsonObjectConst>()) {
158 for (JsonPairConst keyval : cfg.as<JsonObjectConst>()) {
159 if (!keyval.value().is<bool>())
160 return utils::status("IBlock upscaling must be boolean");
161
162 auto input = std::stoul(keyval.key().c_str());
163 if (input > NUM_INPUTS)
164 return utils::status("IBlock upscaling too many values");
165
166 set_upscaling(input, keyval.value());
167 return utils::status::success();
168 }
169 } else if (cfg.is<JsonArrayConst>()) {
170 auto array = cfg.as<JsonArrayConst>();
171 if (array.size() != NUM_INPUTS)
172 return utils::status("IBlock upscaling wrong number of inputs");
173
174 for (uint8_t input = 0; input < NUM_INPUTS; input++) {
175 if (!array[input].is<bool>())
176 return utils::status("IBlock upscaling expecting boolean");
177 set_upscaling(input, array[input]);
178 }
179 return utils::status::success();
180 }
181 return utils::status("IBlock upscaling: Either provide list or object");
182}
183
184FLASHMEM utils::status blocks::IBlock::_connect_from_json(const JsonVariantConst &input_spec, uint8_t output) {
185 if (input_spec.isNull()) {
186 // Output is already disconnected from outer code
187 } else if (input_spec.is<JsonArrayConst>()) {
188 for (auto input : input_spec.as<JsonArrayConst>()) {
189 if (!input.is<uint8_t>())
190 return utils::status("IBlock: input is not an integer");
191 if (!connect(input, output))
192 return utils::status("IBlock: Could not connect %d -> %d, probably out of bounds", input, outputs);
193 }
194 } else if (input_spec.is<uint8_t>()) {
195 if (!connect(input_spec, output))
196 return utils::status("IBlock: Could not connect %d -> %d.", input_spec, output);
197 } else {
198 return utils::status("IBlock: Illegal data type in input specification");
199 }
200 return utils::status::success();
201}
202
203FLASHMEM bool blocks::IBlock::disconnect(uint8_t input, uint8_t output) {
204 // Fail if input was not connected
205 if (!is_connected(input, output))
206 return false;
207 outputs[output] &= ~INPUT_BITMASK(input);
208 return true;
209}
210
211FLASHMEM bool blocks::IBlock::disconnect(uint8_t output) {
212 if (output >= NUM_OUTPUTS)
213 return false;
214 outputs[output] = 0;
215 return true;
216}
217
218FLASHMEM bool blocks::IBlock::set_upscaling(uint8_t input, bool upscale) {
219 if (input >= NUM_INPUTS)
220 return false;
221 scaling_factors[input] = upscale;
222 return true;
223}
224
225FLASHMEM void blocks::IBlock::set_upscaling(std::bitset<NUM_INPUTS> scales) { scaling_factors = scales; }
226
227FLASHMEM void blocks::IBlock::reset_upscaling() { scaling_factors.reset(); }
228
229FLASHMEM bool blocks::IBlock::get_upscaling(uint8_t output) const {
230 if (output > 32)
231 return false;
232 else
233 return scaling_factors[output];
234}
235
236FLASHMEM void blocks::IBlock::config_self_to_json(JsonObject &cfg) {
237 Entity::config_self_to_json(cfg);
238 // Save outputs into cfg
239 auto outputs_cfg = cfg.createNestedArray("outputs");
240 for (uint8_t output = 0; output < NUM_OUTPUTS; output++) {
241 if (outputs[output]) {
242 auto inputs_cfg = outputs_cfg.createNestedArray();
243 for (uint8_t input = 0; input < NUM_INPUTS; input++)
244 if (_is_connected(input, output))
245 inputs_cfg.add(input);
246 } else {
247 outputs_cfg.add(nullptr);
248 }
249 }
250
251 auto upscaling_cfg = cfg.createNestedArray("upscaling");
252 for (uint8_t input = 0; input < NUM_INPUTS; input++) {
253 if (scaling_factors[input])
254 upscaling_cfg.add(true);
255 else
256 upscaling_cfg.add(false);
257 }
258}
259
260FLASHMEM const std::array<uint32_t, blocks::IBlock::NUM_OUTPUTS> &blocks::IBlock::get_outputs() const {
261 return outputs;
262}
263
264FLASHMEM void blocks::IBlock::set_outputs(const std::array<uint32_t, NUM_OUTPUTS> &outputs_) {
265 outputs = outputs_;
266}
Definition bus.h:21