×
// Audio_Monitor_R2.ino
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DHT.h>

// ===== WiFi設定 =====
const char* ssid     = "私のSSID";
const char* password = "私のパスワード";

// ===== DHT11 =====

#define DHTPIN 13
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

float currentTemp = 0;
float currentHumidity = 0;

String tempStatus = "OK";

const float TEMP_WARNING = 30.0;
const float TEMP_ERROR   = 35.0;

// ===== Roon状態変数 =====
String roonStatus     = "UNKNOWN";
String roonVersion    = "-";
String roonUptimeText = "-";
String rockVersion    = "-";
String rockUptime     = "-";
String storageInfo    = "-";
String storageFree    = "-";
String rockIP         = "-";

unsigned long lastRoonCheck = 0;

// ===== Webサーバー =====
WebServer server(80);

// =====================================
// uptime秒 → xxd xxh
// =====================================
String formatUptime(unsigned long sec)
{
  int days  = sec / 86400;
  int hours = (sec % 86400) / 3600;

  return String(days) + "d " +
         String(hours) + "h";
}

// =====================================
// Roon状態取得
// =====================================
void updateRoonStatus()
{
  HTTPClient http;

  http.begin("http://<ROCKのIP-Adress>/1/getstate");

  http.addHeader(
    "Content-Type",
    "application/x-www-form-urlencoded"
  );

  int httpCode = http.POST("");

  if (httpCode > 0)
  {
    String payload = http.getString();

    Serial.println("===== ROON JSON =====");
    Serial.println(payload);
    Serial.println("=====================");

    DynamicJsonDocument doc(8192);

    DeserializationError error =
      deserializeJson(doc, payload);

    if (!error)
    {
      bool running =
        doc["data"]["state"]["roon_running"];

      roonStatus =
        running ? "ONLINE" : "OFFLINE";

      roonVersion =
        doc["data"]["state"]["roon_version"]
          .as<String>();

      rockVersion =
        doc["data"]["device_display_version"]
          .as<String>();

      rockIP =
        doc["data"]["interfaces"][0]["state"]["ip"]
          .as<String>();

      unsigned long rockSec =
        String(doc["data"]["state"]["uptime"])
          .toInt();

      unsigned long roonSec =
        String(doc["data"]["state"]["roon_uptime"])
          .toInt();

      rockUptime =
        formatUptime(rockSec);

      roonUptimeText =
        formatUptime(roonSec);

      storageInfo =
        doc["data"]["state"]
           ["internal_storage"]["info"]
          .as<String>();

      uint64_t used =
        doc["data"]["state"]
           ["internal_storage"]["data_used"];

      uint64_t total =
        doc["data"]["state"]
           ["internal_storage"]["data_total"];

      float freeGB =
        (float)(total - used)
        / 1024.0
        / 1024.0;

      float totalGB =
        (float)total
        / 1024.0
        / 1024.0;

      int percent =
        ((total - used) * 100) / total;

      storageFree =
        String((int)freeGB)
        + " GB / "
        + String((int)totalGB)
        + " GB ("
        + String(percent)
        + "% available)";
    }
    else
    {
      roonStatus = "JSON ERROR";
    }
  }
  else
  {
    roonStatus = "NO RESPONSE";
  }

  http.end();
}

// =====================================
// DHT11取得
// =====================================

void updateEnvironment()
{
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (!isnan(h) && !isnan(t))
  {
    currentHumidity = h;
    currentTemp = t;
  }
  
  if (t >= TEMP_ERROR)
  {
    tempStatus = "ERROR";
  }
  else if (t >= TEMP_WARNING)
  {
    tempStatus = "WARNING";
  }
  else
  {
    tempStatus = "OK";
  }
}

