Meh Belly Lint Collection

That awful moment when you realize,
THIS is YOUR circus and THOSE are YOUR monkeys.

User Tools

Site Tools


server_status_oled_display

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
server_status_oled_display [2025/03/21 07:26] kensonserver_status_oled_display [2026/05/06 04:56] (current) kenson
Line 2: Line 2:
 ===== Displayinator v1.0 ===== ===== Displayinator v1.0 =====
  
-This is a old school project using ATMEGA32U4+This is a device you can shove into spare USB connector on say a server rack system to display info like IP/Hostname/etc. It has an accelerometer so it can determine orientation (for racks which have sideways USB) and a simple font compression selection so it can dynamically fit information on one line.
  
-The board is a clone of an Arduino Micro, and I2C is connnected to the OLED (SSD1315) and the Accelerometer (MMA8451). +{{ :displayinator.jpg?direct&400 |}} 
-The OLED's I2C address 0x3C, and I'm using adafruit's 1306 driver, mainly for display rotation support. + 
-The Accelerometer's address is 0x1C and the adafruit MMA8541 driver is basic but functional. Note that the +This is a old school project using a ATMEGA32U4. Hindsight being 20/20 I should have chosen a more modern microcontroller as the 2.5K of RAM was a significant limitation requiring excessive use of PROGMEM in places you ordinarily wouldn't. 
-more advanced features (e.g. tap to click) built into the chip are unavailable with this library.+ 
 +Notes: 
 +  * The board is a clone of an Arduino Micro, and I2C is connnected to the OLED (SSD1315) and the Accelerometer (MMA8451). 
 +  The OLED's I2C address 0x3C, and I'm using adafruit's 1306 driver, mainly for display rotation support. 
 +  The Accelerometer's address is 0x1C and the adafruit MMA8541 driver is basic but functional. Note that the more advanced features (e.g. tap to click) built into the chip are unavailable with this library. 
 + 
 + 
 +====== TLDR; ====== 
 + 
 + 
 +Probe the port. 
 + 
 +<code> 
 +sudo tee ./probe_displayinator.py >/dev/null <<'EOF' 
 +#!/usr/bin/env bash 
 +set -u 
 + 
 +BAUD=115200 
 + 
 +for PORT in /dev/ttyUSB* /dev/ttyACM*; do 
 +    [ -e "$PORT" ] || continue 
 + 
 +    echo "========================================" 
 +    echo "Probing $PORT" 
 + 
 +    if ! stty -F "$PORT" "$BAUD" raw -echo -echoe -echok -echoctl -echoke 2>/dev/null; then 
 +        echo "Could not configure $PORT" 
 +        continue 
 +    fi 
 + 
 +    # Some Arduino/32U4-style boards reset when serial opens 
 +    sleep 4 
 + 
 +    # Wake / clear any stale output 
 +    printf '\n' > "$PORT" 
 +    sleep 0.2 
 +    timeout 1 cat "$PORT" >/dev/null 2>&
 + 
 +    # Ask version 
 +    printf '!VER\n' > "$PORT" 
 + 
 +    echo "Response from $PORT:" 
 +    timeout 2 cat "$PORT" || true 
 +    echo 
 +done 
 +EOF 
 +chmod +x probe_displayinator.py 
 +./probe_displayinator.py 
 +</code> 
 + 
 +Download the agent: {{ :displayinatorhostip.zip |}} 
 +<code> 
 +wget 'https://mehngineering.com/wiki/lib/exe/fetch.php?media=displayinatorhostip.zip' -O displayinatorhostip.zip 
 +unzip displayinatorhostip.zip 
 +</code> 
 + 
 +Insert the service to autostart 
 +<code> 
 +sudo tee /etc/systemd/system/displayinator.service >/dev/null <<'EOF' 
 +[Unit] 
 +Description=Displayinator Host IP Sender 
 +Wants=network-online.target 
 +After=network-online.target multi-user.target 
 + 
 +[Service] 
 +Type=oneshot 
 +WorkingDirectory=/root 
 +ExecStartPre=/bin/sleep 10 
 +ExecStart=/usr/bin/python3 /root/DisplayinatorHostIP.py --port /dev/ttyACM0 
 +StandardOutput=append:/var/log/displayinator-hostip.log 
 +StandardError=append:/var/log/displayinator-hostip.log 
 + 
 +[Install] 
 +WantedBy=multi-user.target 
 +EOF 
 +</code> 
 + 
 +Edit /etc/systemd/system/displayinator.service and change the port to whatever device the probe finds, usually /dev/ttyACM0 or /dev/ttyUSB0 
 +<code>ExecStart=/usr/bin/python3 /root/DisplayinatorHostIP.py --port /dev/ttyACM0 
 +</code> 
 + 
 +Start the service. 
 + 
 +<code> 
 +systemctl daemon-reload 
 +systemctl enable --now displayinator.service 
 +systemctl status displayinator.service 
 +</code>
  
 ==== Loading the Arduino Bootloader ==== ==== Loading the Arduino Bootloader ====
Line 16: Line 103:
  
 avrdude will complain about older firmware but my recommendation is to ignore it. avrdude will complain about older firmware but my recommendation is to ignore it.
-The TLDR; is the chinese firmware is modified and does some automagic stuff and in general works more reliably than the publicly available open source code version.+The [[USBasp flash|TLDR;]] is the chinese firmware is modified and does some automagic stuff and in general works more reliably than the publicly available open source code version.
  
 Use zadig to make it (USBasp) use libusb-win32 Use zadig to make it (USBasp) use libusb-win32
  
