From ad0b261fe3463510be255ea6abdc2d540e504f94 Mon Sep 17 00:00:00 2001 From: WellsWang Date: Thu, 28 Dec 2023 12:26:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LoongsonPanel.ino | 392 ++++++++++++++++++++++++++++++++++++++++++++++ panelAgent.py | 119 ++++++++++++++ settings.py | 6 + 3 files changed, 517 insertions(+) create mode 100644 LoongsonPanel.ino create mode 100644 panelAgent.py create mode 100644 settings.py diff --git a/LoongsonPanel.ino b/LoongsonPanel.ino new file mode 100644 index 0000000..ba5e08a --- /dev/null +++ b/LoongsonPanel.ino @@ -0,0 +1,392 @@ +/************************************************************************** + Loongson 3A2000C Greatwall Mainborad Control Panel + + Author : Wells Wang + Version : 0.1beta + + Release under GPL v3 License + **************************************************************************/ + +#include +#include +#include +#include +#include + +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels +#define OLED_RESET -1 +#define SCREEN_ADDRESS 0x3C +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +#define B_PREV D5 +#define B_NEXT D4 +#define B_OK D3 +#define B_PWR D6 +#define POWER D7 +OneButton btn_OK = OneButton(B_OK, true, false); +OneButton btn_PREV = OneButton(B_PREV, true, false); +OneButton btn_NEXT = OneButton(B_NEXT, true, false); +OneButton btn_PWR = OneButton(B_PWR, true, false); + +// Following is Chinese Character Matrix for Chinese UI. +// 测(0) 试(1) 中(2) 通(3) 过(4) 失(5) 败(6) +//#define TXT_HEIGHT 12 +//#define TXT_WIDTH 12 +//static const unsigned char PROGMEM txt[] = +//{0x00,0x20,0xBE,0x20,0x62,0xA0,0x2A,0xA0,0x2A,0xA0,0xAA,0xA0,0x6A,0xA0,0x2A,0xA0,0x2A,0xA0,0x48,0x20,0x94,0x20,0x22,0x60,/*"测",0*/ +// 0x80,0xA0,0x40,0x80,0x0F,0xE0,0x00,0x80,0xC0,0x80,0x4E,0x80,0x44,0x80,0x44,0x80,0x44,0x80,0x46,0xA0,0x6C,0x60,0x40,0x20,/*"试",1*/ +// 0x04,0x00,0x04,0x00,0x04,0x00,0x7F,0xC0,0x44,0x40,0x44,0x40,0x44,0x40,0x7F,0xC0,0x44,0x40,0x04,0x00,0x04,0x00,0x04,0x00,/*"中",2*/ +// 0x9F,0x80,0x44,0x80,0x03,0x00,0x1F,0xC0,0xD2,0x40,0x5F,0xC0,0x52,0x40,0x5F,0xC0,0x52,0x40,0x52,0xC0,0x40,0x00,0xBF,0xE0,/*"通",3*/ +// 0x40,0x40,0x20,0x40,0x00,0x40,0x0F,0xE0,0xE0,0x40,0x24,0x40,0x22,0x40,0x22,0x40,0x20,0x40,0x21,0xC0,0x50,0x00,0x8F,0xE0,/*"过",4*/ +// 0xFB,0xFF,0xDB,0xFF,0xDB,0xFF,0xC0,0x7F,0xBB,0xFF,0x7B,0xFF,0xFB,0xFF,0x00,0x1F,0xF5,0xFF,0xEE,0xFF,0xDF,0x7F,0x3F,0x9F,/*"-失",5*/ +// 0xFE,0xFF,0x06,0xFF,0x75,0xFF,0x54,0x1F,0x53,0xBF,0x55,0xBF,0x55,0xBF,0x55,0xBF,0x55,0x7F,0xDE,0xFF,0xAD,0x7F,0x73,0x9F,/*"-败",6*/ +// 0x00,0x00,0x1F,0x80,0x3F,0xC0,0x76,0xE0,0x36,0xC0,0x7F,0xE0,0x3F,0xC0,0x79,0xE0,0x36,0xC0,0x7F,0xE0,0x3F,0xC0,0x1F,0x80,/* FAILED ICON 故障内存图标,7*/ +// 0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x80,0x36,0xC0,0x10,0x80,0x30,0xC0,0x10,0x80,0x30,0xC0,0x10,0x80,0x30,0xC0,0x1F,0x80, /* MEMCHIP ICON 内存图标,8*/ +// 0x00,0x00,0x1F,0x80,0x3F,0xC0,0x76,0xE0,0x36,0xC0,0x7F,0xE0,0x3F,0xC0,0x76,0xE0,0x39,0xC0,0x7F,0xE0,0x3F,0xC0,0x1F,0x80};/* PASSED ICON 内存笑脸图标,9*/ + +#define USAGE_WIDTH 76 //screen_width - width_of_text_"CPU:100%" +#define MODE_STATUS 1 +#define MODE_TEMP 2 +#define MODE_LOG 3 + + +int incomingByte = 0; // for incoming serial data +String inputString = ""; // a String to hold incoming data +bool stringComplete = false; // whether the string is complete +bool update = false; +byte mode[3] = {MODE_STATUS, MODE_TEMP, MODE_LOG}; +byte currentMode = 0; +byte cpuUsage[USAGE_WIDTH]; +byte memUsage[USAGE_WIDTH]; +byte temperature[USAGE_WIDTH]; +String hostname = "MYHOSTNAME"; +String kernel = "0.00.0"; +String ip = "255.255.255.255"; +String displaytime = "00:00"; +String uptime = "000d00h"; +byte cu = 0; +byte mu = 0; +byte page = 0; +String logItem; + + + +// +// UI 相关 +// ======================================================= + +/* +void drawTxt(int x, int y, byte id) { + unsigned char ch[24]; + for (int i=0; i<24; i++){ + ch[i] = pgm_read_word_near(txt + id*24 +i); + // Serial.println(txt[i],BIN); + } + display.drawBitmap(x,y,ch, TXT_WIDTH, TXT_HEIGHT, 1); +} +*/ + +void drawUsageGraph(byte graphData[], int x, int y){ + int i; + + display.fillRect(x, y, USAGE_WIDTH, 10, SSD1306_BLACK); + for (i=0; i < USAGE_WIDTH; i++){ + display.drawLine(x + i, y+10-graphData[i], x+i, y+10, SSD1306_WHITE); + } + display.display(); +} + +void drawTempGraph(byte graphData[], int x, int y){ + int i; + byte max_v = 0; + byte min_v = 255; + int last_y = 0; + int pos_y; + + for (i=0; i < USAGE_WIDTH; i++){ + if (graphData[i] > 0) { + max_v = max(graphData[i], max_v); + min_v = min(graphData[i], min_v); + } + } + + display.fillRect(x, y, USAGE_WIDTH, 20, SSD1306_BLACK); + for (i=0; i < USAGE_WIDTH; i++){ + if (graphData[i]>0){ + pos_y = y + 20 - int((graphData[i] - min_v + 5) *20 / (max_v-min_v+10)); // y + 20(图像宽度) - 当前温度在最大最小值上下5度之间的相对比例位置 + if (last_y > 0) //前面有效数值,和前一点用线连接 + display.drawLine(x + i - 1, last_y, x+i, pos_y, SSD1306_WHITE); + else //前面无有效值,画第一个点 + display.drawPixel(x + i, pos_y , SSD1306_WHITE); + last_y = pos_y; + } + } + display.display(); +} + +void updateUI(){ + display.fillRect(0,0, display.width(), 16, SSD1306_BLACK); + display.setTextSize(2); // Normal 1:1 pixel scale + display.setCursor(0,0); + display.setTextColor(SSD1306_WHITE); // Draw white text + display.print(hostname); + display.fillRect(0,16, display.width(), 10, SSD1306_WHITE); + display.setTextColor(SSD1306_BLACK); // Draw white text + display.setTextSize(1); + display.setCursor(0,17); + display.print(ip); + display.setCursor(display.width()-30,17); + display.print(displaytime); + display.setTextColor(SSD1306_WHITE); // Draw white text + + switch (mode[currentMode]) { + case MODE_STATUS: + display.fillRect(104,33, 24, 20, SSD1306_BLACK); + display.setCursor(104,33); + display.print(cu);display.print("%"); + display.setCursor(104,45); + display.print(mu);display.print("%"); + display.fillRect(0,56, display.width(), 8, SSD1306_BLACK); + display.setCursor(0,56); + display.print(kernel); + display.setCursor(display.width()-uptime.length()*6, 56); + display.print(uptime); + drawUsageGraph(cpuUsage, 0, 30); + drawUsageGraph(memUsage, 0, 42); + break; + case MODE_TEMP: + display.fillRect(86,45, 24, 16, SSD1306_BLACK); + display.setTextSize(2); + display.setCursor(86,45); + display.print(temperature[USAGE_WIDTH-1]); + drawTempGraph(temperature, 0, 42); + break; + case MODE_LOG: + display.fillRect(0,30, display.width(), display.height()-30, SSD1306_BLACK); + if (logItem.length()>0){ + display.setCursor(0,30); + display.print(logItem); + } + break; + } + + display.display(); + update = false; +} + +void drawUI(){ + display.clearDisplay(); + //display.drawLine(0, 25, display.width()-1, 25, SSD1306_WHITE); + //drawTxt(80,20,8); + + switch(mode[currentMode]){ + case MODE_STATUS: + display.setTextColor(SSD1306_WHITE); // Draw white text + display.setTextSize(1); + display.setCursor(80,33); + display.print(F("CPU:")); + display.setCursor(80,45); + display.print(F("MEM:")); + break; + case MODE_TEMP: + display.setTextColor(SSD1306_WHITE); + display.setTextSize(1); + display.setCursor(0,33); + display.print(F("CPU Temperature")); + display.setTextSize(2); + display.setCursor(116,45); + display.print(F("C")); + display.setTextSize(1); + display.setCursor(112,39); + display.print(F("o")); + break; + } +} + +// +// 数据处理 +// ======================================================= + +void updateUsageData(byte usageData[], byte newVal){ + int i; + for (i=0; i< USAGE_WIDTH - 1; i++){ + usageData[i] = usageData[i+1]; + } + usageData[i] = newVal; +} + +byte getByteValue(String data, String cmd){ + byte temp_b[2] = {0,0}; + String temp; + + if (data.indexOf(cmd)>0){ + temp = data.substring(data.indexOf(cmd)+4, data.indexOf(cmd)+5); + temp.getBytes(temp_b, 2); + } + return temp_b[0]; +} + +boolean updateValue(String data, String &key, String cmd){ + String temp; + boolean res = false; + if (data.indexOf(cmd)>0){ + temp = data.substring(data.indexOf(cmd)+4); + temp = temp.substring(0,temp.indexOf("\n")); + if (key != temp){ + key = temp; + res = true; + } + } + return res; +} + +void parseData(String data){ + + if (data){ + data = " " + data; + updateValue(data, hostname, "!HN!"); + updateValue(data, ip, "!IP!"); + updateValue(data, kernel, "!KR!"); + updateValue(data, uptime, "!UT!"); + updateValue(data, displaytime, "!TI!"); + if (data.indexOf("!CG!")>0) updateUsageData(cpuUsage, getByteValue(data, "!CG!")); + if (data.indexOf("!MG!")>0) updateUsageData(memUsage, getByteValue(data, "!MG!")); + if (data.indexOf("!CT!")>0) updateUsageData(temperature, getByteValue(data, "!CT!")); + if (data.indexOf("!CU!")>0) cu = getByteValue(data, "!CU!"); + if (data.indexOf("!MU!")>0) mu = getByteValue(data, "!MU!"); + if (data.indexOf("!LP!")>0) page = getByteValue(data, "!LP!"); + updateValue(data, logItem, "!LG!"); + + update = true; + } + +} + +// +// 事件处理 +// ======================================================= + + +/* + SerialEvent occurs whenever a new data comes in the hardware serial RX. This + routine is run between each time loop() runs, so using delay inside loop can + delay response. Multiple bytes of data may be available. +*/ +void serialEvent() { + while (Serial.available()) { + digitalWrite(LED_BUILTIN, LOW); + // get the new byte: + char inChar = (char)Serial.read(); + // add it to the inputString: + inputString += inChar; + // if the incoming character is a newline, set a flag so the main loop can + // do something about it: + if (inChar == '\n') { + parseData(inputString); + inputString = ""; + } + digitalWrite(LED_BUILTIN, HIGH); + } +} + + +static void clickOK() { + currentMode = (currentMode + 1) % (sizeof(mode) / sizeof(mode[0])); + if (mode[currentMode] == MODE_LOG){ + Serial.write("#GL#"); + Serial.write(page); + Serial.write("\n"); + } + drawUI(); + updateUI(); +} + +static void clickPREV() { + if (mode[currentMode] == MODE_LOG){ + if (page > 0) page = page - 1; + Serial.write("#GL#"); + Serial.write(page); + Serial.write("\n"); + delay(100); + updateUI(); + } +} + +static void clickNEXT() { + if (mode[currentMode] == MODE_LOG){ + if (page <255) page = page + 1; + Serial.write("#GL#"); + Serial.write(page); + Serial.write("\n"); + delay(100); + updateUI(); + } +} + +static void clickPWR() { + digitalWrite(POWER, LOW); + digitalWrite(POWER, HIGH); + delay(5000); + digitalWrite(POWER, LOW); +} + + + +// +// 初始化 +// ======================================================= + +void setup() { + + int i; + + pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output + pinMode(POWER, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + digitalWrite(POWER, LOW); + + Serial.begin(115200); + // reserve 200 bytes for the inputString: + inputString.reserve(250); + logItem.reserve(250); + logItem = ""; + + btn_OK.attachClick(clickOK); + btn_PREV.attachClick(clickPREV); + btn_NEXT.attachClick(clickNEXT); + btn_PWR.attachClick(clickPWR); + + + for (i=0; i < USAGE_WIDTH; i++){ + updateUsageData(cpuUsage, 0); + updateUsageData(memUsage, 0); + updateUsageData(temperature, 0); + } + // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally + if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { + Serial.println(F("SSD1306 allocation failed")); + for(;;); // Don't proceed, loop forever + } + + drawUI(); + updateUI(); + +} + + +// +// 循环体 +// ======================================================= + +void loop() { + int i; + // print the string when a newline arrives: + if (update) { + updateUI(); + } + btn_OK.tick(); + btn_PREV.tick(); + btn_NEXT.tick(); + btn_PWR.tick(); +} \ No newline at end of file diff --git a/panelAgent.py b/panelAgent.py new file mode 100644 index 0000000..725d899 --- /dev/null +++ b/panelAgent.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +################################################# +# Agent of Loongson Control Panel +# Author: Wells Wang +# Version: 0.1beta +# Released under GPL v3 License. +################################################# + + +import serial, threading, psutil, os, datetime, time; +from settings import * + +ser = None +hostname = 'hostname' +kernal = '' +ip = '' +transfering = False + + +def init(): + global ser + try: + ser = serial.Serial(DEVICEPORT, BAUDRATE, timeout = TIMEOUT) + except: + print("Init serial port {} faild!".format(DEVICEPORT)) + +def startTransfer(): + global transfering + while transfering: + time.sleep(0.01) + transfering = True + +def endTransfer(): + global transfering + transfering = False + + +def updateData(): + global ser, hostname, kernal, ip + threading.Timer(5.0, updateData).start() + + startTransfer() + + if (os.uname().nodename != hostname): + hostname = os.uname().nodename + ser.write('!HN!{}\n'.format(hostname).encode('utf8')) + if (os.uname().release != kernal): + kernal = os.uname().release + ser.write('!KR!{}\n'.format(kernal[:kernal.index('-')][:6]).encode('utf8')) + + try: + cip = os.popen("ip a show dev {}".format(NETDEV)).read() + cip = cip[cip.index('inet ') + 5 : cip.index('inet ') + 20] + cip = cip[:cip.index('/')] + if (ip != cip): + ip = cip + ser.write('!IP!{}\n'.format(ip).encode('utf8')) + except: + print ("[ERROR]: Network Device {} IP address get faild!".format(NETDEV)) + cpu_percent = psutil.cpu_percent() + mem_percent = psutil.virtual_memory().percent + ser.write('!CG!{}\n'.format(chr(int(cpu_percent/10))).encode('utf8')) + ser.write('!CU!{}\n'.format(chr(int(round(cpu_percent)))).encode('utf8')) + ser.write('!MG!{}\n'.format(chr(int(mem_percent/10))).encode('utf8')) + ser.write('!MU!{}\n'.format(chr(int(round(mem_percent)))).encode('utf8')) + + ser.write('!TI!{}\n'.format(datetime.datetime.now().strftime("%H:%M")).encode('utf8')) + + uptime = (time.time() - psutil.boot_time()) /3600 + ser.write('!UT!{}d{}h\n'.format(int(uptime / 24), int(uptime % 24)).encode('utf8')) + + endTransfer() + + print("[{}] Data updated.".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + + +def sendLog(page = 0): + global ser + startTransfer() + try: + size = os.path.getsize(LOGFILE) + if page < 0: page = 0 + if (TPP * page > size): page = int(size / TPP) + elif (TPP * page == size): page = page - 1 + + f = open(LOGFILE, "r") + f.seek(TPP * page) + msg = f.read(TPP).replace("\n", " ") + ser.write('!LP!{}\n'.format(chr(page)).encode('utf8')) + ser.write('!LG!{}\n'.format(msg).encode('utf8')) + print("Message sent: {}\n".format(msg)) + except: + print("Log file operation failed!") + endTransfer() + +def serialInputTrigger(): + threading.Timer(0.5, serialInputTrigger).start() + while ser.in_waiting: + response = ser.readline() + print("[RESPONSE]: {}\n".format(response)) + if b'#GL#' in response: + sendLog(response[response.index(b'#GL#')+4]) + + +if __name__=='__main__': + print("Start to initialize serial port '{}' ...".format(DEVICEPORT)) + init() + if ser: + print('Successfull!') + updateData() + serialInputTrigger() + #while 1: + # if ser.in_waiting: + # print("[RESPONSE]: {}\n".format(ser.readline())) + + + + diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..9da104e --- /dev/null +++ b/settings.py @@ -0,0 +1,6 @@ +DEVICEPORT = '/dev/ttyUSB0' +BAUDRATE = 115200 +TIMEOUT = 1 +NETDEV = 'ens33' +LOGFILE = 'msg.log' +TPP = 84 \ No newline at end of file