// =====================================
// トップページ
// =====================================
void handleRoot()
{
  String html;

  html += "<html>";
  html += "<head>";

  html += "<title>Audio System Monitoring</title>";

  html += "<meta http-equiv='refresh' content='30'>";

  html += "<style>";

  html += "body{font-family:Arial;margin:20px;}";

  html += "table{width:100%;border-collapse:collapse;}";

  html += "td{padding:8px;border-bottom:1px solid #cccccc;}";

  html += ".ok{color:green;font-weight:bold;}";
  html += ".ng{color:red;font-weight:bold;}";
  html += ".warning{color:orange;font-weight:bold;}";

  html += "</style>";

  html += "</head>";
  html += "<body>";

  html += "<h1>Audio System Monitoring</h1>";

  html += "<hr>";
  html += "<img src = 'https://keroyon-audio.com/wp-content/uploads/2026/06/audio_dashboard.jpg' width=100%>";

  html += "<h2>System Health : ";

  if (roonStatus == "ONLINE")
  {
    html += "<span class='ok'>OK</span>";
  }
  else
  {
    html += "<span class='ng'>ERROR</span>";
  }

  html += "</h2>";

  html += "<p>Audit since update : ";
  html += String(millis() / 60000);
  html += " min.</p>";

  html += "<h2>Roon ROCK</h2>";

  html += "<table>";

  html += "<tr>";
  html += "<td>Roon OS</td>";
  html += "<td class='ok'>OK</td>";
  html += "<td>Version : ";
  html += rockVersion;
  html += " Running : ";
  html += rockUptime;
  html += "</td>";
  html += "</tr>";

  html += "<tr>";
  html += "<td>Roon Server</td>";
  html += "<td class='ok'>OK</td>";
  html += "<td>Version : ";
  html += roonVersion;
  html += " Running : ";
  html += roonUptimeText;
  html += "</td>";
  html += "</tr>";

  html += "<tr>";
  html += "<td>Music Storage</td>";
  html += "<td class='ok'>OK</td>";
  html += "<td>";
  html += storageFree;
  html += "</td>";
  html += "</tr>";

  html += "<tr>";
  html += "<td>ROCK IP Address</td>";
  html += "<td></td>";
  html += "<td>";
  html += rockIP;
  html += "</td>";
  html += "</tr>";

  html += "<tr>";
  html += "<td>ROCK-side Temp.</td>";
  if(tempStatus == "OK")
  {
    html += "<td class='ok'>OK</td>";
  }
  else if(tempStatus == "WARNING")
  {
    html += "<td class='warning'>WARNING</td>";
  }
  else
  {
    html += "<td class='ng'>NG</td>";
  }
  html += "<td>";
  html += String(currentTemp,1);
  html += " degrees";
  html += "</td>";
  html += "</tr>";

  html += "<tr>";
  html += "<td>ROCK-side Humidity</td>";
  html += "<td class='ok'>OK</td>";
  html += "<td>";
  html += String(currentHumidity,1);
  html += " %";
  html += "</td>";
  html += "</tr>";

  html += "<tr>";
  html += "<td>External Fan Noise</td>";
  html += "<td class='ng'>NG</td>";
  html += "<td>Sensor Not Installed</td>";
  html += "</tr>";

  html += "<tr>";
  html += "<td>Playback Now</td>";
  html += "<td></td>";
  html += "<td>Not Implemented</td>";
  html += "</tr>";

  html += "</table>";

  html += "<hr>";

  html += "<small>";
  html += "ESP32 : ";
  html += WiFi.localIP().toString();

  html += " | RSSI ";
  html += String(WiFi.RSSI());

  html += " dBm";

  html += " | Heap ";
  html += String(ESP.getFreeHeap());

  html += "</small>";

  html += "</body>";
  html += "</html>";

  server.send(200, "text/html", html);
}

// =====================================
// setup
// =====================================
void setup()
{
  Serial.begin(115200);

  delay(3000);

  dht.begin();
  Serial.println("DHT11 READY");

  Serial.println();
  Serial.println("BOOT OK");

  WiFi.mode(WIFI_STA);

  Serial.println("CONNECTING...");

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.println("WiFi CONNECTED");

  Serial.print("IP ADDRESS: ");
  Serial.println(WiFi.localIP());

  updateRoonStatus();

  server.on("/", handleRoot);

  server.begin();

  Serial.println("WEB SERVER STARTED");
}

// =====================================
// loop
// =====================================
void loop()
{
  server.handleClient();

  if (millis() - lastRoonCheck > 10000)
  {
    lastRoonCheck = millis();

    updateRoonStatus();
    updateEnvironment();

    Serial.println("ROON CHECK");
    Serial.println(roonStatus);
    Serial.println(roonVersion);
  }
}

投稿者

KeroYon