Merge branch 'hw-diags-rewrite' into dev

This commit is contained in:
2Shirt 2018-12-18 16:10:34 -07:00
commit fad65b8df6
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
11 changed files with 2099 additions and 1551 deletions

View file

@ -1,35 +0,0 @@
sensors.py
==========
python bindings using ctypes for libsensors3 of the [lm-sensors project](https://github.com/groeck/lm-sensors). The code was written against libsensors 3.3.4.
For documentation of the low level API see [sensors.h](https://github.com/groeck/lm-sensors/blob/master/lib/sensors.h). For an example of the high level API see [example.py](example.py).
For a GUI application that displays the sensor readings and is based on this library, take a look at [sensors-unity](https://launchpad.net/sensors-unity).
Features
--------
* Full access to low level libsensors3 API
* High level iterator API
* unicode handling
* Python2 and Python3 compatible
Licensing
---------
LGPLv2 (same as libsensors3)
Usage Notes
-----------
As Python does not support call by reference for primitive types some of the libsensors API had to be adapted:
```python
# nr is changed by refrence in the C API
chip_name, nr = sensors.get_detected_chips(None, nr)
# returns the value. throws on error
val = sensors.get_value(chip, subfeature_nr)
```
Missing Features (pull requests are welcome):
* `sensors_subfeature_type` enum
* `sensors_get_subfeature`
* Error handlers

View file

@ -1,236 +0,0 @@
"""
@package sensors.py
Python Bindings for libsensors3
use the documentation of libsensors for the low level API.
see example.py for high level API usage.
@author: Pavel Rojtberg (http://www.rojtberg.net)
@see: https://github.com/paroj/sensors.py
@copyright: LGPLv2 (same as libsensors) <http://opensource.org/licenses/LGPL-2.1>
"""
from ctypes import *
import ctypes.util
_libc = cdll.LoadLibrary(ctypes.util.find_library("c"))
# see https://github.com/paroj/sensors.py/issues/1
_libc.free.argtypes = [c_void_p]
_hdl = cdll.LoadLibrary(ctypes.util.find_library("sensors"))
version = c_char_p.in_dll(_hdl, "libsensors_version").value.decode("ascii")
class bus_id(Structure):
_fields_ = [("type", c_short),
("nr", c_short)]
class chip_name(Structure):
_fields_ = [("prefix", c_char_p),
("bus", bus_id),
("addr", c_int),
("path", c_char_p)]
class feature(Structure):
_fields_ = [("name", c_char_p),
("number", c_int),
("type", c_int)]
# sensors_feature_type
IN = 0x00
FAN = 0x01
TEMP = 0x02
POWER = 0x03
ENERGY = 0x04
CURR = 0x05
HUMIDITY = 0x06
MAX_MAIN = 0x7
VID = 0x10
INTRUSION = 0x11
MAX_OTHER = 0x12
BEEP_ENABLE = 0x18
class subfeature(Structure):
_fields_ = [("name", c_char_p),
("number", c_int),
("type", c_int),
("mapping", c_int),
("flags", c_uint)]
_hdl.sensors_get_detected_chips.restype = POINTER(chip_name)
_hdl.sensors_get_features.restype = POINTER(feature)
_hdl.sensors_get_all_subfeatures.restype = POINTER(subfeature)
_hdl.sensors_get_label.restype = c_void_p # return pointer instead of str so we can free it
_hdl.sensors_get_adapter_name.restype = c_char_p # docs do not say whether to free this or not
_hdl.sensors_strerror.restype = c_char_p
### RAW API ###
MODE_R = 1
MODE_W = 2
COMPUTE_MAPPING = 4
def init(cfg_file = None):
file = _libc.fopen(cfg_file.encode("utf-8"), "r") if cfg_file is not None else None
if _hdl.sensors_init(file) != 0:
raise Exception("sensors_init failed")
if file is not None:
_libc.fclose(file)
def cleanup():
_hdl.sensors_cleanup()
def parse_chip_name(orig_name):
ret = chip_name()
err= _hdl.sensors_parse_chip_name(orig_name.encode("utf-8"), byref(ret))
if err < 0:
raise Exception(strerror(err))
return ret
def strerror(errnum):
return _hdl.sensors_strerror(errnum).decode("utf-8")
def free_chip_name(chip):
_hdl.sensors_free_chip_name(byref(chip))
def get_detected_chips(match, nr):
"""
@return: (chip, next nr to query)
"""
_nr = c_int(nr)
if match is not None:
match = byref(match)
chip = _hdl.sensors_get_detected_chips(match, byref(_nr))
chip = chip.contents if bool(chip) else None
return chip, _nr.value
def chip_snprintf_name(chip, buffer_size=200):
"""
@param buffer_size defaults to the size used in the sensors utility
"""
ret = create_string_buffer(buffer_size)
err = _hdl.sensors_snprintf_chip_name(ret, buffer_size, byref(chip))
if err < 0:
raise Exception(strerror(err))
return ret.value.decode("utf-8")
def do_chip_sets(chip):
"""
@attention this function was not tested
"""
err = _hdl.sensors_do_chip_sets(byref(chip))
if err < 0:
raise Exception(strerror(err))
def get_adapter_name(bus):
return _hdl.sensors_get_adapter_name(byref(bus)).decode("utf-8")
def get_features(chip, nr):
"""
@return: (feature, next nr to query)
"""
_nr = c_int(nr)
feature = _hdl.sensors_get_features(byref(chip), byref(_nr))
feature = feature.contents if bool(feature) else None
return feature, _nr.value
def get_label(chip, feature):
ptr = _hdl.sensors_get_label(byref(chip), byref(feature))
val = cast(ptr, c_char_p).value.decode("utf-8")
_libc.free(ptr)
return val
def get_all_subfeatures(chip, feature, nr):
"""
@return: (subfeature, next nr to query)
"""
_nr = c_int(nr)
subfeature = _hdl.sensors_get_all_subfeatures(byref(chip), byref(feature), byref(_nr))
subfeature = subfeature.contents if bool(subfeature) else None
return subfeature, _nr.value
def get_value(chip, subfeature_nr):
val = c_double()
err = _hdl.sensors_get_value(byref(chip), subfeature_nr, byref(val))
if err < 0:
raise Exception(strerror(err))
return val.value
def set_value(chip, subfeature_nr, value):
"""
@attention this function was not tested
"""
val = c_double(value)
err = _hdl.sensors_set_value(byref(chip), subfeature_nr, byref(val))
if err < 0:
raise Exception(strerror(err))
### Convenience API ###
class ChipIterator:
def __init__(self, match = None):
self.match = parse_chip_name(match) if match is not None else None
self.nr = 0
def __iter__(self):
return self
def __next__(self):
chip, self.nr = get_detected_chips(self.match, self.nr)
if chip is None:
raise StopIteration
return chip
def __del__(self):
if self.match is not None:
free_chip_name(self.match)
def next(self): # python2 compability
return self.__next__()
class FeatureIterator:
def __init__(self, chip):
self.chip = chip
self.nr = 0
def __iter__(self):
return self
def __next__(self):
feature, self.nr = get_features(self.chip, self.nr)
if feature is None:
raise StopIteration
return feature
def next(self): # python2 compability
return self.__next__()
class SubFeatureIterator:
def __init__(self, chip, feature):
self.chip = chip
self.feature = feature
self.nr = 0
def __iter__(self):
return self
def __next__(self):
subfeature, self.nr = get_all_subfeatures(self.chip, self.feature, self.nr)
if subfeature is None:
raise StopIteration
return subfeature
def next(self): # python2 compability
return self.__next__()

View file

@ -25,12 +25,14 @@ global_vars = {}
# STATIC VARIABLES
COLORS = {
'CLEAR': '\033[0m',
'RED': '\033[31m',
'GREEN': '\033[32m',
'CLEAR': '\033[0m',
'RED': '\033[31m',
'ORANGE': '\033[31;1m',
'GREEN': '\033[32m',
'YELLOW': '\033[33m',
'BLUE': '\033[34m'
}
'BLUE': '\033[34m',
'CYAN': '\033[36m',
}
try:
HKU = winreg.HKEY_USERS
HKCR = winreg.HKEY_CLASSES_ROOT
@ -305,20 +307,20 @@ def major_exception():
except GenericAbort:
# User declined upload
print_warning('Upload: Aborted')
sleep(30)
sleep(10)
except GenericError:
# No log file or uploading disabled
sleep(30)
sleep(10)
except:
print_error('Upload: NS')
sleep(30)
sleep(10)
else:
print_success('Upload: CS')
pause('Press Enter to exit...')
exit_script(1)
def menu_select(title='~ Untitled Menu ~',
prompt='Please make a selection', secret_exit=False,
prompt='Please make a selection', secret_actions=[], secret_exit=False,
main_entries=[], action_entries=[], disabled_label='DISABLED',
spacer=''):
"""Display options in a menu and return selected option as a str."""
@ -334,8 +336,10 @@ def menu_select(title='~ Untitled Menu ~',
menu_splash = '{}\n{}\n'.format(title, spacer)
width = len(str(len(main_entries)))
valid_answers = []
if (secret_exit):
if secret_exit:
valid_answers.append('Q')
if secret_actions:
valid_answers.extend(secret_actions)
# Add main entries
for i in range(len(main_entries)):
@ -367,7 +371,6 @@ def menu_select(title='~ Untitled Menu ~',
letter = entry['Letter'].upper(),
width = len(str(len(action_entries))),
name = entry['Name'])
menu_splash += '\n'
answer = ''
@ -403,19 +406,24 @@ def ping(addr='google.com'):
def popen_program(cmd, pipe=False, minimized=False, shell=False, **kwargs):
"""Run program and return a subprocess.Popen object."""
startupinfo=None
cmd_kwargs = {'args': cmd, 'shell': shell}
if minimized:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = 6
cmd_kwargs['startupinfo'] = startupinfo
if pipe:
popen_obj = subprocess.Popen(cmd, shell=shell, startupinfo=startupinfo,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
else:
popen_obj = subprocess.Popen(cmd, shell=shell, startupinfo=startupinfo)
cmd_kwargs.update({
'stdout': subprocess.PIPE,
'stderr': subprocess.PIPE,
})
return popen_obj
if 'cwd' in kwargs:
cmd_kwargs['cwd'] = kwargs['cwd']
return subprocess.Popen(**cmd_kwargs)
def print_error(*args, **kwargs):
"""Prints message to screen in RED."""
@ -454,7 +462,7 @@ def print_log(message='', end='\n', timestamp=True):
line = line,
end = end))
def run_program(cmd, args=[], check=True, pipe=True, shell=False):
def run_program(cmd, args=[], check=True, pipe=True, shell=False, **kwargs):
"""Run program and return a subprocess.CompletedProcess object."""
if args:
# Deprecated so let's raise an exception to find & fix all occurances
@ -464,13 +472,18 @@ def run_program(cmd, args=[], check=True, pipe=True, shell=False):
if shell:
cmd = ' '.join(cmd)
if pipe:
process_return = subprocess.run(cmd, check=check, shell=shell,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
else:
process_return = subprocess.run(cmd, check=check, shell=shell)
cmd_kwargs = {'args': cmd, 'check': check, 'shell': shell}
return process_return
if pipe:
cmd_kwargs.update({
'stdout': subprocess.PIPE,
'stderr': subprocess.PIPE,
})
if 'cwd' in kwargs:
cmd_kwargs['cwd'] = kwargs['cwd']
return subprocess.run(**cmd_kwargs)
def set_title(title='~Some Title~'):
"""Set title.
@ -513,6 +526,12 @@ def stay_awake():
print_error('ERROR: No caffeine available.')
print_warning('Please set the power setting to High Performance.')
def strip_colors(s):
"""Remove all ASCII color escapes from string, returns str."""
for c in COLORS.values():
s = s.replace(c, '')
return s
def get_exception(s):
"""Get exception by name, returns Exception object."""
try:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,198 @@
# Wizard Kit: Functions - Sensors
import json
import re
from functions.tmux import *
# STATIC VARIABLES
TEMP_LIMITS = {
'GREEN': 60,
'YELLOW': 70,
'ORANGE': 80,
'RED': 90,
}
# REGEX
REGEX_COLORS = re.compile(r'\033\[\d+;?1?m')
def clear_temps(sensor_data):
"""Clear saved temps but keep structure, returns dict."""
for _section, _adapters in sensor_data.items():
for _adapter, _sources in _adapters.items():
for _source, _data in _sources.items():
_data['Temps'] = []
def fix_sensor_str(s):
"""Cleanup string and return str."""
s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', s, re.IGNORECASE)
s = s.title()
s = s.replace('Coretemp', 'CoreTemp')
s = s.replace('Acpi', 'ACPI')
s = s.replace('ACPItz', 'ACPI TZ')
s = s.replace('Isa ', 'ISA ')
s = s.replace('Id ', 'ID ')
s = re.sub(r'(\D+)(\d+)', r'\1 \2', s, re.IGNORECASE)
s = s.replace(' ', ' ')
return s
def generate_sensor_report(
sensor_data, *temp_labels,
colors=True, core_only=False):
"""Generate report based on temp_labels, returns list if str."""
report = []
for _section, _adapters in sorted(sensor_data.items()):
# CoreTemps then Other temps
if core_only and 'Core' not in _section:
continue
for _adapter, _sources in sorted(_adapters.items()):
# Adapter
report.append(fix_sensor_str(_adapter))
for _source, _data in sorted(_sources.items()):
# Source
_line = '{:18} '.format(fix_sensor_str(_source))
# Temps (skip label for Current)
for _label in temp_labels:
_line += '{}{}{} '.format(
_label.lower() if _label != 'Current' else '',
': ' if _label != 'Current' else '',
get_temp_str(_data.get(_label, '???'), colors=colors))
report.append(_line)
if not core_only:
report.append(' ')
# Handle empty reports (i.e. no sensors detected)
if not report:
report = [
'{}WARNING: No sensors found{}'.format(
COLORS['YELLOW'] if colors else '',
COLORS['CLEAR'] if colors else ''),
' ',
'Please monitor temps manually']
# Done
return report
def get_colored_temp_str(temp):
"""Get colored string based on temp, returns str."""
try:
temp = float(temp)
except ValueError:
return '{YELLOW}{temp}{CLEAR}'.format(temp=temp, **COLORS)
if temp > TEMP_LIMITS['RED']:
color = COLORS['RED']
elif temp > TEMP_LIMITS['ORANGE']:
color = COLORS['ORANGE']
elif temp > TEMP_LIMITS['YELLOW']:
color = COLORS['YELLOW']
elif temp > TEMP_LIMITS['GREEN']:
color = COLORS['GREEN']
elif temp > 0:
color = COLORS['BLUE']
else:
color = COLORS['CLEAR']
return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format(
color = color,
prefix = '-' if temp < 0 else '',
temp = temp,
**COLORS)
def get_raw_sensor_data():
"""Read sensor data and return dict."""
cmd = ['sensors', '-j']
result = run_program(cmd)
return json.loads(result.stdout.decode())
def get_sensor_data():
"""Parse raw sensor data and return new dict."""
json_data = get_raw_sensor_data()
sensor_data = {'CoreTemps': {}, 'Other': {}}
for _adapter, _sources in json_data.items():
if 'coretemp' in _adapter:
_section = 'CoreTemps'
else:
_section = 'Other'
sensor_data[_section][_adapter] = {}
_sources.pop('Adapter', None)
# Find current temp and add to dict
## current temp is labeled xxxx_input
for _source, _labels in _sources.items():
for _label, _temp in _labels.items():
if 'input' in _label:
sensor_data[_section][_adapter][_source] = {
'Current': _temp,
'Label': _label,
'Max': _temp,
'Temps': [_temp],
}
# Remove empty sections
for k, v in sensor_data.items():
v = {k2: v2 for k2, v2 in v.items() if v2}
# Done
return sensor_data
def get_temp_str(temp, colors=True):
"""Get temp string, returns str."""
if colors:
return get_colored_temp_str(temp)
try:
temp = float(temp)
except ValueError:
return '{}'.format(temp)
else:
return '{}{:2.0f}°C'.format(
'-' if temp < 0 else '',
temp)
def monitor_sensors(monitor_pane, monitor_file):
"""Continually update sensor data and report to screen."""
sensor_data = get_sensor_data()
while True:
update_sensor_data(sensor_data)
with open(monitor_file, 'w') as f:
report = generate_sensor_report(sensor_data, 'Current', 'Max')
f.write('\n'.join(report))
sleep(1)
if monitor_pane and not tmux_poll_pane(monitor_pane):
break
def save_average_temp(sensor_data, temp_label, seconds=10):
"""Calculate average temps and record under temp_label, returns dict."""
clear_temps(sensor_data)
# Get temps
for i in range(seconds):
update_sensor_data(sensor_data)
sleep(1)
# Calculate averages
for _section, _adapters in sensor_data.items():
for _adapter, _sources in _adapters.items():
for _source, _data in _sources.items():
_data[temp_label] = sum(_data['Temps']) / len(_data['Temps'])
def update_sensor_data(sensor_data):
"""Read sensors and update existing sensor_data, returns dict."""
json_data = get_raw_sensor_data()
for _section, _adapters in sensor_data.items():
for _adapter, _sources in _adapters.items():
for _source, _data in _sources.items():
_label = _data['Label']
_temp = json_data[_adapter][_source][_label]
_data['Current'] = _temp
_data['Max'] = max(_temp, _data['Max'])
_data['Temps'].append(_temp)
def join_columns(column1, column2, width=55):
return '{:<{}}{}'.format(
column1,
55+len(column1)-len(REGEX_COLORS.sub('', column1)),
column2)
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -0,0 +1,148 @@
# Wizard Kit: Functions - tmux
from functions.common import *
def create_file(filepath):
"""Create file if it doesn't exist."""
if not os.path.exists(filepath):
with open(filepath, 'w') as f:
f.write('')
def tmux_get_pane_size(pane_id=None):
"""Get target, or current, pane size, returns tuple."""
x = -1
y = -1
cmd = ['tmux', 'display', '-p']
if pane_id:
cmd.extend(['-t', pane_id])
cmd.append('#{pane_width} #{pane_height}')
# Run cmd and set x & y
result = run_program(cmd, check=False)
try:
x, y = result.stdout.decode().strip().split()
x = int(x)
y = int(y)
except Exception:
# Ignore and return unrealistic values
pass
return (x, y)
def tmux_kill_all_panes(pane_id=None):
"""Kill all tmux panes except the active pane or pane_id if specified."""
cmd = ['tmux', 'kill-pane', '-a']
if pane_id:
cmd.extend(['-t', pane_id])
run_program(cmd, check=False)
def tmux_kill_pane(*panes):
"""Kill tmux pane by id."""
cmd = ['tmux', 'kill-pane', '-t']
for pane_id in panes:
run_program(cmd+[pane_id], check=False)
def tmux_poll_pane(pane_id):
"""Check if pane exists, returns bool."""
cmd = ['tmux', 'list-panes', '-F', '#D']
result = run_program(cmd, check=False)
panes = result.stdout.decode().splitlines()
return pane_id in panes
def tmux_resize_pane(pane_id=None, x=None, y=None, **kwargs):
"""Resize pane to specific hieght or width."""
if not x and not y:
raise Exception('Neither height nor width specified.')
cmd = ['tmux', 'resize-pane']
if pane_id:
# NOTE: If pane_id not specified then the current pane will be resized
cmd.extend(['-t', pane_id])
if x:
cmd.extend(['-x', str(x)])
elif y:
cmd.extend(['-y', str(y)])
run_program(cmd, check=False)
def tmux_split_window(
lines=None, percent=None,
behind=False, vertical=False,
follow=False, target_pane=None,
working_dir=None, command=None,
text=None, watch=None, watch_cmd='cat'):
"""Run tmux split-window command and return pane_id as str."""
# Bail early
if not lines and not percent:
raise Exception('Neither lines nor percent specified.')
if not command and not text and not watch:
raise Exception('No command, text, or watch file specified.')
# Build cmd
cmd = ['tmux', 'split-window', '-PF', '#D']
if behind:
cmd.append('-b')
if vertical:
cmd.append('-v')
else:
cmd.append('-h')
if not follow:
cmd.append('-d')
if lines is not None:
cmd.extend(['-l', str(lines)])
elif percent is not None:
cmd.extend(['-p', str(percent)])
if target_pane:
cmd.extend(['-t', str(target_pane)])
if working_dir:
cmd.extend(['-c', working_dir])
if command:
cmd.extend(command)
elif text:
cmd.extend(['echo-and-hold "{}"'.format(text)])
elif watch:
create_file(watch)
if watch_cmd == 'cat':
cmd.extend([
'watch', '--color', '--no-title',
'--interval', '1',
'cat', watch])
elif watch_cmd == 'tail':
cmd.extend(['tail', '-f', watch])
# Run and return pane_id
result = run_program(cmd)
return result.stdout.decode().strip()
def tmux_update_pane(
pane_id, command=None, working_dir=None,
text=None, watch=None, watch_cmd='cat'):
"""Respawn with either a new command or new text."""
# Bail early
if not command and not text and not watch:
raise Exception('No command, text, or watch file specified.')
cmd = ['tmux', 'respawn-pane', '-k', '-t', pane_id]
if working_dir:
cmd.extend(['-c', working_dir])
if command:
cmd.extend(command)
elif text:
cmd.extend(['echo-and-hold "{}"'.format(text)])
elif watch:
create_file(watch)
if watch_cmd == 'cat':
cmd.extend([
'watch', '--color', '--no-title',
'--interval', '1',
'cat', watch])
elif watch_cmd == 'tail':
cmd.extend(['tail', '-f', watch])
run_program(cmd)
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -9,22 +9,29 @@ import sys
os.chdir(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(os.getcwd())
from functions.hw_diags import *
from functions.tmux import *
init_global_vars()
if __name__ == '__main__':
try:
# Prep
clear_screen()
# Show menu
try:
state = State()
menu_diags(state, sys.argv)
except KeyboardInterrupt:
print_standard(' ')
print_warning('Aborted')
print_standard(' ')
sleep(1)
pause('Press Enter to exit...')
except SystemExit:
# Normal exit
pass
except:
tmux_kill_all_panes()
major_exception()
# Show menu
menu_diags(*sys.argv)
# Done
#print_standard('\nDone.')
#pause("Press Enter to exit...")
exit_script()
except SystemExit:
pass
except:
major_exception()
# Done
tmux_kill_all_panes()
exit_script()
# vim: sts=2 sw=2 ts=2

View file

@ -14,5 +14,6 @@ if [ ! -d "$1" ]; then
fi
# Run Prime95
mprime -t | grep -iv --line-buffered 'stress.txt' | tee -a "$1/prime.log"
cd "$1"
mprime -t | grep -iv --line-buffered 'stress.txt' | tee -a "prime.log"

View file

@ -1,168 +1,10 @@
#!/bin/python3
#!/bin/bash
#
## Wizard Kit: Sensor monitoring tool
import itertools
import os
import shutil
import sys
WINDOW_NAME="Hardware Sensors"
MONITOR="hw-sensors-monitor"
# Init
os.chdir(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(os.getcwd())
from functions.common import *
from borrowed import sensors
# STATIC VARIABLES
COLORS = {
'CLEAR': '\033[0m',
'RED': '\033[31m',
'GREEN': '\033[32m',
'YELLOW': '\033[33m',
'ORANGE': '\033[31;1m',
'BLUE': '\033[34m'
}
TEMP_LIMITS = {
'GREEN': 60,
'YELLOW': 70,
'ORANGE': 80,
'RED': 90,
}
# REGEX
REGEX_COLORS = re.compile(r'\033\[\d+;?1?m')
def color_temp(temp):
try:
temp = float(temp)
except ValueError:
return '{YELLOW}{temp}{CLEAR}'.format(temp=temp, **COLORS)
if temp > TEMP_LIMITS['RED']:
color = COLORS['RED']
elif temp > TEMP_LIMITS['ORANGE']:
color = COLORS['ORANGE']
elif temp > TEMP_LIMITS['YELLOW']:
color = COLORS['YELLOW']
elif temp > TEMP_LIMITS['GREEN']:
color = COLORS['GREEN']
elif temp > 0:
color = COLORS['BLUE']
else:
color = COLORS['CLEAR']
return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format(
color = color,
prefix = '-' if temp < 0 else '',
temp = temp,
**COLORS)
def get_feature_string(chip, feature):
sfs = list(sensors.SubFeatureIterator(chip, feature)) # get a list of all subfeatures
label = sensors.get_label(chip, feature)
skipname = len(feature.name)+1 # skip common prefix
data = {}
if feature.type != sensors.feature.TEMP:
# Skip non-temperature sensors
return None
for sf in sfs:
name = sf.name[skipname:].decode("utf-8").strip()
try:
val = sensors.get_value(chip, sf.number)
except Exception:
# Ignore upstream sensor bugs and lie instead
val = -123456789
if 'alarm' in name:
# Skip
continue
if '--nocolor' in sys.argv:
try:
temp = float(val)
except ValueError:
data[name] = ' {}°C'.format(val)
else:
data[name] = '{}{:2.0f}°C'.format(
'-' if temp < 0 else '',
temp)
else:
data[name] = color_temp(val)
main_temp = data.pop('input', None)
if main_temp:
list_data = []
for item in ['max', 'crit']:
if item in data:
list_data.append('{}: {}'.format(item, data.pop(item)))
list_data.extend(
['{}: {}'.format(k, v) for k, v in sorted(data.items())])
data_str = '{:18} {} {}'.format(
label, main_temp, ', '.join(list_data))
else:
list_data.extend(sorted(data.items()))
list_data = ['{}: {}'.format(item[0], item[1]) for item in list_data]
data_str = '{:18} {}'.format(label, ', '.join(list_data))
return data_str
def join_columns(column1, column2, width=55):
return '{:<{}}{}'.format(
column1,
55+len(column1)-len(REGEX_COLORS.sub('', column1)),
column2)
if __name__ == '__main__':
try:
# Prep
sensors.init()
# Get sensor data
chip_temps = {}
for chip in sensors.ChipIterator():
chip_name = '{} ({})'.format(
sensors.chip_snprintf_name(chip),
sensors.get_adapter_name(chip.bus))
chip_feats = [get_feature_string(chip, feature)
for feature in sensors.FeatureIterator(chip)]
# Strip empty/None items
chip_feats = [f for f in chip_feats if f]
if chip_feats:
chip_temps[chip_name] = [chip_name, *chip_feats, '']
# Sort chips
sensor_temps = []
for chip in [k for k in sorted(chip_temps.keys()) if 'coretemp' in k]:
sensor_temps.extend(chip_temps[chip])
for chip in sorted(chip_temps.keys()):
if 'coretemp' not in chip:
sensor_temps.extend(chip_temps[chip])
# Wrap columns as needed
screen_size = shutil.get_terminal_size()
rows = screen_size.lines - 1
if len(sensor_temps) > rows and screen_size.columns > 55*2:
sensor_temps = list(itertools.zip_longest(
sensor_temps[:rows], sensor_temps[rows:], fillvalue=''))
sensor_temps = [join_columns(a, b) for a, b in sensor_temps]
# Print data
if sensor_temps:
for line in sensor_temps:
print_standard(line)
else:
if '--nocolor' in sys.argv:
print_standard('WARNING: No sensors found')
print_standard('\nPlease monitor temps manually')
else:
print_warning('WARNING: No sensors found')
print_standard('\nPlease monitor temps manually')
# Done
sensors.cleanup()
exit_script()
except SystemExit:
sensors.cleanup()
pass
except:
sensors.cleanup()
major_exception()
# Start session
tmux new-session -n "$WINDOW_NAME" "$MONITOR"

37
.bin/Scripts/hw-sensors-monitor Executable file
View file

@ -0,0 +1,37 @@
#!/bin/python3
#
## Wizard Kit: Sensor monitoring tool
import os
import sys
# Init
os.chdir(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(os.getcwd())
from functions.sensors import *
from functions.tmux import *
init_global_vars()
if __name__ == '__main__':
background = False
try:
if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
background = True
monitor_file = sys.argv[1]
monitor_pane = None
else:
result = run_program(['mktemp'])
monitor_file = result.stdout.decode().strip()
if not background:
monitor_pane = tmux_split_window(
percent=1, vertical=True, watch=monitor_file)
cmd = ['tmux', 'resize-pane', '-Z', '-t', monitor_pane]
run_program(cmd, check=False)
monitor_sensors(monitor_pane, monitor_file)
exit_script()
except SystemExit:
pass
except:
major_exception()
# vim: sts=2 sw=2 ts=2

View file

@ -4,6 +4,8 @@
ENABLED_OPEN_LOGS = False
ENABLED_TICKET_NUMBERS = False
ENABLED_UPLOAD_DATA = False
HW_OVERRIDES_FORCED = False
HW_OVERRIDES_LIMITED = True # If True this disables HW_OVERRIDE_FORCED
# STATIC VARIABLES (also used by BASH and BATCH files)
## NOTE: There are no spaces around the = for easier parsing in BASH and BATCH
@ -13,7 +15,7 @@ KIT_NAME_FULL='WizardKit'
KIT_NAME_SHORT='WK'
SUPPORT_MESSAGE='Please let 2Shirt know by opening an issue on GitHub'
# Live Linux
MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags
MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags
ROOT_PASSWORD='Abracadabra'
TECH_PASSWORD='Abracadabra'
# Server IP addresses