[New Features] Disk and network activity, fan speed percentage display on OLED display, improved uptime format

Hi all!

I added some extra function to the OLED display (network and disk activity in one sec). And it’s also fixing the “Only Disk: root percentage show storage” problem. You can see them on the images:

1.0. Network:
The network interfaces are automatically recognized and only those will be show up on the OLED which status is UP. Eg. if you only use wired network (eth0), you will only see that on the OLED display. If you use wired (eth0) and wireless (wlan0), you can see both of them. And so on.

1.1. Network OLED explanation:
Network (interface_name): Which interface is measured.
rx: receive (download) speed in MB/s.
tx: transmit (upload) soeed in MB/s.

2.0. Disks:
For the disk, you have to edit the /etc/rockpi-sata.conf file (use sudo to edit it).
You have to add your mount points (where you mount your hdds/ssds) separated with | (pipe character).

You don’t have to add the root (/) device (the SD card) separately, it show always up!
Only the disks.

2.1. Edit the configuration file with sudo: /etc/rockpi-sata.conf

[disk]
# Mount points for disks (separated with |)
mnt_points = /mnt/mount1|/mnt/mount2

2.2. In my case example:

I have 4 HDDs (two of them is in RAID1) so actually I have 3 devices.
I check out their mount points with the df -h command:

pi@raspberry-pi-4:~ $ df -h
Filesystem      Size  Used Available Use% Mounted on
/dev/root        56G   15G       39G  28% /
devtmpfs        1,7G     0      1,7G   0% /dev
tmpfs           1,8G   28K      1,8G   1% /dev/shm
tmpfs           1,8G  9,0M      1,8G   1% /run
tmpfs           5,0M  4,0K      5,0M   1% /run/lock
tmpfs           1,8G     0      1,8G   0% /sys/fs/cgroup
mergerfs        1,8T  1,5T      274G  85% /mnt/torrents
/dev/loop1       46M   46M         0 100% /snap/core18/1937
/dev/loop0       65M   65M         0 100% /snap/gtk-common-themes/1514
/dev/mmcblk0p6  253M   46M      207M  18% /boot
tmpfs           365M     0      365M   0% /run/user/109
/dev/md0        916G  703G      167G  81% /mnt/raid1
/dev/sdc1       916G  624G      247G  72% /mnt/torrent2
/dev/sdd1       916G  842G       28G  97% /mnt/torrent
tmpfs           365M     0      365M   0% /run/user/1000

I have /dev/md0 /dev/sdc1 and /dev/sdd1 (in sdc1 and sdd1 the number means the partition number).

Now we just have to read out these disks mount points.
/dev/md0: /mnt/raid1
/dev/sdc1: /mnt/torrent2
/dev/sdd1: /mnt/torrent

I would like to see all three disks used space percentage.
Separation character for mount points is: |

My rockpi-sata.conf file:

[fan]
# When the temperature is above lv0 (35'C), the fan at 25% power,
# and lv1 at 50% power, lv2 at 75% power, lv3 at 100% power.
# When the temperature is below lv0, the fan is turned off.
# You can change these values if necessary.
lv0 = 35
lv1 = 40
lv2 = 45
lv3 = 50

[key]
# You can customize the function of the key, currently available functions are
# slider: oled display next page
# switch: fan turn on/off switch
# reboot, poweroff
# If you have any good suggestions for key functions,
# please add an issue on https://setq.me/rockpi-sata
click = slider
twice = switch
press = poweroff

[time]
# twice: maximum time between double clicking (seconds)
# press: long press time (seconds)
twice = 0.7
press = 1.8

[slider]
# Whether the oled auto display next page and the time interval (seconds)
auto = true
time = 10

[oled]
# Whether rotate the text of oled 180 degrees, whether use Fahrenheit
rotate = true
f-temp = false

[disk]
# Mount points for disks (separated with |)
mnt_points = /mnt/raid1|/mnt/torrent|/mnt/torrent2

2.4. Other example:
We have two HDD (sda, sdb). These names (sda, sdb) could change for the HDD devices, eg. when we reboot the rpi, it maybe rename them, this is why we use the mount points (they only change if we modify them manually).

We mount sda to /mnt/hdd1 and sdb to /mnt/hdd2.

The /etc/rockpi-sata.conf should contain the following:

[disk]
# Mount points for disks (separated with |)
mnt_points = /mnt/hdd1|/mnt/hdd2

You will see these two devices.

2.5. Disk OLED explanation:
Disk (disk_name): Which disk is measured.
R: read speed in MB/s.
W: write speed in MB/s.

3.0. Update the service files
We have to replace two service files.

