SatNOGS-COMMS  4.1.0
A COMMS subsystem for CubeSats
Loading...
Searching...
No Matches
msg_arbiter.cpp
Go to the documentation of this file.
1/*
2 * SatNOGS-COMMS MCU software
3 *
4 * Copyright (C) 2023, Libre Space Foundation <http://libre.space>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * SPDX-License-Identifier: GNU General Public License v3.0 or later
20 */
21
22#include "msg_arbiter.hpp"
23#include "callbacks.hpp"
24#include "error_handler.hpp"
25#include "io.hpp"
26#include "io_wdg.hpp"
27#include "mission.hpp"
28#include "settings.hpp"
29#include "telecommand.hpp"
30#include "telemetry.hpp"
31#include "tests/test.hpp"
32#include "time.hpp"
33#include <osdlp/uslp.hpp>
34#include <psa/crypto.h>
35#include <satnogs-comms-lib/version.hpp>
36#include <string>
37#include <zephyr/task_wdt/task_wdt.h>
38
39namespace satnogs::comms
40{
41
42static constexpr std::array<uint8_t, 32>
43sha256_key_hex2bytes()
44{
45 std::array<uint8_t, 32> key = {0};
46 static_assert(std::char_traits<char>::length(CONFIG_TLC_SHA256_KEY) == 64,
47 "Hash should be exactly 64 hex annotated chars");
48 for (unsigned int i = 0; i < 64; i += 2) {
49 char s[2] = {CONFIG_TLC_SHA256_KEY[i], CONFIG_TLC_SHA256_KEY[i + 1]};
50 char byte = (char)strtol(s, NULL, 16);
51 key[i / 2] = byte;
52 }
53 return key;
54}
55
56static struct k_thread msg_arbiter_thread_data;
57K_THREAD_STACK_DEFINE(msg_arbiter_thread_stack, CONFIG_MSG_ARBITER_STACK_SIZE);
58
59static char rx_msgq_buffer[CONFIG_RX_MSGQ_SIZE * sizeof(msg_arbiter::msg)];
60static struct k_msgq rx_msgq;
61
62static char
63 can1_tx_msgq_buffer[CONFIG_CAN1_TX_MSGQ_SIZE * sizeof(msg_arbiter::msg)];
64static struct k_msgq can1_tx_msgq;
65
66static char
67 can2_tx_msgq_buffer[CONFIG_CAN2_TX_MSGQ_SIZE * sizeof(msg_arbiter::msg)];
68static struct k_msgq can2_tx_msgq;
69
70static char
71 uhf_tx_msgq_buffer[CONFIG_UHF_TX_MSGQ_SIZE * sizeof(msg_arbiter::msg)];
72static struct k_msgq uhf_tx_msgq;
73
74static char
75 sband_tx_msgq_buffer[CONFIG_SBAND_TX_MSGQ_SIZE * sizeof(msg_arbiter::msg)];
76static struct k_msgq sband_tx_msgq;
77
78namespace scl = satnogs::comms::lib;
79
80static const auto &tlc_key = sha256_key_hex2bytes();
81
82static msg_arbiter::msg response;
83static msg_arbiter::msg space_packet;
84static msg_arbiter::msg fw_response;
85
97int
98msg_arbiter::push(const msg_arbiter::msg &m, k_timeout_t wait)
99{
100 return k_msgq_put(&rx_msgq, &m, wait);
101}
102
114int
116{
117 switch (s) {
118 case subsys::CAN1:
119 return k_msgq_get(&can1_tx_msgq, &m, wait);
120 case subsys::CAN2:
121 return k_msgq_get(&can2_tx_msgq, &m, wait);
123 return k_msgq_get(&uhf_tx_msgq, &m, wait);
125 return k_msgq_get(&sband_tx_msgq, &m, wait);
126 default:
127 throw scl::inval_arg_exception(__FILE__, __LINE__);
128 }
129}
130
144int
145msg_arbiter::fwd(const msg_arbiter::msg &m, subsys s, k_timeout_t wait)
146{
147 switch (s) {
148 case subsys::CAN1: {
149#if CONFIG_IO_CAN1_ENABLE
150 return k_msgq_put(&can1_tx_msgq, &m, wait);
151#else
152 throw scl::inval_arg_exception(__FILE__, __LINE__);
153#endif
154 }
155 case subsys::CAN2: {
156#if CONFIG_IO_CAN2_ENABLE
157 return k_msgq_put(&can2_tx_msgq, &m, wait);
158#else
159 throw scl::inval_arg_exception(__FILE__, __LINE__);
160#endif
161 }
163 return k_msgq_put(&uhf_tx_msgq, &m, wait);
165 return k_msgq_put(&sband_tx_msgq, &m, wait);
166 default:
167 throw scl::inval_arg_exception(__FILE__, __LINE__);
168 }
169}
170
171int
172msg_arbiter::fwd(uint8_t mapid, uint8_t vcid, const msg_arbiter::msg &m,
173 subsys s, k_timeout_t wait)
174{
175 osdlp::tf_primary_header uslp_tf_hdr(
176 CONFIG_CCSDS_SPACECRAFT_ID,
177 osdlp::tf_source_destination_id::SOURCE_TRANSFER_FRAME, vcid, mapid,
178 osdlp::tf_bypass_sequence_control_flag::SEQUENCE_CONTROLLED_QOS, 0,
179 osdlp::tf_operational_control_field_flag::OCF_NOT_PRESENT, 0);
180
181 osdlp::tf_data_field_header uslp_tfdf_hdr;
182 osdlp::uslp uslp_resp(uslp_tf_hdr, uslp_tfdf_hdr);
183
184 auto uslp_len =
185 uslp_resp.serialize(fw_response.data, CONFIG_MAX_MTU, m.data, m.len);
186
187 fw_response.iface = m.iface;
188 fw_response.len = uslp_len;
189 return fwd(fw_response, s, wait);
190}
191
199uint32_t
201{
202 switch (s) {
203 case subsys::CAN1:
204 return k_msgq_num_used_get(&can1_tx_msgq);
205 case subsys::CAN2:
206 return k_msgq_num_used_get(&can2_tx_msgq);
208 return k_msgq_num_used_get(&uhf_tx_msgq);
210 return k_msgq_num_used_get(&sband_tx_msgq);
211 default:
212 throw scl::inval_arg_exception(__FILE__, __LINE__);
213 }
214}
215
221void
223{
224 m_tid = k_thread_create(&msg_arbiter_thread_data, msg_arbiter_thread_stack,
225 K_THREAD_STACK_SIZEOF(msg_arbiter_thread_stack),
226 parse_thread, NULL, NULL, NULL,
227 CONFIG_MSG_ARBITER_PRIO, 0, K_NO_WAIT);
228 if (!m_tid) {
229 k_oops();
230 }
231 k_thread_name_set(m_tid, "msg_arbiter");
232}
233
234void
235msg_arbiter::parse_thread(void *arg1, void *arg2, void *arg3)
236{
237 int task_wdt_id = task_wdt_add(CONFIG_WATCHDOG_PERIOD_MSG_ARBITER,
238 task_wdt_callback, (void *)k_current_get());
241
242 while (1) {
243 try {
244 task_wdt_feed(task_wdt_id);
245 if (k_msgq_get(&rx_msgq, &arb.m_msg, K_MSEC(1000)) == 0) {
246 /*
247 * SatNOGS-COMMS expects CCSDS USLP frames. However if none received, we
248 * forward the message to the mission specific parser in case the
249 * mission uses something custom
250 */
251
252 osdlp::tf_primary_header uslp_tf_hdr;
253 osdlp::tf_data_field_header uslp_tfdf_hdr;
254
255 osdlp::uslp<> uslp(uslp_tf_hdr, uslp_tfdf_hdr);
256 uslp.deserialize(arb.m_msg.data, arb.m_msg.len);
257 if (uslp.get_is_valid_packet()) {
258 /* Drop any frame that is not for this spacecraft */
259 if (uslp_tf_hdr.get_spacecraft_id() != CONFIG_CCSDS_SPACECRAFT_ID) {
260 continue;
261 }
262
263 /* COMMS is the final destination */
264 if (uslp_tf_hdr.get_source_destination_id() !=
265 osdlp::tf_source_destination_id::DESTINATION_TRANSFER_FRAME) {
266 continue;
267 }
268
269 /*
270 * Everything ok! Get the CCSDS space packet now, which is
271 * encapsulated in the payload section of the USLP frame
272 */
273 const uint8_t *sp = uslp.get_payload();
274 size_t sp_len = uslp.get_payload_length();
275
276 if (tc.ccsds_frame_valid(sp, sp_len) == false) {
277 continue;
278 }
279
280 // TC/TM cares only about the CCSDS Space packet portion
281 space_packet.len = std::min<size_t>(sp_len, CONFIG_MAX_MTU);
282 space_packet.iface = arb.m_msg.iface;
283 std::copy_n(sp, space_packet.len, space_packet.data);
284
285 /*
286 * SatNOGS-COMMS uses XTCE for the TC/TM try to decode the agreed TCs.
287 * In case of failure, forward the space packet contents to the
288 * mission specific parser as a last resort
289 */
290 if (tc.decode_ccsds_xtce(response, space_packet, task_wdt_id)) {
291 // For now just response to the interface that we got the request
292 response.iface = space_packet.iface;
293
294 /* The response is a space packet. Make it a USLP */
295 // FIXME Define properly the VCs
296 osdlp::tf_primary_header uslp_resp_tf_hdr(
297 CONFIG_CCSDS_SPACECRAFT_ID,
298 osdlp::tf_source_destination_id::SOURCE_TRANSFER_FRAME, 0, 0,
299 osdlp::tf_bypass_sequence_control_flag::SEQUENCE_CONTROLLED_QOS,
300 0, osdlp::tf_operational_control_field_flag::OCF_NOT_PRESENT,
301 0);
302
303 osdlp::tf_data_field_header uslp_resp_tfdf_hdr;
304 osdlp::uslp uslp_resp(uslp_resp_tf_hdr, uslp_resp_tfdf_hdr);
305
306 /* Serialize the USLP inside a msg_arbiter::msg */
307 auto uslp_len = uslp_resp.serialize(arb.m_msg.data, CONFIG_MAX_MTU,
308 response.data, response.len);
309 arb.m_msg.len = uslp_len;
310 arb.m_msg.iface = response.iface;
311 arb.fwd(arb.m_msg, arb.m_msg.iface);
312 } else {
313 /* Try with the mission specific parser */
314 mission::get_instance().msg_parser(space_packet, true);
315 }
316 } else {
317 /* Try with the mission specific parser */
318 mission::get_instance().msg_parser(arb.m_msg, false);
319 }
320 }
321 } catch (const scl::exception &e) {
322 auto &err = error_handler::get_instance();
323 err.handle(e);
324 } catch (const std::exception &e) {
325 auto &err = error_handler::get_instance();
326 err.handle(e);
327 }
328 }
329}
330
332{
333 k_msgq_init(&rx_msgq, rx_msgq_buffer, sizeof(msg_arbiter::msg), rx_msgq_size);
334 k_msgq_init(&can1_tx_msgq, can1_tx_msgq_buffer, sizeof(msg_arbiter::msg),
336 k_msgq_init(&can2_tx_msgq, can2_tx_msgq_buffer, sizeof(msg_arbiter::msg),
338 k_msgq_init(&uhf_tx_msgq, uhf_tx_msgq_buffer, sizeof(msg_arbiter::msg),
340 k_msgq_init(&sband_tx_msgq, sband_tx_msgq_buffer, sizeof(msg_arbiter::msg),
342 k_mutex_init(&m_mtx);
343}
344
345} // namespace satnogs::comms
static error_handler & get_instance()
Singleton access to the error_handler subsystem.
Generic exception indicating an invalid argument.
bool msg_parser(msg_arbiter::msg &msg, bool is_sp)
This method is called inside the msg_arbiter, in case an incoming message was not parsed by the defau...
Definition mission.cpp:122
static mission & get_instance()
Get a singleton access to the mission subsystem.
Definition mission.hpp:91
uint8_t data[mtu]
Buffer to hold the data.
size_t len
Data size in bytes.
subsys iface
The interface from which the msg was received.
Incoming/Outgoing Message Arbiter.
static constexpr size_t rx_msgq_size
uint32_t backpressure(subsys s)
Returns the number of messages that are waiting in the message queue of a specific subsystem.
int pull(msg_arbiter::msg &m, subsys s, k_timeout_t wait=K_MSEC(CONFIG_MSG_ARBITER_TIMEOUT_MS))
Pulls a message for a specific IO interface.
static constexpr size_t uhf_tx_msgq_size
int fwd(const msg_arbiter::msg &m, subsys s, k_timeout_t wait=K_MSEC(CONFIG_MSG_ARBITER_TIMEOUT_MS))
Forward a received message for processing.
static msg_arbiter & get_instance()
Singleton access to a unique and global msg_arbiter instance.
subsys
Peripheral identifier.
static constexpr size_t sband_tx_msgq_size
void start()
Starts the processing task for incoming messages handling.
msg_arbiter(msg_arbiter const &)=delete
static constexpr size_t can2_tx_msgq_size
int push(const msg_arbiter::msg &m, k_timeout_t wait=K_MSEC(CONFIG_MSG_ARBITER_TIMEOUT_MS))
Push a received message for processing.
static constexpr size_t can1_tx_msgq_size
bool decode_ccsds_xtce(msg_arbiter::msg &m, int wdgid)
static telecommand & get_instance()
bool ccsds_frame_valid(const uint8_t *b, size_t len)
void task_wdt_callback(int channel_id, void *user_data)
Definition callbacks.cpp:37
K_THREAD_STACK_DEFINE(radio_rx_thread_stack, CONFIG_RADIO_RX_THREAD_STACK_SIZE)