Raspberry Pi throughout winter and how to make it warm

During the summertime, I have created and assembled a monitoring station based on Raspberry Pi and Raspberry Pi Camera module. The main purpose of this monitoring station was a video survivable system with some digital outputs to control the lights. The overall device is based on the Raspberry Pi 4 that sits inside a tight casing with the power supply module and the relay module. I do not want to write much about how it was built, but a bit more about how it is being maintained plus how it works during winter when temperatures are starting to go below zero.

First things first. Let us look at the casing. A good casing is an important factor since the device is supposed to work outside 24/7 under different weather conditions. Since the Raspberry Pi needs to be powered and control some external devices like LED lamps, the case needs to have some holes. To not expose the interior of the case to humidity from the external environment, holes were drilled and cable glands were used to make it secure. Below an image of the case with cable glans mounted can be seen. One is used for the 230V power input, and three others are used to control LED lamps.

The upper cover of the casing is made from transparent plastic. Due to its transparency the camera was directly mounted on it. The challenge was how to mount everything securely. Some threaded sleeves were glued with epoxy to the casing. In retrospective, UV-hardened epoxy could be used and the final effect would be much cleaner.

Above mounting points for the camera are visible along with the camera attached to them. The same threaded sleeves were used to create mounting points for a Raspberry Pi and a relay module.

The complete and assembled device is shown below. It contains the following:

  • Raspberry Pi 4,
  • 4 channel relay module,
  • Raspberry Pi Camera Module 3,
  • AC/DC converter and
  • some cables with connectors.

During the summer, the device was directly exposed to the sunlight. In rush hours, the core temperature reached 80 Celsius degrees and I could see that the CPU throttling was triggered. This can be easily checked with

vcgencmd get_throttled

This command allows you to obtain the information about e.g. CPU throttling. It returns a number which can be interpreted using the following table that can be found in the official Raspberry Pi documentation. This number is a logic sum of possible codes presented in this table.

BitHexadecimal valueMeaning
00x1Undervoltage detected
10x2Arm frequency capped
20x4Currently throttled
30x8Soft temperature limit active
160x10000Undervoltage has occurred
170x20000Arm frequency capping has occurred
180x40000Throttling has occurred
190x80000Soft temperature limit has occurred

I was able to observer 0xe0000 which means that throttling has occurred (0x40000) and soft temperature limit has occurred as well (0x80000).

80 degrees Celsius was the maximum I was able to observe. To mitigate this from occurring too frequently, I have mounted a double 5V fun. This turned out to be a good choice since after that I rarely observed any throttling. There were other solutions that I have considered for a moment. The first was about attaching some heat sinks. However, I was afraid that under these conditions they might simply fall because most of the heat sinks for RPis are attached using double sided tape. I would not risk them detaching and falling. Falling would not be the worst thing that could happen, but during this it could short circuit some components and damage them. The second solution, in my opinion, the most efficient one, would be to cover the device under a small roof. I do not rule out doing this in the future because it could significantly decrease the temperature. In the end, using the double fan, I was able to reach 60-70 degrees Celsius under similar environmental conditions.

So, enough about how to keep the RPi cool. The real question is how to keep it warm! During summer, excess of warmth was the prime factor that I tried to reduce. However, during winter, the core temperature went down to around 20 Celsius degrees. The core temperature was measured with the same tool as previously but with a different parameter, namely:

vcgencmd measure_temp

It directly returns temperature expressed in Celsius degrees, for example:

temp=50.6'C

So, the question is defined: How to keep RPi SoC warm? The straightforward answer would be to add a heater inside the case and turn it on when the temperature goes below some threshold. But is it necessary? Well, yes and no. The CPU is an excellent heater itself. It just needs to blow a whistle to warm up. I have prepared a simple Python script that does exactly that. When temperature drops below given limit, it will start to spin off some calculation processes that just do some maths calculations.

However, before we go into explanation on how to make CPU busy and warm, let me ask another question: Why is it important to keep the device warm? On purpose, I have written the device, since this is the primary goal. Generally, when the CPU is cool, it is able to be overclocked. When its frequency is raised, it will emit more heat. Therefore, we apply a heat sink, cooler, or even liquid nitrogen. The temperature of liquid nitrogen is well below 0 degrees Celsius. So why it is important to struggle with temperatures around 20 degrees while the cooler, the better? The temperature is not the main factor I want to eliminate or it is a side effect of what needs to be eliminated. The real reason is humidity. I have mentioned at the beginning that the case is sealed so that it should not allow air or water to enter. Also, the cable glans are tight. Under laboratory conditions, humidity can be controlled, but when a device exposed to environment conditions is considered, humidity plays important role. With negative temperature, water can condensate and freeze. This can cause electrical or mechanical failure. When the device would freeze over and then warm up periodically (normal winter conditions), it could be damaged in a short period of time. To prevent it from happening, two precautions were taken. A humidity absorber was placed inside the container, and the internal temperature is positive. The humidity absorber catches excess water inside the box and binds it to its structure. To make this process more efficient, I try to keep the temperature positive, hence the idea to heat up the CPU with some calculations. Additionally, I want to prevent the water from freezing inside the box, and in turn, to prevent the possibility of the cycle (freeze/warm up cycle) from happening.

The device contains two important pieces of electronics: SBC (Single Board Computer) in the shape of a Raspberry Pi and the camera module. The camera device is particularly sensitive to excess humidity. If malfunction prevention would not be the prime factor, then image quality surely is. If the sealing between the optical lens and the sensor is not tight, it might happen that the condensed water might collect on its surface. In the end, it would lead to optical distortion, and hence low quality images.