3.1. Backing up the service files
Please create a backup from these files (they will be replaced completly):
/usr/bin/rockpi-sata/misc.py
/usr/bin/rockpi-sata/oled.py

3.2. Replace the files
And finally, you have to replace these two files which are using the rockpi-sata service (use sudo to edit them):

3.3. /usr/bin/rockpi-sata/misc.py:

#!/usr/bin/env python3
import re
import os
import sys
import time
import subprocess
import RPi.GPIO as GPIO
import multiprocessing as mp
from collections import defaultdict
from configparser import ConfigParser

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17, GPIO.OUT)
GPIO.output(17, GPIO.HIGH)

cmds = {
    'blk': "lsblk | awk '{print $1}'",
    'up': "echo Uptime: `uptime | sed 's/.*up \\([^,]*\\), .*/\\1/'`",
    'temp': "cat /sys/class/thermal/thermal_zone0/temp",
    'ip': "hostname -I | awk '{printf \"IP %s\", $1}'",
    'cpu': "uptime | tr , . | awk '{printf \"CPU Load: %.2f%%\", $(NF-2)}'",
    'men': "free -m | awk 'NR==2{printf \"Mem: %s/%s MB\", $3,$2}'",
    'disk': "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%d GB %s\", $3,$2,$5}'"
}

lv2dc = {'lv3': 100, 'lv2': 75, 'lv1': 50, 'lv0': 25}


# pin37(bcm26) sata0, pin22(bcm25) sata1
def set_mode(pin, mode):
    try:
        GPIO.setup(pin, GPIO.OUT)
        GPIO.output(pin, mode)
    except Exception as ex:
        print(ex)


def disk_turn_on():
    #blk1 = get_blk()
    set_mode(26, GPIO.HIGH)
    time.sleep(0.5)
    set_mode(25, GPIO.HIGH)
    wait_blk(10)
    #blk2 = get_blk()
    #conf['disk'] = sorted(list(set(blk2) - set(blk1)))


def disk_turn_off():
    set_mode(26, GPIO.LOW)
    time.sleep(0.5)
    set_mode(25, GPIO.LOW)


def check_output(cmd):
    return subprocess.check_output(cmd, shell=True).decode().strip()


def check_call(cmd):
    return subprocess.check_call(cmd, shell=True)


def wait_blk(t1=10):
    t = 0
    while t <= t1:
        try:
            check_call('lsblk /dev/sda > /dev/null 2>&1')
            check_call('lsblk /dev/sdb > /dev/null 2>&1')
            check_call('lsblk /dev/sdc > /dev/null 2>&1')
            check_call('lsblk /dev/sdd > /dev/null 2>&1')
        except Exception:
            time.sleep(0.1)
            t += 0.1
            continue
        else:
            time.sleep(0.5)
            break


def get_blk():
    return check_output(cmds['blk']).strip().split('\n')


def get_info(s):
    return check_output(cmds[s])


def get_cpu_temp():
    t = float(get_info('temp')) / 1000
    if conf['oled']['f-temp']:
        temp = "CPU Temp: {:.0f}°F".format(t * 1.8 + 32)
    else:
        temp = "CPU Temp: {:.1f}°C".format(t)
    return temp


def read_conf():
    conf = defaultdict(dict)

    try:
        cfg = ConfigParser()
        cfg.read('/etc/rockpi-sata.conf')
        # fan
        conf['fan']['lv0'] = cfg.getfloat('fan', 'lv0')
        conf['fan']['lv1'] = cfg.getfloat('fan', 'lv1')
        conf['fan']['lv2'] = cfg.getfloat('fan', 'lv2')
        conf['fan']['lv3'] = cfg.getfloat('fan', 'lv3')
        # key
        conf['key']['click'] = cfg.get('key', 'click')
        conf['key']['twice'] = cfg.get('key', 'twice')
        conf['key']['press'] = cfg.get('key', 'press')
        # time
        conf['time']['twice'] = cfg.getfloat('time', 'twice')
        conf['time']['press'] = cfg.getfloat('time', 'press')
        # other
        conf['slider']['auto'] = cfg.getboolean('slider', 'auto')
        conf['slider']['time'] = cfg.getfloat('slider', 'time')
        conf['oled']['rotate'] = cfg.getboolean('oled', 'rotate')
        conf['oled']['f-temp'] = cfg.getboolean('oled', 'f-temp')
        # disk
        conf['disk']['mnt_points'] = cfg.get('disk', 'mnt_points').split('|')
        #conf['disk']['disks'] = get_disk_list()
    except Exception:
        # fan
        conf['fan']['lv0'] = 35
        conf['fan']['lv1'] = 40
        conf['fan']['lv2'] = 45
        conf['fan']['lv3'] = 50
        # key
        conf['key']['click'] = 'slider'
        conf['key']['twice'] = 'switch'
        conf['key']['press'] = 'none'
        # time
        conf['time']['twice'] = 0.7  # second
        conf['time']['press'] = 1.8
        # other
        conf['slider']['auto'] = True
        conf['slider']['time'] = 10  # second
        conf['oled']['rotate'] = False
        conf['oled']['f-temp'] = False
        # disk
        conf['disk']['mnt_points'] = []
        #conf['disk']['disks'] = []

    return conf


