Skip to content

ROCK SBC Advanced Status Monitor

Introduction

Adding a status monitor to your ROCK SBC project is a great idea. It will let you keep an eye on real-time CPU performance, temperature, RAM usage, and other important connectivity details. If you're into overclocking, this tool becomes even more valuable, helping you gauge the impact of your settings on your ROCK SBC and make adjustments as needed.

For this project, the REPTOR-250 display paired with the Pi adapter is an excellent choice. Not only is it user-friendly, but setting it up and getting it running requires minimal code complexity.

One cool feature is the touch capability, allowing you to swipe through different status page styles. Plus, our Python library gives you a hassle-free Python coding experience. Let’s make your ROCK SBC project even more awesome with this simple yet powerful status monitor setup.

intro

Note

When using REPTOR-250 as a Serial UART device, it is always recommended to simply use Mates Studio Architect or Commander environment since it provides a simple command protocol without the need for you to write code for the display.

However, some controller boards may require custom syncing and timing requirements that projects made using Architect and Commander environments can conflict with. This example project shows how a custom Genius project can solve such requirements.

Requirements

To proceed with the project, the following are required.

Hardware

  • ROCK SBC
  • REPTOR-250
  • Pi Adapter
  • HDMI Monitor or TV
  • Keyboard & Mouse
  • Storage Device
    • microSD card
    • eMMC module

Software

REPTOR-250 Setup

The Pi Adapter will need to be attached to the ROCK SBC GPIO Header and the REPTOR-250 attached to the adapter as shown below.

rock-setup

As the display needs to be configured for the Status Monitor, the switch on the Pi adapter needs to be set to PROG.

programmer-attached

Connect a USB cable to the Mates Programmer and to a PC USB port. The REPTOR-250 is now ready for the Status Monitor project to be installed.

Mates Studio will be required to configure the REPTOR-250.

Uploading the Genius Project

Step 1: When you start Mates Studio you will be prompted to select your product. As we are using an already-created project, we can simply load the project from this screen.

select-product

Step 2: Simply Click on Browse Computer and navigate to the downloaded project file. The project will open in the Genius environment.

open-project

Step 3: To upload the project to the REPTOR we need to first select the correct COM port from the Tools menu.

select-comport

Step 4: Next, click on the upload button.

upload-icon

Step 5: After an animated logo has shown the Status Monitor will now be displayed on the REPTOR-250.

uploaded

The USB lead and Mates Programmer can now be removed from the Pi Adapter. The Mates Programmer switch can now be set to HOST ready to receive commands from the ROCK SBC.

ROCK SBC Setup

Setting up the ROCK SBC is very easy to achieve by visiting Armbian for ROCK 4 and following the instructions for installing the OS.

When the OS has been transferred to the uSD card or eMMC and insert into the ROCK SBC and then power it up. ROCK SBC can consume quite a lot of current which may exceed the available current from a PC USB port so it may be necessary to use the official ROCK SBC power supply.

The ROCK SBC OS will need to be configured to connect to the internet, SSH, and also to enable the Serial port (UART) that we will use to talk to the REPTOR-250.

Armbian Configuration

Use the armbian configuration utility to connect to network for internet connectivity, enable SSH for remote control and enable UART. Type the command below on the terminal.

sudo armbian-config

This will open the Armbian Configuration window.

armbian-config

First, set the internet connection. Select Network from the armbian config option then Select Wifi.

select-wifi

Select the network that you want to connect. Activate and save the configuration.

select-ssid

After a successful connection to the network, enable the SSH by select System from the armbian config option then select SSH.

select-ssh

Use arrow keys to navigate to SSH Key login then use space bar to enable and save the configuration

enable-ssh-key-login

You now have an option to control the ROCK SBC from remote computer using SSH client software.

To enable the UART, select System from the armbian-config option and select Hardware.

select-hw

Use arrow keys to navigate to rk-3328-uart1 then use space bar to enable and save the configuration

enable-uart1

Reboot the system to apply the configuration changes.

Installing Python Libraries

All recent Armbian OS Distro's are pre-loaded with Python 3, but you need to check if pip and python3-dev are installed.

To install pip, type the command on the terminal.

sudo apt install python3-pip

To install python3-dev, type the command on the terminal

sudo apt install python3-dev

