@andrew_cb Hi, I have updated the widget code a bit.
/usr/local/www/widgets/widgets/emmc_health.widget.php
<?php
require_once("functions.inc");
require_once("guiconfig.inc");
define('SEND_ALERTS', false); // Sending alerts not tested
define('AUTO_INSTALL_MMC_UTILS', false); // Don't presume to install packages on the system
// Function to retrieve eMMC health data
function get_emmc_health() {
// Check if eMMC device exists
if(!file_exists("/dev/mmcsd0rpmb")) {
//return ["status" => "error", "message" => "/dev/mmcsd0rpmb not found."];
}
// Check for the mmc-utils binary and install if missing
if(!file_exists("/usr/local/sbin/mmc")) {
if (AUTO_INSTALL_MMC_UTILS) {
exec("pkg install -y mmc-utils", $code);
if ($code <> 0) {
return ["status" => "error", "message" => "Failed to install mmc-utils."];
}
} else {
return ["status" => "error", "message" => "mmc-utils not installed."];
}
}
$cmd = "/usr/local/sbin/mmc extcsd read /dev/mmcsd0rpmb | egrep 'LIFE|EOL'";
$output = shell_exec($cmd);
if (!$output) {
return ["status" => "error", "message" => "Failed to retrieve eMMC health data."];
}
// Explode the output into separate lines (assumes the lines appear in the right order with no false positives)
$outputArray = explode("\n", $output);
// Get the value of 'TYP_A' (SLC) wear
preg_match('/.*TYP_A]:\s+(0x[0-9A-F]+)/i', $outputArray[0], $matchA);
// Get the value of 'TYP_B' (MLC) wear
preg_match('/.*TYP_B]:\s+(0x[0-9A-F]+)/i', $outputArray[1], $matchB);
// Get the value of Pre-EOL information
preg_match('/.*EOL_INFO]:\s+(0x[0-9A-F]+)/i', $outputArray[2], $matchC);
// Convert the wear values from hex to decimal
$lifeA = isset($matchA[1]) ? hexdec($matchA[1]) * 10 : null;
$lifeB = isset($matchB[1]) ? hexdec($matchB[1]) * 10 : null;
$eol = isset($matchC[1]) ? hexdec($matchC[1]) : null;
if (is_null($lifeA) || is_null($lifeB) || is_null($eol)) {
return ["status" => "error", "message" => "Invalid eMMC health data."];
}
return ["status" => "ok", "lifeA" => $lifeA, "lifeB" => $lifeB, "eol" => $eol];
}
// Determine color class based on wear level
function get_wear_color_class($value) {
if ($value < 70) {
return "success"; // Green
} elseif ($value < 90) {
return "warning"; // Yellow
} else {
return "danger"; // Red
}
}
function get_eol_color_class($value) {
switch ($value) {
case 0x01: return "success"; // Green
case 0x02: return "warning"; // Yellow
case 0x03: return "danger"; // Red
}
return "warning";
if ($value < 1) {
return "warning"; // Yellow
} elseif ($value < 2) {
return "success"; // Green
} elseif ($value < 3) {
return "warning"; // Yellow
} else {
return "danger"; // Red
}
}
function get_eol_percent($value) {
switch ($value) {
case 0x01: return "< 80%";
case 0x02: return "> 80%";
case 0x03: return "> 90%";
}
return "Not defined";
if ($value < 1) {
return "Not defined";
} elseif ($value < 2) {
return "< 80%";
} elseif ($value < 3) {
return "> 80%";
} else {
return "> 90%";
}
}
// Send email notification if wear level is critical
function send_emmc_alert($lifeA, $lifeB, $eol) {
global $config;
$eol_percent = get_eol_percent($eol);
$subject = "[pfSense] eMMC Wear Level Warning";
$message = "Warning: eMMC wear level is high!\n\n" .
"Life A: {$lifeA}%\nLife B: {$lifeB}%\nEOL: {$eol_percent}\n\n" .
"Consider replacing the storage device.";
if ($lifeA >= 90 || $lifeB >= 90 || $eol >= 3) {
notify_via_smtp($subject, $message);
}
}
// Main program logic
// Get get the eMMC health data
$data = get_emmc_health();
if (SEND_ALERTS && $data["status"] === "ok") {
// Send an email notification if eMMC is not healthy
send_emmc_alert($data["lifeA"], $data["lifeB"], $data["eol"]);
}
// Format the data into HTML for display in the widget
?><div class="panel panel-default">
<!--div class="panel-heading">
<h3 class="panel-title">eMMC Disk Health (redundant header)</h3>
</div-->
<div class="panel-body">
<?php if ($data["status"] === "error"): ?>
<div class="alert alert-danger"><?php echo $data["message"]; ?></div>
<?php else: ?>
<table class="table">
<tr>
<th>Type A Wear (Lower is better)</th>
<td class="bg-<?php echo get_wear_color_class($data['lifeA']); ?>"> <?php echo ($data['lifeA'] - 10) . "-" . $data['lifeA']; ?>%</td>
</tr>
<tr>
<th>Type B Wear (Lower is better)</th>
<td class="bg-<?php echo get_wear_color_class($data['lifeB']); ?>"> <?php echo ($data['lifeB'] - 10) . "-" . $data['lifeB']; ?>%</td>
</tr>
<tr>
<th>Pre-EOL (% of reserved blocks used)</th>
<td class="bg-<?php echo get_eol_color_class($data['eol']); ?>"> <?php echo get_eol_percent($data['eol']); ?></td>
</tr>
</table>
<?php endif; ?>
</div>
</div>
And a quick include to neaten up the title (which removes the need for the redundant panel heading):
/usr/local/www/widgets/include/emmc_health.inc
<?php // /usr/local/www/widgets/include/emmc_health.inc
$emmc_health_title = gettext("eMMC Disk Health");
?>
I changed the output to report percentage ranges, and added a row for the Pre-EOL info.
I've included the Pre-EOL information in the email notification, but I've not tested this functionality, so I've disabled sending emails with a define() near the top.
There was previously a function to automatically install emmc-utils, but it wasn't called. I've added the ability to install it automatically, but also left it disabled by default with a separate define().
I've also included error reporting for trivial cases like the device not existing or mmc not existing (or if applicable, failing to install).
One caveat is that it still assumes the LifeA/LifeB/Pre-EOL info will all appear in the output in that order (and no other lines slip in). If that assumption breaks, better parsing will be needed.
(In case there's any hope of including a widget like this in pfSense, all my changes are Public Domain.)