def read_key(pattern, size):
    s = ''
    while True:
        s = s[-size:] + str(GPIO.input(17))
        for t, p in pattern.items():
            if p.match(s):
                return t
        time.sleep(0.1)


def watch_key(q=None):
    size = int(conf['time']['press'] * 10)
    wait = int(conf['time']['twice'] * 10)
    pattern = {
        'click': re.compile(r'1+0+1{%d,}' % wait),
        'twice': re.compile(r'1+0+1+0+1{3,}'),
        'press': re.compile(r'1+0{%d,}' % size),
    }

    while True:
        q.put(read_key(pattern, size))


def get_interface_list():
    interfaces = []
    cmd = "ip -o link show | awk '{print $2,$9}'"
    list = check_output(cmd).split('\n')
    for x in list:
        name_status = x.split(': ')
        if "UP" in name_status[1]:
            interfaces.append(name_status[0])

    interfaces.sort()
    return interfaces


def get_interface_rx_info(interface):
    cmd = "R1=$(cat /sys/class/net/" + interface + "/statistics/rx_bytes); sleep 1; R2=$(cat /sys/class/net/" + interface + "/statistics/rx_bytes); echo | awk -v r1=$R1 -v r2=$R2 '{printf \"rx: %.5f MB/s\", (r2 - r1) / 1024 / 1024}';"
    output = check_output(cmd)
    return output


def get_interface_tx_info(interface):
    cmd = "T1=$(cat /sys/class/net/" + interface + "/statistics/tx_bytes); sleep 1; T2=$(cat /sys/class/net/" + interface + "/statistics/tx_bytes); echo | awk -v t1=$T1 -v t2=$T2 '{printf \"tx: %.5f MB/s\", (t2 - t1) / 1024 / 1024}';"
    output = check_output(cmd)
    return output


def delete_disk_partition_number(disk):
    if "sd" in disk:
        disk = disk[:-1]
    return disk


def get_disk_list():
    disks = []
    for x in conf['disk']['mnt_points']:
        cmd = "df -Bg | awk '$6==\"{}\" {{printf \"%s\", $1}}'".format(x)
        output = check_output(cmd).split('/')[-1]
        if output != '':
            disks.append(output)

    #if len(disks) < 3:
    #    disks = ['md0', 'sdc1', 'sdd1']

    disks.sort()
    return disks


def get_disk_io_read_info(disk):
    cmd = "R1=$(cat /sys/block/" + disk + "/stat | awk '{print $3}'); sleep 1; R2=$(cat /sys/block/" + disk + "/stat | awk '{print $3}'); echo | awk -v r1=$R1 -v r2=$R2 '{printf \"R: %.5f MB/s\", (r2 - r1) / 2 / 1024}';"
    output = check_output(cmd)
    return output


def get_disk_io_write_info(disk):
    cmd = "W1=$(cat /sys/block/" + disk + "/stat | awk '{print $7}'); sleep 1; W2=$(cat /sys/block/" + disk + "/stat | awk '{print $7}'); echo | awk -v w1=$W1 -v w2=$W2 '{printf \"W: %.5f MB/s\", (w2 - w1) / 2 / 1024}';"
    output = check_output(cmd)
    return output


def get_disk_info(cache={}):
    if not cache.get('time') or time.time() - cache['time'] > 30:
        info = {}
        cmd = "df -h | awk '$NF==\"/\"{printf \"%s\", $5}'"
        info['root'] = check_output(cmd)
        conf['disk']['disks'] = get_disk_list()
        for x in conf['disk']['disks']:
            delete_disk_partition_number(x)
            cmd = "df -Bg | awk '$1==\"/dev/{}\" {{printf \"%s\", $5}}'".format(x)
            info[x] = check_output(cmd)
        cache['info'] = list(zip(*info.items()))
        cache['time'] = time.time()

    return cache['info']


