init_tempmon.py 9.08 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#!/usr/bin/env python
# encoding: utf-8
'''
# Temperature monitor for Elphel393 camera.
# This python script should be started as a daemon at startup. It will continuously monitor
# camera temperature and turn external fan on and off. It will also turn camera off in case
# of extreme temperature limit is exceeded.
#
# Copyright (C) 2016, Elphel.inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''

import os
import sys
import time
import subprocess
29
import atexit
30 31 32 33 34
from posix import EX_OSFILE, F_OK

DEBUG = 0

""" Set temperature when the fan should be turned on. """
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
35
TEMP_FAN_ON = 65.0
36
""" Set temperature when the system should be turned off. """
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
37
TEMP_SHUTDOWN = 90.0
38
""" Temperature hysteresis in degrees. """
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
39
TEMP_HYSTERESIS = 3.0
40 41 42 43
""" Temperature sampling time in seconds. This constant may be a floating point value. """ 
SAMPLING_TIME = 1
""" Temperature scaling factor. """
SCALE_CFT = 0.001
44 45
""" The name of the file in /var/run which will contain PID """
PID_NAME = "init_tempmon"
46 47 48 49 50 51 52

ctrl_path_pref = "/sys/devices/soc0/elphel393-pwr@0/"
ctrl_fn = {"poweroff_fn": "power_off",
           "vp5_en_fn":   "channels_en",
           "vp5_dis_fn":  "channels_dis",
           "gpio_fn":     "gpio_10389"}
hwmon_path_pref = "/sys/devices/soc0/amba@0/f8007100.ps7-xadc/iio:device0/"
53 54 55
hwmon_fn = {"offset":     "in_temp0_offset",
            "raw":        "in_temp0_raw",
            "scale":      "in_temp0_scale"}
56
out_path_pref = "/var/volatile/tmp/"
57 58
out_fn = {"core_temp_fn": "core_temp",
          "temp_params_fn": "core_temp_params"}
59 60 61 62 63 64

class temp_monitor():
    def __init__(self, ctrl_path_pref, ctrl_file_names, hwmon_path_pref, hwmon_fn,
                 out_path_prefix, out_fnames):
        self.is_fan_on = False
        self.is_vp5_enabled = False
65
        self.is_10389_present = True
66 67 68 69 70
        self.ctrl_path_prefix = ctrl_path_pref
        self.ctrl_fnames = ctrl_file_names
        self.hwmon_path_prefix = hwmon_path_pref
        self.hwmon_fnames = hwmon_fn
        self.samples_window = []
71
        self.pid_fname = "/var/run/" + PID_NAME + ".pid"
72 73 74 75
        """ Number of samples for temperature averaging """
        self.samples_num = 5
        self.fan_cmd = {"fan_cmd_on":  "0x101",
                        "fan_cmd_off": "0x100"}
76 77 78 79
        self.params = {"temp_minor":   TEMP_FAN_ON,
                       "temp_major":   TEMP_SHUTDOWN,
                       "temp_hyst":    TEMP_HYSTERESIS,
                       "temp_sampling_time": SAMPLING_TIME}
80 81 82 83 84 85
        
        """ Create output files """
        try:
            self.core_temp_out_f = open(out_path_prefix + out_fnames["core_temp_fn"], "w")
        except IOError:
            print "Failed to create file: '%s%s'" % (out_path_prefix, out_fnames["core_temp_fn"])
86
        try:
87
            self.core_temp_params_f = open(out_path_prefix + out_fnames["temp_params_fn"], "w+")
88
            self.write_temp_params()
89 90 91
        except IOError:
            self.core_temp_out_f.close()
            print "Failed to create file: '%s%s'" % (out_path_prefix, out_fnames["temp_params_fn"])
92
        
93 94 95 96 97 98 99 100 101
        """ Create pid file """
        with open(self.pid_fname, "w") as pid_file:
            pid_file.write(str(os.getpid()))
        atexit.register(self.delpid)
    
    def delpid(self):
        if os.access(self.pid_fname, F_OK):
            os.remove(self.pid_fname) 

102 103 104 105 106 107 108 109 110 111
    def check_files(self):
        """
        Check if all files exist in the system.
        """
        for key, val in self.hwmon_fnames.items():
            if not os.access(self.hwmon_path_prefix + val, F_OK):
                print "Failed to open sysfs file: '%s%s'" % (self.hwmon_path_prefix, val)
                return False
        for key, val in self.ctrl_fnames.items():
            if not os.access(self.ctrl_path_prefix + val, F_OK):
112 113 114 115 116
                if key == "gpio_fn":
                    self.is_10389_present = False
                else:
                    print "Failed to open sysfs file: '%s%s'" % (self.ctrl_path_prefix, val)
                    return False
117 118 119 120
        return True
                
    def shutdown(self):
        """
121
        Turn system off. This function will be called continuously until the system is turned off.
122 123 124 125 126 127 128 129 130
        """
        subprocess.call(["/sbin/shutdown", "-hP", "now"])        
    
    def fan_ctrl(self, ctrl):
        """
        Control fan by writing to sysfs files.
        @param ctrl: can be "on" or "off" to turn fan on or off
        """
        if ctrl is "on":
131
            """ check if +5V is active and turn it on if it is not (it should not happen) """
132 133 134 135 136
            with open(self.ctrl_path_prefix + self.ctrl_fnames["vp5_en_fn"], "r+") as f:
                channels = f.read()
                if channels.find("vp5") == -1:
                    f.write("vp5")
                    f.flush()
137 138 139 140
            if self.is_10389_present:
                with open(self.ctrl_path_prefix + self.ctrl_fnames["gpio_fn"], 'w') as f:
                    f.write(self.fan_cmd["fan_cmd_on"])
                    f.flush()
141 142
            self.is_fan_on = True
        elif ctrl is "off":
143 144 145 146 147 148 149 150
            if self.is_10389_present:
                with open(self.ctrl_path_prefix + self.ctrl_fnames["gpio_fn"], 'w') as f:
                    f.write(self.fan_cmd["fan_cmd_off"])
                    f.flush()
            else:
                with open(self.ctrl_path_prefix + self.ctrl_fnames["vp5_dis_fn"], 'w') as f:
                    f.write("vp5")
                    f.flush()
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
            self.is_fan_on = False
        else:
            print "'%s': unrecognized command: '%s'" % (__name__, ctrl)
    
    def monitor(self):
        """
        Continuously read temperature and control fan.
        """
        try:
            while True:
                with open(self.hwmon_path_prefix + self.hwmon_fnames["offset"]) as f:
                    offset = float(f.read().strip())
                with open(self.hwmon_path_prefix + self.hwmon_fnames["raw"]) as f:
                    raw = float(f.read().strip())
                with open(self.hwmon_path_prefix + self.hwmon_fnames["scale"]) as f:
                    scale = float(f.read().strip())
                
                if raw > 4000:
                    raw -= 4096
                core_temp = (raw + offset) * scale * SCALE_CFT
                self.samples_window.append(core_temp)
                if len(self.samples_window) > self.samples_num:
                    self.samples_window.pop(0)
                samples = list(self.samples_window)
                samples.sort()

                avg = samples[len(samples) / 2]
178
                if core_temp < (self.params["temp_minor"] - self.params["temp_hyst"]) and self.is_fan_on:
179
                    self.fan_ctrl("off")
180
                elif core_temp >= self.params["temp_minor"] and core_temp < self.params["temp_major"] and not self.is_fan_on:
181
                    self.fan_ctrl("on")
182
                elif avg >= self.params["temp_major"]:
183 184 185 186 187 188
                    self.shutdown()
                
                self.core_temp_out_f.seek(0)
                self.core_temp_out_f.write(str(core_temp))
                self.core_temp_out_f.flush()
                
189 190
#                 print "Core temperature: '%f', median: '%f'" % (core_temp, avg)
                time.sleep(self.params["temp_sampling_time"])
191
                self.read_temp_params()
192
                self.write_temp_params()
193
        except (KeyboardInterrupt, SystemExit):
194 195 196 197
            self.core_temp_out_f.close()
            self.core_temp_params_f.close()
            os.remove(self.pid_fname)
            print "'%s': got keyboard interrupt. Exiting." % os.path.basename(__file__)
198
            sys.exit(0)
199 200 201 202 203
    
    def read_temp_params(self):
        """
        Read parameters from file and update local values.
        """
204 205
        self.core_temp_params_f.seek(0)
        file_lines = self.core_temp_params_f.readlines()
206 207 208
        for key, val in self.params.items():
            for line in file_lines:
                if line.find(key) == 0:
209 210 211 212 213 214 215 216 217 218
                    self.params[key] = float(line.split(":")[1].strip())
    
    def write_temp_params(self):
        """
        Write current parameters to file.
        """
        self.core_temp_params_f.seek(0)
        for key, val in self.params.items():
            self.core_temp_params_f.write(str(key) + ": " + str(val) + "\n")
        self.core_temp_params_f.flush()
219 220 221 222 223 224 225 226 227 228 229

if __name__ == "__main__":
    if DEBUG:
        ctrl_path_pref = ""
        hwmon_path_pref = ""
        out_path_pref = ""
    tm = temp_monitor(ctrl_path_pref, ctrl_fn, hwmon_path_pref, hwmon_fn, out_path_pref, out_fn)
    if tm.check_files():
        tm.monitor()
    else:
        sys.exit(1)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
230