The psutil and uptime can be installed by entering the following commands.

pip install psutil 
pip install uptime

Since the REPTOR-250 display needs to communicate via serial UART. You need to install pyserial library to access serial(UART) using python. Type the command below to install.

pip install pyserial

The Serial port of the ROCK SBC is used by the console so it needs to be released after boot. This can be achieved by using these commands:

cd /etc/systemd/system
systemctl mask serial-getty@ttyS2.service
sync

Reboot the ROCK SBC.

The source code for this project can be found in this GitHub repository: BreadBoardMates/ROCKSBC-Status-Monitor

First, install git using the command below.

sudo apt install git

Clone the project repository to the user folder using the commands:

cd ~/
git clone https://github.com/BreadBoardMates/ROCKSBC-Status-Monitor.git

Project Discussion

Genius Graphics Design

This project use 10 pages which contains different widgets.

Page 0: It contains animation and image widgets which both need to source to display on the screen. The animation shows a 120 frames of BBM logo.

page-0

Page 1: This page contains a pre-built page design. It can be found under Application > Navigations > Status and Resource Monitor.

page-1

Page 2: This page is done by duplicating Page 1, and changing the widgets in the lower right corner. The lower right corner widgets were replaced by the same widgets present in the other corners, a gauge, led digits and a label. For the complete widget properties, please refer to the project.

page-2

Page 3: This page is made by duplicating Page 1 and modifying the widget properties to have different full round gauges.

page-3

Please refer to the project file for the complete widget properties.

Page 4: This page is made by duplicating Page 2 and modifying the widget properties to have different full round gauges.

page-4

Please refer to the project file for the complete widget properties.

Page 5: This page is a custom page which include Led Digits, Media Gauges and Media Temperature for displaying CPU temperature, CPU and RAM use. Below is the layout of the page.

page-5

Page 6: This page is from the default page templates Application > Environmental > Dual Thermometer Centigrade which is for displaying CPU temperature values. There are property changes on the widgets. Please refer to the project file for the complete widget properties.

page-6

Page 7: This page is a custom page which includes Labels and Text Area for displaying System informtaion like System processor, number of CPU cores, OS Release and Version, System RAM and System HDD. This page also uses an image as its background. Below is the layout of the page.

page-7

Please refer to the project file for the complete page and widget properties.

Page 8: This page is made by duplicating and modifying Page 7. It includes Labels and Text Area for dislaying WiFi Information such as WiFi SSID, Host Name, IP Address, MAC Address, Bytes Rx and Bytes Tx. Below is the layout of the page.

page-8

Please refer to the project file for the complete widget properties.

Page 9: This page is made by duplicating and modifying Page 7 or 8. It includes Labels and Text Area for displaying Display Information like Module which is a REPTOR-250, Resolution, External flash size, Baud rate used, Bytes Rx and Bytes Tx. Below is the layout of the page.

page-9

Please refer to the project file for the complete widget properties.

Genius Code

Genius projects allow you to custumize the behavior of the display. For this project, this is used to introduce a simple Serial UART command protocol. The code starts by listing the commands and declaring variables to be used.

#CONST
    CPU_COUNT           0x10
    CPU_PERCENT         0x11
    CPU_TEMPERATURE     0x12
    GPU_TEMPERATURE     0x13
    RAM_USE             0x14
    DISK_USE            0x15
    UP_TIME             0x16
    IP_WIFI             0x17
    IP_ETHERNET         0x18
    MOVE_TO             0x19
    FONT_CHANGE         0x1A
    PRINT_STRING        0x1B
    TXT_FG              0x1C
    TXT_BG              0x1D
    START_STRING        0x1E
    PAGE_SWAP           0x1F
    SYSTEM_INFO_0       0x20
    SYSTEM_INFO_1       0x21
    SYSTEM_INFO_2       0x22
    SYSTEM_INFO_3       0x23
    SYSTEM_INFO_4       0x24
    SYSTEM_INFO_5       0x25
    WIFI_INFO_0         0x30
    WIFI_INFO_1         0x31
    WIFI_INFO_2         0x32
    WIFI_INFO_3         0x33
    WIFI_INFO_4         0x34
    WIFI_INFO_5         0x35
    SEND_STARTUP_SIG    0xC8
    MAX_PAGE            0x08           
