SatNOGS-COMMS  4.1.0
A COMMS subsystem for CubeSats
Loading...
Searching...
No Matches
thermal.cpp
Go to the documentation of this file.
1/*
2 * SatNOGS-COMMS MCU software
3 *
4 * Copyright (C) 2025, 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 "thermal.hpp"
23#include <callbacks.hpp>
24#include <io.hpp>
25#include <logger.hpp>
27#include <startup.hpp>
28
29namespace scl = satnogs::comms::lib;
30
31namespace satnogs::comms
32{
33
34static struct k_thread thermal_thread_data;
35K_THREAD_STACK_DEFINE(thermal_thread_stack,
36 CONFIG_THERMAL_MONITOR_THREAD_STACK_SIZE);
37
38thermal::thermal() : m_uhf_triggered(false), m_sband_triggered(false) {}
39
44void
46{
47 m_tid = k_thread_create(&thermal_thread_data, thermal_thread_stack,
48 K_THREAD_STACK_SIZEOF(thermal_thread_stack),
49 thermal_thread, NULL, NULL, NULL,
50 CONFIG_THERMAL_MONITOR_THREAD_PRIORITY, 0, K_NO_WAIT);
51 if (!m_tid) {
52 k_oops();
53 }
54 k_thread_name_set(m_tid, "thermal_monitor");
55}
56
57void
58thermal::thermal_thread(void *arg1, void *arg2, void *arg3)
59{
60 static_assert(task_wdg_period / 2 > CONFIG_THERMAL_MONITOR_PERIOD_SECS * 1000,
61 "Wrong configuration");
62 int task_wdt_id =
63 task_wdt_add(60000, task_wdt_callback, (void *)k_current_get());
64
65 while (1) {
66 task_wdt_feed(task_wdt_id);
67 k_sleep(K_SECONDS(CONFIG_THERMAL_MONITOR_PERIOD_SECS));
68
69 /* Step 1: Get temperatures from the various sensors and check their
70 * validity
71 */
72 auto &th = thermal::get_instance();
73 auto &log = logger::get_instance();
74 auto &radio_ctrl = io::get_instance().radio_ctrl();
75 auto &s = settings::get_instance();
76 try {
79
80 /* Check if the reported temperature is within the expected range */
81 if (std::isfinite(t) &&
84 /* If previously invalid, reset the moving average */
85 if (th.m_state.uhf_sensor_valid == false) {
86 th.m_uhf_temp.reset(t);
87 } else {
88 th.m_uhf_temp(t);
89 }
90 th.m_state.uhf_sensor_valid = true;
91
92 } else {
93 th.m_state.uhf_sensor_valid = false;
94 }
95 } catch (...) {
96 th.m_state.uhf_sensor_valid = false;
97 }
98
99 try {
102 /* Check if the reported temperature is within the expected range */
103 if (std::isfinite(t) &&
104 t > s.get<settings::param::THERMAL_MIN_VALID_TEMP>() &&
105 t < s.get<settings::param::THERMAL_MAX_VALID_TEMP>()) {
106 /* If previously invalid, reset the moving average */
107 if (th.m_state.sband_sensor_valid == false) {
108 th.m_sband_temp.reset(t);
109 } else {
110 th.m_sband_temp(t);
111 }
112 th.m_state.sband_sensor_valid = true;
113
114 } else {
115 th.m_state.sband_sensor_valid = false;
116 }
117 } catch (...) {
118 th.m_state.sband_sensor_valid = false;
119 }
120
121 try {
122 float t =
124 /* Check if the reported temperature is within the expected range */
125 if (std::isfinite(t) &&
126 t > s.get<settings::param::THERMAL_MIN_VALID_TEMP>() &&
127 t < s.get<settings::param::THERMAL_MAX_VALID_TEMP>()) {
128 /* If previously invalid, reset the moving average */
129 if (th.m_state.pcb_sensor_valid == false) {
130 th.m_pcb_temp.reset(t);
131 } else {
132 th.m_pcb_temp(t);
133 }
134 th.m_state.pcb_sensor_valid = true;
135
136 } else {
137 th.m_state.pcb_sensor_valid = false;
138 }
139 } catch (...) {
140 th.m_state.pcb_sensor_valid = false;
141 }
142
143 /* Step 2: Take actions! */
144
145 /* UHF interface */
146 if (th.m_state.uhf_sensor_valid) {
147 if (th.m_state.uhf_triggered) {
148 if (th.m_uhf_temp() <
149 s.get<settings::param::THERMAL_UHF_ENABLE_TEMP>()) {
150 th.m_state.uhf_triggered = false;
151 log.log("Thermal UHF: Re-enabling UHF interface");
152 radio_ctrl.enable(scl::radio::interface::UHF);
153 }
154 } else {
155 if (th.m_uhf_temp() >
156 s.get<settings::param::THERMAL_UHF_SHUTDOWN_TEMP>()) {
157 th.m_state.uhf_triggered = true;
158 log.log("Thermal UHF: Disabling UHF interface");
159 radio_ctrl.disable(scl::radio::interface::UHF);
160 }
161 }
162 }
163 /* Use the PCB sensor as backup */
164 else if (th.m_state.pcb_sensor_valid) {
165 if (th.m_state.uhf_triggered) {
166 if (th.m_pcb_temp() <
167 s.get<settings::param::THERMAL_PCB_ENABLE_TEMP>()) {
168 th.m_state.uhf_triggered = false;
169 log.log("Thermal UHF: Re-enabling UHF interface from backup sensor");
170 radio_ctrl.enable(scl::radio::interface::UHF);
171 }
172 } else {
173 if (th.m_pcb_temp() >
174 s.get<settings::param::THERMAL_PCB_SHUTDOWN_TEMP>()) {
175 th.m_state.uhf_triggered = true;
176 log.log("Thermal UHF: Disabling UHF interface from backup sensor");
177 radio_ctrl.disable(scl::radio::interface::UHF);
178 }
179 }
180 }
181 /* We cannot do much at this point. Re-enable the interface and pray! */
182 else {
183 if (th.m_state.uhf_triggered) {
184 th.m_state.uhf_triggered = false;
185 log.log("Thermal UHF: Re-enabling UHF interface. Lost all sensors");
186 radio_ctrl.enable(scl::radio::interface::UHF);
187 }
188 }
189
190 /* S-band interface*/
191 if (th.m_state.sband_sensor_valid) {
192 if (th.m_state.sband_triggered) {
193 if (th.m_sband_temp() <
194 s.get<settings::param::THERMAL_SBAND_ENABLE_TEMP>()) {
195 th.m_state.sband_triggered = false;
196 log.log("Thermal S-band: Re-enabling S-band interface");
197 radio_ctrl.enable(scl::radio::interface::SBAND);
198 }
199 } else {
200 if (th.m_sband_temp() >
201 s.get<settings::param::THERMAL_SBAND_SHUTDOWN_TEMP>()) {
202 th.m_state.sband_triggered = true;
203 log.log("Thermal S-band: Disabling S-band interface");
204 radio_ctrl.disable(scl::radio::interface::SBAND);
205 }
206 }
207 }
208 /* Use the PCB sensor as backup */
209 else if (th.m_state.pcb_sensor_valid) {
210 if (th.m_state.sband_triggered) {
211 if (th.m_pcb_temp() <
212 s.get<settings::param::THERMAL_PCB_ENABLE_TEMP>()) {
213 th.m_state.sband_triggered = false;
214 log.log("Thermal S-band: Re-enabling S-band interface from backup "
215 "sensor");
216 radio_ctrl.enable(scl::radio::interface::SBAND);
217 }
218 } else {
219 if (th.m_pcb_temp() >
220 s.get<settings::param::THERMAL_PCB_SHUTDOWN_TEMP>()) {
221 th.m_state.sband_triggered = true;
222 log.log(
223 "Thermal S-band: Disabling S-band interface from backup sensor");
224 radio_ctrl.disable(scl::radio::interface::SBAND);
225 }
226 }
227 }
228 /* We cannot do much at this point. Re-enable the interface and pray! */
229 else {
230 if (th.m_state.sband_triggered) {
231 th.m_state.sband_triggered = false;
232 log.log(
233 "Thermal S-band: Re-enabling S-band interface. Lost all sensors");
234 radio_ctrl.enable(scl::radio::interface::SBAND);
235 }
236 }
237 }
238}
239
240} // namespace satnogs::comms
static io & get_instance()
Definition io.hpp:181
static board & get_instance()
Gets a reference to the single instance of the Board interface class.
Definition board.cpp:66
float temperature(temperature_sensor s) const
Gets the temperature from a specific sensor.
Definition board.cpp:162
static logger & get_instance()
Singleton access to the logger subsystem.
Definition logger.hpp:102
static settings & get_instance()
Get a singleton access to the settings subsystem.
Definition settings.hpp:210
void start()
Activates and starts the thermal monitoring mechanism.
Definition thermal.cpp:45
static thermal & get_instance()
Definition thermal.hpp:75
@ UHF_PA
UHF PA temperature sensor.
@ SBAND_PA
S-Band PA temperature sensor.
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)