// 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);
}
}