diff --git a/README.md b/README.md index 131fa71..8a1f388 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,15 @@ On RHEL/CentOS systems: # yum install sg3_utils +FreeBSD systems via `pkg`: + + # pkg install sysutils/sg3_utils + FreeNAS 9.10 includes `sg_ses` as part of the standard image. ## Usage -Finds the ThinkServer Enclosure automatically. Works when the devices are either `/dev/sg*` or `/dev/ses*` +Finds the ThinkServer Enclosure automatically. Works when the devices are either `/dev/sg*`, `/dev/ses*`, or `/dev/bsg/*` Use `fancontrol.py` to set the fan speed: diff --git a/fancontrol.py b/fancontrol.py index 7634c79..d448cc4 100755 --- a/fancontrol.py +++ b/fancontrol.py @@ -1,81 +1,127 @@ #!/usr/bin/env python +# -*- coding: utf8 -*- + +import os import sys import time import glob +import stat + +from io import BytesIO +from subprocess import check_output, Popen, PIPE, STDOUT, CalledProcessError + +devices_to_check = ['/dev/sg*', '/dev/ses*', '/dev/bsg/*'] + + +def usage(): + print('python fancontrol.py 1-7') + sys.exit(-1) + + +def get_requested_fan_speed(): + if len(sys.argv) < 2: + return usage() + try: + speed = int(sys.argv[1]) + except ValueError: + return usage() + if not 1 <= speed <= 7: + return usage() + return speed -try: - from StringIO import StringIO -except ImportError: - from io import StringIO -from subprocess import check_output, Popen, PIPE, STDOUT def print_speeds(device): - for i in range(0, 6): - print("Fan {} speed: {}".format(i, check_output(['sg_ses', '--index=coo,{}'.format(i), '--get=1:2:11', device]).decode('utf-8').split('\n')[0])) - -if len(sys.argv) < 1: - print("python fancontrol.py 1-7") - sys.exit(-1) - -fan = int(sys.argv[1]) - -if fan <= 0 or fan > 6: - raise Exception("Fan speed must be between 1 and 7") + for i in range(0, 6): + print('Fan {} speed: {}'.format(i, check_output( + ['sg_ses', '--maxlen=32768', '--index=coo,{}'.format(i), '--get=1:2:11', device]) + .decode('utf-8').split('\n')[0])) -devices_to_check = ['/dev/sg*','/dev/ses*'] +def find_sa120_devices(): + devices = [] + seen_devices = set() + for device_glob in devices_to_check: + for device in glob.glob(device_glob): + stats = os.stat(device) + if not stat.S_ISCHR(stats.st_mode): + print('Enclosure not found on ' + device) + continue + device_id = format_device_id(stats) + if device_id in seen_devices: + print('Enclosure already seen on ' + device) + continue + seen_devices.add(device_id) + try: + output = check_output(['sg_ses', '--maxlen=32768', device], stderr=STDOUT) + if b'ThinkServerSA120' in output: + print('Enclosure found on ' + device) + devices.append(device) + else: + print('Enclosure not found on ' + device) + except CalledProcessError: + print('Enclosure not found on ' + device) + return devices -device = "" -for chk_device in devices_to_check: - for dev_node in glob.glob(chk_device): +def format_device_id(stats): + return '{},{}'.format(os.major(stats.st_rdev), os.minor(stats.st_rdev)) + + +def set_fan_speeds(device, speed): + print_speeds(device) + print('Reading current configuration...') + out = check_output(['sg_ses', '--maxlen=32768', '-p', '0x2', device, '--raw']) + + s = out.split() + + for i in range(0, 6): + print('Setting fan {} to {}'.format(i, speed)) + idx = 88 + 4 * i + s[idx + 0] = b'80' + s[idx + 1] = b'00' + s[idx + 2] = b'00' + s[idx + 3] = u'{:x}'.format(1 << 5 | speed & 7).encode('utf-8') + + output = BytesIO() + off = 0 + count = 0 + + while True: + output.write(s[off]) + off = off + 1 + count = count + 1 + if count == 8: + output.write(b' ') + elif count == 16: + output.write(b'\n') + count = 0 + else: + output.write(b' ') + if off >= len(s): + break + + output.write(b'\n') + p = Popen(['sg_ses', '--maxlen=32768', '-p', '0x2', device, '--control', '--data', '-'], + stdout=PIPE, stdin=PIPE, stderr=PIPE) + print(p.communicate(input=output.getvalue())[0].decode('utf-8')) + print('Set fan speeds... Waiting to get fan speeds (ctrl+c to skip)') try: - out = check_output(["sg_ses", dev_node], stderr=STDOUT) - if 'ThinkServerSA120' in out: - device = dev_node - print("Enclosure found on " + device); + time.sleep(10) + print_speeds(device) + except KeyboardInterrupt: + pass - print_speeds(device) - print("Reading current configuration...") - out = check_output(["sg_ses", "-p", "0x2", device, "--raw"]).decode('utf-8') - s = out.split() - for i in range(0, 6): - print("Setting fan {} to {}".format(i, fan)) - idx = 88 + 4 * i - s[idx+0] = "80" - s[idx+1] = "00" - s[idx+2] = "00" - s[idx+3] = format(1 << 5 | fan & 7, "x") +def main(): + speed = get_requested_fan_speed() + devices = find_sa120_devices() + if not devices: + print('Could not find enclosure') + sys.exit(1) + for device in devices: + set_fan_speeds(device, speed) + print('\nDone') - output = StringIO() - off = 0 - count = 0 - line = '' - while True: - output.write(s[off]) - off = off + 1 - count = count + 1 - if count == 8 : - output.write(" ") - elif count == 16: - output.write("\n") - count=0 - else: - output.write(" ") - if off >= len(s): - break - - output.write("\n") - p = Popen(['sg_ses', '-p', '0x2', device, '--control', '--data', '-'], stdout=PIPE, stdin=PIPE, stderr=PIPE) - print("Set fan speeds... Waiting to get fan speeds (ctrl+c to skip)") - print(p.communicate(input=bytearray(output.getvalue(), 'utf-8'))[0].decode('utf-8')) - time.sleep(10) - print_speeds(device) - except: - print("Enclosure not found on " + dev_node) - -if device == "": - print("Could not find enclosure") - sys.exit(1) +if __name__ == '__main__': + main()