Will Robbins
Published © MIT

PC Hardware Monitor - Real-Time Cloud Dashboard

I built a remote PC monitoring system that lets me check my computer's performance from anywhere. My Particle device connects to my PC, capt

IntermediateFull instructions provided10 hours13
PC Hardware Monitor - Real-Time Cloud Dashboard

Things used in this project

Hardware components

Photon 2
Particle Photon 2
×1
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1
LED (generic)
LED (generic)
Not fully needed
×3
Resistor 220 ohm
Resistor 220 ohm
only needed if using leds
×1
Breadboard (generic)
Breadboard (generic)
only needed if using leds
×1
Jumper wires (generic)
Jumper wires (generic)
only needed if using leds
×1

Software apps and online services

Particle Build Web IDE
Particle Build Web IDE
for coding the Particle photon 2
GitHub API
VS Code
Microsoft VS Code
for coding in c++ terminal monitoring and so on

Story

Read more

Code

Particle Firmware (main .ino file)

C/C++
the code on your particle board
SYSTEM_MODE(AUTOMATIC);
SerialLogHandler logHandler(LOG_LEVEL_INFO);

int toggle_led = 4;
int onoffled = 5;
bool isOn = false;
String input;
String type;
String sectype;
String ep;
String currentSha = "[YOUR_INITIAL_SHA_HERE]"; // Get this from your GitHub file
bool shaRequested = false;
bool on;

// Base64 encoding table
const char base64_chars[] = 
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/";

// Base64 encode function
String base64_encode(String input) {
    String output = "";
    int val = 0;
    int valb = -6;
    
    for (unsigned int i = 0; i < input.length(); i++) {
        unsigned char c = input.charAt(i);
        val = (val << 8) + c;
        valb += 8;
        while (valb >= 0) {
            output += base64_chars[(val >> valb) & 0x3F];
            valb -= 6;
        }
    }
    
    if (valb > -6) {
        output += base64_chars[((val << 8) >> (valb + 8)) & 0x3F];
    }
    
    while (output.length() % 4) {
        output += '=';
    }
    
    return output;
}

// Escape quotes in strings for JSON
String escapeJson(String str) {
    String result = "";
    for (unsigned int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        if (c == '"' || c == '\\') {
            result += '\\';
        }
        result += c;
    }
    return result;
}

void shaResponseHandler(const char *event, const char *data) {
    Serial.println("Hook response received!");
    if (data) {
        currentSha = String(data);
        currentSha.trim();
        Serial.println("SHA updated: " + currentSha);
        shaRequested = false;
    } else {
        Serial.println("No data in response");
    }
}

void uploadResponseHandler(const char *event, const char *data) {
    Serial.println("Upload response received!");
    if (data) {
        String newSha = String(data);
        newSha.trim();
        Serial.println("New SHA from upload: " + newSha);
        if (newSha.length() > 10) {
            currentSha = newSha;
            Serial.println("SHA auto-updated for next upload");
        }
    }
}

void setup() {
    pinMode(toggle_led, OUTPUT);
    pinMode(onoffled, OUTPUT);
    pinMode(7, OUTPUT);
    Particle.function("blink", blinkLED);
    Particle.function("setSha", setSha);
    
    Particle.subscribe("hook-response/[YOUR_SHA_WEBHOOK_NAME]/", shaResponseHandler, MY_DEVICES);
    Particle.subscribe("hook-response/[YOUR_UPLOAD_WEBHOOK_NAME]/", uploadResponseHandler, MY_DEVICES);
    
    digitalWrite(onoffled, LOW);
    digitalWrite(toggle_led, LOW);
    Serial.begin(9600);
    
    Serial.println("Setup complete, requesting SHA...");
    delay(5000);
    Particle.publish("[YOUR_SHA_WEBHOOK_NAME]", "", PRIVATE);
    shaRequested = true;
}

int setSha(String sha) {
    currentSha = sha;
    Serial.println("SHA updated via function: " + sha);
    Particle.publish("sha_update", sha, PRIVATE);
    return 1;
}

