using System; using System.IO; using System.Net; using System.Threading; namespace UPSNet { public class UPSApiFetcher : IDisposable { // Configuration private string _apiUrl; private string _apiUrlOnce; private int _pollingInterval; private int _pollingIntervalOnce; // State tracking private Thread _workerThread; private bool _isRunning; private DateTime _lastSuccessfulFetch; private ManualResetEvent _sleepEvent = new ManualResetEvent(false); private readonly object _syncLock = new object(); // API Response Data public int Success { get; private set; } public int ChargeLevel { get; private set; } public int BattMaxLevel { get; private set; } public bool OnState { get; private set; } public int ShutdownSuggestMode { get; private set; } public bool ShutdownSuggested { get; private set; } public bool OnTimedShutdown { get; private set; } public int TimedShutdownRemaining { get; private set; } public bool BatteryCritical { get; private set; } public int UPSLineFrom { get; private set; } public bool IsChargingOrBlink { get; private set; } public bool IsReachable { get; private set; } public DateTime LastUpdateTime { get; private set; } public bool IsOnRequestUrlOnce { get; private set; } private string json; // Event public event Action StatusUpdated; public UPSApiFetcher(string apiUrl, int pollingIntervalMs = 1000) { _apiUrl = apiUrl; _pollingInterval = pollingIntervalMs; } public void SetApiUrl(string apiUrl, int pollingIntervalMs = 1000) { lock (_syncLock) { _apiUrl = apiUrl; _pollingInterval = pollingIntervalMs; } } public void SetApiUrlOnce(string apiUrl, int pollingIntervalMs = 500) { lock (_syncLock){ _apiUrlOnce = apiUrl; _pollingIntervalOnce = pollingIntervalMs; IsOnRequestUrlOnce = true; _sleepEvent.Set(); // Wake up thread for immediate fetch } } public void Start() { if (_isRunning) return; _isRunning = true; _workerThread = new Thread(WorkerLoop){ IsBackground = true, Priority = ThreadPriority.Normal//ThreadPriority.BelowNormal }; _workerThread.Start(); } public void Stop() { _isRunning = false; _sleepEvent.Set(); // Wake thread to exit _workerThread.Join(1000); } public void FetchNow() { _sleepEvent.Set(); // Wake thread for immediate fetch } private void WorkerLoop() { while (_isRunning) { FetchApiData(); // Wait for next interval or immediate fetch int interval; lock (_syncLock) { interval = IsOnRequestUrlOnce ? _pollingIntervalOnce : _pollingInterval; } _sleepEvent.WaitOne(interval); _sleepEvent.Reset(); } } private void FetchApiData() { try { string currentUrl; lock (_syncLock) { currentUrl = IsOnRequestUrlOnce ? _apiUrlOnce : _apiUrl; } var request = WebRequest.Create(currentUrl); request.Timeout = 5000; // Add Basic Authentication for V2 compatibility string credentials = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("admin:admin123")); request.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials; using (var response = (HttpWebResponse)request.GetResponse()) using (var stream = response.GetResponseStream()) using (var reader = new StreamReader(stream)) { string tmp = reader.ReadToEnd(); if (json!=tmp){ json = tmp; ParseJsonResponse(json); } IsReachable = true; _lastSuccessfulFetch = DateTime.Now; LastUpdateTime = DateTime.Now; lock (_syncLock) { if (IsOnRequestUrlOnce) { IsOnRequestUrlOnce = false; } } } }catch{ UpdateReachability(); }finally{ StatusUpdated.Invoke(this); } } private void UpdateReachability(){ if ((DateTime.Now - _lastSuccessfulFetch).TotalSeconds > 5) { IsReachable = false; LastUpdateTime = DateTime.Now; } } private void ParseJsonResponse(string json) { try { json = json.Trim('{', '}', ' ', '\n', '\r', '\t'); string[] pairs = json.Split(','); foreach (string pair in pairs) { string[] keyValue = pair.Split(':'); if (keyValue.Length != 2) continue; string key = keyValue[0].Trim().Trim('"'); string value = keyValue[1].Trim().Trim('"'); switch (key.ToLower()){ case "success": Success = int.Parse(value); break; case "chargelv": ChargeLevel = int.Parse(value); break; case "shutdownsuggestmode": ShutdownSuggestMode = int.Parse(value); break; case "shutdownsuggest": ShutdownSuggested = int.Parse(value) > 0; break; case "timedshutdown": OnTimedShutdown = int.Parse(value) > 0; break; case "shutdownremain": TimedShutdownRemaining = int.Parse(value); break; case "battcritical": BatteryCritical = int.Parse(value) > 0; break; case "line": UPSLineFrom = int.Parse(value); break; case "battblink": IsChargingOrBlink = int.Parse(value) > 0; break; case "state": OnState = int.Parse(value) > 0; break; case "battmax": BattMaxLevel = int.Parse(value); break; } } }catch{ // Keep previous values on parse error } } public void Dispose(){ Stop(); _sleepEvent.Dispose(); } } }