def slider_next(pages):
    conf['idx'].value += 1
    return pages[conf['idx'].value % len(pages)]


def slider_sleep():
    time.sleep(conf['slider']['time'])


def fan_temp2dc(t):
    for lv, dc in lv2dc.items():
        if t >= conf['fan'][lv]:
            return dc
    return 0


def fan_switch():
    conf['run'].value = not(conf['run'].value)


def get_func(key):
    return conf['key'].get(key, 'none')


def open_w1_i2c():
    with open('/boot/config.txt', 'r') as f:
        content = f.read()

    if 'dtoverlay=w1-gpio' not in content:
        with open('/boot/config.txt', 'w') as f:
            f.write(content.strip() + '\ndtoverlay=w1-gpio')

    if 'dtparam=i2c1=on' not in content:
        with open('/boot/config.txt', 'w') as f:
            f.write(content.strip() + '\ndtparam=i2c1=on')

    os.system('/sbin/modprobe w1-gpio')
    os.system('/sbin/modprobe w1-therm')
    os.system('/sbin/modprobe i2c-dev')


conf = {'disk': [], 'idx': mp.Value('d', -1), 'run': mp.Value('d', 1)}
conf.update(read_conf())


if __name__ == '__main__':
    if sys.argv[-1] == 'open_w1_i2c':
        open_w1_i2c()

3.4. /usr/bin/rockpi-sata/oled.py:

#!/usr/bin/python3
import time
import misc
import Adafruit_SSD1306
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

font = {
    '10': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 10),
    '11': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 11),
    '12': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 12),
    '14': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 14),
}

misc.set_mode(23, 0)
time.sleep(0.2)
misc.set_mode(23, 1)


def disp_init():
    disp = Adafruit_SSD1306.SSD1306_128_32(rst=None)
    [getattr(disp, x)() for x in ('begin', 'clear', 'display')]
    return disp


try:
    disp = disp_init()
except Exception:
    misc.open_w1_i2c()
    time.sleep(0.2)
    disp = disp_init()

image = Image.new('1', (disp.width, disp.height))
draw = ImageDraw.Draw(image)


def disp_show():
    im = image.rotate(180) if misc.conf['oled']['rotate'] else image
    disp.image(im)
    disp.display()
    draw.rectangle((0, 0, disp.width, disp.height), outline=0, fill=0)


def welcome():
    draw.text((0, 0), 'ROCK Pi SATA HAT', font=font['14'], fill=255)
    draw.text((32, 16), 'Loading...', font=font['12'], fill=255)
    disp_show()


def goodbye():
    draw.text((32, 8), 'Exiting...', font=font['14'], fill=255)
    disp_show()
    time.sleep(2)
    disp_show()  # clear


def put_disk_info():
    k, v = misc.get_disk_info()
    text1 = 'Disk: {} {}'.format(k[0], v[0])

    if len(k) == 5:
        text2 = '{} {}  {} {}'.format(k[1], v[1], k[2], v[2])
        text3 = '{} {}  {} {}'.format(k[3], v[3], k[4], v[4])
        page = [
            {'xy': (0, -2), 'text': text1, 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': text2, 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': text3, 'fill': 255, 'font': font['11']},
        ]
    elif len(k) == 4:
        text2 = '{} {}  {} {}'.format(k[1], v[1], k[2], v[2])
        text3 = '{} {}'.format(k[3], v[3])
        page = [
            {'xy': (0, -2), 'text': text1, 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': text2, 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': text3, 'fill': 255, 'font': font['11']},
        ]
    elif len(k) == 3:
        text2 = '{} {}  {} {}'.format(k[1], v[1], k[2], v[2])
        page = [
            {'xy': (0, 2), 'text': text1, 'fill': 255, 'font': font['12']},
            {'xy': (0, 18), 'text': text2, 'fill': 255, 'font': font['12']},
        ]
    elif len(k) == 2:
        text2 = '{} {}'.format(k[1], v[1])
        page = [
            {'xy': (0, 2), 'text': text1, 'fill': 255, 'font': font['12']},
            {'xy': (0, 18), 'text': text2, 'fill': 255, 'font': font['12']},
        ]
    else:
        page = [{'xy': (0, 2), 'text': text1, 'fill': 255, 'font': font['14']}]

    return page


def put_disk_io_info(pages_len):
    pages = {}
    start_key = pages_len
    disks = misc.get_disk_list()

    for x in disks:
        x = misc.delete_disk_partition_number(x)

        pages[start_key] = [
            {'xy': (0, -2), 'text': 'Disk (' + x + '):', 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': misc.get_disk_io_read_info(x), 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': misc.get_disk_io_write_info(x), 'fill': 255, 'font': font['11']}
        ]
        start_key = start_key + 1

    return pages


def put_interface_info(pages_len):
    pages = {}
    start_key = pages_len
    interfaces = misc.get_interface_list()

    for x in interfaces:
        pages[start_key] = [
            {'xy': (0, -2), 'text': 'Network (' + x + '):', 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': misc.get_interface_rx_info(x), 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': misc.get_interface_tx_info(x), 'fill': 255, 'font': font['11']}
        ]
        start_key = start_key + 1

    return pages


def gen_pages():
    pages = {
        0: [
            {'xy': (0, -2), 'text': misc.get_info('up'), 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': misc.get_cpu_temp(), 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': misc.get_info('ip'), 'fill': 255, 'font': font['11']}
        ],
        1: [
            {'xy': (0, 2), 'text': misc.get_info('cpu'), 'fill': 255, 'font': font['12']},
            {'xy': (0, 18), 'text': misc.get_info('men'), 'fill': 255, 'font': font['12']}
        ],
        2: put_disk_info()
    }

    pages.update(put_interface_info(len(pages)))
    pages.update(put_disk_io_info(len(pages)))

    return pages


def slider(lock):
    with lock:
        for item in misc.slider_next(gen_pages()):
            draw.text(**item)
        disp_show()


def auto_slider(lock):
    while misc.conf['slider']['auto']:
        slider(lock)
        misc.slider_sleep()
    else:
        slider(lock)

4.0. After the edit, reboot your rpi (sudo reboot).

5.0. Notes:
I tested it on “Linux raspberry-pi-4 5.4.83-v7l+ #1379 SMP Mon Dec 14 13:11:54 GMT 2020 armv7l GNU/Linux” with HUN language.

Eg. in Hungarian the system in decimal fraction use , (comma) for separation instead of . (dot).
As I see, nothing else is different, but please try it with your own risk!

I changed some texts on the OLED display (eg. “Goodbye ~” text to “Exiting…”).
You have to write it back in the oled.py file if you want.

Sorry for my English, if you have any question or idea what I can add to extra function, please ask/tell me.

2 Likes

Nice. Let’s merge it :smiley:

Thank you! I think @setq has to check this and do the merge.

Fantastic. This will bring us comfort about the dire construction of the sata hat…

Thank you! Please try it and share your experiences and feedback with me/us! :blush:

I took a look in the other topics and improved the uptime display and now you can see the fan speed in percentage.

You have to do everything like in the first post.
The difference is only in 3.3 and 3.4 steps (the improved code).

3.3. /usr/bin/rockpi-sata/misc.py:

#!/usr/bin/env python3
import re
import os
import sys
import time
import subprocess
import RPi.GPIO as GPIO
import multiprocessing as mp
from collections import defaultdict
from configparser import ConfigParser

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17, GPIO.OUT)
GPIO.output(17, GPIO.HIGH)

cmds = {
    'blk': "lsblk | awk '{print $1}'",
    #'up': "echo Uptime: `uptime | sed 's/.*up \\([^,]*\\), .*/\\1/'`",
    'up': "echo Up: $(uptime -p | sed 's/ years,/y/g;s/ year,/y/g;s/ months,/m/g;s/ month,/m/g;s/ weeks,/w/g;s/ week,/w/g;s/ days,/d/g;s/ day,/d/g;s/ hours,/h/g;s/ hour,/h/g;s/ minutes/m/g;s/ minute/m/g' | cut -d ' ' -f2-)",
    'temp': "cat /sys/class/thermal/thermal_zone0/temp",
    'ip': "hostname -I | awk '{printf \"IP %s\", $1}'",
    'cpu': "uptime | tr , . | awk '{printf \"CPU Load: %.2f%%\", $(NF-2)}'",
    'men': "free -m | awk 'NR==2{printf \"Mem: %s/%s MB\", $3,$2}'",
    'disk': "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%d GB %s\", $3,$2,$5}'"
}

lv2dc = {'lv3': 100, 'lv2': 75, 'lv1': 50, 'lv0': 25}


# pin37(bcm26) sata0, pin22(bcm25) sata1
def set_mode(pin, mode):
    try:
        GPIO.setup(pin, GPIO.OUT)
        GPIO.output(pin, mode)
    except Exception as ex:
        print(ex)


def disk_turn_on():
    #blk1 = get_blk()
    set_mode(26, GPIO.HIGH)
    time.sleep(0.5)
    set_mode(25, GPIO.HIGH)
    wait_blk(10)
    #blk2 = get_blk()
    #conf['disk'] = sorted(list(set(blk2) - set(blk1)))


