diff --git a/ESP32_LVGL/LVGL8/5_System_Monitor/ESP32ForSysMonitor/ESP32ForSysMonitor.ino b/ESP32_LVGL/LVGL8/5_System_Monitor/ESP32ForSysMonitor/ESP32ForSysMonitor.ino new file mode 100644 index 0000000..a74c47d --- /dev/null +++ b/ESP32_LVGL/LVGL8/5_System_Monitor/ESP32ForSysMonitor/ESP32ForSysMonitor.ino @@ -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 //https://arduinojson.org/v6/doc/installation/ +#include //https://github.com/fbiego/ESP32Time +#include //https://github.com/lvgl/lvgl + +#define LGFX_USE_V1 //https://github.com/lovyan03/LovyanGFX +#include +#include //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); + } + } +} \ No newline at end of file diff --git a/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/RepeatedTimer.py b/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/RepeatedTimer.py new file mode 100644 index 0000000..237c24b --- /dev/null +++ b/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/RepeatedTimer.py @@ -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 \ No newline at end of file diff --git a/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/main.py b/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/main.py new file mode 100644 index 0000000..021c840 --- /dev/null +++ b/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/main.py @@ -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=' (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() \ No newline at end of file diff --git a/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/requirements.txt b/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/requirements.txt new file mode 100644 index 0000000..edd382c --- /dev/null +++ b/ESP32_LVGL/LVGL8/5_System_Monitor/PythonScriptForSysMonitor/requirements.txt @@ -0,0 +1,2 @@ +psutil +pyserial \ No newline at end of file