#END

var RXbuff[1000];
var baud := 11520;
var recString[200];
var startString[10];
var messageID;
var recData[3];
var mCount;
var valueUpdate[100];
var cpuCount;
var cpuPercent;
var cpuTemp;
var gpuTemp;
var ramUse;
var diskUse;
var strLength;
var txEnable;
var buffer[15];
var bufferBAK[15];
var sbPos;
var sb;
var sbb;
var swipeEvent;
var currPage;
var valueStr[10];
var tempstring[20];

Setup

In this project, the setup function is used to initialize string pointers and the Serial UART. It also runs a start up sequence which includes an animation. Finally, a startup signal is sent to indicate that the display is ready to accept commands.

/*
 * User Setup Function
 */
func setup()
    // put your setup code here, to run once:
    sb := str_Ptr(buffer);
    sbb := str_Ptr(bufferBAK);
    CommsInit();
    to(startString);
    print("BBMROCKSBCSTART");
    pause(1000);
    runLogo();
    pause(1500);
    setPage(1);
    serout(SEND_STARTUP_SIG);
endfunc

Loop

The loop function is mainly responsible for handling the serial commands coming from the host controller, in this case the ROCK SBC.

It features a simple command structure which is as follows:

Message ID, Checksum, Data 1, Data 2

Each item in this is an 8-bit data.

  • Message ID: indicates the command
  • Checksum: a simple checksum value computed by the formula ((MessageID + Data1 + Data2) & 0xFF)
  • Data 1 and Data 2: Values to use for the command when necessary, otherwise ignored. For more information, please refer to the code below.
/*
 * User Loop Function
 */
func loop()
    // put your main code here, to run repeatedly:
    if (com_Count() > 3)
        messageID := getSerialByte();
        recData[0] := getSerialByte();
        recData[1] := getSerialByte();
        recData[2] := getSerialByte();
        if (((messageID + recData[1] + recData[2]) & 0xff) == recData[0])
            switch (messageID)
                case CPU_COUNT:
                    cpuCount := recData[1];
                    updateCPU_COUNT();
                    break;
                case CPU_PERCENT:
                    cpuPercent := recData[1];
                    updateCPU_PERCENT();
                    break;
                case CPU_TEMPERATURE:
                    cpuTemp := recData[1] + (recData[2] << 8);
                    updateCPU_TEMPERATURE();
                    break;
                case GPU_TEMPERATURE:
                    gpuTemp := recData[1] + (recData[2] << 8);
                    updateGPU_TEMPERATURE();
                    break;
                case RAM_USE:
                    ramUse := recData[1];
                    updateRAM_USE();
                    break;
                case DISK_USE:
                    diskUse := recData[1];
                    updateDISK_USE();
                    break;
                case UP_TIME:
                    strLength := recData[1];
                    updateUP_TIME();
                    break;
                case IP_WIFI:
                    strLength := recData[1];
                    updateIP_WIFI();
                    break;
                case IP_ETHERNET:
                    strLength := recData[1];
                    updateIP_ETHERNET();
                    break;
                case MOVE_TO:
                    updateMOVE_TO();
                    break;
                case FONT_CHANGE:
                    updateFONT_CHANGE();
                    break;
                case PRINT_STRING:
                    strLength := recData[1];
                    updatePRINT_STRING();
                    break;
                case TXT_FG:
                    updateTXT_FG();
                    break;
                case TXT_BG:
                    updateTXT_BG();
                    break;
                case START_STRING:
                    strLength := recData[1];
                    updateSTART_STRING();
                    break;
                case SYSTEM_INFO_0:
                    strLength := recData[1];
                    updateSYSTEM_INFO_0();
                    break;
                case SYSTEM_INFO_1:
                    strLength := recData[1];
                    updateSYSTEM_INFO_1();
                    break;
                case SYSTEM_INFO_2:
                    strLength := recData[1];
                    updateSYSTEM_INFO_2();
                    break;
                case SYSTEM_INFO_3:
                    strLength := recData[1];
                    updateSYSTEM_INFO_3();
                    break;
                case SYSTEM_INFO_4:
                    strLength := recData[1];
                    updateSYSTEM_INFO_4();
                    break;
                case SYSTEM_INFO_5:
                    strLength := recData[1];
                    updateSYSTEM_INFO_5();
                    break;
                case WIFI_INFO_0:
                    strLength := recData[1];
                    updateWIFI_INFO_0();
                    break;
                case WIFI_INFO_1:
                    strLength := recData[1];
                    updateWIFI_INFO_1();
                    break;
                case WIFI_INFO_2:
                    strLength := recData[1];
                    updateWIFI_INFO_2();
                    break;
                case WIFI_INFO_3:
                    strLength := recData[1];
                    updateWIFI_INFO_3();
                    break;
                case WIFI_INFO_4:
                    strLength := recData[1];
                    updateWIFI_INFO_4();
                    break;
                case WIFI_INFO_5:
                    strLength := recData[1];
                    updateWIFI_INFO_5();
                    break;
            endswitch
        endif
    endif
    if (getSwipeEventCount() > 0)
        swipeEvent := getNextSwipeEvent();
        if (swipeEvent == MATES_SWIPE_WEST)
            currPage ++;
            if (currPage > MAX_PAGE) currPage := 1;
            setPage(currPage);
            serout(currPage + 201);
        else if (swipeEvent == MATES_SWIPE_EAST)
            currPage --;
            if (currPage < 1) currPage := MAX_PAGE;
            setPage(currPage);
            serout(currPage + 201);
        endif
    endif