def disk_turn_off():
    set_mode(26, GPIO.LOW)
    time.sleep(0.5)
    set_mode(25, GPIO.LOW)


def check_output(cmd):
    return subprocess.check_output(cmd, shell=True).decode().strip()


def check_call(cmd):
    return subprocess.check_call(cmd, shell=True)


def wait_blk(t1=10):
    t = 0
    while t <= t1:
        try:
            check_call('lsblk /dev/sda > /dev/null 2>&1')
            check_call('lsblk /dev/sdb > /dev/null 2>&1')
            check_call('lsblk /dev/sdc > /dev/null 2>&1')
            check_call('lsblk /dev/sdd > /dev/null 2>&1')
        except Exception:
            time.sleep(0.1)
            t += 0.1
            continue
        else:
            time.sleep(0.5)
            break


def get_blk():
    return check_output(cmds['blk']).strip().split('\n')


def get_info(s):
    return check_output(cmds[s])


def get_cpu_temp():
    t = float(get_info('temp')) / 1000
    if conf['oled']['f-temp']:
        temp = "CPU Temp: {:.0f}°F".format(t * 1.8 + 32)
    else:
        temp = "CPU Temp: {:.1f}°C".format(t)
    return temp


def read_conf():
    conf = defaultdict(dict)

    try:
        cfg = ConfigParser()
        cfg.read('/etc/rockpi-sata.conf')
        # fan
        conf['fan']['lv0'] = cfg.getfloat('fan', 'lv0')
        conf['fan']['lv1'] = cfg.getfloat('fan', 'lv1')
        conf['fan']['lv2'] = cfg.getfloat('fan', 'lv2')
        conf['fan']['lv3'] = cfg.getfloat('fan', 'lv3')
        # key
        conf['key']['click'] = cfg.get('key', 'click')
        conf['key']['twice'] = cfg.get('key', 'twice')
        conf['key']['press'] = cfg.get('key', 'press')
        # time
        conf['time']['twice'] = cfg.getfloat('time', 'twice')
        conf['time']['press'] = cfg.getfloat('time', 'press')
        # other
        conf['slider']['auto'] = cfg.getboolean('slider', 'auto')
        conf['slider']['time'] = cfg.getfloat('slider', 'time')
        conf['oled']['rotate'] = cfg.getboolean('oled', 'rotate')
        conf['oled']['f-temp'] = cfg.getboolean('oled', 'f-temp')
        # disk
        conf['disk']['mnt_points'] = cfg.get('disk', 'mnt_points').split('|')
        #conf['disk']['disks'] = get_disk_list()
    except Exception:
        # fan
        conf['fan']['lv0'] = 35
        conf['fan']['lv1'] = 40
        conf['fan']['lv2'] = 45
        conf['fan']['lv3'] = 50
        # key
        conf['key']['click'] = 'slider'
        conf['key']['twice'] = 'switch'
        conf['key']['press'] = 'none'
        # time
        conf['time']['twice'] = 0.7  # second
        conf['time']['press'] = 1.8
        # other
        conf['slider']['auto'] = True
        conf['slider']['time'] = 10  # second
        conf['oled']['rotate'] = False
        conf['oled']['f-temp'] = False
        # disk
        conf['disk']['mnt_points'] = []
        #conf['disk']['disks'] = []

    return conf


def read_key(pattern, size):
    s = ''
    while True:
        s = s[-size:] + str(GPIO.input(17))
        for t, p in pattern.items():
            if p.match(s):
                return t
        time.sleep(0.1)


def watch_key(q=None):
    size = int(conf['time']['press'] * 10)
    wait = int(conf['time']['twice'] * 10)
    pattern = {
        'click': re.compile(r'1+0+1{%d,}' % wait),
        'twice': re.compile(r'1+0+1+0+1{3,}'),
        'press': re.compile(r'1+0{%d,}' % size),
    }

    while True:
        q.put(read_key(pattern, size))


def get_interface_list():
    interfaces = []
    cmd = "ip -o link show | awk '{print $2,$9}'"
    list = check_output(cmd).split('\n')
    for x in list:
        name_status = x.split(': ')
        if "UP" in name_status[1]:
            interfaces.append(name_status[0])

    interfaces.sort()
    return interfaces


def get_interface_rx_info(interface):
    cmd = "R1=$(cat /sys/class/net/" + interface + "/statistics/rx_bytes); sleep 1; R2=$(cat /sys/class/net/" + interface + "/statistics/rx_bytes); echo | awk -v r1=$R1 -v r2=$R2 '{printf \"rx: %.5f MB/s\", (r2 - r1) / 1024 / 1024}';"
    output = check_output(cmd)
    return output


def get_interface_tx_info(interface):
    cmd = "T1=$(cat /sys/class/net/" + interface + "/statistics/tx_bytes); sleep 1; T2=$(cat /sys/class/net/" + interface + "/statistics/tx_bytes); echo | awk -v t1=$T1 -v t2=$T2 '{printf \"tx: %.5f MB/s\", (t2 - t1) / 1024 / 1024}';"
    output = check_output(cmd)
    return output


def delete_disk_partition_number(disk):
    if "sd" in disk:
        disk = disk[:-1]
    return disk


def get_disk_list():
    disks = []
    for x in conf['disk']['mnt_points']:
        cmd = "df -Bg | awk '$6==\"{}\" {{printf \"%s\", $1}}'".format(x)
        output = check_output(cmd).split('/')[-1]
        if output != '':
            disks.append(output)

    #if len(disks) < 3:
    #    disks = ['md0', 'sdc1', 'sdd1']

    disks.sort()
    return disks


def get_disk_io_read_info(disk):
    cmd = "R1=$(cat /sys/block/" + disk + "/stat | awk '{print $3}'); sleep 1; R2=$(cat /sys/block/" + disk + "/stat | awk '{print $3}'); echo | awk -v r1=$R1 -v r2=$R2 '{printf \"R: %.5f MB/s\", (r2 - r1) / 2 / 1024}';"
    output = check_output(cmd)
    return output


def get_disk_io_write_info(disk):
    cmd = "W1=$(cat /sys/block/" + disk + "/stat | awk '{print $7}'); sleep 1; W2=$(cat /sys/block/" + disk + "/stat | awk '{print $7}'); echo | awk -v w1=$W1 -v w2=$W2 '{printf \"W: %.5f MB/s\", (w2 - w1) / 2 / 1024}';"
    output = check_output(cmd)
    return output


def get_disk_info(cache={}):
    if not cache.get('time') or time.time() - cache['time'] > 30:
        info = {}
        cmd = "df -h | awk '$NF==\"/\"{printf \"%s\", $5}'"
        info['root'] = check_output(cmd)
        conf['disk']['disks'] = get_disk_list()
        for x in conf['disk']['disks']:
            delete_disk_partition_number(x)
            cmd = "df -Bg | awk '$1==\"/dev/{}\" {{printf \"%s\", $5}}'".format(x)
            info[x] = check_output(cmd)
        cache['info'] = list(zip(*info.items()))
        cache['time'] = time.time()

    return cache['info']


def slider_next(pages):
    conf['idx'].value += 1
    return pages[conf['idx'].value % len(pages)]


def slider_sleep():
    time.sleep(conf['slider']['time'])


def fan_temp2dc(t):
    for lv, dc in lv2dc.items():
        if t >= conf['fan'][lv]:
            return dc
    return 0


def fan_switch():
    conf['run'].value = not(conf['run'].value)


def get_func(key):
    return conf['key'].get(key, 'none')


def open_w1_i2c():
    with open('/boot/config.txt', 'r') as f:
        content = f.read()

    if 'dtoverlay=w1-gpio' not in content:
        with open('/boot/config.txt', 'w') as f:
            f.write(content.strip() + '\ndtoverlay=w1-gpio')

    if 'dtparam=i2c1=on' not in content:
        with open('/boot/config.txt', 'w') as f:
            f.write(content.strip() + '\ndtparam=i2c1=on')

    os.system('/sbin/modprobe w1-gpio')
    os.system('/sbin/modprobe w1-therm')
    os.system('/sbin/modprobe i2c-dev')


conf = {'disk': [], 'idx': mp.Value('d', -1), 'run': mp.Value('d', 1)}
conf.update(read_conf())


if __name__ == '__main__':
    if sys.argv[-1] == 'open_w1_i2c':
        open_w1_i2c()

3.4. /usr/bin/rockpi-sata/oled.py:

#!/usr/bin/python3
import time
import misc
import fan
import Adafruit_SSD1306
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

font = {
    '10': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 10),
    '11': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 11),
    '12': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 12),
    '14': ImageFont.truetype('fonts/DejaVuSansMono-Bold.ttf', 14),
}

misc.set_mode(23, 0)
time.sleep(0.2)
misc.set_mode(23, 1)


def disp_init():
    disp = Adafruit_SSD1306.SSD1306_128_32(rst=None)
    [getattr(disp, x)() for x in ('begin', 'clear', 'display')]
    return disp


try:
    disp = disp_init()