Now we can look at the code of the temperature regulator. First, let us create a function that reads the current core temperature and returns it.

def get_temperature():
    try:
        temperature = subprocess.check_output(["vcgencmd", "measure_temp"]).decode("utf-8")
        temperature = temperature.split("=")[1]
        temperature = temperature.split("'")[0]
        return float(temperature)
    except Exception as e:
        return None

If run successfully, it will execute vcgencmd to measure core temperature and return it as a floating number. If something goes wrong, a None type will be returned instead.

Let us now create a worker to blow some steam and a function to instantiate it:

workers = []

def heat_worker():
    print("Starting worker")
    while True:
        x = np.random.rand(100) * 2 * np.pi
        s = np.sqrt(np.sum(np.sin(x) ** 2))

def create_worker():
    worker = multiprocessing.Process(target=heat_worker)
    worker.start()
    return worker

We have a worker that generates some random numbers and does some math operations like calculating the sinus function. This is the core functionality. Running some operations to increase temperature. The main loop looks like this:

def main():
    args = parse_args()
    
    signal.signal(signal.SIGTERM, signal_handler)
    
    while True:
        temperature = get_temperature()
        if temperature is not None:
            load = os.getloadavg()
            print(f"Temperature: {temperature}, PID: {os.getpid()}, "
                  f"Loads: {load[0]:.2f}, {load[1]:.2f}, {load[2]:.2f}, "
                  f"Workers: {len(workers)}/{args.workers}")
            if temperature < args.temperature:
                if len(workers) < args.workers:
                    print("Creating worker")
                    worker = create_worker()
                    workers.append(worker)
            elif temperature > args.temperature + args.hysteresis:
                print("Terminating worker")
                if len(workers) > 0:
                    worker = workers.pop()
                    worker.terminate()
                
        time.sleep(args.interval)

It constantly measures temperature, with some given interval. When this temperature drops below given threshold, it spawns a new worker process and appends it to the list to keep track of them. In case of reaching some given temperature (threshold temperature plus offset), it will terminate a single thread. This condition defines a hysteresis in order to avoid some bang-bang type of control.

Below an entire script can be found:

import time
import subprocess
import multiprocessing
import argparse
import numpy as np
import signal
import os


def heat_worker():
    print("Starting worker")
    while True:
        x = np.random.rand(100) * 2 * np.pi
        s = np.sqrt(np.sum(np.sin(x) ** 2))

def get_temperature():
    try:
        temperature = subprocess.check_output(["vcgencmd", "measure_temp"]).decode("utf-8")
        temperature = temperature.split("=")[1]
        temperature = temperature.split("'")[0]
        return float(temperature)
    except Exception as e:
        return None


def parse_args():
    parser = argparse.ArgumentParser(description="Heat load")
    # add argument to store in temperature
    parser.add_argument("--temp", dest="temperature", type=float, default=20.0, help="Threshold temperature")
    parser.add_argument("--hist", dest="hysteresis", type=float, default=5.0, help="Temperature hysteresis")
    parser.add_argument("--interval", dest="interval", type=float, default=10.0, help="Interval in seconds")
    parser.add_argument("--workers", dest="workers", type=int, default=2, help="Number of workers")
    return parser.parse_args()
    
    
def create_worker():
    worker = multiprocessing.Process(target=heat_worker)
    worker.start()
    return worker

workers = []

def signal_handler(sig, frame):
    print("Terminating workers")
    for worker in workers:
        worker.terminate()
    exit(0)

def main():
    args = parse_args()
    
    signal.signal(signal.SIGTERM, signal_handler)
    
    while True:
        temperature = get_temperature()
        if temperature is not None:
            load = os.getloadavg()
            print(f"Temperature: {temperature}, PID: {os.getpid()}, "
                  f"Loads: {load[0]:.2f}, {load[1]:.2f}, {load[2]:.2f}, "
                  f"Workers: {len(workers)}/{args.workers}")
            if temperature < args.temperature:
                if len(workers) < args.workers:
                    print("Creating worker")
                    worker = create_worker()
                    workers.append(worker)
            elif temperature > args.temperature + args.hysteresis:
                print("Terminating worker")
                if len(workers) > 0:
                    worker = workers.pop()
                    worker.terminate()
                
        time.sleep(args.interval)


if __name__ == '__main__':
    main()

All of the parameters are defined from command line and include the following:

  • temperature, to set the desired threshold temperature,
  • hysteresis, to define the offset for the hysteresis control,
  • interval expressed in seconds and defining how often the script should measure temperature and act on it,
  • workers, defining the number of maths processes to do the calculations.

I hope after reading this post you come to a conclusion. Why not do some useful calculations in the first place? What would that be? Maybe mining some cryptocurrency like bitcoin would be a more “wise” way to warm up the Raspberry Pi. Or, if not, cryptocurrency, then what? On the Internet there are multiple projects that could utilise some computation power and help humanity. There are some projects worth considering, like

  • Milkyway@Home that aims to create a highly accurate 3D model of the Milky Way galaxy using volunteers’ computing resources,
  • DreamLab that fights climate change, Covid-19 and cancer,
  • Einstein@Home searched for pulsars using radio signals and gravitational wave data.

These are only some examples to consider, so if you have not only a Raspberry Pi that most of the time does nothing critical or a PC it is worth to have a look at these projects. The pool of volunteer computing project is much wider; some more examples can be found on Wikipedia.

Let me know in the comment section how you struggle with harsh environment conditions and how to do you prevent them from harming your Raspberry Pi.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.