mirror of
https://github.com/0015/ThatProject.git
synced 2026-01-12 01:07:44 +03:00
Make your own System Monitor with ESP32 + LVGL 8
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
Make your own System Monitor with ESP32 + LVGL 8
|
||||
For More Information: https://youtu.be/gliwNg25fLE
|
||||
Created by Eric N. (ThatProject)
|
||||
*/
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////
|
||||
// Arduino IDE
|
||||
// ESP v2.0.3
|
||||
// LVGL v8.2.0
|
||||
////////////////////
|
||||
|
||||
#include <ArduinoJson.h> //https://arduinojson.org/v6/doc/installation/
|
||||
#include <ESP32Time.h> //https://github.com/fbiego/ESP32Time
|
||||
#include <lvgl.h> //https://github.com/lvgl/lvgl
|
||||
|
||||
#define LGFX_USE_V1 //https://github.com/lovyan03/LovyanGFX
|
||||
#include <LovyanGFX.hpp>
|
||||
#include <FT6236.h> //https://github.com/DustinWatts/FT6236
|
||||
#define SDA_FT6236 21
|
||||
#define SCL_FT6236 22
|
||||
FT6236 ts = FT6236();
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device {
|
||||
lgfx::Panel_ST7796 _panel_instance;
|
||||
lgfx::Bus_SPI _bus_instance;
|
||||
lgfx::Light_PWM _light_instance;
|
||||
|
||||
public:
|
||||
LGFX(void) {
|
||||
{
|
||||
auto cfg = _bus_instance.config();
|
||||
cfg.spi_host = VSPI_HOST;
|
||||
cfg.spi_mode = 0;
|
||||
cfg.freq_write = 40000000;
|
||||
cfg.freq_read = 16000000;
|
||||
cfg.spi_3wire = false;
|
||||
cfg.use_lock = true;
|
||||
cfg.dma_channel = 1;
|
||||
cfg.pin_sclk = 18;
|
||||
cfg.pin_mosi = 19;
|
||||
cfg.pin_miso = 23;
|
||||
cfg.pin_dc = 27;
|
||||
_bus_instance.config(cfg);
|
||||
_panel_instance.setBus(&_bus_instance);
|
||||
}
|
||||
|
||||
{
|
||||
auto cfg = _panel_instance.config();
|
||||
cfg.pin_cs = 5;
|
||||
cfg.pin_rst = -1;
|
||||
cfg.pin_busy = -1;
|
||||
cfg.memory_width = 320;
|
||||
cfg.memory_height = 480;
|
||||
cfg.panel_width = 320;
|
||||
cfg.panel_height = 480;
|
||||
cfg.offset_x = 0;
|
||||
cfg.offset_y = 0;
|
||||
cfg.offset_rotation = 0;
|
||||
cfg.dummy_read_pixel = 8;
|
||||
cfg.dummy_read_bits = 1;
|
||||
cfg.readable = true;
|
||||
cfg.invert = false;
|
||||
cfg.rgb_order = false;
|
||||
cfg.dlen_16bit = false;
|
||||
cfg.bus_shared = true;
|
||||
|
||||
_panel_instance.config(cfg);
|
||||
}
|
||||
|
||||
{
|
||||
auto cfg = _light_instance.config();
|
||||
|
||||
cfg.pin_bl = 12;
|
||||
cfg.invert = false;
|
||||
cfg.freq = 44100;
|
||||
cfg.pwm_channel = 7;
|
||||
|
||||
_light_instance.config(cfg);
|
||||
_panel_instance.setLight(&_light_instance);
|
||||
}
|
||||
|
||||
setPanel(&_panel_instance);
|
||||
}
|
||||
};
|
||||
|
||||
LGFX tft;
|
||||
|
||||
ESP32Time rtc;
|
||||
|
||||
StaticJsonDocument<512> doc;
|
||||
|
||||
struct queue_t {
|
||||
String data_string;
|
||||
};
|
||||
static QueueHandle_t data_queue;
|
||||
|
||||
/*Change to your screen resolution*/
|
||||
static const uint32_t screenWidth = 480;
|
||||
static const uint32_t screenHeight = 320;
|
||||
static lv_disp_draw_buf_t draw_buf;
|
||||
static lv_color_t buf[screenWidth * 10];
|
||||
int prev_cpu_usage = 0;
|
||||
int prev_mem_usage = 0;
|
||||
|
||||
/* Display flushing */
|
||||
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
|
||||
uint32_t w = (area->x2 - area->x1 + 1);
|
||||
uint32_t h = (area->y2 - area->y1 + 1);
|
||||
|
||||
tft.startWrite();
|
||||
tft.setAddrWindow(area->x1, area->y1, w, h);
|
||||
tft.writePixels((lgfx::rgb565_t *)&color_p->full, w * h);
|
||||
tft.endWrite();
|
||||
|
||||
lv_disp_flush_ready(disp);
|
||||
}
|
||||
|
||||
/*Read the touchpad*/
|
||||
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
|
||||
if (ts.touched()) {
|
||||
data->state = LV_INDEV_STATE_PR;
|
||||
TS_Point p = ts.getPoint();
|
||||
data->point.x = tft.width() - p.y;
|
||||
data->point.y = p.x;
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_REL;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
tft.begin();
|
||||
tft.setRotation(3);
|
||||
tft.setBrightness(255);
|
||||
|
||||
if (!ts.begin(40, SDA_FT6236, SCL_FT6236)) {
|
||||
Serial.println("Unable to start the capacitive touch Screen.");
|
||||
}
|
||||
|
||||
lv_init();
|
||||
lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 10);
|
||||
|
||||
/*Initialize the display*/
|
||||
static lv_disp_drv_t disp_drv;
|
||||
lv_disp_drv_init(&disp_drv);
|
||||
|
||||
/*Change the following line to your display resolution*/
|
||||
disp_drv.hor_res = screenWidth;
|
||||
disp_drv.ver_res = screenHeight;
|
||||
disp_drv.flush_cb = my_disp_flush;
|
||||
disp_drv.draw_buf = &draw_buf;
|
||||
lv_disp_drv_register(&disp_drv);
|
||||
|
||||
/*Initialize the (dummy) input device driver*/
|
||||
static lv_indev_drv_t indev_drv;
|
||||
lv_indev_drv_init(&indev_drv);
|
||||
indev_drv.type = LV_INDEV_TYPE_POINTER;
|
||||
indev_drv.read_cb = my_touchpad_read;
|
||||
lv_indev_drv_register(&indev_drv);
|
||||
|
||||
lv_main();
|
||||
|
||||
data_queue = xQueueCreate(8, sizeof(queue_t));
|
||||
|
||||
xTaskCreatePinnedToCore(lvglTimerHandler,
|
||||
"lvglTimerHandler",
|
||||
4096,
|
||||
NULL,
|
||||
2,
|
||||
NULL,
|
||||
1);
|
||||
|
||||
xTaskCreatePinnedToCore(serialTask,
|
||||
"serialTask",
|
||||
2048,
|
||||
NULL,
|
||||
1,
|
||||
NULL,
|
||||
0);
|
||||
}
|
||||
|
||||
void lvglTimerHandler(void *pvParameters) {
|
||||
queue_t q;
|
||||
|
||||
while (1) {
|
||||
if (xQueueReceive(data_queue, &q, 0) == pdTRUE) {
|
||||
dataParser(q.data_string);
|
||||
loading_spinner_delete();
|
||||
}
|
||||
lv_timer_handler();
|
||||
vTaskDelay(5);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
|
||||
static lv_obj_t *loading_screen;
|
||||
static lv_obj_t *meter;
|
||||
static lv_obj_t *date_time_label;
|
||||
static lv_obj_t *cpu_label;
|
||||
static lv_obj_t *mem_label;
|
||||
static lv_obj_t *battery_label;
|
||||
static lv_meter_indicator_t *cpu_indic;
|
||||
static lv_meter_indicator_t *mem_indic;
|
||||
static lv_obj_t *process_list;
|
||||
static lv_anim_t a;
|
||||
|
||||
static void set_value(void *indic, int32_t v) {
|
||||
lv_meter_set_indicator_end_value(meter, (lv_meter_indicator_t *)indic, v);
|
||||
}
|
||||
|
||||
void lv_main(void) {
|
||||
meter = lv_meter_create(lv_scr_act());
|
||||
lv_obj_align(meter, LV_ALIGN_BOTTOM_LEFT, 0, 0);
|
||||
lv_obj_set_size(meter, 280, 280);
|
||||
lv_obj_remove_style(meter, NULL, LV_PART_INDICATOR);
|
||||
|
||||
lv_meter_scale_t *scale = lv_meter_add_scale(meter);
|
||||
lv_meter_set_scale_ticks(meter, scale, 11, 2, 10, lv_palette_main(LV_PALETTE_GREY));
|
||||
lv_meter_set_scale_major_ticks(meter, scale, 1, 2, 30, lv_color_hex3(0xeee), 15);
|
||||
lv_meter_set_scale_range(meter, scale, 0, 100, 270, 90);
|
||||
|
||||
cpu_indic = lv_meter_add_arc(meter, scale, 10, lv_palette_main(LV_PALETTE_RED), 0);
|
||||
mem_indic = lv_meter_add_arc(meter, scale, 10, lv_palette_main(LV_PALETTE_BLUE), -20);
|
||||
|
||||
lv_anim_init(&a);
|
||||
lv_anim_set_exec_cb(&a, set_value);
|
||||
|
||||
static lv_style_t style_red;
|
||||
lv_style_init(&style_red);
|
||||
lv_style_set_radius(&style_red, 5);
|
||||
lv_style_set_bg_opa(&style_red, LV_OPA_COVER);
|
||||
lv_style_set_bg_color(&style_red, lv_palette_main(LV_PALETTE_RED));
|
||||
lv_style_set_outline_width(&style_red, 2);
|
||||
lv_style_set_outline_color(&style_red, lv_palette_main(LV_PALETTE_RED));
|
||||
lv_style_set_outline_pad(&style_red, 4);
|
||||
|
||||
static lv_style_t style_blue;
|
||||
lv_style_init(&style_blue);
|
||||
lv_style_set_radius(&style_blue, 5);
|
||||
lv_style_set_bg_opa(&style_blue, LV_OPA_COVER);
|
||||
lv_style_set_bg_color(&style_blue, lv_palette_main(LV_PALETTE_BLUE));
|
||||
lv_style_set_outline_width(&style_blue, 2);
|
||||
lv_style_set_outline_color(&style_blue, lv_palette_main(LV_PALETTE_BLUE));
|
||||
lv_style_set_outline_pad(&style_blue, 4);
|
||||
|
||||
lv_obj_t *red_obj = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(red_obj, 10, 10);
|
||||
lv_obj_add_style(red_obj, &style_red, 0);
|
||||
lv_obj_align(red_obj, LV_ALIGN_CENTER, -60, 80);
|
||||
|
||||
cpu_label = lv_label_create(lv_scr_act());
|
||||
lv_obj_set_width(cpu_label, 200);
|
||||
lv_label_set_text(cpu_label, "CPU Usage: 0%");
|
||||
lv_obj_align_to(cpu_label, red_obj, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
|
||||
|
||||
lv_obj_t *blue_obj = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(blue_obj, 10, 10);
|
||||
lv_obj_add_style(blue_obj, &style_blue, 0);
|
||||
lv_obj_align(blue_obj, LV_ALIGN_CENTER, -60, 110);
|
||||
|
||||
mem_label = lv_label_create(lv_scr_act());
|
||||
lv_obj_set_width(mem_label, 200);
|
||||
lv_label_set_text(mem_label, "MEM Usage: 0%");
|
||||
lv_obj_align_to(mem_label, blue_obj, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
|
||||
|
||||
date_time_label = lv_label_create(lv_scr_act());
|
||||
lv_obj_set_style_text_align(date_time_label, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
lv_obj_align(date_time_label, LV_ALIGN_TOP_RIGHT, -10, 0);
|
||||
lv_label_set_text(date_time_label, "Mon 1:25 PM");
|
||||
|
||||
battery_label = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(battery_label, LV_SYMBOL_CHARGE " 0%");
|
||||
lv_obj_set_style_text_align(battery_label, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
lv_obj_align(battery_label, LV_ALIGN_TOP_RIGHT, -130, 0);
|
||||
|
||||
process_list = lv_list_create(lv_scr_act());
|
||||
lv_obj_set_size(process_list, 240, 190);
|
||||
lv_obj_align(process_list, LV_ALIGN_TOP_RIGHT, 0, 30);
|
||||
lv_obj_set_style_pad_row(process_list, -10, 0);
|
||||
lv_obj_set_style_pad_hor(process_list, -6, 0);
|
||||
|
||||
loading_spinner_make();
|
||||
}
|
||||
|
||||
void loading_spinner_make() {
|
||||
|
||||
loading_screen = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(loading_screen, 480, 320);
|
||||
lv_obj_center(loading_screen);
|
||||
|
||||
lv_obj_t *loading_spinner = lv_spinner_create(loading_screen, 1000, 60);
|
||||
lv_obj_set_size(loading_spinner, 240, 240);
|
||||
lv_obj_center(loading_spinner);
|
||||
|
||||
lv_obj_t *loading_label = lv_label_create(loading_spinner);
|
||||
lv_label_set_text(loading_label, "Waiting for Data...");
|
||||
lv_obj_center(loading_label);
|
||||
}
|
||||
|
||||
void loading_spinner_delete() {
|
||||
if (loading_screen != NULL) {
|
||||
lv_obj_del(loading_screen);
|
||||
loading_screen = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void serialTask(void *pvParameters) {
|
||||
queue_t q;
|
||||
while (1) {
|
||||
if (Serial.available()) {
|
||||
String input = Serial.readStringUntil('\n');
|
||||
input.trim();
|
||||
q.data_string = input;
|
||||
xQueueSend(data_queue, &q, portMAX_DELAY);
|
||||
}
|
||||
vTaskDelay(100);
|
||||
}
|
||||
}
|
||||
|
||||
void dataParser(String jsonData) {
|
||||
Serial.println(jsonData);
|
||||
|
||||
if (jsonData.length() < 10)
|
||||
return;
|
||||
|
||||
DeserializationError error = deserializeJson(doc, jsonData);
|
||||
|
||||
if (error) {
|
||||
Serial.print(F("deserializeJson() failed: "));
|
||||
Serial.println(error.f_str());
|
||||
return;
|
||||
}
|
||||
|
||||
long local_time = doc["local_time"];
|
||||
long utc_offset = doc["utc_offset"];
|
||||
update_time(local_time, utc_offset);
|
||||
|
||||
float cpu_percent_total = doc["cpu_percent_total"];
|
||||
float mem_percent = doc["mem_percent"];
|
||||
update_cpu_mem_usage(cpu_percent_total, mem_percent);
|
||||
|
||||
float battery_percent = doc["battery_percent"];
|
||||
bool power_plugged = doc["power_plugged"];
|
||||
update_battery(battery_percent, power_plugged);
|
||||
|
||||
update_process(doc["cpu_top5_process"]);
|
||||
}
|
||||
|
||||
void update_time(long local_time, long utc_offset) {
|
||||
rtc.setTime(local_time);
|
||||
rtc.offset = utc_offset;
|
||||
lv_label_set_text(date_time_label, rtc.getTime("%a %I:%M %p").c_str());
|
||||
}
|
||||
|
||||
void update_cpu_mem_usage(float cpu_percent_total, float mem_percent) {
|
||||
String cpu_text = String("CPU Usage: ") + cpu_percent_total + "%";
|
||||
lv_label_set_text(cpu_label, cpu_text.c_str());
|
||||
|
||||
lv_anim_set_var(&a, cpu_indic);
|
||||
lv_anim_set_time(&a, 500);
|
||||
lv_anim_set_values(&a, prev_cpu_usage, int(cpu_percent_total));
|
||||
lv_anim_start(&a);
|
||||
prev_cpu_usage = int(cpu_percent_total);
|
||||
|
||||
String mem_text = String("MEM Usage: ") + mem_percent + "%";
|
||||
lv_label_set_text(mem_label, mem_text.c_str());
|
||||
|
||||
lv_anim_set_var(&a, mem_indic);
|
||||
lv_anim_set_time(&a, 500);
|
||||
lv_anim_set_values(&a, prev_mem_usage, int(mem_percent));
|
||||
lv_anim_start(&a);
|
||||
prev_mem_usage = int(mem_percent);
|
||||
}
|
||||
|
||||
void update_battery(float battery_percent, bool power_plugged) {
|
||||
char buffer[16];
|
||||
if (power_plugged) {
|
||||
sprintf(buffer, "\xEF\x83\xA7 %d %%", int(battery_percent));
|
||||
} else {
|
||||
if (battery_percent >= 95) {
|
||||
sprintf(buffer, "\xEF\x89\x80 %d %%", int(battery_percent));
|
||||
} else if (battery_percent >= 75 && battery_percent < 95) {
|
||||
sprintf(buffer, "\xEF\x89\x81 %d %%", int(battery_percent));
|
||||
} else if (battery_percent >= 50 && battery_percent < 75) {
|
||||
sprintf(buffer, "\xEF\x89\x82 %d %%", int(battery_percent));
|
||||
} else if (battery_percent >= 20 && battery_percent < 50) {
|
||||
sprintf(buffer, "\xEF\x89\x83 %d %%", int(battery_percent));
|
||||
} else {
|
||||
sprintf(buffer, "\xEF\x89\x84 %d %%", int(battery_percent));
|
||||
}
|
||||
}
|
||||
|
||||
lv_label_set_text(battery_label, buffer);
|
||||
}
|
||||
|
||||
void update_process(JsonArray processes) {
|
||||
int arraySize = processes.size();
|
||||
if (arraySize > 0) {
|
||||
lv_obj_clean(process_list);
|
||||
for (int i = 0; i < arraySize; i++) {
|
||||
lv_obj_t *list_item_button = lv_list_add_btn(process_list, LV_SYMBOL_WARNING, processes[i]);
|
||||
lv_obj_t *child = lv_obj_get_child(list_item_button, 1);
|
||||
lv_label_set_long_mode(child, LV_LABEL_LONG_WRAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import threading
|
||||
import time
|
||||
|
||||
class RepeatedTimer(object):
|
||||
def __init__(self, interval, function, *args, **kwargs):
|
||||
self._timer = None
|
||||
self.interval = interval
|
||||
self.function = function
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.is_running = False
|
||||
self.next_call = time.time()
|
||||
self.start()
|
||||
|
||||
def _run(self):
|
||||
self.is_running = False
|
||||
self.start()
|
||||
self.function(*self.args, **self.kwargs)
|
||||
|
||||
def start(self):
|
||||
if not self.is_running:
|
||||
self.next_call += self.interval
|
||||
self._timer = threading.Timer(self.next_call - time.time(), self._run)
|
||||
self._timer.start()
|
||||
self.is_running = True
|
||||
|
||||
def stop(self):
|
||||
self._timer.cancel()
|
||||
self.is_running = False
|
||||
@@ -0,0 +1,105 @@
|
||||
'''
|
||||
Make your own System Monitor with ESP32 + LVGL 8
|
||||
For More Information: https://youtu.be/gliwNg25fLE
|
||||
Created by Eric N. (ThatProject)
|
||||
'''
|
||||
from serial import SerialTimeoutException, SerialException
|
||||
|
||||
from RepeatedTimer import RepeatedTimer
|
||||
import sys
|
||||
import psutil
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
import argparse
|
||||
import serial
|
||||
|
||||
serial_baudrate = 115200
|
||||
ser = None
|
||||
update_interval = 2
|
||||
|
||||
def write_to_serial(str_json):
|
||||
try:
|
||||
ser.write(str_json.encode())
|
||||
except SerialTimeoutException:
|
||||
print('[Error] Timeout when writing to the serial.')
|
||||
except SerialException:
|
||||
print('[Error] An unexpected exception occurred while writing to the serial.')
|
||||
|
||||
def process_list(delay=1):
|
||||
proccesses = list(psutil.process_iter())
|
||||
procs = []
|
||||
try:
|
||||
for proc in proccesses:
|
||||
proc.cpu_percent(None)
|
||||
sys.stdout.flush()
|
||||
time.sleep(delay)
|
||||
|
||||
for proc in proccesses:
|
||||
percent = proc.cpu_percent(None)
|
||||
if percent:
|
||||
procs.append((percent, proc.name()))
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
|
||||
res = sorted(procs, key=lambda x: x[0], reverse=True)[:5]
|
||||
return res
|
||||
|
||||
def get_system_info():
|
||||
data = {}
|
||||
# Get System LocalTime
|
||||
ts = time.time()
|
||||
utc_offset = (datetime.datetime.fromtimestamp(ts) -
|
||||
datetime.datetime.utcfromtimestamp(ts)).total_seconds()
|
||||
data['utc_offset'] = int(utc_offset)
|
||||
|
||||
presentDate = datetime.datetime.now()
|
||||
unix_timestamp = datetime.datetime.timestamp(presentDate)
|
||||
data['local_time'] = int(unix_timestamp)
|
||||
|
||||
# CPU Usage
|
||||
cpu_percent_cores = psutil.cpu_percent(interval=2, percpu=True)
|
||||
avg = sum(cpu_percent_cores) / len(cpu_percent_cores)
|
||||
data['cpu_percent_total'] = round(avg, 2)
|
||||
|
||||
# MEM Usage
|
||||
data['mem_percent'] = psutil.virtual_memory().percent
|
||||
|
||||
# Battery Info
|
||||
battery = psutil.sensors_battery()
|
||||
data['battery_percent'] = battery.percent if battery.percent is not None else 100
|
||||
data['power_plugged'] = battery.power_plugged
|
||||
|
||||
# Top 5 CPU Usage Processes
|
||||
data['cpu_top5_process'] = ['%.1f%% %s' % x for x in process_list()]
|
||||
write_to_serial(json.dumps(data))
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='<Need to set your Serial Port, BaudRate and Interval to fetch system info.> (sudo permission required on Mac)', usage='python main.py --port /dev/cu.SLAB_USBtoUART')
|
||||
parser.add_argument('-p', '--port', help='Set the Connected USB Port')
|
||||
parser.add_argument('-b', '--baud', help='Set Serial Baud Rate(default=115200)', type=int, default=115200)
|
||||
parser.add_argument('-i', '--interval', help='Set Update Interval in Second(default=2)', type=int, default=2)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.port is None:
|
||||
print('usage: sudo python main.py --port /dev/cu.SLAB_USBtoUART')
|
||||
sys.exit()
|
||||
|
||||
if args.baud is not None:
|
||||
serial_baudrate = args.baud
|
||||
|
||||
if args.interval is not None and args.interval > 1:
|
||||
update_interval = args.interval
|
||||
|
||||
ser = serial.Serial(args.port, serial_baudrate)
|
||||
print(ser)
|
||||
|
||||
if ser is not None and ser.is_open:
|
||||
get_system_info()
|
||||
rt = RepeatedTimer(update_interval, get_system_info)
|
||||
else:
|
||||
print('[Error] Check your Serial Port.')
|
||||
sys.exit()
|
||||
@@ -0,0 +1,2 @@
|
||||
psutil
|
||||
pyserial
|
||||
Reference in New Issue
Block a user