except Exception:
    misc.open_w1_i2c()
    time.sleep(0.2)
    disp = disp_init()

image = Image.new('1', (disp.width, disp.height))
draw = ImageDraw.Draw(image)


def disp_show():
    im = image.rotate(180) if misc.conf['oled']['rotate'] else image
    disp.image(im)
    disp.display()
    draw.rectangle((0, 0, disp.width, disp.height), outline=0, fill=0)


def welcome():
    draw.text((0, 0), 'ROCK Pi SATA HAT', font=font['14'], fill=255)
    draw.text((32, 16), 'Loading...', font=font['12'], fill=255)
    disp_show()


def goodbye():
    draw.text((30, 8), 'Power off...', font=font['14'], fill=255)
    disp_show()
    time.sleep(2)
    disp_show()  # clear


def put_disk_info():
    k, v = misc.get_disk_info()
    text1 = 'Disk: {} {}'.format(k[0], v[0])

    if len(k) == 5:
        text2 = '{} {}  {} {}'.format(k[1], v[1], k[2], v[2])
        text3 = '{} {}  {} {}'.format(k[3], v[3], k[4], v[4])
        page = [
            {'xy': (0, -2), 'text': text1, 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': text2, 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': text3, 'fill': 255, 'font': font['11']},
        ]
    elif len(k) == 4:
        text2 = '{} {}  {} {}'.format(k[1], v[1], k[2], v[2])
        text3 = '{} {}'.format(k[3], v[3])
        page = [
            {'xy': (0, -2), 'text': text1, 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': text2, 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': text3, 'fill': 255, 'font': font['11']},
        ]
    elif len(k) == 3:
        text2 = '{} {}  {} {}'.format(k[1], v[1], k[2], v[2])
        page = [
            {'xy': (0, 2), 'text': text1, 'fill': 255, 'font': font['12']},
            {'xy': (0, 18), 'text': text2, 'fill': 255, 'font': font['12']},
        ]
    elif len(k) == 2:
        text2 = '{} {}'.format(k[1], v[1])
        page = [
            {'xy': (0, 2), 'text': text1, 'fill': 255, 'font': font['12']},
            {'xy': (0, 18), 'text': text2, 'fill': 255, 'font': font['12']},
        ]
    else:
        page = [{'xy': (0, 2), 'text': text1, 'fill': 255, 'font': font['14']}]

    return page


def put_disk_io_info(pages_len):
    pages = {}
    start_key = pages_len
    disks = misc.get_disk_list()

    for x in disks:
        x = misc.delete_disk_partition_number(x)

        pages[start_key] = [
            {'xy': (0, -2), 'text': 'Disk (' + x + '):', 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': misc.get_disk_io_read_info(x), 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': misc.get_disk_io_write_info(x), 'fill': 255, 'font': font['11']}
        ]
        start_key = start_key + 1

    return pages


def put_interface_info(pages_len):
    pages = {}
    start_key = pages_len
    interfaces = misc.get_interface_list()

    for x in interfaces:
        pages[start_key] = [
            {'xy': (0, -2), 'text': 'Network (' + x + '):', 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': misc.get_interface_rx_info(x), 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': misc.get_interface_tx_info(x), 'fill': 255, 'font': font['11']}
        ]
        start_key = start_key + 1

    return pages


def gen_pages():
    pages = {
        0: [
            {'xy': (0, -2), 'text': misc.get_info('up'), 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': misc.get_cpu_temp(), 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': misc.get_info('ip'), 'fill': 255, 'font': font['11']}
        ],
        1: [
            #{'xy': (0, 2), 'text': misc.get_info('cpu'), 'fill': 255, 'font': font['12']},
            #{'xy': (0, 18), 'text': misc.get_info('men'), 'fill': 255, 'font': font['12']}
            {'xy': (0, -2), 'text': 'Fan speed: ' + str(fan.get_dc()) + '%', 'fill': 255, 'font': font['11']},
            {'xy': (0, 10), 'text': misc.get_info('cpu'), 'fill': 255, 'font': font['11']},
            {'xy': (0, 21), 'text': misc.get_info('men'), 'fill': 255, 'font': font['11']},
        ],
        2: put_disk_info()
    }

    pages.update(put_interface_info(len(pages)))
    pages.update(put_disk_io_info(len(pages)))

    return pages


def slider(lock):
    with lock:
        for item in misc.slider_next(gen_pages()):
            draw.text(**item)
        disp_show()


def auto_slider(lock):
    while misc.conf['slider']['auto']:
        slider(lock)
        misc.slider_sleep()
    else:
        slider(lock)