void loop() {
    static int dataCount = 0;
    const int expectedDataCount = 12;

    if (isOn && Serial.available()) {
        input = Serial.readStringUntil('\n');
        input.trim();
        Serial.println("Received: '" + input + "'");

        if (input == "END") {
            Serial.println("Received END marker, all data received");
            Serial.println("DONE");
            Serial.println("All data uploaded and DONE sent to serial.");
            dataCount = 0;
            Serial.println("Ready for next cycle");
        } else if (input.length() >= 5 && input.charAt(1) == ' ') {
            Serial.println("OK");
            String out = check(input);
            if(out != "Requesting SHA..." && out!="C s -2147483648 not understood" && out!="" && out.indexOf("sha") == -1)
            {
                Serial.println("Original: " + out);
                String encoded = base64_encode(out);
                Serial.println("Encoded: " + encoded);
                Serial.println("Sha: "+currentSha);
                
                if (currentSha == "" || currentSha.length() < 10)
                {
                    if (!shaRequested)
                    {
                        Serial.println("No valid SHA, requesting...");
                        Particle.publish("[YOUR_SHA_WEBHOOK_NAME]", "", PRIVATE);
                        shaRequested = true;
                    }
                    delay(5000);
                }

                if (currentSha != "" && currentSha.length() > 10)
                {
                    String jsonPayload = "{\"content\":\"" + escapeJson(encoded) +
                                       "\",\"sha\":\"" + escapeJson(currentSha) + "\"}";

                    Serial.println("Publishing JSON payload");
                    Serial.println("SHA: " + currentSha);
                    Particle.publish("[YOUR_UPLOAD_WEBHOOK_NAME]", jsonPayload, PRIVATE);

                    digitalWrite(toggle_led, HIGH);
                    delay(200);
                    digitalWrite(toggle_led, LOW);
                }
                else
                {
                    Serial.println("Still no SHA available, skipping upload");
                }

                dataCount++;
            }
        }
        else
        {
            Serial.println("Invalid input received: '" + input + "'");
        }
    }
    delay(100);
}

bool numcheck(String oneinput) 
{
    int good = 0;
    String nums = "1234567890";
    for(int k = 0; k < oneinput.length(); k++) 
    {
        for (int i = 0; i < nums.length(); i++) 
        {
            if (oneinput.charAt(k) == nums.charAt(i)) 
            {
                good++;
            }
        }
    }
    return (oneinput.length() == good);
}

bool capcheck(char oneinput) 
{
    String cap = "QWERTYUIOPASDFGHJKLZXCVBNM";
    for (int i = 0; i < cap.length(); i++) 
    {
        if (oneinput == cap.charAt(i)) 
        {
            return true;
        }
    }
    return false;
}

bool lowercheck(char n) 
{
    String lett = "qwertyuiopasdfghjklzxcvbnm";
    for (int i = 0; i < lett.length(); i++) 
    {
        if (lett.charAt(i) == n) 
        {
            return true;
        }
    }
    return false;
}

String check(String &in) {
    int line = in.indexOf('\n');
    int pause=reciving();
    if (line != -1) 
    {
        ep = in.substring(0, line);
        in = in.substring(line + 1); 
    } 
    else 
    {
        ep = in;
        in = "";
    }
    
    if (ep.length() < 3) 
    {
        return "input too short";
    }
    
    char M = ep.charAt(0);
    char S = ep.charAt(2);
    String V = ep.substring(4);
    
    if (capcheck(M) && lowercheck(S) && numcheck(V)) 
    {
        switch (M) 
        {
            case 'C': type = "cpu"; break;
            case 'G': type = "gpu"; break;
            case 'R': type = "ram"; break;
            case 'S': type = "ssd"; break;
            default: return "invalid input " + M;
        }
        
        switch (S) 
        {
            case 't': sectype = "temp"; break;
            case 'u': sectype = "usage"; break;
            case 's': sectype = "speed"; break;
            case 'p': sectype = "power"; break;
            default: return S + " not recognized";
        }
    } 
    else 
    {
        return ep + " not understood";
    }
    
    return type + " " + sectype + " " + V;
}

int reciving()
{
    if(on)
    {
        digitalWrite(7, LOW);
    }
    else
    {
        digitalWrite(7, HIGH);
    }
    on!=on;
    return 0;
}

int blinkLED(String param) {
    isOn = !isOn;
    digitalWrite(onoffled, isOn ? HIGH : LOW);
    
    digitalWrite(toggle_led, HIGH);
    delay(100);
    digitalWrite(toggle_led, LOW);
    
    return 0;
}

C++ Data Formatter ([YOUR_CPP_FILENAME].cpp)

C/C++
Data formater
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>

std::vector<std::string> parseRow(const std::string& row) {
    std::vector<std::string> cells;
    std::stringstream ss(row);
    std::string cell;
    while (std::getline(ss, cell, ',')) {
        cells.push_back(cell);
    }
    return cells;
}

int main() {
    std::ifstream infile("[YOUR_INPUT_FILE].txt");
    if (!infile.is_open()) {
        std::cerr << "Error: Cannot open [YOUR_INPUT_FILE].txt" << std::endl;
        return 1;
    }

    std::ofstream outfile("[YOUR_OUTPUT_FILE].txt", std::ios::app);
    if (!outfile.is_open()) {
        std::cerr << "Error: Cannot open [YOUR_OUTPUT_FILE].txt" << std::endl;
        infile.close();
        return 1;
    }

    std::string line;
    bool wrote = false;
    while (std::getline(infile, line)) {
        if (!line.empty()) {
            std::vector<std::string> cells = parseRow(line);
            for (size_t i = 0; i < cells.size(); ++i) {
                outfile << cells[i];
                if (i < cells.size() - 1) outfile << ",";
            }
            outfile << std::endl;
            wrote = true;
        }
    }
    infile.close();
    outfile.close();

    std::ofstream resetfile("[YOUR_INPUT_FILE].txt");
    if (resetfile.is_open()) {
        resetfile << "Hello World!" << std::endl;
        resetfile.close();
        std::cout << "Reset [YOUR_INPUT_FILE].txt to 'Hello World!'" << std::endl;
    } else {
        std::cerr << "Error: Cannot reset [YOUR_INPUT_FILE].txt" << std::endl;
        return 1;
    }

    if (wrote)
        std::cout << "Appended CSV lines from [YOUR_INPUT_FILE].txt to [YOUR_OUTPUT_FILE].txt and cleared [YOUR_INPUT_FILE].txt." << std::endl;
    else
        std::cout << "No lines to append. [YOUR_INPUT_FILE].txt cleared." << std::endl;
    return 0;
}

Web Dashboard ([YOUR_HTML_FILENAME].html)

HTML
Website portion of it
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>[YOUR_PROJECT_NAME] - PC Monitor Dashboard</title>
</head>
<body>
  <h1>PC Hardware Monitor</h1>
  <p id="particle-output">Waiting for updates...</p>
  <script src="[YOUR_JS_FILENAME].js"></script>
</body>
</html>

JavaScript Event Listener ([YOUR_JS_FILENAME].js)

JavaScript
the event listener for html
// REPLACE WITH YOUR ACTUAL TOKEN FROM PARTICLE CONSOLE
const PARTICLE_ACCESS_TOKEN = "[YOUR_PARTICLE_ACCESS_TOKEN_HERE]";

const eventSource = new EventSource(
  `https://api.particle.io/v1/devices/events?access_token=${PARTICLE_ACCESS_TOKEN}`
);

eventSource.onmessage = function(event) {
  const data = JSON.parse(event.data);

  if (data.event === "[YOUR_UPLOAD_WEBHOOK_NAME]") {
    const content = data.data;
    
    console.log("New Particle event:", content);

    const display = document.getElementById("particle-output");
    if (display) {
      display.textContent = content;
    }
  }
};

eventSource.onerror = function(err) {
  console.error("EventSource failed:", err);
};

Credits

Will Robbins
2 projects • 0 followers
Aspiring EE and MecE hopefully going into robotics, I like building machines and have been making projects like these for almost 3 years

Comments