endfunc

The loop function uses getSerialByte to read the next byte received from the ROCK SBC.

func getSerialByte()
    var getchr;
    getchr := serin();
    str_PutByte(sbb + sbPos, getchr);
    sbPos ++;
    if (sbPos >= 15)
        str_CopyN(sb, sbb + 1, 14);
        str_CopyN(sbb, sb, 14);
        sbPos := 14;
    endif
    if (mem_Compare(startString, bufferBAK, 15))
        txEnable := 0;
    else
        Reset();
        txEnable := 1;
    endif
    return getchr;
endfunc

This function additionally keeps track of the last 14 characters and checks it against the string "BBMROCKSBCSTART". If this string is found, the REPTOR module will reset the Serial RX buffer. This provides a "hotplug" or hotswap solution to the project.

The loop function also handles swipes detected by the touch display. In this project, it is used to navigate between pages. This also sends a report to the host indicating the new active page number.

The supporting functions used when handling commands are primarily used to update widgets included in the project.

Some widgets require string handling and therefore string data sometimes needs to be received as well. In this case, the Data 1 value indicates the number of characters and the display will expect to receive the string. Some examples for these are the functions:

func updateSTART_STRING()
    var slen;
    getString();
    slen := strlen(startString);
    if(mem_Compare(startString, recString, slen))
        txEnable := 0;
    else
        txEnable := 1;
    endif
endfunc
func updateSYSTEM_INFO_0()
    getString();
    updateTextArea(TextArea12, recString);    
endfunc
func updateWIFI_INFO_0()
    getString();
    updateTextArea(TextArea18, recString);
endfunc

Please refer to the project for the information on which widgets get updated by each command and which involves a string.

Python Program

The Python application is consisted of two files:

serialcontrol.py

This file contains a Python class RockPiBBMController which uses the serial class to handle the Serial UART port of the ROCK SBC. It also manages the commands sent to the display.

The ROCKSBCBBMController class needs to be initialized using the begin function provided:

def begin(self, baudrate: int):
    self.serial_port.baudrate = baudrate
    self.serial_port.open()

The main code should look like this:

from serialcontrol import ROCKSBCBBMController

BBM = ROCKSBCBBMController()
BBM.begin(115200)

There are two main functions used to send commands to the REPTOR-250: sendCommand and sendCommandString

The sendCommand function sends the command structure as described here. While sendCommandString takes it another step and handles sending a string for commands that requires it, as described here.

Another utility function sendCommandReset is available to add hotswap/hotplug feature to the REPTOR project.

def sendCommandReset(self):
    text_string = "BBMROCKSBCSTART"
    self.__write_string(text_string)
    self.__write_int8(0)
    self.__write_int8(0)
    self.__write_int8(0)
    self.__write_int8(0)
    self.__write_int8(0)
    self.__write_int8(0)

This is used by the main python script to reset the REPTOR RX buffer effectively restarting the commands.

