├── .DS_Store ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── boards └── lilygo-t-display-s3.json ├── images ├── IMG_0277.jpg └── IMG_0279.jpeg ├── include ├── README └── pin_config.h ├── lib └── README ├── metrics-collector ├── collector.py ├── metrics-collector.service └── requirments.txt ├── platformio.ini ├── src └── main.cpp └── test └── README /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denmark111/esp32ServerMon/ca7569a4f1c2fdb1addeb29a05f087dd17753c5b/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Moon, S.H. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32ServerMon 2 | Offline server monitoring tool 3 | Any fixes or improvements are always welcome!! 4 | 5 | ## Working examples 6 | 7 | 8 | 9 | ## Available metrics on screen 10 | - Current time 11 | - Wifi address 12 | - CPU usage in % 13 | - CPU usage history graph 14 | - CPU1/2 Temperature 15 | - Memory usage in % 16 | - Load Average (15m) 17 | - Disk R/W IOPS (Cycles between read and write with a button) 18 | - Disk health (green dots in top right corner of disk grid) 19 | - Overall health **(Currently not implemented)** 20 | 21 | ## Tested environment 22 | 1. Hardware 23 | - Lilygo T-Display S3 (Non-touch version) 24 | - Dell R740xd 25 | 2. Software 26 | - PlatformIO w/ VSCode 27 | - ProxmoxVE 8 28 | - Python3.11 29 | 30 | ## Usage 31 | #### First, upload the firmware to lilygo t-display s3 32 | 1. Install PlatformIO vscode plugin (install vscode first if not installed) 33 | 2. Clone this repo 34 | 3. Open repo directory in vscode 35 | - platformio should setup environment automatically 36 | - if not, check if platformio.ini file is visiable in vscode explorer. 37 | 4. Connect lilygo t-display s3 via USB 38 | 5. Start compile & upload 39 | - If you successfully installed PlatformIO, right arrow button should be visiable on the bottom left corner 40 | 6. Reset lilygo t-display s3 and check if default UI is loaded. 41 | 42 | #### Next, Setup collector in Proxmox 43 | 1. Make sure python3.11 is installed 44 | - python3.11 is installed by default in Proxmox 8 45 | 2. Copy collector script 46 | ``` bash 47 | cd esp32servermon 48 | cp -r metrics-collector /opt/ 49 | ``` 50 | 3. Install python requirments 51 | ``` python 52 | cd /opt/metrics-collector 53 | python3 -m venv venv 54 | venv/bin/pip install -r requirments.txt 55 | ``` 56 | 4. Copy systemd service file 57 | 5. Config collector script 58 | - Change DEVICE_NAME 59 | - Change BAUD_RATE **(Only if necessary)** 60 | - Change CHECK_INTERVAL **(Only if necessary)** 61 | ``` python 62 | DEVICE_NAME = "/dev/ttyACM0" 63 | BAUD_RATE = 115200 64 | CHECK_INTERVAL = 30 65 | ``` 66 | 6. Run collector 67 | ``` bash 68 | systemctl daemon-reload 69 | systemctl start metrics-collector 70 | ``` 71 | 72 | #### Finally, check if all metrics are visiable in lilygo t-display s3 73 | 1. Press right button to check if diskIO cycles between read and write. 74 | 2. Check everything is working. 75 | 76 | -------------------------------------------------------------------------------- /boards/lilygo-t-display-s3.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "arduino": { 4 | "ldscript": "esp32s3_out.ld", 5 | "memory_type": "qio_opi", 6 | "partitions": "default_16MB.csv" 7 | }, 8 | "core": "esp32", 9 | "extra_flags": [ 10 | "-DBOARD_HAS_PSRAM" 11 | ], 12 | "f_cpu": "240000000L", 13 | "f_flash": "80000000L", 14 | "flash_mode": "qio", 15 | "hwids": [ 16 | [ 17 | "0X303A", 18 | "0x1001" 19 | ] 20 | ], 21 | "mcu": "esp32s3", 22 | "variant": "esp32s3" 23 | }, 24 | "connectivity": [ 25 | "wifi", 26 | "bluetooth" 27 | ], 28 | "debug": { 29 | "openocd_target": "esp32s3-builtin.cfg" 30 | }, 31 | "frameworks": [ 32 | "arduino", 33 | "espidf" 34 | ], 35 | "name": "T-DisplayS3", 36 | "upload": { 37 | "flash_size": "16MB", 38 | "maximum_ram_size": 327680, 39 | "maximum_size": 16777216, 40 | "require_upload_port": true, 41 | "speed": 921600 42 | }, 43 | "url": "https://www.lilygo.cc/products/t-display-s3", 44 | "vendor": "LILYGO" 45 | } -------------------------------------------------------------------------------- /images/IMG_0277.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denmark111/esp32ServerMon/ca7569a4f1c2fdb1addeb29a05f087dd17753c5b/images/IMG_0277.jpg -------------------------------------------------------------------------------- /images/IMG_0279.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denmark111/esp32ServerMon/ca7569a4f1c2fdb1addeb29a05f087dd17753c5b/images/IMG_0279.jpeg -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /include/pin_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | /*ESP32S3*/ 5 | #define PIN_LCD_BL 38 6 | 7 | #define PIN_LCD_D0 39 8 | #define PIN_LCD_D1 40 9 | #define PIN_LCD_D2 41 10 | #define PIN_LCD_D3 42 11 | #define PIN_LCD_D4 45 12 | #define PIN_LCD_D5 46 13 | #define PIN_LCD_D6 47 14 | #define PIN_LCD_D7 48 15 | 16 | #define PIN_POWER_ON 15 17 | 18 | #define PIN_LCD_RES 5 19 | #define PIN_LCD_CS 6 20 | #define PIN_LCD_DC 7 21 | #define PIN_LCD_WR 8 22 | #define PIN_LCD_RD 9 23 | 24 | #define PIN_BUTTON_1 0 25 | #define PIN_BUTTON_2 14 26 | #define PIN_BAT_VOLT 4 27 | 28 | #define PIN_IIC_SCL 17 29 | #define PIN_IIC_SDA 18 30 | 31 | #define PIN_TOUCH_INT 16 32 | #define PIN_TOUCH_RES 21 -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /metrics-collector/collector.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | import serial 3 | from subprocess import PIPE, run 4 | import time 5 | 6 | 7 | # USB device name, your device name could be different 8 | DEVICE_NAME = "/dev/ttyACM0" 9 | BAUD_RATE = 115200 10 | # Interval between metrics sent to esp32 11 | CHECK_INTERVAL = 30 12 | 13 | 14 | def sendSerialData(data, device, baudrate): 15 | dev = serial.Serial( 16 | port=device, 17 | baudrate=baudrate, 18 | timeout=1 19 | ) 20 | 21 | dev.write(bytes(f"[{data}]", "utf-8")) 22 | 23 | 24 | def checkHealth(): 25 | # TODO 26 | result = 200 27 | 28 | return f"OVERALL:{result}" 29 | 30 | 31 | def getCpuMetrics(): 32 | cpuPercent = psutil.cpu_percent(interval=1) 33 | loadAvg = psutil.getloadavg()[2] 34 | 35 | return f"CPU:{cpuPercent},LAVG:{loadAvg}" 36 | 37 | 38 | def getCpuTempMetrics(): 39 | temp = psutil.sensors_temperatures() 40 | cpuTempRes = {} 41 | for elem in temp['coretemp']: 42 | if elem.label == "Package id 0": 43 | cpuTempRes[0] = elem.current 44 | if elem.label == "Package id 1": 45 | cpuTempRes[1] = elem.current 46 | 47 | output = "CPUTEMP:" 48 | for cpuid, cputemp in cpuTempRes.items(): 49 | output += f"{cputemp}|" 50 | 51 | return output 52 | 53 | 54 | def getDiskMetrics(): 55 | slotNumIndex = 5 56 | queryDiskCmd = "find /dev/disk/by-path -type l -ls | grep -e scsi -e ata | grep -v sr | grep -v usb | grep -v part" 57 | diskInfo = run(queryDiskCmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, shell=True).stdout.splitlines() 58 | 59 | identDisk = {} 60 | for di in diskInfo: 61 | if di is not None: 62 | if di.split(":")[slotNumIndex] is not None: 63 | identDisk[di.split("/")[-1]] = di.split(":")[slotNumIndex] 64 | 65 | diskUsage = {} 66 | pDiskStat = psutil.disk_io_counters(perdisk=True) 67 | time.sleep(1) 68 | diskStat = psutil.disk_io_counters(perdisk=True) 69 | for devName, slotNum in identDisk.items(): 70 | 71 | querySmartCmd = f"smartctl --attributes --log=selftest --quietmode=errorsonly /dev/{devName}" 72 | smartOutput = run(querySmartCmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, shell=True).stdout.splitlines() 73 | if smartOutput: 74 | smtResult = 2 75 | else: 76 | smtResult = 1 77 | 78 | pRead = pDiskStat[devName].read_count 79 | pWrite = pDiskStat[devName].write_count 80 | cRead = diskStat[devName].read_count 81 | cWrite = diskStat[devName].write_count 82 | diskUsage[slotNum] = (cRead - pRead, cWrite - pWrite, smtResult) 83 | 84 | output = "DISKIO:" 85 | for n, s in diskUsage.items(): 86 | output += f"{n}-{s[0]}-{s[1]}-{s[2]}-|" 87 | 88 | return output 89 | 90 | 91 | def getMemoryMetrics(): 92 | memoryUsage = psutil.virtual_memory().percent 93 | 94 | return f"MEM:{memoryUsage}" 95 | 96 | 97 | def getPowerUsage(): 98 | pass 99 | 100 | 101 | def main(): 102 | payload = f",{getCpuMetrics()},{getMemoryMetrics()},{getDiskMetrics()},{getCpuTempMetrics()},{checkHealth()}\n" 103 | 104 | ## Uncomment to debug 105 | #print(getCpuMetrics()) 106 | #print(getMemoryMetrics()) 107 | #print(getDiskMetrics()) 108 | #print(getCpuTempMetrics()) 109 | #print(payload) 110 | 111 | sendSerialData(payload, DEVICE_NAME, BAUD_RATE) 112 | 113 | 114 | if __name__ in "__main__": 115 | while(True): 116 | main() 117 | time.sleep(CHECK_INTERVAL) -------------------------------------------------------------------------------- /metrics-collector/metrics-collector.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ESP32 Metrics Collector 3 | After=network.target 4 | 5 | [Service] 6 | Type=idle 7 | Restart=on-failure 8 | User=root 9 | ExecStart=/opt/metrics-collector/venv/bin/python3 /opt/metrics-collector/collector.py 10 | 11 | [Install] 12 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /metrics-collector/requirments.txt: -------------------------------------------------------------------------------- 1 | psutil==6.0.0 2 | pyserial==3.5 -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | src_dir = ./src 13 | boards_dir = ./boards 14 | 15 | [env] 16 | platform = espressif32 17 | board = lilygo-t-display-s3 18 | framework = arduino 19 | debug_tool = esp-builtin 20 | upload_protocol = esptool 21 | build_flags = 22 | -DLV_LVGL_H_INCLUDE_SIMPLE 23 | -DARDUINO_USB_CDC_ON_BOOT=1 24 | -DDISABLE_ALL_LIBRARY_WARNINGS 25 | -DARDUINO_USB_MODE=1 26 | -DTOUCH_MODULES_CST_MUTUAL 27 | 28 | [env:lilygo-t-display-s3] 29 | build_flags = 30 | ${env.build_flags} 31 | -D USER_SETUP_LOADED=1 32 | -include $PROJECT_LIBDEPS_DIR/$PIOENV/TFT_eSPI/User_Setups/Setup206_LilyGo_T_Display_S3.h 33 | lib_deps = 34 | bodmer/TFT_eSPI@^2.5.43 35 | mathertel/OneButton@^2.5.0 36 | lib_ignore = 37 | GFX Library for Arduino 38 | arduino-nofrendo 39 | Adafruit MPR121 40 | DabbleESP32 41 | PCF8575 library 42 | PCA95x5 -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include // Graphics and font library for ST7735 driver chip 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | #include "pin_config.h" 8 | #include "time.h" 9 | 10 | 11 | #define SCREEN_WIDTH 320 12 | #define SCREEN_HEIGHT 170 13 | #define PIN_INPUT_BUTTON 14 14 | 15 | 16 | TaskHandle_t drawGuiUpdate; 17 | 18 | 19 | TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h 20 | TFT_eSprite sprite = TFT_eSprite(&tft); 21 | 22 | const char* ssid = "WIFI SSID"; 23 | const char* password = "WIFI PASSWORD"; 24 | const char* ntpServer = "time.google.com"; 25 | const long gmtOffset = 3600; 26 | const int daylightOffset = 0; 27 | const int wifiTimeout = 10000; 28 | 29 | const int fontSize = 15; 30 | 31 | // Splash screen config 32 | String productName = "ServerMon"; 33 | String productVer = "v0.1"; 34 | String productType = "For Dell R740xd"; 35 | 36 | // Graph data 37 | const int cpuUsagePoints = 100; 38 | int cpuUsage[cpuUsagePoints] = {0, }; 39 | 40 | // Metrics gui config 41 | const int cpu1TempPosX = 6; 42 | const int cpu1TempPosY = 43; 43 | const int cpu2TempPosX = 6; 44 | const int cpu2TempPosY = 58; 45 | const int cpuUsePosX = 6; 46 | const int cpuUsePosY = 73; 47 | const int statusPosX = 160; 48 | const int statusPosY = 43; 49 | const int memoryPosX = 160; 50 | const int memoryPosY = 58; 51 | const int loadAvgPosX = 160; 52 | const int loadAvgPosY = 73; 53 | const int metricsValueSpacing = 104; 54 | 55 | // Disk gui config 56 | const int diskTextSize = 8; 57 | const int diskPosX = 0; 58 | const int diskPosY = 110; 59 | const int totalDiskCount = 12; 60 | const int diskRow = 3; 61 | const int cellSpacing = 2; 62 | const int cellWidth = 40; 63 | const int cellHeight = 20; 64 | // 1 = ReadIO, 2 = WriteIO 65 | int diskUsageRW = 1; 66 | 67 | // Cpu graph gui config 68 | const int graphPosX = 160; 69 | const int graphPosY = 110; 70 | const int graphHeight = 60; 71 | const int graphWidth = 160; 72 | const int legendSize = 22; 73 | 74 | // Status modelbox config 75 | const int mbPosX = 220; 76 | const int mbPosY = 4; 77 | const String mbName = "Dell R740xd"; 78 | 79 | char attributeMarker = ':'; 80 | char valueMarker = ','; 81 | char valueSpace = '|'; 82 | String buff = ""; 83 | String property = ""; 84 | String value = ""; 85 | 86 | int ntpUpdateTimer = 0; 87 | int graphUpdateTimer = 0; 88 | 89 | int overallStatus = -1; 90 | float cpuPercentage = -1; 91 | float loadAvg15 = -1; 92 | float memPercentage = -1; 93 | String diskUsage = ""; 94 | String cpuTemp = ""; 95 | typedef struct cpuTemp_ { 96 | int cpuNum = 0; 97 | float temperature = -1; 98 | } cpuTemp_st; 99 | typedef struct diskUsage_ { 100 | int diskRead = -1; 101 | int diskWrite = -1; 102 | int diskStatus = -1; 103 | } diskUsage_st; 104 | 105 | // Button config 106 | OneButton button(PIN_INPUT_BUTTON, true); 107 | 108 | 109 | void drawGraphDots(int offsetX, int offsetY, int width, int height) { 110 | // Draw graph dots 111 | int dotPerRow = 5; 112 | int dotPerCol = 3; 113 | int dotSpacingX = width / (dotPerRow + 1); 114 | int dotSpacingY = height / (dotPerCol + 1); 115 | int posX = offsetX + (dotSpacingX / 2); 116 | int posY = offsetY + dotSpacingY; 117 | for (int i=0; i= cpuUsagePoints) { 145 | cpuUsage[i] = 0; 146 | } 147 | else { 148 | cpuUsage[i] = cpuUsage[i+1]; 149 | } 150 | } 151 | if (data > 100) { 152 | cpuUsage[cpuUsagePoints - 1] = 100; 153 | } 154 | else if (data < 0) { 155 | cpuUsage[cpuUsagePoints - 1] = 0; 156 | } 157 | else { 158 | cpuUsage[cpuUsagePoints - 1] = data; 159 | } 160 | 161 | float interval = width / (float) cpuUsagePoints; 162 | float prevPosX, nextPosX; 163 | int prevPosY, nextPosY; 164 | prevPosX = offsetX; 165 | prevPosY = offsetY - cpuUsage[0]; 166 | for(int j=0; j 45) { 294 | color = TFT_YELLOW; 295 | } 296 | if (data > 55) { 297 | color = TFT_ORANGE; 298 | } 299 | if (data > 60) { 300 | color = TFT_RED; 301 | } 302 | sprite.fillCircle(indicatorPosX, offsetY, 3, color); 303 | break; 304 | 305 | case 2: 306 | if (data <= 70) { 307 | color = TFT_GREEN; 308 | } 309 | if (data > 70) { 310 | color = TFT_YELLOW; 311 | } 312 | if (data > 80) { 313 | color = TFT_ORANGE; 314 | } 315 | if (data > 90) { 316 | color = TFT_RED; 317 | } 318 | sprite.fillCircle(indicatorPosX, offsetY, 3, color); 319 | break; 320 | 321 | case 3: 322 | if (data <= 70) { 323 | color = TFT_GREEN; 324 | } 325 | if (data > 70) { 326 | color = TFT_YELLOW; 327 | } 328 | if (data > 80) { 329 | color = TFT_ORANGE; 330 | } 331 | if (data > 90) { 332 | color = TFT_RED; 333 | } 334 | sprite.fillCircle(indicatorPosX, offsetY, 3, color); 335 | break; 336 | 337 | case 4: 338 | if (data <= 12) { 339 | color = TFT_GREEN; 340 | } 341 | if (data > 14) { 342 | color = TFT_YELLOW; 343 | } 344 | if (data > 17) { 345 | color = TFT_ORANGE; 346 | } 347 | if (data > 20) { 348 | color = TFT_RED; 349 | } 350 | sprite.fillCircle(indicatorPosX, offsetY, 3, color); 351 | break; 352 | 353 | case 5: 354 | if (data == 200) { 355 | textBuff = " OK "; 356 | color = TFT_GREEN; 357 | } 358 | else if (data == 400) { 359 | textBuff = " FAULT "; 360 | color = TFT_RED; 361 | } 362 | else { 363 | textBuff = " "; 364 | color = TFT_PURPLE; 365 | } 366 | sprite.fillRect(offsetX - 5, offsetY - (fontSize / 2), textBuff.length() * fontWidth + 10, fontSize - 2, TFT_BLACK); 367 | sprite.drawString(textBuff, offsetX, offsetY); 368 | sprite.drawRect(offsetX - 5, offsetY - (fontSize / 2), textBuff.length() * fontWidth + 10, fontSize - 2, color); 369 | break; 370 | 371 | default: 372 | break; 373 | } 374 | } 375 | 376 | 377 | void drawNtpTime() { 378 | char textBuff[9]; 379 | struct tm timeinfo; 380 | 381 | char hour[3]; 382 | char minute[3]; 383 | char second[3]; 384 | 385 | if(getLocalTime(& timeinfo)) { 386 | strftime(hour, sizeof(hour), "%H", & timeinfo); 387 | strftime(minute, sizeof(minute), "%M", & timeinfo); 388 | strftime(second, sizeof(second), "%S", & timeinfo); 389 | } 390 | 391 | sprintf(textBuff, "%s:%s:%s", String(hour), String(minute), String(second)); 392 | sprite.drawString(textBuff, 120, 6); 393 | } 394 | 395 | 396 | void drawInitScreen() { 397 | String textBuff = " "; 398 | 399 | tft.setTextSize(fontSize); 400 | 401 | // Config current time 402 | configTzTime("KST-9", ntpServer); 403 | 404 | // Draw NTP TIme 405 | textBuff = "Time(Asia/Seoul) :"; 406 | sprite.drawString(textBuff, 6, 6); 407 | 408 | // Draw modelbox 409 | sprite.drawString(mbName, mbPosX + 8, mbPosY + 10); 410 | sprite.drawRoundRect(mbPosX, mbPosY, 80, 20, 4, TFT_DARKGREY); 411 | 412 | // Draw current IP 413 | textBuff = "ESP32 IP(WiFi) : "; 414 | char ipChar[16]; 415 | IPAddress ip = WiFi.localIP(); 416 | sprintf(ipChar, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); 417 | String ipStr(ipChar); //convert char array to string here. 418 | sprite.drawString(textBuff + ipStr, 6, 21); 419 | 420 | // Draw ping status 421 | // textBuff = "Ping : "; 422 | // sprite.drawString(textBuff, 100, 30); 423 | 424 | // Draw current CPU Temperature 425 | textBuff = "CPU1 Temp(C) : "; 426 | sprite.drawString(textBuff, cpu1TempPosX, cpu1TempPosY); 427 | drawMetricText(1, -1, cpu1TempPosX + metricsValueSpacing, cpu1TempPosY); 428 | textBuff = "CPU2 Temp(C) : "; 429 | sprite.drawString(textBuff, cpu2TempPosX, cpu2TempPosY); 430 | drawMetricText(1, -1, cpu2TempPosX + metricsValueSpacing, cpu2TempPosY); 431 | 432 | // Draw total CPU/RAM usage in percentage 433 | textBuff = "CPU Usage(%) : "; 434 | sprite.drawString(textBuff, cpuUsePosX, cpuUsePosY); 435 | drawMetricText(2, -1, cpuUsePosX + metricsValueSpacing, cpuUsePosY); 436 | textBuff = "RAM Usage(%) : "; 437 | sprite.drawString(textBuff, memoryPosX, memoryPosY); 438 | drawMetricText(3, -1, memoryPosX + metricsValueSpacing, memoryPosY); 439 | 440 | // Draw Load Average 441 | textBuff = "Load Avg 15m : "; 442 | sprite.drawString(textBuff, loadAvgPosX, loadAvgPosY); 443 | drawMetricText(4, -1, loadAvgPosX + metricsValueSpacing, loadAvgPosY); 444 | 445 | // Draw Overall Status 446 | textBuff = "Overall Status : "; 447 | sprite.drawString(textBuff, statusPosX, statusPosY); 448 | drawMetricText(5, -1, statusPosX + metricsValueSpacing, statusPosY); 449 | 450 | drawDiskGrid(diskPosX, diskPosY); 451 | drawGraph(graphPosX, graphPosY); 452 | // drawDiskStatusIndicator(0, 0); 453 | 454 | sprite.pushSprite(0, 0); 455 | } 456 | 457 | 458 | void parseCpuTemp(String data, cpuTemp_st *output) { 459 | int r=0,t=0; 460 | 461 | for(int i=0;i 1) 466 | { 467 | output[t].cpuNum = t; 468 | output[t].temperature = data.substring(r,i).toFloat(); 469 | t++; 470 | } 471 | r = (i+1); 472 | } 473 | } 474 | } 475 | 476 | 477 | void parseDiskUsage(String data, diskUsage_st *output) { 478 | String temp[totalDiskCount]; 479 | 480 | int idx = 0; 481 | int startIdx = 0; 482 | for(int i=0; i graphUpdateTimer + 10000) { 532 | // Draw actual graph 533 | drawAndSaveCpuGraph(cpuPercentage, graphPosX, graphPosY + graphHeight, graphWidth, graphHeight, true); 534 | graphUpdateTimer = millis(); 535 | } 536 | 537 | cpuTemp_st cpuTempParsed[2]; 538 | parseCpuTemp(cpuTemp, cpuTempParsed); 539 | 540 | drawMetricText(1, cpuTempParsed[0].temperature, cpu1TempPosX + metricsValueSpacing, cpu1TempPosY); 541 | drawMetricText(1, cpuTempParsed[1].temperature, cpu2TempPosX + metricsValueSpacing, cpu2TempPosY); 542 | drawMetricText(2, cpuPercentage, cpuUsePosX + metricsValueSpacing, cpuUsePosY); 543 | drawMetricText(3, memPercentage, memoryPosX + metricsValueSpacing, memoryPosY); 544 | drawMetricText(4, loadAvg15, loadAvgPosX + metricsValueSpacing, loadAvgPosY); 545 | drawMetricText(5, overallStatus, statusPosX + metricsValueSpacing, statusPosY); 546 | 547 | delay(50); 548 | } 549 | } 550 | 551 | 552 | void changeDiskIoStat() { 553 | if (diskUsageRW == 1) { 554 | diskUsageRW = 2; 555 | } 556 | else { 557 | diskUsageRW = 1; 558 | } 559 | drawDiskGrid(diskPosX, diskPosY); 560 | } 561 | 562 | 563 | void setup(void) { 564 | int timeoutCounter = 0; 565 | bool isWifiConnnected = false; 566 | // put your setup code here, to run once: 567 | Serial.begin(115200); 568 | 569 | pinMode(PIN_POWER_ON, OUTPUT); 570 | digitalWrite(PIN_POWER_ON, HIGH); 571 | 572 | tft.init(); 573 | tft.setRotation(3); 574 | tft.setSwapBytes(true); 575 | 576 | sprite.createSprite(320, 170); 577 | sprite.setTextColor(TFT_WHITE,TFT_BLACK); 578 | sprite.setTextDatum(ML_DATUM); 579 | 580 | // Splash screen 581 | // sprite.fillScreen(TFT_BLACK); 582 | // sprite.setTextDatum(MC_DATUM); 583 | // sprite.drawString(productName + " " + productVer, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 3); 584 | // sprite.drawString(productType, SCREEN_WIDTH / 2, (SCREEN_HEIGHT / 3) + 20); 585 | // sprite.pushSprite(0, 0); 586 | // delay(2000); 587 | 588 | WiFi.begin(ssid, password); 589 | while (WiFi.status() != WL_CONNECTED) { 590 | Serial.println("Connecting to WiFi..."); 591 | delay(500); 592 | timeoutCounter += 500; 593 | if (timeoutCounter > wifiTimeout) { 594 | Serial.println("WiFi timeout reached"); 595 | break; 596 | } 597 | isWifiConnnected = true; 598 | } 599 | if (isWifiConnnected) { 600 | Serial.println("WiFi Connected"); 601 | } 602 | else { 603 | Serial.println("WiFi Failed"); 604 | } 605 | 606 | // tft.fillScreen(TFT_BLACK); 607 | // sprite.setTextDatum(ML_DATUM); 608 | // sprite.pushSprite(0, 0); 609 | // delay(500); 610 | 611 | button.attachClick(changeDiskIoStat); 612 | 613 | drawInitScreen(); 614 | // drawPVGraph("Test"); 615 | 616 | xTaskCreatePinnedToCore( 617 | drawGuiUpdateTask, 618 | "drawGuiUpdate", 619 | 10000, 620 | NULL, 621 | 1, 622 | &drawGuiUpdate, 623 | 0 624 | ); 625 | 626 | Serial.println("Initialization Done"); 627 | } 628 | 629 | void loop() { 630 | // put your main code here, to run repeatedly: 631 | 632 | /* Expected input format from Serial */ 633 | /* 634 | Format(comma seperated) : cpu:,memory:,network:,disk: 635 | Value description : 636 | cpu -> float number within the range of 0.0 ~ 100.0 637 | memory -> 638 | network -> 639 | disk -> 640 | */ 641 | 642 | if (millis() > ntpUpdateTimer + 1000) { 643 | drawNtpTime(); 644 | ntpUpdateTimer = millis(); 645 | } 646 | 647 | button.tick(); 648 | 649 | while (Serial.available()) { 650 | char input = Serial.read(); 651 | 652 | if (input == ':') { 653 | property = buff; 654 | buff = ""; 655 | } 656 | else if (input == valueMarker || input == '\n') { 657 | value = buff; 658 | buff = ""; 659 | 660 | Serial.println("========================="); 661 | Serial.println("property: " + property + ", value: " + value); 662 | Serial.println("========================="); 663 | 664 | if (property == "CPU") { 665 | cpuPercentage = value.toFloat(); 666 | } 667 | else if (property == "LAVG") { 668 | loadAvg15 = value.toFloat(); 669 | } 670 | else if (property == "MEM") { 671 | memPercentage = value.toFloat(); 672 | } 673 | else if (property == "DISKIO") { 674 | diskUsage = value; 675 | } 676 | else if (property == "CPUTEMP") { 677 | cpuTemp = value; 678 | } 679 | else if (property == "OVERALL") { 680 | overallStatus = value.toInt(); 681 | } 682 | else { 683 | 684 | } 685 | } 686 | else { 687 | buff += input; 688 | } 689 | } 690 | } 691 | 692 | // TFT Pin check 693 | #if PIN_LCD_WR != TFT_WR || \ 694 | PIN_LCD_RD != TFT_RD || \ 695 | PIN_LCD_CS != TFT_CS || \ 696 | PIN_LCD_DC != TFT_DC || \ 697 | PIN_LCD_RES != TFT_RST || \ 698 | PIN_LCD_D0 != TFT_D0 || \ 699 | PIN_LCD_D1 != TFT_D1 || \ 700 | PIN_LCD_D2 != TFT_D2 || \ 701 | PIN_LCD_D3 != TFT_D3 || \ 702 | PIN_LCD_D4 != TFT_D4 || \ 703 | PIN_LCD_D5 != TFT_D5 || \ 704 | PIN_LCD_D6 != TFT_D6 || \ 705 | PIN_LCD_D7 != TFT_D7 || \ 706 | PIN_LCD_BL != TFT_BL || \ 707 | TFT_BACKLIGHT_ON != HIGH || \ 708 | 170 != TFT_WIDTH || \ 709 | 320 != TFT_HEIGHT 710 | #error "Error! Please make sure is selected in " 711 | #endif 712 | 713 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) 714 | #error "The current version is not supported for the time being, please use a version below Arduino ESP32 3.0" 715 | #endif -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | --------------------------------------------------------------------------------