-Copy avrdude to the arduino avrdude location. (You can "Show verbose output during [X] compile [X] upload" in File->Preferences to try to upload to see where it is)+Copy avrdude v8 (The one that ships with arduino is v5 and doesn't work.) to the arduino avrdude location. (You can "Show verbose output during [X] compile [X] upload" in File->Preferences to try to upload to see where it is) 
  
 Then from Arduino, select USBasp in Tools->Programmer and burn using Tools->Burn Bootloader Then from Arduino, select USBasp in Tools->Programmer and burn using Tools->Burn Bootloader
Line 34: Line 121:
  
 Arduino Code: {{ :oledusbdisplayaccx1.zip |}} Arduino Code: {{ :oledusbdisplayaccx1.zip |}}
 +
 +==== Controlling the device ====
 +
 +When the device boots, it checks the EEPROM for stored key-value pairs and displays them on screen.
 +I uses the accelerometer to detect orientation so it should be right side up.
 +
 +The USB serial is nominally 115200.
 +
 +==== Command structure ====
 +
 +The command protocol is simple: 
 +  * Commands are prepended with an '!' and terminated with a newline '\n'.
 +  * Key-Value pairs are prepended with a '@', space ' ' delimited and terminated with a newline '\n'
 +  * You can also indicate not to store values in EEPROM by putting a caret '^' after the @ symbol
 +
 +==== Commands ====
 +
 +!LIST which displays the stored key-value pairs
 +
 +<code>
 +!LIST
 +Key-Value Pairs:
 +Hostname = DESKTOP-PRYPYAT
 +IP1 = 10.10.22.102
 +IP2 = 10.10.22.61
 +GW = 10.10.22.1
 +WAN = 99.29.30.10
 +</code>
 +
 +!VER reports the devicename and version of firmware
 +<code>
 +!VER
 +Displayinator v1.0
 +</code>
 +
 +!NUKE clears all stored key-value pairs from memory and eeprom
 +<code>
 +!NUKE
 +All key-value pairs cleared.
 +</code>
 +
 +!ORIENT [0..3] overrides the display orientation, but hasn't been implemented yet
 +<code>
 +!ORIENT 0
 +Orientation argument extracted: '0'
 +Orientation set to 0
 +</code>
 +
 +
 +==== KEY VALUE STORAGE ====
 +
 +If the line starts with a '@', it will indicate that we're passing in a Key Value pair. The Key-Value pair is space delimited. 
 +
 +Note that this '@' symbol is not passed in. 
 +
 +<code>
 +@HAPPY GILMORE
 +Space index found at: 5
 +Key extracted: 'HAPPY', Value extracted: 'GILMORE'
 +Added HAPPY = GILMORE
 +</code>
 +
 +Note only the first space is used as a delimiter, successive spaces are considered part of the Value.
 +
 +<code>
 +@KEY Value cow
 +KEY Value cow, Space @: 3
 +Key extracted: 'KEY', Value extracted: 'Value cow'
 +Added KEY = Value cow
 +</code>
 +
 +KV pairs are normally stored in flash and will be loaded on power on. Sometimes this behavior is undesired, for example rapidly changing information like CPU Load. In this case, a '^' is used to specify skipping permanent storage. Note that the caret is stored in the Key but not displayed.
 +
 +<code>
 +@^CPU 48%
 +^CPU 48%, Space @: 4
 +Key extracted: '^CPU', Value extracted: '48%'
 +Updated ^CPU = 48%
 +</code>
 +
 Python Code: {{ :displayinatorhostip.zip |}} Python Code: {{ :displayinatorhostip.zip |}}
  
-Arduino+==== Code Tips ==== 
 + 
 +  * The 32u4 is sloooow by modern standards, so after you send a command, I wait up to half a second before checking for a response. 
 +  * It also likes to reset when you connect via serial so I wait 3 to 5 seconds for it to boot up before poking it.  
 +  * Python raises DTR, and for whatever reason it breaks things, so I set ser.dtr = False. 
 +  * You never know whats in a serial buffer when you connect so send a '\n' and read the buffer to make sure everything is sane when you connect. 
 + 
 +==== TrueNAS ===== 
 + 
 +Code{{ :truenasserialdisplay.zip |}} 
 + 
 +I also wrote a bash script for TrueNAS because they removed pip.  
 +It polls version, network and storage info and updates the display every 15 seconds. 
 + 
 +==== Code Dump: Arduino (32U4) ==== 
 + 
 <code cpp> <code cpp>
 #include <Wire.h> #include <Wire.h>
Line 43: Line 225:
 #include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
 #include <Adafruit_SSD1306.h> #include <Adafruit_SSD1306.h>
-#include <Fonts/TomThumb.h> // TomThumb +#include <Fonts/TomThumb.h>
-//#include <Fonts/Org_01.h> // Org_01 +
-//#include <Fonts/Picopixel.h> // Picopixel+
 #include <EEPROM.h> #include <EEPROM.h>
  
Line 52: Line 232:
   MySSD1306(int16_t w, int16_t h, TwoWire *twi, int8_t rst_pin)   MySSD1306(int16_t w, int16_t h, TwoWire *twi, int8_t rst_pin)
     : Adafruit_SSD1306(w, h, twi, rst_pin) {}     : Adafruit_SSD1306(w, h, twi, rst_pin) {}
-  +
   int16_t getCursorX() { return cursor_x; }   int16_t getCursorX() { return cursor_x; }
   int16_t getCursorY() { return cursor_y; }   int16_t getCursorY() { return cursor_y; }
Line 58: Line 238:
  
 // ----- EEPROM Layout Settings ----- // ----- EEPROM Layout Settings -----
-#define MAX_KEY_LENGTH 16       // Maximum characters for a key +#define MAX_KEY_LENGTH 32       // Maximum characters for a key 
-#define MAX_VALUE_LENGTH 16     // Maximum characters for a value+#define MAX_VALUE_LENGTH 32     // Maximum characters for a value
 #define PAIR_SIZE (MAX_KEY_LENGTH + MAX_VALUE_LENGTH) #define PAIR_SIZE (MAX_KEY_LENGTH + MAX_VALUE_LENGTH)
 #define EEPROM_KEY_COUNT_OFFSET 0 #define EEPROM_KEY_COUNT_OFFSET 0
Line 65: Line 245:
 // ----------------------------------- // -----------------------------------
  
-// Use a renamed enum to avoid potential conflicts +// Renamed enum to avoid potential conflicts 
-enum Fuckme {+enum umopapisdn {
   UNKNOWN,   UNKNOWN,
   FACE_UP,   FACE_UP,
Line 77: Line 257:
  
 // CLI Code // CLI Code
-#define MAX_KEYS 10  // Maximum number of key-value pairs+#define MAX_KEYS  // Maximum number of key-value pairs
 struct KeyValue { struct KeyValue {
-  String key; +  char key[MAX_KEY_LENGTH+1]
-  String value;+  char value[MAX_VALUE_LENGTH+1];
 }; };
 KeyValue keyValueStore[MAX_KEYS];  // Storage for key-value pairs KeyValue keyValueStore[MAX_KEYS];  // Storage for key-value pairs
 int keyCount = 0;                  // Current number of keys stored int keyCount = 0;                  // Current number of keys stored
 int orient = 0;                    // Stores Display Orientation (0-3) int orient = 0;                    // Stores Display Orientation (0-3)
-String inputString ""          // Buffer for incoming serial data + 
-bool stringComplete = false;       // Indicates if a full line has been received+#define INPUT_BUFFER_SIZE 67 
 +char inputBuffer[INPUT_BUFFER_SIZE]; 
 +int inputIndex 0
 +bool commandReady = false;
 int orientChanged = 0; int orientChanged = 0;
  
Line 97: Line 280:
 int landscape = -1; int landscape = -1;
 const float THRESHOLD = 7.0;         // Minimum m/s² value to consider an axis "dominant" const float THRESHOLD = 7.0;         // Minimum m/s² value to consider an axis "dominant"
-const unsigned long DEBOUNCE_DELAY = 250;  // Time in milliseconds to wait before confirming a change +const unsigned long DEBOUNCE_DELAY = 250;  // Debounce delay in milliseconds 
-Fuckme lastStableOrientation = UNKNOWN;+umopapisdn lastStableOrientation = UNKNOWN;
 unsigned long lastChangeTime = 0; unsigned long lastChangeTime = 0;
  
 #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_WIDTH 128 // OLED display width, in pixels
 #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels
-#define SCREEN_ADDRESS 0x3C // I2C Addr, Commonly 0x3D or 0x3C+#define SCREEN_ADDRESS 0x3C // I2C Addr (commonly 0x3C or 0x3D)
 #define OLED_RESET     -1  #define OLED_RESET     -1 
 MySSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); MySSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Line 113: Line 296:
 //-------------------------------------------------- EEPROM Functions --------------------------------------- //-------------------------------------------------- EEPROM Functions ---------------------------------------
 void saveKeyValuesToEEPROM() { void saveKeyValuesToEEPROM() {
-  // Save the number of key-value pairs at address 0. +  int storedKeyCount = 0; 
-  EEPROM.update(EEPROM_KEY_COUNT_OFFSET, keyCount);+  // Count only keys that should be stored (skip keys starting with '^')
   for (int i = 0; i < keyCount; i++) {   for (int i = 0; i < keyCount; i++) {
-    int base = EEPROM_KEY_VALUE_OFFSET + * PAIR_SIZE; +    if (keyValueStore[i].key[0] == '^') continue; 
-    // Save key as fixed-length char array+    storedKeyCount++; 
 +  } 
 +  EEPROM.update(EEPROM_KEY_COUNT_OFFSET, storedKeyCount); 
 + 
 +  int storedIndex = 0; 
 +  // Store only non-filtered key-value pairs. 
 +  for (int i = 0; i < keyCount; i++) { 
 +    if (keyValueStore[i].key[0] == '^') continue; 
 +    int base = EEPROM_KEY_VALUE_OFFSET + storedIndex * PAIR_SIZE; 
 +    // Save key as fixed-length
     for (int j = 0; j < MAX_KEY_LENGTH; j++) {     for (int j = 0; j < MAX_KEY_LENGTH; j++) {
-      char c = (j < keyValueStore[i].key.length()) ? keyValueStore[i].key.charAt(j: '\0';+      char c = (j < (int)strlen(keyValueStore[i].key)) ? keyValueStore[i].key[j: '\0';
       EEPROM.update(base + j, c);       EEPROM.update(base + j, c);
     }     }
-    // Save value as fixed-length char array+    // Save value as fixed-length
     for (int j = 0; j < MAX_VALUE_LENGTH; j++) {     for (int j = 0; j < MAX_VALUE_LENGTH; j++) {
-      char c = (j < keyValueStore[i].value.length()) ? keyValueStore[i].value.charAt(j: '\0';+      char c = (j < (int)strlen(keyValueStore[i].value)) ? keyValueStore[i].value[j: '\0';
       EEPROM.update(base + MAX_KEY_LENGTH + j, c);       EEPROM.update(base + MAX_KEY_LENGTH + j, c);
     }     }
 +    storedIndex++;
   }   }
 } }
Line 133: Line 326:
   keyCount = EEPROM.read(EEPROM_KEY_COUNT_OFFSET);   keyCount = EEPROM.read(EEPROM_KEY_COUNT_OFFSET);
   // Validate keyCount (if invalid, reset to 0)   // Validate keyCount (if invalid, reset to 0)
-  if (keyCount < 0 || keyCount > MAX_KEYS) +  if (keyCount < 0 || keyCount > MAX_KEYS) keyCount = 0;
-    keyCount = 0; +
-  }+
   for (int i = 0; i < keyCount; i++) {   for (int i = 0; i < keyCount; i++) {
     int base = EEPROM_KEY_VALUE_OFFSET + i * PAIR_SIZE;     int base = EEPROM_KEY_VALUE_OFFSET + i * PAIR_SIZE;
Line 148: Line 339:
     }     }
     valueBuffer[MAX_VALUE_LENGTH] = '\0';     valueBuffer[MAX_VALUE_LENGTH] = '\0';
-    keyValueStore[i].key = String(keyBuffer); +    strncpy(keyValueStore[i].keykeyBuffer, MAX_KEY_LENGTH); 
-    keyValueStore[i].value String(valueBuffer);+    keyValueStore[i].key[MAX_KEY_LENGTH] '\0'; 
 +    strncpy(keyValueStore[i].value, valueBuffer, MAX_VALUE_LENGTH)
 +    keyValueStore[i].value[MAX_VALUE_LENGTH] = '\0';
   }   }
 } }
Line 155: Line 348:
  
 const void* activeFont = NULL; const void* activeFont = NULL;
- +// customPrintln() prints text and repositions the cursor when the font changes. 
-// customPrintln() prints text and only repositions if the font changes. +// If the string length exceeds 'threshold', the TomThumb font is used
-// If the string length exceeds 'threshold', we use the TomThumb font; otherwise, the default+void customPrintln(const char *str, size_t threshold) { 
-void customPrintln(const String &str, size_t threshold) { +  const void* desiredFont = (strlen(str) > threshold) ? (void*)&TomThumb : NULL;
-  // Determine which font we want based on the string length. +
-  const void* desiredFont = (str.length() > threshold) ? (void*)&TomThumb : NULL; +
- +
-  //Serial.print("Y:"); +
-  //Serial.println(display.getCursorY()); +
   int currY = display.getCursorY();   int currY = display.getCursorY();
-  if ((currY == 0) && (!landscape)) { // weird bug +  if ((currY == 0) && (!landscape)) { 
-    display.setCursor(0,7);+    display.setCursor(0, 7);
   }   }
-   
-  // Only change font (and reposition) if it differs from the active font. 
   if (activeFont != desiredFont) {   if (activeFont != desiredFont) {
     display.setFont(desiredFont);     display.setFont(desiredFont);
     activeFont = desiredFont;     activeFont = desiredFont;
-     +    // Reposition cursor when switching fonts.
-    // Reposition cursor when the font changes cause reasons. +
-    // For example, when switching to TomThumb, move up by 1 pixel; +
-    // when switching back to default, move down by 1 pixel.+
     if (desiredFont == (void*)&TomThumb) {     if (desiredFont == (void*)&TomThumb) {
-      //Serial.println(display.getCursorY()); 
       display.setCursor(0, display.getCursorY() - 1);       display.setCursor(0, display.getCursorY() - 1);
     } else {     } else {
Line 185: Line 366:
     }     }
   }   }
-   
-  // Print the string (this also moves the cursor downward). 
   display.println(str);   display.println(str);
 } }