This Python file also includes "get" functions which handles receiving commands from the REPTOR-250. This is primarily used to receive command indicating the new active page.

BBMROCKSBCStatusMonitor.py

This is the main Python program which uses serialcontrol.py and its ROCKSBCBBMController class.

The program starts by defining helper functions for getting network and system information, uptime, temperature and storage information.

# Get the name of the current Wi-Fi connection.
def getSSID():
    ipaddress = os.popen("ifconfig wlan0 \
                    | grep 'inet addr' \
                    | awk -F: '{print $2}' \
                    | awk '{print $1}'").read()
    ssid = os.popen("iwconfig wlan0 \
                | grep 'ESSID' \
                | awk '{print $4}' \
                | awk -F\\\" '{print $2}'").read()
    return ssid
# Collect data specific to ROCK Pi platform and HDD data and write it to the display
def getSystemInfo():
    BBM.sendCommandString(33, "Total number of Cores: " + str(psutil.cpu_count(logical=True),))
    BBM.sendCommandString(34, platform.release())
    BBM.sendCommandString(35, str(platform.version()))
    BBM.sendCommandString(32, str(platform.machine()))
    BBM.sendCommandString(36, "Total RAM: "+ str(round(psutil.virtual_memory().total / (1024.0 **3)))+" GB")
    hdd = psutil.disk_usage('/')
    BBM.sendCommandString(37, "Total HDD Capacity: %d GB" % (hdd.total / (2**30)))
# Collect data specific to the current network connection and write it to the display
def getNetworkInfo():
    BBM.sendCommandString(48, getSSID())
    BBM.sendCommandString(49, socket.gethostname())
    BBM.sendCommandString(50, get_interface_ipaddress('wlan0'))
    BBM.sendCommandString(51, ':'.join(re.findall('..', '%012x' % uuid.getnode())))
    iostat = psutil.net_io_counters(pernic=False)
    BBM.sendCommandString(52, str(iostat[1]) + " bytes")
    BBM.sendCommandString(53, str(iostat[0]) + " bytes")
# Calculate time elapsed since boot and output it as string
def up():
    t = int(time.clock_gettime(time.CLOCK_BOOTTIME))
    days = 0
    hours = 0
    min = 0
    out = ''
    days = int(t / 86400)
    t = t - (days * 86400)
    hours = int(t / 3600)
    t = t - (hours * 3600)
    min = int(t / 60)
    out += str(days) + 'd '
    out += str(hours) + 'h '
    out += str(min) + 'm'
    return out

# Get the current temperature of selected sensor, either cpu ("cpu_thermal") or gpu ("gpu_thermal")
def get_temp(sensor: str):
    cpu_temp = psutil.sensors_temperatures()
    cpu_temp = psutil.sensors_temperatures()[sensor]
    cpu_temp = psutil.sensors_temperatures()[sensor][0]
    return int(cpu_temp.current)
# Calculate the used percentage of the HDD
def get_hdd():
    hdd = psutil.disk_partitions(0)
    drive = psutil.disk_usage('/')
    percent = drive.percent
    return percent

Some Armbian operating systems does not support thermal sensors which is required for the psutil library as of this writing. To make it work, you may need to manually create an overlay.

From your home directory, create the .dts file, type

nano myfile.dts

Then enter the following into the file.

/dts-v1/;
/plugin/;

/ {
        compatible = "radxa,rockpi4c-plus", "radxa,rockpi4", "rockchip,rk3399";

        fragment@0 {
                target=<&tsadc>;
                __overlay__ {
                        status = "okay";
                        /* tshut mode 0:CRU 1:GPIO */
                        rockchip,hw-tshut-mode = <1>;
                        /* tshut polarity 0:LOW 1:HIGH */
                        rockchip,hw-tshut-polarity = <1>;
                };
        };

};

Save the file. Then add the file to the overlay using the command below.

sudo armbian-add-overlay myfile.dts

This will compile, add to the overlays directory and add to the configuration file. You need to reboot the system to use the overlay.

If this file is run as the main file, it will initialize variables and the Serial UART through the class file. It also gathers initial information of the network and system.

