You’re right. Copper is a far better thermal conductor than the pads, and fewer interfaces are usually better. However, thickness and mechanical tolerances also matter. The pads compensate for tiny height differences and eliminate air gaps, ensuring consistent contact. The copper spreads the heat, while the pads make sure the heat can actually reach the next layer efficiently.
It is hard to sort through your comment. I feel overwhelmed with information and starved for knowledge.
I really would rather see your LLM prompts than their output.
My prompt was simple: “Take this text <Exact content of the July 20 message> and turn it into an infographic.” I used NotebookLM for that.
Why include the infographic if it’s just a summary of what you wrote? Its inaccuracies are distracting and misleading. You are showing us a picture you didn’t feel was worth it to take the time to create.
5Ah * 3.7V (a typical rechargeable battery nominal voltage) = 18.5Wh; 18.5Wh / 2h = 9.5W power usage, which sounds like a lot of energy being used just on the cooling system. My CM5 (Lite 16GB), screen, AIOv1, and keyboard backlight use about 7W all together.
We’re talking about two completely different machines, with completely different configurations, power profiles, power consumption, and workloads (SDR + DSD-FME).
My uConsole is configured as:
CM5104032 + AIOv2 + 7 antennas + AC1200 Wi-Fi + NVMe Battery Board + 512 GB ADATA M.2 Gen4 SSD + 2x Molicel P30B 3000 mAh (30A).
So our uConsoles have in common a mainboard, a CM5 module with WiFi, much of their SDR circuitry, NVMe capability and installed SSD, 2x 18650 cells, and antennas, but your uConsole has an additional WiFi adapter and the benefit of a newer and more efficient AIO card. How are these “completely different”? Why list antennas when discussing power consumption?
What is your uConsole’s power consumption? Where did you document it?
Trinty, I’m sorry, but you are the only one here making technical comments with some level of grounding. However, you are extremely theoretical.
I do not know what you do for a living, but I have been working with computing for more than 30 years. Please do not keep throwing “theory” and science without practice at me. That does not work in the real world.
If you want to refute anything, you need to take exactly everything I documented and run the tests yourself. Either you did not read what I showed, or you chose to ignore it. There is no point in turning this into some academic defense using scientific arguments only.
All the elements for you to test this yourself are here. You need to prove that you are right. Take all the components and refute, in practice, that this setup is not efficient, using the same method.
And please do not come back with other people’s articles, other people’s solutions, or other people’s texts. Bring your own effort and your own conclusions.
Actually, don’t even do that. I am done discussing this with you. From my point of view, nothing you said was useful to me. It did not help me achieve the result I was trying to reach. I am sorry, but that is my perception.
I will no longer collaborate with anything in this forum. Congratulations for that — it will save me a lot of time, and I have better things to do.
As far as I am concerned, this discussion and my active participation in this forum end here.
While I tested, you only commented. Now you test it yourself and bring practical data that refutes mine, not theory.
Actually, to be honest, don’t even bother. I am done sharing my experiments. It is not worth it. That is my conclusion.
Still, I hope to see at least one experiment from you. But unlike you, I will not criticize it — because, in my view, your intention is not to help people.
And my life is not this forum. EOF
hey, please not. I found your post very interesting.
I did joke about magnetic aluminium, but only because I wasn’t sure that you weren’t a bot with AI output.
But when I realize that you’ve actually did an it worked – I was glad I read it.
I wish you would stay as you provided good info to the community. Please don’t let one known bad apple run you off. I feel the community could benefit from you, and you could benefit from the community as well. At the end of the day it is your decision, but I will suggest you reconsider.
@trinity isn’t the only one with a background in this field; he’s simply the only one who takes the trouble to question and refute @Wander_Menezes ideas. And @trinity isn’t a “bad apple” just because he argues with rigorous logic and is less considerate of people’s feelings.
Even if @Wander_Menezes has 30 years of experience in IT, that doesn’t override scientific and physical principles. The question is whether we’re on this forum to please each other, or whether we’re here to look at the facts.
the definition of a forum: “a public space, meeting, or medium for open discussion and the exchange of ideas”.
so @trinity, a very active and engaging member of this community, brings forth ideas and questions to discuss the thread YOU, @Wander_Menezes, started and you get upset? Is that not the point of creating a thread on a FORUM site?
you brought forth a LOT of good information and results and that follows with a lot of good questions, so you should be excited to see someone expand on that!
You didn’t document your uConsole’s power consumption. In order to know how much heat you’re actually displacing, one needs to know how much energy your uConsole is drawing. You said our machines are completely different, so I wanted to know how. Power consumption is a good place to start.
If my uConsole is indeed as different as you say, I couldn’t replicate your results without sinking hundreds of dollars into the experiment. Instead of convincing me you became personally defensive when I started asking questions.
You have spent what I would consider to be a lot of money on this cooling system. Your sunk cost makes me worry you have a vested interest in proving it is a good solution. Scientists who are overly attached to certain outcomes of experiments don’t tend to accurately report results, or may neglect to report results that disagree with them.
I think we are done here.
Respectfully, I think I was the only one who actually did science here, not you. Like I said above, there is a very big difference between theory and practice. I know the theory behind nuclear fusion, but if you want to build a real fusion reactor, you need money, materials, prototypes, failures, and measurements… not just theory.
I respectfully ask that you stop directing comments at me. Please discuss this with the other people here, not with me. What I needed to do, I already did.
If you want to refute my work, do it with data and practical results. Again, it is obvious that you did not read the entire thread. If you had, you would have seen that someone even wrote a script so you can properly measure the efficiency of your own uConsole. The data was already posted above, at the point where you were trying to dismiss the idea. The person who created the measurement method (bash script) was me, not you.
Also remember that I use different batteries than you do. Before talking about power consumption, you first have to say what power source you are using. Up to this point, you still haven’t done that, so we are not even comparing equivalent conditions.
Let’s just keep the discussion respectful.
Yes, this was personal. It was about solving my problem, and I solved it… That’s what matters to me. And you didn’t help at all. But anyway, I appreciate your time, despite the lack of any practical results from our interactions.
I didn’t spend money trying to prove a theory. I spent money because I needed a working solution for a very specific use case. If you don’t want to build it or test it, that’s completely your decision. I respect that. But I wasn’t interested in debating the problem forever…I wanted to solve it.
I graduated in Computer Science when I was 18, and I applied everything I learned about the scientific method: hypothesis, prototype, testing, measurement, falsification, iteration, and improvement. I changed the design several times because the data led me there, not because someone convinced me with theory.
To be honest, none of the improvements came from your input. I improved the experiment by eliminating problems one by one until I reached the result I wanted.
I also made a better infographic after showing the idea to a friend. We are actually working with graphene (~2.000-5.300 W/m-K)…not to solve the uConsole problem, because for me that problem is already solved…but to explore cooling solutions for SDR radios, which consume a lot of power, e genarate alot of Heat, But these ideas could become real products. They just need to be built.
And, as I said earlier (which you clearly didn’t read), this could be an excellent solution for Raspberry Pi devices in general. A temperature of around 50°C is quite common for a handheld gaming device, but not for radio applications - MY user case. Heat has a significant impact on radio performance.. If you think the infographic could be useful, feel free to create your own version or your own prompt. Maybe that will be more useful to people than this discussion.
One year from now, nobody is going to care about this argument. They will simply have an overheating uConsole and want to fix it. If my work helps them solve that problem faster, then that’s enough for me.
I honestly don’t care whether you agree with me or not. What matters to me is that I solved my problem, documented the process, shared the measurements, published the tools, and made the experiment reproducible. If someone can reproduce it and benefit from it, then the project was worth it.
<@allall>
If anyone wants to improve these ideas, please feel free to do so. I won’t be participating in this thread anymore because I’m not interested in discussions that aren’t pragmatic and practical.
I’d like to thank everyone who understood why I eventually lost my patience.And to my colleagues here who are actually scientists, Richard Feynman said it best:
“Experiment is the sole judge of scientific truth.”
That’s the principle I followed throughout this project, and it’s the one I’ll continue to follow.
Kind regards, and I wish everyone all the best.
It doesn’t look like that at all; it’s only then that you really get going. ![]()
Your whole approach is not very scientific, but highly experimental.
What I can tell you is that by using the active fan-peltier element, you could have saved yourself all that faff with the countless layers. The uConsole’s original thermal pad would have been sufficient to conduct the heat to the case and, consequently, to the active cooler.
Alright, let’s do this.
If you build it with graphene, I might even agree with you.
But for something that anyone can actually build today, I tested five layers of copper. It only started performing well once I alternated materials with different thermal properties.
Now I’m looking forward to seeing your version using a single 4.5 mm copper block to reach the back cover. A suggestion: use my measurement script so we can compare results using the same methodology.
From this point on, I’ll just be observing.
Talking is much easier than building. So go ahead, build it that way and come back with data.
One more note: if you’re going to use solid graphene blocks, they’ll need to be precision-machined so the gap between surfaces is less than 0.02 mm. Otherwise, you’ll end up fighting heat trapped in microscopic air gaps. Manufacturing and sourcing those parts is significantly more complex than simply buying the components I used, which are readily available to anyone.
Anyway, I’ll be looking forward to seeing your solution.
From now on, I’m just a spectator in this thread.
Your objective is simple: keep the system between 32°C and 42°C while running DSD-FME continuously, recording to the local filesystem, and transferring millions of IQ samples to a remote file server, using a standard 10A 18650 battery, which is what most people use, at a minimum, you’ll need to be able to receive and transmit for two hours… It also needs to have an SDR installed… I recommend (Suggestion: the experiment is yours), the AIOv2, as other people have already tested it and reported good results with that setup.
That’s the requirement.
Note: By the way, you won’t be able to use the Peltier cooler or modify your uConsole. Okay?
Good luck.
This isn’t a game where you get to set the goals and rules and then force others to participate.
Hes saying that those are the requirements he built his around and therefore if you wanna prove him wrong follow the same requirements yourself and see if you can fix the issue that he is having. He made it to fix his issue after all and then gracefully told the forum and then got ridiculed for no reason.
This debate is excellent! In computing, the performance/cost curve isn’t linear. Personally, I use a simple bent copper plate that dissipates heat towards the back of the uConsole. Why? Because reaching 42°C doesn’t interest me! I believe that letting my CM5 run hotter is justified by the low cost of my cooling “solution.” In the case of @Wander_Menezes´s project, reaching a maximum of 42°C under such a load certainly justifies all this investment. This temperature is very low and requires significantly more resources to achieve. My two cents.
you are using only two blobs of thermal paste, scientifically speaking you should put 4 blobs of thermal paste and it will improve thermal conductivity by 37.2% ![]()
he’s presenting it as the ultimate solution, and some of us think there’s a few weird choices and it could probably easily be improved by changing those choices
There are people who make very unusual choices, and sometimes get good results, and still insist on helping other people!
1 - Graphene really was an incredible experience, and in fact, no other material reduced the temperature as much — I reached 20°C with graphene, sustained at 100% CPU! However, 80 grams of graphene in Brazil costs around $100, and it’s very hard to handle, press, create the alloy, and mold the block, and it’s very unlikely that anyone could easily replicate it — so this idea stayed in the lab to improve the radios, not the uConsole.
2 - I read the whole forum again, and made an effort to reduce the number of layers, not because I had to, but because I’m curious. I bought the Waveshare adapter, and I noticed that, for someone who has nothing, it’s an improvement, but it didn’t reach my goal, and I believe it creates a heat pocket underneath the CM5 that hurts dissipation — I always went above 42 degrees, staying around 46/50 degrees, which didn’t meet my needs. So I moved on to another idea.
3 - As I mentioned above, the only viable solution was to go with the machined copper block, 99.80% purity, and I found the magic number. The magic number is 2.14 mm in height. I kept the 1 mm thermal pad layer, created a thermal pad “sheet,” and carefully covered the board to remove all bubbles and gaps, and to avoid causing a short circuit (I read that some people here on the forum burned boards because of mechanical pressure). Well, the last layer was simple thermal paste, and I covered the ENTIRE copper block with it, so as not to create any bubbles. This made it possible for the size and thickness to fit together mechanically in a perfect way, without bubbles or gaps, without putting pressure on the cover or the CM5. Obviously without causing any shorts on the board, since the thermal pad “quilt” also served as electrical insulation.
4 - With this 3-layer design (Thermal Pad (1 mm) + copper block sized exactly to the CM5, solid, 2.14 mm + Thermal paste - 0.5 mm + Peltier cooler (It’s an external, independent system — it doesn’t need to be factored into the count!!!
) ), I’m able to operate consistently in the 32/38°C range, in a stable way, without major variations.
5 - I also noticed that you shouldn’t pay attention only to the CPU, but all the surrounding components also generate heat: the NVMe, the NVMe controller, the RPI_ADC, and the mt7921. So I improved the script, and I’m making it available to everyone. Just copy the script, run it with “sudo,” and save it under the name (uconsole-battery-mon.sh). The command line would be: sudo ./uconsole-battery-mon.sh … and remember… chmod and chown.
Photos below:
Note: Ahhhhh, to whoever came up with the “biohazard” tag, my sincere thanks! You paid tribute to one of my favorite bands… “F… the system” is my all-time favorite song!!
And as always, I hope I’ve helped someone, because everything described here can be replicated and used by anyone. I wish you all the best.
#!/usr/bin/env bash
# uConsole CM5 HW Monitor - btop-like layout
# No external UI framework. Uses ANSI cursor positioning and fixed panels.
# Copyright (C) 2026 Wander Menezes
# Licensed under the GNU General Public License v3.0 or later.
INTERVAL=${INTERVAL:-1}
SMART_INTERVAL=${SMART_INTERVAL:-30}
BAR_W=${BAR_W:-18}
ESC=$'\033'
RESET="${ESC}[0m"
BOLD="${ESC}[1m"
DIM="${ESC}[2m"
GREEN="${ESC}[32m"
YELLOW="${ESC}[33m"
RED="${ESC}[31m"
BLUE="${ESC}[36m"
MAG="${ESC}[35m"
WHITE="${ESC}[37m"
START_TS=$(date +%s)
LAST_TS=""
LAST_TEMP=""
FAN_ON_SEC=0
FAN_TOTAL_SEC=0
LAST_SMART_TS=0
SMART_CACHE="NVMES collecting..."
cleanup() {
printf "%b" "${ESC}[?25h${RESET}\n"
stty sane 2>/dev/null || true
}
trap cleanup EXIT INT TERM
printf "%b" "${ESC}[?25l"
term_rows() { tput lines 2>/dev/null || echo 24; }
term_cols() { tput cols 2>/dev/null || echo 80; }
mvcur() { printf "%b" "${ESC}[$1;$2H"; }
clreol() { printf "%b" "${ESC}[K"; }
line_at() {
local r="$1" c="$2" text="$3"
mvcur "$r" "$c"
printf "%b" "$text"
clreol
}
read_sys() { [[ -r "$1" ]] && cat "$1" 2>/dev/null || echo ""; }
clamp_pct() {
local pct="${1%.*}"
[[ -z "$pct" || "$pct" == "N/A" ]] && pct=0
(( pct < 0 )) && pct=0
(( pct > 100 )) && pct=100
echo "$pct"
}
bar_plain() {
local pct color filled empty i
pct=$(clamp_pct "$1")
color="$2"
filled=$((pct * BAR_W / 100))
empty=$((BAR_W - filled))
printf "%b" "$color"
for ((i=0;i<filled;i++)); do printf "█"; done
printf "%b" "$DIM"
for ((i=0;i<empty;i++)); do printf "░"; done
printf "%b" "$RESET"
}
bar_high_bad() {
local pct color
pct=$(clamp_pct "$1")
if (( pct < 60 )); then color="$GREEN"; elif (( pct < 85 )); then color="$YELLOW"; else color="$RED"; fi
bar_plain "$pct" "$color"
}
bar_high_good() {
local pct color
pct=$(clamp_pct "$1")
if (( pct >= 60 )); then color="$GREEN"; elif (( pct >= 30 )); then color="$YELLOW"; else color="$RED"; fi
bar_plain "$pct" "$color"
}
status_temp_short() {
local t="$1"
[[ "$t" == "N/A" ]] && echo "N/A" && return
awk -v t="$t" 'BEGIN { if (t<40) print "OK cool"; else if (t<50) print "OK warm"; else if (t<60) print "ATT warm"; else if (t<70) print "BAD hot"; else print "CRIT" }'
}
status_color() {
case "$1" in
OK*) printf "%b%s%b" "$GREEN" "$1" "$RESET" ;;
ATT*|WARN*|CHECK*) printf "%b%s%b" "$YELLOW" "$1" "$RESET" ;;
BAD*|CRIT*) printf "%b%s%b" "$RED" "$1" "$RESET" ;;
*) printf "%b%s%b" "$BLUE" "$1" "$RESET" ;;
esac
}
fmt_seconds() { local s="$1"; printf "%dh%02dmin" $((s/3600)) $(((s%3600)/60)); }
fmt_time_h() {
awk -v h="$1" 'BEGIN { if(h<0)h=0; total=int(h*60+0.5); printf "%dh%02dmin", int(total/60), total%60 }'
}
find_battery() {
for p in /sys/class/power_supply/*; do
[[ -d "$p" ]] || continue
[[ "$(read_sys "$p/type")" == "Battery" ]] && echo "$p" && return
done
}
ac_online() {
for p in /sys/class/power_supply/*; do
[[ -d "$p" ]] || continue
case "$(read_sys "$p/type")" in
Mains|USB|USB_C|USB_PD|AC) [[ "$(read_sys "$p/online")" == "1" ]] && echo 1 && return ;;
esac
done
echo 0
}
temp_cpu() {
local max="" z v c
for z in /sys/class/thermal/thermal_zone*/temp; do
[[ -r "$z" ]] || continue
v=$(cat "$z" 2>/dev/null)
[[ "$v" =~ ^[0-9]+$ ]] || continue
(( v > 1000 )) || continue
c=$(awk "BEGIN {printf \"%.1f\", $v/1000}")
if [[ -z "$max" ]] || awk "BEGIN {exit !(\$c > \$max)}"; then max="$c"; fi
done
echo "${max:-N/A}"
}
temp_nvme() {
local f name v t
for f in /sys/class/hwmon/hwmon*/temp*_input; do
[[ -r "$f" ]] || continue
name=$(cat "$(dirname "$f")/name" 2>/dev/null)
case "$name" in
nvme|*nvme*)
v=$(cat "$f" 2>/dev/null)
[[ "$v" =~ ^[0-9]+$ ]] || continue
t=$(awk "BEGIN {printf \"%.1f\", $v/1000}")
echo "$t"; return ;;
esac
done
echo "N/A"
}
cpu_freq() { local f="/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"; [[ -r "$f" ]] && awk '{printf "%d", $1/1000}' "$f" || echo "N/A"; }
cpu_governor() { local f="/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"; [[ -r "$f" ]] && cat "$f" || echo "N/A"; }
ram_info() {
awk '
/MemTotal:/ {total=$2}
/MemAvailable:/ {avail=$2}
END {used=total-avail; pct=(used/total)*100; printf "%.0f %.1f %.1f", pct, used/1048576, total/1048576}' /proc/meminfo
}
throttle_info() {
if command -v vcgencmd >/dev/null 2>&1; then
local out code
out=$(vcgencmd get_throttled 2>/dev/null)
code=${out#*=}
[[ -z "$code" || "$code" == "$out" ]] && { echo "N/A CHECK"; return; }
if [[ "$code" == "0x0" ]]; then echo "0x0 OK"; else echo "$code CHECK"; fi
else
echo "N/A no-vcgencmd"
fi
}
gpu_info() {
local v3d="N/A" core="N/A" out
if command -v vcgencmd >/dev/null 2>&1; then
out=$(vcgencmd measure_clock v3d 2>/dev/null); [[ "$out" == frequency* ]] && v3d=$(awk -F= '{printf "%d", $2/1000000}' <<<"$out")
out=$(vcgencmd measure_clock core 2>/dev/null); [[ "$out" == frequency* ]] && core=$(awk -F= '{printf "%d", $2/1000000}' <<<"$out")
fi
echo "V3D ${v3d}MHz CORE ${core}MHz"
}
pcie_lines() {
local n=0 d speed width
for d in /sys/bus/pci/devices/*; do
[[ -d "$d" ]] || continue
speed=$(read_sys "$d/current_link_speed")
width=$(read_sys "$d/current_link_width")
[[ -z "$speed" && -z "$width" ]] && continue
n=$((n+1))
printf "%-12s %s x%s\n" "$(basename "$d")" "${speed:-?}" "${width:-?}"
(( n >= 4 )) && break
done
(( n == 0 )) && echo "N/A"
}
sensors_lines() {
local count=0 f name v t base line seen=""
for f in /sys/class/hwmon/hwmon*/temp*_input; do
[[ -r "$f" ]] || continue
base=$(dirname "$f")
name=$(cat "$base/name" 2>/dev/null)
[[ -z "$name" ]] && name="$(basename "$base")"
v=$(cat "$f" 2>/dev/null)
[[ "$v" =~ ^[0-9]+$ ]] || continue
t=$(awk "BEGIN {printf \"%.1f\", $v/1000}")
line="${name} ${t}"
[[ "$seen" == *"|$line|"* ]] && continue
seen+="|$line|"
count=$((count+1))
printf "%-14s %5s C %s\n" "$name" "$t" "$(status_temp_short "$t")"
(( count >= 7 )) && break
done
(( count == 0 )) && echo "N/A"
}
status_soc_short() {
local c="$1"
[[ -z "$c" || "$c" == "N/A" ]] && echo "N/A" && return
if (( c >= 80 )); then echo "OK high"; elif (( c >= 40 )); then echo "OK norm"; elif (( c >= 20 )); then echo "ATT charge"; elif (( c >= 10 )); then echo "BAD low"; else echo "CRIT"; fi
}
health_from_energy_short() {
local full="$1" design="$2"
[[ -z "$full" ]] && echo "N/A" && return
if [[ -n "$design" && "$design" -gt 0 ]]; then
awk -v f="$full" -v d="$design" 'BEGIN {h=(f/d)*100; if(h>100)h=100; if(h>=90)printf "OK %.0f%%",h; else if(h>=80)printf "OK wear %.0f%%",h; else if(h>=70)printf "ATT %.0f%%",h; else printf "BAD %.0f%%",h}'
else
awk -v f="$full" 'BEGIN {printf "%.1fWh", f/1000000}'
fi
}
battery_info() {
local BAT ONLINE CAP STAT VOLT_RAW CURR_RAW PWR_RAW ENOW EFULL EDESIGN VOLT_A CURR_A PWR_W NAME
BAT=$(find_battery); ONLINE=$(ac_online)
if [[ -z "$BAT" ]]; then echo "BAT N/A"; return; fi
NAME=$(basename "$BAT")
CAP=$(read_sys "$BAT/capacity"); [[ -z "$CAP" ]] && CAP="N/A"
STAT=$(read_sys "$BAT/status"); [[ -z "$STAT" ]] && STAT="Unknown"
VOLT_RAW=$(read_sys "$BAT/voltage_now"); CURR_RAW=$(read_sys "$BAT/current_now"); PWR_RAW=$(read_sys "$BAT/power_now")
ENOW=$(read_sys "$BAT/energy_now"); EFULL=$(read_sys "$BAT/energy_full"); EDESIGN=$(read_sys "$BAT/energy_full_design")
[[ -z "$ENOW" ]] && ENOW=$(read_sys "$BAT/charge_now")
[[ -z "$EFULL" ]] && EFULL=$(read_sys "$BAT/charge_full")
[[ -z "$EDESIGN" ]] && EDESIGN=$(read_sys "$BAT/charge_full_design")
[[ -n "$VOLT_RAW" ]] && VOLT_A=$(awk "BEGIN {printf \"%.2f\", $VOLT_RAW/1000000}") || VOLT_A="N/A"
[[ -n "$CURR_RAW" ]] && CURR_A=$(awk "BEGIN {printf \"%.2f\", $CURR_RAW/1000000}") || CURR_A="N/A"
if [[ -n "$PWR_RAW" && "$PWR_RAW" =~ ^[0-9]+$ && "$PWR_RAW" -gt 0 ]]; then PWR_W=$(awk "BEGIN {printf \"%.2f\", $PWR_RAW/1000000}");
elif [[ "$VOLT_A" != "N/A" && "$CURR_A" != "N/A" ]]; then PWR_W=$(awk "BEGIN {printf \"%.2f\", $VOLT_A*$CURR_A}"); else PWR_W="N/A"; fi
local src="BAT"; [[ "$ONLINE" == 1 ]] && src="USB-C"
local eng="N/A" health="N/A"
if [[ -n "$ENOW" && -n "$EFULL" ]]; then eng=$(awk "BEGIN {printf \"%.2f/%.2fWh\", $ENOW/1000000, $EFULL/1000000}"); health=$(health_from_energy_short "$EFULL" "$EDESIGN"); fi
echo "$CAP|$NAME|$src|$STAT|$VOLT_A|$CURR_A|$PWR_W|$eng|$health|$(status_soc_short "$CAP")"
}
fan_info() {
local dev type cur max pct delta duty
for dev in /sys/class/thermal/cooling_device*; do
[[ -d "$dev" ]] || continue
type=$(read_sys "$dev/type")
[[ "$type" == "pwm-fan" ]] || continue
cur=$(read_sys "$dev/cur_state"); max=$(read_sys "$dev/max_state")
[[ -z "$cur" ]] && cur=0; [[ -z "$max" || "$max" == 0 ]] && max=255
pct=$(awk "BEGIN {printf \"%.0f\", ($cur/$max)*100}")
FAN_TOTAL_SEC=$((FAN_TOTAL_SEC + INTERVAL))
(( cur > 0 )) && FAN_ON_SEC=$((FAN_ON_SEC + INTERVAL))
duty=$(awk "BEGIN {printf \"%.0f\", ($FAN_ON_SEC/$FAN_TOTAL_SEC)*100}")
echo "$pct|$(basename "$dev")|$type|$cur/$max|$([[ $cur -gt 0 ]] && echo on || echo off)|$(fmt_seconds "$FAN_ON_SEC")|$duty"
return
done
echo "N/A|not detected|--|--|off|0h00min|0"
}
smart_update() {
local now dev out health temp used spare thres read written hours unsafe media log crit
now=$(date +%s)
(( now - LAST_SMART_TS < SMART_INTERVAL )) && return
LAST_SMART_TS=$now
for dev in /dev/nvme0 /dev/nvme0n1; do
[[ -e "$dev" ]] || continue
out=$(sudo -n smartctl -a "$dev" 2>/dev/null || smartctl -a "$dev" 2>/dev/null)
[[ "$out" == *"SMART/Health Information"* ]] || continue
health=$(awk -F: '/SMART overall-health/{gsub(/^ +/,"",$2); print $2; exit}' <<<"$out")
temp=$(awk -F: '/^Temperature:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
used=$(awk -F: '/Percentage Used:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
spare=$(awk -F: '/Available Spare:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
thres=$(awk -F: '/Available Spare Threshold:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
read=$(awk -F'[][]' '/Data Units Read:/{print $2; exit}' <<<"$out")
written=$(awk -F'[][]' '/Data Units Written:/{print $2; exit}' <<<"$out")
hours=$(awk -F: '/Power On Hours:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
unsafe=$(awk -F: '/Unsafe Shutdowns:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
media=$(awk -F: '/Media and Data Integrity Errors:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
log=$(awk -F: '/Error Information Log Entries:/{gsub(/[^0-9]/,"",$2); print $2; exit}' <<<"$out")
crit=$(awk -F: '/Critical Warning:/{gsub(/^ +/,"",$2); print $2; exit}' <<<"$out")
SMART_CACHE="$dev|${health:-N/A}|${temp:-N/A}|${used:-N/A}|${spare:-N/A}/${thres:-N/A}|${read:-N/A}|${written:-N/A}|${hours:-N/A}|${unsafe:-N/A}|${media:-N/A}|${log:-N/A}|${crit:-N/A}"
return
done
SMART_CACHE="N/A|unable to read SMART|N/A|N/A|N/A|N/A|N/A|N/A|N/A|N/A|N/A|N/A"
}
box_hline() { local w="$1" i; printf "┌"; for ((i=0;i<w-2;i++)); do printf "─"; done; printf "┐"; }
bot_hline() { local w="$1" i; printf "└"; for ((i=0;i<w-2;i++)); do printf "─"; done; printf "┘"; }
sep_line() { local w="$1" i; printf "├"; for ((i=0;i<w-2;i++)); do printf "─"; done; printf "┤"; }
inner_rule() { local w="$1" i; printf ""; for ((i=0;i<w;i++)); do printf "─"; done; }
strip_ansi() { sed $'s/\033\\[[0-9;]*m//g' <<<"$1"; }
pad_line() {
local content="$1" w="$2" plain len
plain=$(strip_ansi "$content")
len=${#plain}
printf "│%b" "$content"
if (( len < w-2 )); then printf "%*s" $((w-2-len)) ""; fi
printf "│"
}
row2() {
local r="$1" l="$2" rr="$3" pw="$4" gap_col=$((pw+1))
mvcur "$r" 1; pad_line "$l" "$pw"
mvcur "$r" "$gap_col"; pad_line "$rr" "$pw"
clreol
}
fmt_uptime() {
local up; up=$(cut -d. -f1 /proc/uptime 2>/dev/null || echo 0)
printf "%02d:%02d:%02d" $((up/3600)) $(((up%3600)/60)) $((up%60))
}
kernel_short() { uname -r 2>/dev/null | cut -c1-20; }
arch_short() { uname -m 2>/dev/null; }
platform_short() {
if [[ -r /proc/device-tree/model ]]; then tr -d '\0' </proc/device-tree/model | sed 's/Raspberry Pi/RPi/;s/Compute Module/CM/' | cut -c1-24
else echo "CM5/Linux"; fi
}
# btop/neon-style fixed dashboard. It redraws the dashboard block in place, not the scrollback.
draw() {
local cols pw w now load cores load1 loadp freq temp nvme gov ram throt gpu batt fan smart sensors pcie uptime trend
cols=$(term_cols); (( cols < 120 )) && cols=120
pw=$(((cols-3)/2)); (( pw > 70 )) && pw=70; (( pw < 58 )) && pw=58
w=$((pw*2+1))
now=$(date '+%Y-%m-%d %H:%M:%S')
load=$(cut -d' ' -f1-3 /proc/loadavg); cores=$(nproc); load1=$(awk '{print $1}' <<<"$load")
loadp=$(awk -v l="$load1" -v c="$cores" 'BEGIN {printf "%.0f", (l/c)*100}')
freq=$(cpu_freq); temp=$(temp_cpu); nvme=$(temp_nvme); gov=$(cpu_governor); ram=($(ram_info)); throt=$(throttle_info); gpu=$(gpu_info)
batt=$(battery_info); fan=$(fan_info); smart_update
sensors=$(sensors_lines); pcie=$(pcie_lines); uptime=$(fmt_uptime)
if [[ -z "$LAST_TEMP" || -z "$LAST_TS" || "$temp" == "N/A" ]]; then trend="baseline"; LAST_TEMP="$temp"; LAST_TS=$(date +%s); else
local nowts dt rate; nowts=$(date +%s); dt=$((nowts-LAST_TS)); ((dt<=0)) && dt=1
rate=$(awk -v cur="$temp" -v last="$LAST_TEMP" -v dt="$dt" 'BEGIN {printf "%.1f", ((cur-last)/dt)*60}')
trend="${rate}C/min"; LAST_TEMP="$temp"; LAST_TS="$nowts"
fi
local bcap bname bsrc bstat bvolt bcurr bpwr beng bhealth bsoc
IFS='|' read -r bcap bname bsrc bstat bvolt bcurr bpwr beng bhealth bsoc <<<"$batt"
local fpct fdev ftype fpwm fstat ftime fduty
IFS='|' read -r fpct fdev ftype fpwm fstat ftime fduty <<<"$fan"
local sm_dev sm_health sm_temp sm_used sm_spare sm_read sm_wr sm_hours sm_unsafe sm_media sm_log sm_crit life_status unsafe_status
IFS='|' read -r sm_dev sm_health sm_temp sm_used sm_spare sm_read sm_wr sm_hours sm_unsafe sm_media sm_log sm_crit <<<"$SMART_CACHE"
[[ "$sm_used" =~ ^[0-9]+$ ]] && (( sm_used >= 100 )) && life_status="BAD" || life_status="OK"
[[ "$sm_unsafe" =~ ^[0-9]+$ ]] && (( sm_unsafe >= 50 )) && unsafe_status="ATT" || unsafe_status="OK"
printf "%b" "${ESC}[H"
line_at 1 1 "${GREEN}──────${RESET} ${BOLD}uConsole CM5 HW Monitor${RESET} ${now} | q/CTRL+C exit | no clear | btop-like ${GREEN}──────${RESET}"
mvcur 2 1; printf "%b" "${GREEN}"; box_hline "$pw"; printf "%b" "$RESET"; mvcur 2 $((pw+1)); printf "%b" "${YELLOW}"; box_hline "$pw"; printf "%b" "$RESET"; clreol
row2 3 " ${GREEN}${BOLD}CPU / SYSTEM${RESET}" " ${YELLOW}${BOLD}BATTERY / POWER / FAN${RESET}" "$pw"
row2 4 " CPU Usage $(bar_high_bad "$loadp") ${GREEN}${loadp}%${RESET}" " Battery $(bar_high_good "$bcap") ${GREEN}${bcap}%${RESET}" "$pw"
row2 5 " Load Avg ${GREEN}${load}${RESET}" " Source ${GREEN}${bsrc}${RESET} State ${GREEN}${bstat}${RESET}" "$pw"
row2 6 " Freq ${GREEN}${freq} MHz${RESET}" " Voltage ${GREEN}${bvolt} V${RESET} Current ${GREEN}${bcurr} A${RESET}" "$pw"
row2 7 " Governor ${GREEN}${gov}${RESET}" " Power ${GREEN}${bpwr} W${RESET} Energy ${GREEN}${beng}${RESET}" "$pw"
row2 8 " RAM $(bar_high_bad "${ram[0]}") ${GREEN}${ram[0]}% ${ram[1]}/${ram[2]} GB${RESET}" " Health $(status_color "$bhealth")" "$pw"
row2 9 " Temp (CPU) $(bar_high_bad "${temp%.*}") ${GREEN}${temp} °C${RESET} $(status_color "$(status_temp_short "$temp")")" " Status $(status_color "$bsoc") Name ${GREEN}${bname}${RESET}" "$pw"
row2 10 " NVMe Temp $(bar_high_bad "${nvme%.*}") ${GREEN}${nvme} °C${RESET} $(status_color "$(status_temp_short "$nvme")")" " ---------------------------------------------------- " "$pw"
row2 11 " Throttle $(status_color "$throt")" " Fan $(bar_high_bad "$fpct") ${GREEN}${fpct}%${RESET}" "$pw"
row2 12 " GPU (${gpu})" " Device ${GREEN}${fdev}${RESET} PWM ${GREEN}${fpwm}${RESET}" "$pw"
row2 13 " " " State ${GREEN}${fstat}${RESET} Duty ${GREEN}${fduty}%${RESET}" "$pw"
mvcur 14 1; printf "%b" "${GREEN}"; bot_hline "$pw"; printf "%b" "$RESET"; mvcur 14 $((pw+1)); printf "%b" "${YELLOW}"; bot_hline "$pw"; printf "%b" "$RESET"; clreol
mvcur 15 1; printf "%b" "${BLUE}"; box_hline "$pw"; printf "%b" "$RESET"; mvcur 15 $((pw+1)); printf "%b" "${MAG}"; box_hline "$pw"; printf "%b" "$RESET"; clreol
row2 16 " ${BLUE}${BOLD}SENSORS (HWMON)${RESET}" " ${MAG}${BOLD}NVMe SMART${RESET}" "$pw"
local i=0 sline name tv stat qual
while IFS= read -r sline; do
((i++)); ((i>7)) && break
name=$(awk '{print $1}' <<<"$sline"); tv=$(awk '{print $2}' <<<"$sline"); stat=$(awk '{print $4}' <<<"$sline"); qual=$(awk '{print $5}' <<<"$sline")
local leftline=" ${name}"
leftline+="$(printf '%*s' $((18-${#name})) '')"
leftline+="${BLUE}${tv} °C${RESET} $(status_color "$stat") ${qual}"
case $i in
1) row2 $((16+i)) "$leftline" " Device ${sm_dev} Health $(status_color "$sm_health")" "$pw" ;;
2) row2 $((16+i)) "$leftline" " Temp ${sm_temp} °C Critical ${sm_crit}" "$pw" ;;
3) row2 $((16+i)) "$leftline" " Life Used ${sm_used}% Spare ${sm_spare} $(status_color "$life_status")" "$pw" ;;
4) row2 $((16+i)) "$leftline" " Read ${sm_read} Written ${sm_wr}" "$pw" ;;
5) row2 $((16+i)) "$leftline" " Hours ${sm_hours} h Unsafe Shutdowns ${sm_unsafe} $(status_color "$unsafe_status")" "$pw" ;;
6) row2 $((16+i)) "$leftline" " Media Errors ${sm_media} Log Entries ${sm_log}" "$pw" ;;
7) row2 $((16+i)) "$leftline" " Warning Composite Temperature Time 0" "$pw" ;;
esac
done <<<"$sensors"
while (( i < 7 )); do
((i++));
case $i in
1) row2 $((16+i)) " -- -- -- --" " Device ${sm_dev} Health $(status_color "$sm_health")" "$pw" ;;
2) row2 $((16+i)) " -- -- -- --" " Temp ${sm_temp} °C Critical ${sm_crit}" "$pw" ;;
3) row2 $((16+i)) " -- -- -- --" " Life Used ${sm_used}% Spare ${sm_spare} $(status_color "$life_status")" "$pw" ;;
4) row2 $((16+i)) " -- -- -- --" " Read ${sm_read} Written ${sm_wr}" "$pw" ;;
5) row2 $((16+i)) " -- -- -- --" " Hours ${sm_hours} h Unsafe Shutdowns ${sm_unsafe} $(status_color "$unsafe_status")" "$pw" ;;
6) row2 $((16+i)) " -- -- -- --" " Media Errors ${sm_media} Log Entries ${sm_log}" "$pw" ;;
7) row2 $((16+i)) " -- -- -- --" " SMART refresh every ${SMART_INTERVAL}s" "$pw" ;;
esac
done
mvcur 24 1; printf "%b" "${BLUE}"; bot_hline "$pw"; printf "%b" "$RESET"; mvcur 24 $((pw+1)); printf "%b" "${MAG}"; bot_hline "$pw"; printf "%b" "$RESET"; clreol
mvcur 25 1; printf "%b" "$WHITE"; box_hline "$w"; printf "%b" "$RESET"; clreol
line_at 26 1 "│ ${WHITE}${BOLD} PCIe /DIAGNOSTICS ${RESET}$(printf '%*s' $((w-22)) '')│"
local pl row=27 diag idx
mapfile -t pciel < <(pcie_lines)
for idx in 0 1 2 3; do
pl="${pciel[$idx]:---}"
case $idx in
0) diag="Architecture $(arch_short) | Thermal Trend ${trend}" ;;
1) diag="Kernel $(kernel_short) | Throttling ${throt}" ;;
2) diag="Platform $(platform_short) | Power Model ${gov}" ;;
3) diag="Uptime ${uptime} | Fan Control auto" ;;
esac
line_at "$row" 1 "│ PCIE_${pl} | ${diag}"
mvcur "$row" "$w"; printf "│"; clreol
((row++))
done
mvcur 31 1; printf "%b" "$WHITE"; bot_hline "$w"; printf "%b" "$RESET"; clreol
line_at 32 1 "${BLUE}Tip:${RESET} terminal font size is controlled by terminal/SSH client, not Bash. ${BLUE}SMART may need sudo NOPASSWD for smooth refresh.${RESET}"
}
printf "%b" "${ESC}[2J${ESC}[H"
while true; do
draw
sleep "$INTERVAL"
done
Ive got a standard setup with no modifications done. The only “cooling” is the thermopad you get with the OG uConsole which sits between the CM5 and the backside.
Walking outside during summer sun doing wardriving with kismet I never or rarely go above 50°C (121.99°F).
- uConsole
- AIOv2
- SDR/USB
- AC1200
- 18650 module with NVME installed
But now when you mention the NVME I actually has never thought of checking the temperature of it. Especially since it can glitch out when battery current is too low.
Good on with the script, will test it out!
addition:
Tested it when it just lays on my desk and looks great. Will maybe look into creating a script which records temperature so one can create an report. Like each seconds pulling temp(CPU, NVME, GPU), GPS and CPU % ![]()
Great and Thanks ! Comparing both measurements isn’t really a fair one-to-one comparison because they’re running different workloads. My system was under a light, steady load and had already reached thermal equilibrium, keeping the CPU at 27°C with a 0°C/min Thermal Trend, meaning temperatures had stabilized. The CM5 Lite, on the other hand, was handling a much heavier workload (36% CPU at 1500 MHz versus 13% at 600 MHz) and was still warming up. The script estimated a +15°C/min Thermal Trend, so it hadn’t reached its equilibrium yet and would likely continue heating over the next few minutes before leveling off. The biggest difference was the NVMe, which went from 26°C to 49°C, while its controller reached 65°C, making it the main heat source under sustained load rather than the CPU - My use case with SDR. In short, my snapshot shows a system that has already stabilized, while your captures a system that’s still climbing toward its normal operating temperature. 3 minutes of uptime, exactly like how you measured it.
Then install smartmontools, and run and test the script again:
sudo apt update
sudo apt install smartmontools -y
Another test you can do is use stress-ng, this will simulate the same workload.
sudo apt update
sudo apt install stress-ng -y
In 10 minutes, my result was this:
#sudo stress-ng --cpu 4 --cpu-method matrixprod --timeout 30m
Note: My system start the test with 00:39:27 uptime
Note that the system stays stable for 10 minutes under heavy load (my use case), and it’s good to run these measurements — with them you can choose the best cooling configuration for YOUR use case. There’s no right or wrong, good or bad, as I said above, everyone will have a different use and need. I stopped at 10 minutes because I already knew my result… I’ve tested this script many times — and I lose my patience! ![]()
I also had a great thermal recovery and dissipation… thanks to the Peltier cooler.
ATTENTION EVERYONE: The name “Stress Test” isn’t random — it will generate load on your equipment that can significantly raise the temperature, so run the tests carefully.
Note: When you charge the uConsole, this by itself will increase the uConsole’s temperature, generating more heat in the components. In my test, I had it charging on purpose, just to generate more heat in the system.
I
Peltier.










