using System.Net;
using SnmpSharpNet;
namespace ETTerms.PduCore;
///
/// PDU controller for iPoMan II/III models via SNMP。
/// GUI 與 ETTerms.PduMcp 共用的唯一實作(v0.4.0 起取代兩份複製的版本)。
/// 診斷輸出走建構子注入的 log 委派:GUI 給 AppLogger、MCP server 給 stderr
/// (stdio MCP 的 stdout 專供 JSON-RPC,不可污染)。
/// SNMP 走 UDP、非獨佔,多個行程可同時對同一台 PDU 操作。
///
public sealed class PduController : IDisposable
{
private readonly string _ip;
private readonly Action? _logInfo;
private readonly Action? _logWarn;
private const string Community = "private";
private const int SnmpPort = 161;
private const int Timeout = 3000;
public string Ip => _ip;
public PduController(string ip, Action? logInfo = null, Action? logWarn = null)
{
_ip = ip;
_logInfo = logInfo;
_logWarn = logWarn;
}
/// 讀 PDU 型號/名稱 OID;非空且含 "PDU" 代表連得上。
public string? GetModelName() => SnmpGet(".1.3.6.1.4.1.2468.1.4.2.1.1.4");
public bool CheckConnection()
{
var name = GetModelName();
_logInfo?.Invoke($"[PDU] CheckConnection {_ip}: name='{name ?? ""}'");
return !string.IsNullOrEmpty(name) && name.Contains("PDU");
}
public bool SetPortOn(int port) => SnmpSet(PortControlOid(port), new Integer32(3));
public bool SetPortOff(int port) => SnmpSet(PortControlOid(port), new Integer32(4));
/// true = on, false = off, null = unknown/unreachable.
public bool? GetPortState(int port) => ParseState(SnmpGet(PortStateOid(port)));
public int? GetPortCurrent(int port) => int.TryParse(SnmpGet(PortCurrentOid(port)), out int v) ? v : null;
public double? GetPortPowerWatts(int port) => int.TryParse(SnmpGet(PortPowerOid(port)), out int v) ? v / 10.0 : null;
///
/// 一次讀回所有插座的狀態 / 電流(mA) / 功率(W)。
/// 以 3 個批次 SNMP GET(每個 PDU 含 個 varbind)取代
/// 逐 port 逐 OID 的 3×N 個請求 —— 12 port 從 36 個 UDP 來回縮成 3 個,
/// 逾時時的最壞情況也從 36×Timeout 縮到 3×Timeout。
///
public (bool? State, int? CurrentMilliAmps, double? PowerWatts)[] GetAllPortsStatus(int portCount)
{
var ports = Enumerable.Range(1, portCount).ToArray();
var states = SnmpGetMany(ports.Select(PortStateOid).ToArray());
var currents = SnmpGetMany(ports.Select(PortCurrentOid).ToArray());
var powers = SnmpGetMany(ports.Select(PortPowerOid).ToArray());
var result = new (bool?, int?, double?)[portCount];
for (int i = 0; i < portCount; i++)
{
result[i] = (
ParseState(states[i]),
int.TryParse(currents[i], out int c) ? c : null,
int.TryParse(powers[i], out int p) ? p / 10.0 : null);
}
return result;
}
private static bool? ParseState(string? r) => r == "3" ? true : (r == "2" || r == "4") ? false : null;
private static string PortControlOid(int port) => $".1.3.6.1.4.1.2468.1.4.2.1.3.2.4.1.2.{port}";
private static string PortStateOid(int port) => $".1.3.6.1.4.1.2468.1.4.2.1.3.2.3.1.2.{port}";
private static string PortCurrentOid(int port) => $".1.3.6.1.4.1.2468.1.4.2.1.3.2.3.1.3.{port}";
private static string PortPowerOid(int port) => $".1.3.6.1.4.1.2468.1.4.2.1.3.2.3.1.5.{port}";
private bool SnmpSet(string oid, AsnType value)
{
try
{
var param = new AgentParameters(new OctetString(Community)) { Version = SnmpVersion.Ver1 };
using var target = new UdpTarget((IPAddress)new IpAddress(_ip), SnmpPort, Timeout, 1);
var pdu = new SnmpSharpNet.Pdu(PduType.Set);
pdu.VbList.Add(new Oid(oid), value);
var result = (SnmpV1Packet)target.Request(pdu, param);
return result?.Pdu.ErrorStatus == 0;
}
catch (Exception ex) { _logWarn?.Invoke($"[PDU] SNMP SET {oid} exception: {ex.Message}"); return false; }
}
private string? SnmpGet(string oid)
{
var r = SnmpGetMany(new[] { oid });
return r[0];
}
/// 一個 SNMP GET 帶多個 OID(varbind),回傳同序的值;失敗整批回 null。
private string?[] SnmpGetMany(string[] oids)
{
var result = new string?[oids.Length];
try
{
var param = new AgentParameters(new OctetString(Community)) { Version = SnmpVersion.Ver1 };
using var target = new UdpTarget((IPAddress)new IpAddress(_ip), SnmpPort, Timeout, 1);
var pdu = new SnmpSharpNet.Pdu(PduType.Get);
foreach (var oid in oids) pdu.VbList.Add(new Oid(oid));
var resp = (SnmpV1Packet)target.Request(pdu, param);
if (resp == null) { _logWarn?.Invoke($"[PDU] SNMP GET ({oids.Length} oids): no response (timeout)"); return result; }
if (resp.Pdu.ErrorStatus != 0) { _logWarn?.Invoke($"[PDU] SNMP GET ({oids.Length} oids): ErrorStatus={resp.Pdu.ErrorStatus}"); return result; }
// SNMP GET 回應的 varbind 順序與請求一致,直接依 index 對回去。
int n = Math.Min(oids.Length, resp.Pdu.VbList.Count);
for (int i = 0; i < n; i++) result[i] = resp.Pdu.VbList[i].Value.ToString();
}
catch (Exception ex) { _logWarn?.Invoke($"[PDU] SNMP GET ({oids.Length} oids) exception: {ex.Message}"); }
return result;
}
public void Dispose() { }
}