if __name__ == '__main__':

    BBM = ROCKSBCBBMController()
    BBM.begin(115200)

    gtime = up()
    lastCpuUse = 0
    lastTemp = 0
    lastTempG = 0
    lastlTemp = 0
    lastlTempG = 0
    lastRamUse = 0
    lastHDD = 0
    lastWIPaddr = '0.0.0.0'
    lastEIPaddr = '0.0.0.0'
    lastPage = 0
    currPage = 0
    lastbytesRX = 0
    lastbytesTX = 0

    BBM.sendCommandReset()
    BBM.sendCommandString(22, gtime)

    lcpu = int(get_temp("cpu_thermal") * 10)
    lgpu = int(get_temp("gpu_thermal") * 10)

    IPinterval = 0

    getSystemInfo()
    getNetworkInfo()

The program proceeds by updating the display with the updated values every short interval

    while True:
        reccommand = BBM.getCommand() 
        if reccommand == 200:
            BBM.sendCommandReset()
            tempTime = up()
            BBM.sendCommandString(22, tempTime)
            BBM.sendCommand(20, lastRamUse)
            BBM.sendCommand(18, lastlTemp)
            BBM.sendCommandString(24, lastEIPaddr)
            BBM.sendCommandString(23, lastWIPaddr)
            getSystemInfo()
            getNetworkInfo()
            currPage = 1

        if reccommand > 200:
            currPage = reccommand - 201

        lcpu = int(get_temp("cpu_thermal") * 10)
        lgpu = int(get_temp("gpu_thermal") * 10)

        cpuuse = int(psutil.cpu_percent())
        ramuse = int(psutil.virtual_memory().percent)
        hdd = get_hdd()

        if currPage == 8:
            iostat = psutil.net_io_counters(pernic=False)
            if lastbytesRX != iostat[1]:
                lastbytesRX = iostat[1]
                BBM.sendCommandString(52, str(iostat[1]) + " bytes")
            if lastbytesTX != iostat[0]:
                lastbytesTX = iostat[0]
                BBM.sendCommandString(53, str(iostat[0]) + " bytes")

        if cpuuse != lastCpuUse:
            lastCpuUse = lastCpuUse - increment(cpuuse, lastCpuUse)
            BBM.sendCommand(17, lastCpuUse)

        if lcpu != lastlTemp:
            lastlTemp = lastlTemp - increment(lcpu, lastlTemp)
            BBM.sendCommand(18, lastlTemp)

        if lgpu != lastlTempG:
            lastlTempG = lastlTempG - increment(lgpu, lastlTempG)
            BBM.sendCommand(19, lastlTempG)

        if ramuse != lastRamUse:
            lastRamUse = lastRamUse - increment(ramuse, lastRamUse)
            BBM.sendCommand(20, lastRamUse)

        if hdd != lastHDD:
            lastHDD = lastHDD - increment(hdd, lastHDD)
            BBM.sendCommand(21, lastHDD)

        if IPinterval > 20:
            tempIPaddr = get_interface_ipaddress('eth0')
            if tempIPaddr != lastEIPaddr:
                BBM.sendCommandString(24, tempEIPaddr)
                lastEIPaddr = tempIPaddr
            tempIPaddr = get_interface_ipaddress('wlan0')
            if tempIPaddr != lastWIPaddr:
                BBM.sendCommandString(23, tempIPaddr)
                lastWIPaddr = tempIPaddr
                getNetworkInfo()
            IPinterval = 0

        IPinterval = IPinterval + 1
        time.sleep(0.060)

        tempTime = up()
        if tempTime != gtime:
            BBM.sendCommandString(22, gtime)
            gtime = tempTime
        refresh = 0
        time.sleep(0.040)

Running the Python Script

Go into the ROCKSBC-Status-Monitor folder the run the project using the code below

cd ~/ROCKSBC-Status-Monitor
python3 BBMROCKSBCStatusMonitor.py

The REPTOR-250 will then start showing the boot logo followed by the status of CPU use, CPU temp, and RAM use along with the connected IP address and uptime.

CPU use and RAM use percentages are calculated in the main loop and sent like other values when there is a change in state. If the display is disconnected from the ROCK SBC while running and then reconnected all values will be refreshed, and the display will continue to run.

You can then swipe through the available pages.

This project can be simply altered or improved to get the desired look by creating a new page in Mates Studio and changing the Python code to match with any new widgets used. The only limit is imagination.

demo