From 539ea9bc7aa9aa780753ded1329c26f2a9648a33 Mon Sep 17 00:00:00 2001 From: xad1 Date: Fri, 10 Mar 2023 15:22:25 +0000 Subject: [PATCH] added current version of program --- include/README | 39 +++ lib/README | 46 +++ platformio.ini | 17 ++ src/odroid_server_manager_noop2.cpp | 451 ++++++++++++++++++++++++++++ test/README | 11 + 5 files changed, 564 insertions(+) create mode 100644 include/README create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/odroid_server_manager_noop2.cpp create mode 100644 test/README diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..627e413 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:odroid_esp32] +platform = espressif32 +board = odroid_esp32 +framework = arduino +lib_deps = + leeseungcheol/ODROID-GO@^1.0.0 + bblanchon/ArduinoJson@^6.20.1 diff --git a/src/odroid_server_manager_noop2.cpp b/src/odroid_server_manager_noop2.cpp new file mode 100644 index 0000000..721af03 --- /dev/null +++ b/src/odroid_server_manager_noop2.cpp @@ -0,0 +1,451 @@ +#include +#include +#include +#include +#include + +const char* WIFI_SSID = ""; +const char* WIFI_PASS = ""; +const String PROXMOX_ADDRESS = ""; +const String PROXMOX_TOKEN_USER = ""; +const String PROXMOX_TOKEN_NAME = ""; +const String PROXMOX_TOKEN_SECRET = ""; + +int selectedItem = 0; + +String selectedNode; +// These ones are int as we use the ID instead of the name +int selectedVM; +int selectedLXC; + + +void listNodes(JsonArray nodes) { + GO.lcd.clearDisplay(); + GO.lcd.setCursor(0, 0); + int numItems = nodes.size(); + for (int i = 0; i < numItems; i++) { + if (selectedItem == i) { + GO.lcd.print("> "); + } + GO.lcd.println(nodes[i]["node"].as()); + } + + switch (buttonListener(numItems)) { + case 1: + Serial.println("selected " + selectedItem); + selectedNode = nodes[selectedItem]["node"].as(); + break; + case 2: + Serial.println("back"); + break; + default: + listNodes(nodes); + } +} + +void listContainers(JsonArray containers) { + GO.lcd.clearDisplay(); + GO.lcd.setCursor(0, 0); + int numItems = containers.size(); + for (int i = 0; i < numItems; i++) { + if (selectedItem == i) { + GO.lcd.print("> "); + } + GO.lcd.println(containers[i]["vmid"].as() + ": " + containers[i]["name"].as()); + } + + switch (buttonListener(numItems)) { + case 1: + Serial.println("selected " + selectedItem); + selectedLXC = containers[selectedItem]["vmid"].as(); + break; + case 2: + Serial.println("back"); + break; + default: + listContainers(containers); + } +} + +void listVMs(JsonArray vms) { + GO.lcd.clearDisplay(); + GO.lcd.setCursor(0, 0); + int numItems = vms.size(); + for (int i = 0; i < numItems; i++) { + if (selectedItem == i) { + GO.lcd.print("> "); + } + GO.lcd.println(vms[i]["vmid"].as() + ": " + vms[i]["name"].as()); + } + + switch (buttonListener(numItems)) { + case 1: + Serial.println("selected " + selectedItem); + selectedVM = vms[selectedItem]["vmid"].as(); + break; + case 2: + Serial.println("back"); + break; + default: + listVMs(vms); + } +} + +void printNodeStats(JsonObject node) { + String name = node["node"].as(); + int cpu = round(node["cpu"].as() * 100); + int threads = node["maxcpu"].as(); + String onlineStatus = node["status"].as(); + String mem = node["mem"].as(); + String maxmem = node["maxmem"].as(); + String disk = node["disk"].as(); + String maxdisk = node["maxdisk"].as(); + String uptime = node["uptime"].as(); + // these are strings for now because they could be way too big + + GO.lcd.fillScreen(WHITE); + GO.lcd.setCursor(0, 0); + GO.lcd.setTextColor(BLACK); + + GO.lcd.setTextSize(3); + GO.lcd.println(name); + GO.lcd.setTextSize(2); + GO.lcd.println("--------------------------"); + GO.lcd.println("Status: " + onlineStatus); + GO.lcd.println("Uptime: " + uptime + "s"); + GO.lcd.println("CPU: " + String(cpu) + "%"); + GO.lcd.println("Threads: " + String(threads)); + GO.lcd.println("RAM: " + mem); + GO.lcd.println("Max RAM: " + maxmem); + GO.lcd.println("Disk: " + disk); + GO.lcd.println("Max Disk: " + maxdisk); + +} + +void printContainerStats(JsonObject container) { + String name = container["name"].as(); + String id = container["vmid"].as(); + String onlineStatus = container["status"].as(); + String maxmem = container["maxmem"].as(); + String maxdisk = container["maxdisk"].as(); + String uptime = container["uptime"].as(); + // these are strings for now because they could be way too big + + GO.lcd.fillScreen(WHITE); + GO.lcd.setCursor(0, 0); + GO.lcd.setTextColor(BLACK); + + GO.lcd.setTextSize(3); + GO.lcd.println(id + ": " + name); + GO.lcd.setTextSize(2); + GO.lcd.println("--------------------------"); + GO.lcd.println("Status: " + onlineStatus); + GO.lcd.println("Uptime: " + uptime + "s"); + GO.lcd.println("Max RAM: " + maxmem); + GO.lcd.println("Max Disk: " + maxdisk); + +} + +void printVMStats(JsonObject vm) { + String name = vm["name"].as(); + String id = vm["vmid"].as(); + int cpu = round(vm["cpu"].as() * 100); + int cores = vm["cpus"].as(); + String onlineStatus = vm["status"].as(); + String mem = vm["mem"].as(); + String maxmem = vm["maxmem"].as(); + String maxdisk = vm["maxdisk"].as(); + String uptime = vm["uptime"].as(); + String netin = vm["netin"].as(); + String netout = vm["netout"].as(); + // these are strings for now because they could be way too big + + GO.lcd.fillScreen(WHITE); + GO.lcd.setCursor(0, 0); + GO.lcd.setTextColor(BLACK); + + GO.lcd.setTextSize(3); + GO.lcd.println(id + ": " + name); + GO.lcd.setTextSize(2); + GO.lcd.println("--------------------------"); + GO.lcd.println("Status: " + onlineStatus); + GO.lcd.println("Uptime: " + uptime + "s"); + GO.lcd.println("CPU: " + String(cpu) + "%"); + GO.lcd.println("Cores: " + String(cores)); + GO.lcd.println("RAM: " + mem); + GO.lcd.println("Max RAM: " + maxmem); + GO.lcd.println("Max Disk: " + maxdisk); + GO.lcd.println("Net In: " + netin); + GO.lcd.println("Net Out: " + netout); + + +} + + +void connectWiFi() { + WiFi.begin(WIFI_SSID, WIFI_PASS); + GO.lcd.print("Connecting"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + GO.lcd.print("."); + } + +} + +// Struct for static menu items such as on the main menu. Has the name of the menu item and a function to run when it is selected. +typedef struct { + char *name; + void (*function)(); +} MenuItem; + +// Array of the main menu items +MenuItem mainMenuItems[] = { + {"Node Information", &nodeInfo}, + {"Container Information", &containerInfo}, + {"VM Information", &vmInfo} +}; + + + +void nodeInfo() { + Serial.println("nodeinfo"); + while (true) { + printNodeStats(getNode(selectedNode, getNodeInfo())); + //printAllNodeCPU(getNodeInfo()); + delay(2000); + } +} + +void containerInfo() { + Serial.println("container info"); + JsonArray containerArray = getContainerInfo(selectedNode); + selectedItem = 0; + listContainers(containerArray); + JsonObject container = getContainer(selectedLXC, containerArray); + printContainerStats(container); + delay(1000); + + // only redraw screen if changed + while (true) { + if (getContainer(selectedLXC, containerArray) != container) { + containerInfo(); + } + } + +} + +void vmInfo() { + Serial.println("vm info"); + JsonArray vmArray = getVMInfo(selectedNode); + selectedItem = 0; + listVMs(vmArray); + while (true) { + printVMStats(getContainer(selectedVM, vmArray)); + delay(2000); + } + +} + +void mainMenu() { + GO.lcd.clearDisplay(); + GO.lcd.setCursor(0, 0); + int numItems = sizeof(mainMenuItems) / sizeof(MenuItem); + + for (int i = 0; i < numItems; i++) { + if (selectedItem == i) { + GO.lcd.print("> "); + } + GO.lcd.println(mainMenuItems[i].name); + } + + switch (buttonListener(numItems)) { + case 1: + Serial.println("selected " + selectedItem); + mainMenuItems[selectedItem].function(); + break; + case 2: + Serial.println("back"); + break; + default: + mainMenu(); + } + +} + +int buttonListener(int numItems) { + delay(200); + while (true) { + GO.update(); + if (GO.JOY_Y.isAxisPressed() == 1 && selectedItem < (numItems - 1)) { + selectedItem++; + break; + } + if (GO.JOY_Y.isAxisPressed() == 2 && selectedItem > 0) { + selectedItem--; + break; + } + if (GO.BtnA.isPressed() == 1) { + delay(300); + return 1; + } + if (GO.BtnB.isPressed() == 1) { + delay(300); + return 2; + } + + } + return 0; +} + +// todo make the bottom part of these into one function +// ###################################### +// ###################################### +// ###################################### +// ###################################### +// ###################################### +JsonArray getNodeInfo() { + HTTPClient http; + String apiAddress = PROXMOX_ADDRESS + "/api2/json/nodes/"; + String auth = "PVEAPIToken=" + PROXMOX_TOKEN_USER + "!" + PROXMOX_TOKEN_NAME + "=" + PROXMOX_TOKEN_SECRET; + char authc[auth.length() + 1]; + auth.toCharArray(authc, auth.length() + 1); + http.begin(apiAddress); + http.setAuthorization(authc); + int httpCode = http.GET(); + + if (httpCode > 0) { + String json = http.getString(); + DynamicJsonDocument doc(1024); + deserializeJson(doc, json); + JsonArray nodes = doc["data"].as(); + return nodes; + } + + displayError("Error getting data."); + return getNodeInfo(); +} + +JsonArray getContainerInfo(String node) { + HTTPClient http; + String apiAddress = PROXMOX_ADDRESS + "/api2/json/nodes/" + node + "/lxc"; + String auth = "PVEAPIToken=" + PROXMOX_TOKEN_USER + "!" + PROXMOX_TOKEN_NAME + "=" + PROXMOX_TOKEN_SECRET; + char authc[auth.length() + 1]; + auth.toCharArray(authc, auth.length() + 1); + http.begin(apiAddress); + http.setAuthorization(authc); + int httpCode = http.GET(); + + if (httpCode > 0) { + String json = http.getString(); + DynamicJsonDocument doc(1024); + deserializeJson(doc, json); + JsonArray nodes = doc["data"].as(); + return nodes; + } + + displayError("Error getting data."); + return getNodeInfo(); +} + +JsonArray getVMInfo(String node) { + HTTPClient http; + String apiAddress = PROXMOX_ADDRESS + "/api2/json/nodes/" + node + "/qemu"; + String auth = "PVEAPIToken=" + PROXMOX_TOKEN_USER + "!" + PROXMOX_TOKEN_NAME + "=" + PROXMOX_TOKEN_SECRET; + char authc[auth.length() + 1]; + auth.toCharArray(authc, auth.length() + 1); + http.begin(apiAddress); + http.setAuthorization(authc); + int httpCode = http.GET(); + + if (httpCode > 0) { + String json = http.getString(); + DynamicJsonDocument doc(1024); + deserializeJson(doc, json); + JsonArray nodes = doc["data"].as(); + return nodes; + } + + displayError("Error getting data."); + return getNodeInfo(); +} + + +/** + Get a specific node from an array of nodes +*/ +JsonObject getNode(String name, JsonArray nodes) { + for (JsonObject node : nodes) { + if (node["node"] == name) { + return node; + } + } + displayError("Node not found!"); + +} + +JsonObject getContainer(int id, JsonArray containers) { + for (JsonObject container : containers) { + Serial.println(id); + Serial.println(container["vmid"].as()); + if (container["vmid"].as() == id) { + return container; + } + } + displayError("Machine not found!"); + +} + + +/** + Display an error on the screen +*/ +void displayError(String message) { + GO.lcd.clearDisplay(); + GO.lcd.setCursor(0, 0); + GO.lcd.setTextSize(2); + GO.lcd.setTextColor(RED); + GO.lcd.print(message); + delay(3000); +} + + +void printAllNodeCPU(JsonArray nodes) { + GO.lcd.fillScreen(WHITE); + GO.lcd.setCursor(0, 0); + for (JsonObject node : nodes) { + Serial.print("CPU Usage for node "); + Serial.print(node["node"].as()); + Serial.print(": "); + Serial.println(String(round(node["cpu"].as() * 100)) + "%"); + + GO.lcd.setTextSize(2); + GO.lcd.setTextColor(BLACK); + GO.lcd.print("CPU Usage for node "); + GO.lcd.print(node["node"].as()); + GO.lcd.println(": "); + GO.lcd.println(String(round(node["cpu"].as() * 100)) + "%"); + } +} + + +void setup() { + Serial.begin(115200); + GO.begin(); + connectWiFi(); +} + + +void loop() { + + if (WiFi.status() != WL_CONNECTED) { + displayError("WiFi Connection Lost"); + connectWiFi(); + } + listNodes(getNodeInfo()); + selectedItem = 0; + mainMenu(); + + +} diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html