- 
  
 //-------------------------------------------------- SETUP ------------------------------------------- //-------------------------------------------------- SETUP -------------------------------------------
 void setup() { void setup() {
   Serial.begin(115200);   Serial.begin(115200);
-  //while (!Serial) delay(10);     // will pause until serial console opens 
- 
-  //Serial.println("Displayinator Boot"); 
-   
-  // Load key-value pairs from EEPROM 
   loadKeyValuesFromEEPROM();   loadKeyValuesFromEEPROM();
      
   if (!mma.begin(ACCEL_ADDRESS)) {   if (!mma.begin(ACCEL_ADDRESS)) {
-    Serial.println("MMA8451 FAIL!"); +    Serial.println(F("MMA8451 FAIL!")); 
-    for(;;); // Don't proceed, loop forever +    for(;;); 
-  }   +  } 
-  Serial.println("MMA8451 OK."); +  Serial.println(F("MMA8451 OK.")); 
 +  
   if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {   if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
     Serial.println(F("SSD1306 FAIL"));     Serial.println(F("SSD1306 FAIL"));
-    for(;;); // Don't proceed, loop forever+    for(;;);
   }   }
-  Serial.println("SSD1306 OK."); +  Serial.println(F("SSD1306 OK.")); 
- +  
   display.clearDisplay();   display.clearDisplay();
   display.display();   display.display();
 +  
   mma.getEvent(&event);   mma.getEvent(&event);
-  float ax = event.acceleration.x; // acceleration is measured in m/s^2+  float ax = event.acceleration.x;
   float ay = event.acceleration.y;   float ay = event.acceleration.y;
   float az = event.acceleration.z;   float az = event.acceleration.z;
   updateOrientation(ax, ay, az);   updateOrientation(ax, ay, az);
- +   
-  //display.setFont(&Picopixel); +  display.setTextSize(1); 
-  display.setTextSize(1);      // Normal 1:1 pixel scale +  display.setTextColor(SSD1306_WHITE); 
-  display.setTextColor(SSD1306_WHITE); // Draw white text +  customPrintln("Displayinator", 10); 
-  customPrintln(F("Displayinator"),10); +  
- +
-/*  customPrintln(F("Displayinator"),10); +
-  customPrintln(F("123456789012345678901234567890"),10); +
-  customPrintln(F("123.456.789.012"),10); +
-  customPrintln(F("COW"),10); +
-*/+
   display.display();   display.display();
-  inputString.reserve(20);     // Reserve memory for the input string +   
- +  // Initialize input buffer 
 +  inputIndex = 0; 
 +  inputBuffer[0] = '\0';
      
   mma.setRange(MMA8451_RANGE_2_G);   mma.setRange(MMA8451_RANGE_2_G);
-   
-  //Serial.print("ACCRANGE = ");  Serial.print(2 << mma.getRange());  Serial.println("G"); 
 } }
  
 //-------------------------------------------------- LOOP -------------------------------------------- //-------------------------------------------------- LOOP --------------------------------------------
 void loop() { void loop() {
-  /* Get a new sensor event */  
- 
   mma.getEvent(&event);   mma.getEvent(&event);
-  float ax = event.acceleration.x; // acceleration is measured in m/s^2+  float ax = event.acceleration.x;
   float ay = event.acceleration.y;   float ay = event.acceleration.y;
   float az = event.acceleration.z;   float az = event.acceleration.z;
   updateOrientation(ax, ay, az);   updateOrientation(ax, ay, az);
 +  
   updateDisplay();   updateDisplay();
- +   
-  while (Serial.available()) { // Nonblocking serial input: read available characters one at a time+  while (Serial.available()) {
     char inChar = (char)Serial.read();     char inChar = (char)Serial.read();
-    //Serial.print(inChar); +    if (inChar == '\n') { 
-    if (inChar == '\n') {  // Newline signals end of command +      inputBuffer[inputIndex= '\0'
-      //Serial.print("[LF]")+      commandReady = true; 
-      stringComplete = true; +      break; 
-      break; // Process this command after breaking out +    } else if (inChar != '\r') { 
-    } else if (inChar != '\r') { // Ignore carriage return +      if (inputIndex < INPUT_BUFFER_SIZE - 1) { 
-      inputString += inChar; +        inputBuffer[inputIndex++] = inChar; 
-    else { +        inputBuffer[inputIndex] = '\0'; 
-      //Serial.print("[CR]");+      
 +      // If the input buffer is full, force processing. 
 +      if (inputIndex >= INPUT_BUFFER_SIZE - 1
 +        commandReady = true; 
 +        break; 
 +      }
     }     }
   }   }
      
-  // If a complete command was received, process it +  if (commandReady) { 
-  if (stringComplete) { +    // Trim leading and trailing whitespace in the full input line. 
-    inputString.trim(); +    int start = 0; 
-    if (inputString.length() > 0) { +    while (inputBuffer[start] == ' ' || inputBuffer[start] == '\t'start++
-      //Serial.println(inputString); +    int end = strlen(inputBuffer- 1; 
-      processCommand(inputString);+    while (end start && (inputBuffer[end] == ' ' || inputBuffer[end] == '\t')) { 
 +      inputBuffer[end] = '\0'; 
 +      end--; 
 +    } 
 +    if (strlen(inputBuffer + start> 0) { 
 +      processCommand(inputBuffer + start);
     }     }
-    // Reset buffer for the next command +    inputIndex = 0; 
-    inputString ""+    inputBuffer[0] '\0'
-    stringComplete = false;+    commandReady = false;
   }   }
   delay(250);   delay(250);
Line 284: Line 457:
  
 //-------------------------------------------------- DISPLAY --------------------------------------- //-------------------------------------------------- DISPLAY ---------------------------------------
-unsigned long lastDisplayUpdate = 0;         // Tracks the last display update time +unsigned long lastDisplayUpdate = 0; 
-const unsigned long DISPLAY_UPDATE_INTERVAL = 1000; // Update interval in milliseconds+const unsigned long DISPLAY_UPDATE_INTERVAL = 1000;
  
 void updateDisplay() { void updateDisplay() {
   unsigned long now = millis();   unsigned long now = millis();
-  int sWidth = 10; +  int sWidth = landscape 21 10; 
-  if (landscape) { +  
-    sWidth = 21+
-  } else { +
-    sWidth = 10; +
-  +
   if (now - lastDisplayUpdate >= DISPLAY_UPDATE_INTERVAL) {   if (now - lastDisplayUpdate >= DISPLAY_UPDATE_INTERVAL) {
     lastDisplayUpdate = now;     lastDisplayUpdate = now;
-     
     display.clearDisplay();     display.clearDisplay();
     display.setCursor(0, 0);     display.setCursor(0, 0);
          
-    // Display key-value pairs 
     if (keyCount == 0) {     if (keyCount == 0) {
-      customPrintln(F("No KV data"),sWidth);+      customPrintln("No KV data", sWidth);
     } else {     } else {
       for (int i = 0; i < keyCount; i++) {       for (int i = 0; i < keyCount; i++) {
-        if ((keyValueStore[i].key.length() + keyValueStore[i].value.length() + 1) <sWidth) { +        char displayKey[MAX_KEY_LENGTH+1]; 
-          String result = keyValueStore[i].key + ":" + keyValueStore[i].value+        if (keyValueStore[i].key[0] == '^') { 
-          customPrintln(result,sWidth);+          strncpy(displayKey, keyValueStore[i].key + 1, MAX_KEY_LENGTH)
 +          displayKey[MAX_KEY_LENGTH] = '\0';
         } else {         } else {
-          customPrintln(keyValueStore[i].key,sWidth); +          strncpy(displayKey, keyValueStore[i].key, MAX_KEY_LENGTH); 
-          customPrintln(keyValueStore[i].value,sWidth);+          displayKey[MAX_KEY_LENGTH] = '\0'; 
 +        } 
 +        char result[2 * MAX_KEY_LENGTH + MAX_VALUE_LENGTH + 3]; 
 +        if ((strlen(displayKey) + strlen(keyValueStore[i].value) + 1) <= (unsigned)sWidth) { 
 +          snprintf(result, sizeof(result), "%s %s", displayKey, keyValueStore[i].value); 
 +          customPrintln(result, sWidth); 
 +        } else { 
 +          customPrintln(displayKey, sWidth); 
 +          customPrintln(keyValueStore[i].value, sWidth);
         }         }
       }       }
     }     }
-     
     display.display();     display.display();
   }   }
Line 322: Line 496:
  
 //-------------------------------------------------- ORIENTATION --------------------------------------- //-------------------------------------------------- ORIENTATION ---------------------------------------
-Fuckme detectOrientation(float ax, float ay, float az) {+umopapisdn detectOrientation(float ax, float ay, float az) {
   if (az > THRESHOLD) return FACE_UP;   if (az > THRESHOLD) return FACE_UP;
   if (az < -THRESHOLD) return FACE_DOWN;   if (az < -THRESHOLD) return FACE_DOWN;
Line 333: Line 507:
  
 void updateOrientation(float ax, float ay, float az) { void updateOrientation(float ax, float ay, float az) {
-  Fuckme current = detectOrientation(ax, ay, az);+  umopapisdn current = detectOrientation(ax, ay, az);
   unsigned long now = millis();   unsigned long now = millis();
 +  
   if (current != lastStableOrientation) {   if (current != lastStableOrientation) {
     if (now - lastChangeTime >= DEBOUNCE_DELAY) {     if (now - lastChangeTime >= DEBOUNCE_DELAY) {
       lastStableOrientation = current;       lastStableOrientation = current;
       orientChanged = 1;       orientChanged = 1;
-      Serial.print("REORIENT: "); 
       switch (current) {       switch (current) {
-        case FACE_UP:          +        case FACE_UP: 
-          Serial.println("FACE_UP"); +          display.setRotation(0)
 +          landscape = 1;
           break;           break;
-        case FACE_DOWN:        +        case FACE_DOWN: 
-          Serial.println("FACE_DOWN"); +          display.setRotation(0)
 +          landscape = 1;
           break;           break;
-        case LANDSCAPE_RIGHT:  +        case LANDSCAPE_RIGHT:
-          Serial.println("LANDSCAPE_RIGHT"); +
           display.setRotation(0);           display.setRotation(0);
           landscape = 1;           landscape = 1;
           break;           break;
-        case LANDSCAPE_LEFT:   +        case LANDSCAPE_LEFT:
-          Serial.println("LANDSCAPE_LEFT"); +
           display.setRotation(2);           display.setRotation(2);
           landscape = 1;           landscape = 1;
           break;           break;
-        case PORTRAIT_UP:      +        case PORTRAIT_UP:
-          Serial.println("PORTRAIT_UP"); +
           display.setRotation(3);           display.setRotation(3);
           landscape = 0;           landscape = 0;
           break;           break;
-        case PORTRAIT_DOWN:    +        case PORTRAIT_DOWN:
-          Serial.println("PORTRAIT_DOWN"); +
           display.setRotation(1);           display.setRotation(1);
           landscape = 0;           landscape = 0;
           break;           break;
         default:         default:
-          Serial.println("UNKNOWN"); +          display.setRotation(0)
 +          landscape = 1;
           break;           break;
       }       }
Line 379: Line 551:
  
 //-------------------------------------------------- COMMAND PROCESSING --------------------------------------- //-------------------------------------------------- COMMAND PROCESSING ---------------------------------------
-void processCommand(String command) { +void processCommand(const char *command) { 
-  // Check if command is special (starts with '!') +  // System commands starting with '!' 
-  if (command.startsWith("!")) {+  if (command[0] == '!') {
     if (startsWithProgmem(command, cmd_list)) {     if (startsWithProgmem(command, cmd_list)) {
       listKeyValues();       listKeyValues();
Line 391: Line 563:
       processOrientCommand(command);       processOrientCommand(command);
     } else {     } else {
-      Serial.println(F("Error: Unknown command"));+      Serial.println(F("Error: bad command"));
     }     }
-  } else { +  } 
-    // Assume key-value pair in the form "KEY VALUE" +  // Key-value pair commands must start with '@' 
-    int spaceIndex = command.indexOf(' '); +  else if (command[0] == '@') { 
-    Serial.print(F("Space index found at: "));+    // Skip '@' and following whitespace. 
 +    const char *ptr = command + 1; 
 +    while (*ptr == ' ' || *ptr == '\t') { 
 +      ptr++; 
 +    } 
 +    int spaceIndex = -1; 
 +    for (int i = 0; ptr[i] != '\0'; i++) { 
 +      if (ptr[i] == ' ') { 
 +        spaceIndex = i; 
 +        break; 
 +      } 
 +    } 
 +    Serial.print(ptr); 
 +    Serial.print(F("Space @: "));
     Serial.println(spaceIndex);     Serial.println(spaceIndex);
     if (spaceIndex == -1) {     if (spaceIndex == -1) {
-      Serial.println(F("Error: Command format incorrect. Expected 'KEY VALUE'"));+      Serial.println(F("Error: Expected '@KEY VALUE'"));
     } else {     } else {
-      String key = command.substring(0spaceIndex); +      char key[MAX_KEY_LENGTH+1]; 
-      String value command.substring(spaceIndex + 1); +      char value[MAX_VALUE_LENGTH+1]; 
-      key.trim(); +      int i; 
-      value.trim();+      // Copy at most MAX_KEY_LENGTH characters for the key. 
 +      for (i = 0; i < spaceIndex && i < MAX_KEY_LENGTH; i++
 +        key[i] = ptr[i]
 +      
 +      key[i] '\0'; 
 +      int j = 0; 
 +      // Copy at most MAX_VALUE_LENGTH characters for the value. 
 +      for (int k = spaceIndex + 1; ptr[k] != '\0' && j < MAX_VALUE_LENGTH; k++, j++
 +        value[j] = ptr[k]
 +      } 
 +      value[j] = '\0';
       Serial.print(F("Key extracted: '"));       Serial.print(F("Key extracted: '"));
       Serial.print(key);       Serial.print(key);
Line 412: Line 607:
       setKeyValue(key, value);       setKeyValue(key, value);
     }     }
 +  } else {
 +    Serial.println(F("Error: Invalid command. Key-value commands must start with '@'"));
   }   }
 } }
  
-void setKeyValue(String key, String value) { +void setKeyValue(const char *key, const char *value) { 
-  // Update if key exists+  // Update if key exists.
   for (int i = 0; i < keyCount; i++) {   for (int i = 0; i < keyCount; i++) {
-    if (keyValueStore[i].key == key) { +    if (strcmp(keyValueStore[i].key, key) == 0) { 
-      keyValueStore[i].value value;+      strncpy(keyValueStore[i].valuevalue, MAX_VALUE_LENGTH); 
 +      keyValueStore[i].value[MAX_VALUE_LENGTH] = '\0';
       Serial.print(F("Updated "));       Serial.print(F("Updated "));
       Serial.print(key);       Serial.print(key);
Line 428: Line 626:
     }     }
   }   }
-  // Add new key-value pair if space is available+  // Add new key-value pair if space is available.
   if (keyCount < MAX_KEYS) {   if (keyCount < MAX_KEYS) {
-    keyValueStore[keyCount].key key; +    strncpy(keyValueStore[keyCount].keykey, MAX_KEY_LENGTH)
-    keyValueStore[keyCount].value = value;+    keyValueStore[keyCount].key[MAX_KEY_LENGTH] '\0'; 
 +    strncpy(keyValueStore[keyCount].value, value, MAX_VALUE_LENGTH); 
 +    keyValueStore[keyCount].value[MAX_VALUE_LENGTH] = '\0';
     keyCount++;     keyCount++;
     Serial.print(F("Added "));     Serial.print(F("Added "));
Line 439: Line 639:
     saveKeyValuesToEEPROM();     saveKeyValuesToEEPROM();
   } else {   } else {
-    Serial.println(F("Error: Key store is full"));+    Serial.println(F("Error: full"));
   }   }
 } }
Line 453: Line 653:
  
 void nukeKeyValues() { void nukeKeyValues() {
-  keyCount = 0;  // Clear the store by resetting the count+  keyCount = 0;
   Serial.println(F("All key-value pairs cleared."));   Serial.println(F("All key-value pairs cleared."));
   saveKeyValuesToEEPROM();   saveKeyValuesToEEPROM();
Line 462: Line 662:
 } }
  
-void processOrientCommand(String command) { +void processOrientCommand(const char *command) { 
-  int spaceIndex = command.indexOf(' '); +  // Process orientation command if needed.
-  if (spaceIndex == -1) { +
-    Serial.println(F("Error: !ORIENT requires an argument (0-3)")); +
-    return; +
-  } +
-  String arg = command.substring(spaceIndex + 1); +
-  arg.trim(); +
-  Serial.print(F("Orientation argument extracted: '")); +
-  Serial.print(arg); +
-  Serial.println(F("'")); +
-  int newOrient = arg.toInt(); +
-  if (newOrient < 0 || newOrient > 3) { +
-    Serial.println(F("Error: Orientation must be between 0 and 3.")); +
-  } else { +
-    orient = newOrient; +
-    Serial.print(F("Orientation set to ")); +
-    Serial.println(orient); +
-  }+
 } }
  
-// Helper function: compare a String to a command stored in PROGMEM. +// Helper: compare a C string to a command stored in PROGMEM. 
-bool startsWithProgmem(const String &s, const char *progmemStr) { +bool startsWithProgmem(const char *s, const char *progmemStr) { 
-  char buffer[20]; // Adjust size if commands grow longer+  char buffer[20];
   strcpy_P(buffer, progmemStr);   strcpy_P(buffer, progmemStr);
-  return s.startsWith(buffer);+  int len = strlen(buffer); 
 +  return (strncmp(s, buffer, len) == 0);
 } }
  
Line 503: Line 687:
 </code> </code>
  
-Python:+==== Code DumpPython (Windows/Linux) ==== 
 <code python> <code python>
 import serial import serial
Line 681: Line 866:
     # Query version     # Query version
     response_ver = send_command(ser, "!VER")     response_ver = send_command(ser, "!VER")
-    print(f"Response from !VER: {response_ver}")+    print(f"Flush !VER: {response_ver}"
 +     
 +    response_ver = send_command(ser, "!VER"
 +    print(f"Response from !VER: {response_ver}"    
 +    
     if "Displayinator v1.0" not in response_ver:     if "Displayinator v1.0" not in response_ver:
         print(f"Error: Version mismatch or no valid response from device. Expected 'Displayinator v1.0' but received: '{response_ver}'")         print(f"Error: Version mismatch or no valid response from device. Expected 'Displayinator v1.0' but received: '{response_ver}'")
Line 707: Line 896:
         print("Stored key-value pairs are up-to-date. No need to issue !NUKE.")         print("Stored key-value pairs are up-to-date. No need to issue !NUKE.")
  
-    # Update hostname regardless; after nuke it should be empty +    # Update hostname regardless; after nuke it should be empty
-    cmd_hostname = f"Hostname {local_hostname}"+    # Prepend '@' to indicate key-value command. 
 +    cmd_hostname = f"@Hostname {local_hostname}"
     print(f"Updating hostname: {cmd_hostname}")     print(f"Updating hostname: {cmd_hostname}")
     update_response = send_command(ser, cmd_hostname)     update_response = send_command(ser, cmd_hostname)
Line 716: Line 906:
     for index, ip in enumerate(local_ips, start=1):     for index, ip in enumerate(local_ips, start=1):
         key = f"IP{index}"         key = f"IP{index}"
-        cmd_ip = f"{key} {ip}"+        cmd_ip = f"@{key} {ip}"
         print(f"Updating IP with command: {cmd_ip}")         print(f"Updating IP with command: {cmd_ip}")
         update_response = send_command(ser, cmd_ip)         update_response = send_command(ser, cmd_ip)
Line 726: Line 916:
     if len(default_gateways) == 1:     if len(default_gateways) == 1:
         # Only one gateway; use key "GW"         # Only one gateway; use key "GW"
-        cmd_gw = f"GW {default_gateways[0]}"+        cmd_gw = f"@GW {default_gateways[0]}"
         print(f"Updating gateway with command: {cmd_gw}")         print(f"Updating gateway with command: {cmd_gw}")
         update_response = send_command(ser, cmd_gw)         update_response = send_command(ser, cmd_gw)
Line 734: Line 924:
         for index, gw in enumerate(default_gateways, start=1):         for index, gw in enumerate(default_gateways, start=1):
             key = f"GW{index}"             key = f"GW{index}"
-            cmd_gw = f"{key} {gw}"+            cmd_gw = f"@{key} {gw}"
             print(f"Updating gateway with command: {cmd_gw}")             print(f"Updating gateway with command: {cmd_gw}")
             update_response = send_command(ser, cmd_gw)             update_response = send_command(ser, cmd_gw)
Line 741: Line 931:
     # Retrieve and update WAN IP address using key "WAN"     # Retrieve and update WAN IP address using key "WAN"
     wan_ip = get_wan_ip()     wan_ip = get_wan_ip()
-    cmd_wan = f"WAN {wan_ip}"+    cmd_wan = f"@WAN {wan_ip}"
     print(f"Updating WAN IP with command: {cmd_wan}")     print(f"Updating WAN IP with command: {cmd_wan}")
     update_response = send_command(ser, cmd_wan)     update_response = send_command(ser, cmd_wan)
Line 752: Line 942:
     main()     main()
 </code> </code>
 +
 +==== Code Dump: Bash (TrueNAS Scale) ====
 +
 +<code bash>
 +#!/bin/bash
 +# Script for TrueNAS Scale 24 to interface with a USB serial display.
 +# Advanced operations: validate device response, gather network and storage info,
 +# update key-value pairs on the display, and periodically refresh pool usage if changed.
 +#
 +# Modify the serial port based on the device it shows up in dmesg
 +# launch with 'nohup ./truenasserialdisplay.sh &'
 +
 +SERIAL_PORT="/dev/ttyACM0"
 +BAUD=115200
 +
 +# Delay settings (in seconds)
 +SLEEP_BOOT=3
 +SLEEP_AFTER_CMD=1
 +UPDATE_INTERVAL=15
 +
 +# Function to send a command to the device and read back the response.
 +# Usage: send_cmd "COMMAND"
 +send_cmd() {
 +  local cmd="$1"
 +  printf "%s\n" "$cmd" >&3
 +  sleep "$SLEEP_AFTER_CMD"
 +  read -t 1 -r DEVICE_RESPONSE <&3
 +  echo "Device response: $DEVICE_RESPONSE"
 +}
 +
 +# Configure the serial port (8N1, no parity)
 +stty -F "$SERIAL_PORT" "$BAUD" cs8 -cstopb -parenb
 +
 +# Open the serial port for reading and writing (using file descriptor 3)
 +exec 3<> "$SERIAL_PORT"
 +if [ $? -ne 0 ]; then
 +  echo "Failed to open $SERIAL_PORT"
 +  exit 1
 +fi
 +
 +echo "Waiting ${SLEEP_BOOT} seconds for system boot up..."
 +sleep "$SLEEP_BOOT"
 +
 +send_cmd "!VER"
 +
 +# Flush any initial device data
 +read -t 1 -r DEVICE_RESPONSE <&3
 +echo "2nd Flush of Device Buffers: $DEVICE_RESPONSE"
 +
 +echo "Querying Device"
 +# Send the "!VER" command to the device (system command remains unchanged)
 +send_cmd "!VER"
 +
 +# Validate that the response starts with "Displayinator"
 +if [[ $DEVICE_RESPONSE != Displayinator* ]]; then
 +  echo "Error: Device did not report a valid version. Got: $DEVICE_RESPONSE"
 +  exec 3>&-
 +  exit 1
 +fi
 +
 +# Get OS version from /etc/version
 +if [[ -f /etc/version ]]; then
 +  OS_VERSION=$(cat /etc/version)
 +  echo "OS version from /etc/version: $OS_VERSION"
 +else
 +  echo "File /etc/version not found."
 +  exec 3>&-
 +  exit 1
 +fi
 +
 +# Get non-loopback IPv4 addresses
 +mapfile -t IP_ADDRESSES < <(ip -4 addr show | grep -v "127.0.0.1" | grep "inet " | awk '{print $2}' | cut -d'/' -f1)
 +# Get primary gateway (assumes default route)
 +GATEWAY=$(ip route | awk '/^default/ {print $3; exit}')
 +
 +echo "IP Addresses: ${IP_ADDRESSES[*]}"
 +echo "Gateway: $GATEWAY"
 +
 +# Automatically determine the pool names using zpool.
 +readarray -t POOLS < <(zpool list -H -o name)
 +if [ ${#POOLS[@]} -eq 0 ]; then
 +  echo "No ZFS pools found."
 +  exec 3>&-
 +  exit 1
 +fi
 +echo "Pools found: ${POOLS[*]}"
 +
 +# Get ZFS pool info using zfs list for all detected pools.
 +POOL_INFO=$(zfs list -H -o name,used,available "${POOLS[@]}")
 +
 +# Function to convert human-readable size (e.g., 2.66G) to bytes.
 +convert_to_bytes() {
 +    local value="$1"
 +    local number unit multiplier
 +    number=$(echo "$value" | sed -E 's/([^0-9.]*)([0-9.]+)([A-Za-z]*)/\2/')
 +    unit=$(echo "$value" | sed -E 's/([^0-9.]*)([0-9.]+)([A-Za-z]*)/\3/' | tr '[:lower:]' '[:upper:]')
 +    case "$unit" in
 +        T) multiplier=1099511627776 ;;
 +        G) multiplier=1073741824 ;;
 +        M) multiplier=1048576 ;;
 +        K) multiplier=1024 ;;
 +        *) multiplier=1 ;;
 +    esac
 +    awk -v num="$number" -v mult="$multiplier" 'BEGIN { printf "%.0f", num * mult }'
 +}
 +
 +# Function to convert bytes to a human-readable format.
 +human_readable() {
 +    local bytes="$1"
 +    awk -v bytes="$bytes" 'BEGIN {
 +      if (bytes >= 1099511627776) {
 +        printf "%.2fT", bytes/1099511627776;
 +      } else if (bytes >= 1073741824) {
 +        printf "%.2fG", bytes/1073741824;
 +      } else if (bytes >= 1048576) {
 +        printf "%.2fM", bytes/1048576;
 +      } else if (bytes >= 1024) {
 +        printf "%.2fK", bytes/1024;
 +      } else {
 +        printf "%dB", bytes;
 +      }
 +    }'
 +}
 +
 +# Process each pool line and prepare storage info lines.
 +# Expected POOL_INFO output per line: "poolname used available"
 +STORAGE_LINES=()
 +while read -r pool used avail; do
 +  used_bytes=$(convert_to_bytes "$used")
 +  avail_bytes=$(convert_to_bytes "$avail")
 +  total_bytes=$(( used_bytes + avail_bytes ))
 +  human_used=$(human_readable "$used_bytes")
 +  human_total=$(human_readable "$total_bytes")
 +  # Prepend a caret to mark this key as temporary
 +  STORAGE_LINES+=("^$pool $human_used/$human_total")
 +done <<< "$POOL_INFO"
 +
 +# Send initial key-value pairs to the device (each key-value command now starts with '@')
 +
 +# OS version (TrueNAS version)
 +send_cmd "@TrueNAS $OS_VERSION"
 +
 +# IP addresses: if multiple, number them; if one, use unnumbered key.
 +if [ ${#IP_ADDRESSES[@]} -gt 1 ]; then
 +  index=1
 +  for ip in "${IP_ADDRESSES[@]}"; do
 +    send_cmd "@IP${index} ${ip}"
 +    ((index++))
 +  done
 +elif [ ${#IP_ADDRESSES[@]} -eq 1 ]; then
 +  send_cmd "@IP ${IP_ADDRESSES[0]}"
 +fi
 +
 +# Primary gateway.
 +send_cmd "@GW $GATEWAY"
 +
 +# Storage info for each pool (e.g., "@^boot-pool 2.66G/12.11G")
 +for line in "${STORAGE_LINES[@]}"; do
 +  send_cmd "@$line"
 +done
 +
 +# Store the initial storage info for each pool in an associative array.
 +declare -A PREV_STORAGE
 +for line in "${STORAGE_LINES[@]}"; do
 +    key=$(echo "$line" | awk '{print $1}')
 +    value=$(echo "$line" | cut -d' ' -f2-)
 +    PREV_STORAGE["$key"]="$value"
 +done
 +
 +echo "Initial storage values set. Entering update loop..."
 +
 +#echo EXITING for DEBUG!
 +#exec 3>&-
 +#exit 0
 +
 +# Trap to close the serial port on exit.
 +cleanup() {
 +  echo "Cleaning up..."
 +  exec 3>&-
 +  exit 0
 +}
 +trap cleanup SIGINT SIGTERM
 +
 +# Infinite loop: every 15 seconds, re-check pool usage and update device if changed.
 +while true; do
 +  sleep "$UPDATE_INTERVAL"
 +  for pool in "${POOLS[@]}"; do
 +    pool_info=$(zfs list -H -o name,used,available "$pool")
 +    cur_pool=$(echo "$pool_info" | awk '{print $1}')
 +    used=$(echo "$pool_info" | awk '{print $2}')
 +    avail=$(echo "$pool_info" | awk '{print $3}')
 +    used_bytes=$(convert_to_bytes "$used")
 +    avail_bytes=$(convert_to_bytes "$avail")
 +    total_bytes=$(( used_bytes + avail_bytes ))
 +    human_used=$(human_readable "$used_bytes")
 +    human_total=$(human_readable "$total_bytes")
 +    new_value="$human_used/$human_total"
 +
 +    # Caret-prefixed key for temporary display
 +    key="^$cur_pool"
 +
 +    # If the new value is different from the stored value, update the display.
 +    if [[ "${PREV_STORAGE[$key]}" != "$new_value" ]]; then
 +      echo "Updating pool $cur_pool: was ${PREV_STORAGE[$key]}, now $new_value"
 +      send_cmd "@$key $new_value"
 +      PREV_STORAGE["$key"]="$new_value"
 +    fi
 +  done
 +done
 +</code>
 +
  
server_status_oled_display.1742541965.txt.gz · Last modified: 2025/03/21 07:26 by kenson

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki