366 lines
13 KiB
C#
366 lines
13 KiB
C#
/*
|
|
* Created by SharpDevelop.
|
|
* User: EVXIO
|
|
* Date: 4/3/2025
|
|
* Time: 10:36 PM
|
|
*
|
|
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
|
*/
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace UPSNet {
|
|
/// <summary>
|
|
/// Static application controller for UPS management
|
|
/// </summary>
|
|
///
|
|
|
|
|
|
|
|
enum ShutdownMode{
|
|
Disabled = 0,
|
|
OnBattery = 1,
|
|
OnBattery75 = 2,
|
|
OnBattery50 = 3,
|
|
OnBattery25 = 4,
|
|
OnBatteryCritical = 5
|
|
};
|
|
|
|
public static class UPSApp {
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool FlashWindow(IntPtr hWnd, bool bInvert);
|
|
|
|
public static bool dialogOk = false;
|
|
public static UPSForm upsForm = null;
|
|
public static string deviceAddress = "192.168.0.99";
|
|
public static int shutdownMode = 0;
|
|
public static bool initialized = false;
|
|
|
|
public static UPSApiFetcher fetcher = new UPSApiFetcher("http://your-ups-api/status");
|
|
|
|
public static System.Threading.SynchronizationContext uiContext;
|
|
|
|
public static bool isShutdownRequested = false;
|
|
public static long lastTickShutdownRequested = 0;
|
|
public static bool isShutdownCancelledByUser = false;
|
|
|
|
public static int lastKnownLineFrom = 1;
|
|
public static bool lastKnownBatteryCritical = false;
|
|
public static int lastKnownChargeLevel = 4;
|
|
|
|
static void OnStatusChanged(UPSApiFetcher fetcher){
|
|
//Debug.WriteLine("Charge: " + fetcher.ChargeLevel + ", Online: " + fetcher.IsReachable);
|
|
ManageShutdownRoutine();
|
|
}
|
|
|
|
static UPSApp() {
|
|
Init();
|
|
}
|
|
|
|
public static void Init(){
|
|
if (initialized) return;
|
|
initialized = true;
|
|
|
|
if (AppConfig.exists){
|
|
// Initialize default values
|
|
deviceAddress = AppConfig.Get("address", deviceAddress);
|
|
shutdownMode = AppConfig.GetInt("mode", 0);
|
|
AppConfig.Set("initialized", "true");
|
|
SaveConfig();
|
|
}else{
|
|
// Load saved values
|
|
deviceAddress = AppConfig.Get("address", deviceAddress);
|
|
if (deviceAddress.Length>200) deviceAddress = deviceAddress.Substring(0,200);
|
|
shutdownMode = AppConfig.GetInt("mode", shutdownMode);
|
|
if (shutdownMode>6) shutdownMode = 6;
|
|
if (shutdownMode<0) shutdownMode = 0;
|
|
}
|
|
|
|
fetcher.SetApiUrl("http://" + deviceAddress + "/state");
|
|
fetcher.StatusUpdated += OnStatusChanged;
|
|
fetcher.Start();
|
|
Debug.WriteLine("Initialized");
|
|
}
|
|
|
|
public static void SaveConfig(){
|
|
AppConfig.Set("address", deviceAddress);
|
|
AppConfig.Set("mode", shutdownMode);
|
|
AppConfig.SaveSettings();
|
|
}
|
|
|
|
public static void ShowForm(){
|
|
if (upsForm==null){
|
|
upsForm = new UPSForm();
|
|
upsForm.Show();
|
|
return;
|
|
}else{
|
|
if (upsForm.WindowState == FormWindowState.Minimized){
|
|
upsForm.WindowState = FormWindowState.Normal;
|
|
}else if (upsForm.WindowState == FormWindowState.Normal){
|
|
if (!upsForm.Visible){
|
|
upsForm.Visible = true;
|
|
upsForm.Show();
|
|
upsForm.Activate();
|
|
return;
|
|
}
|
|
}
|
|
upsForm.Activate(); // First bring to foreground
|
|
FlashWindow(upsForm.Handle, true);
|
|
}
|
|
}
|
|
|
|
public static void ShowOrHideForm(){
|
|
if (upsForm==null){
|
|
upsForm = new UPSForm();
|
|
upsForm.Show();
|
|
return;
|
|
}else{
|
|
if (upsForm.WindowState == FormWindowState.Minimized){
|
|
upsForm.WindowState = FormWindowState.Normal;
|
|
upsForm.Activate(); // First bring to foreground
|
|
FlashWindow(upsForm.Handle, true);
|
|
}else if (upsForm.WindowState == FormWindowState.Normal){
|
|
if (!upsForm.Visible){
|
|
upsForm.Visible = true;
|
|
upsForm.Show();
|
|
upsForm.Activate();
|
|
//upsForm.Close();
|
|
//upsForm = null;
|
|
return;
|
|
}else{
|
|
upsForm.Close();
|
|
upsForm = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void PurgeUpsForm(){
|
|
upsForm = null;
|
|
}
|
|
|
|
public static void RestartFetcher(){
|
|
fetcher.Stop();
|
|
fetcher.SetApiUrl("http://" + deviceAddress + "/state");
|
|
fetcher.Start();
|
|
}
|
|
|
|
public static bool IsGoingToShutdown(){
|
|
if (isShutdownRequested){
|
|
//timeout after 35s
|
|
if ((Environment.TickCount-lastTickShutdownRequested) >= 35000){
|
|
isShutdownRequested = false;
|
|
}else{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static bool IsRunningInDebuggerOrIDE(){
|
|
// Check for attached debugger
|
|
if (System.Diagnostics.Debugger.IsAttached)
|
|
return true;
|
|
|
|
// Check for common IDE environment variables
|
|
string[] ideVariables = {
|
|
"VisualStudioVersion",
|
|
"VSAPPIDNAME",
|
|
"SharpDevelop" // For SharpDevelop
|
|
};
|
|
|
|
foreach (var variable in ideVariables){
|
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(variable)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool RequestForShutdown(int sec){
|
|
if (IsGoingToShutdown()) return true;
|
|
Debug.WriteLine("Shutdown requested.");
|
|
|
|
// Mark as requested immediately to prevent re-entry
|
|
isShutdownRequested = true;
|
|
lastTickShutdownRequested = Environment.TickCount;
|
|
|
|
// Show countdown form on UI thread first, then execute shutdown only if not cancelled
|
|
if (uiContext != null) {
|
|
uiContext.Post(new System.Threading.SendOrPostCallback(delegate(object state) {
|
|
ShowCountdownThenShutdown(sec);
|
|
}), null);
|
|
}else{
|
|
// Fallback: no UI context, shutdown directly
|
|
ExecuteWindowsShutdown(sec);
|
|
}
|
|
|
|
return isShutdownRequested;
|
|
}
|
|
|
|
private static void ShowCountdownThenShutdown(int sec){
|
|
using (var countdownForm = new ShutdownCountdownForm(sec)){
|
|
// Show form and also show main form behind it
|
|
ShowForm();
|
|
countdownForm.ShowDialog();
|
|
|
|
if (countdownForm.WasCancelled){
|
|
// User cancelled - abort everything
|
|
Debug.WriteLine("Shutdown cancelled by user via countdown dialog.");
|
|
WindowsShutdown.AbortShutdown();
|
|
CancelTimedShutdown();
|
|
isShutdownRequested = false;
|
|
MessageBox.Show("Shutdown command has been aborted.", "Cancel Shutdown", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}else if (countdownForm.CountdownFinished){
|
|
// Countdown reached 0 - execute actual Windows shutdown
|
|
Debug.WriteLine("Countdown finished. Executing Windows shutdown.");
|
|
ExecuteWindowsShutdown(1); // 1 second remaining, shutdown immediately
|
|
}else{
|
|
// Form was closed without explicit action - treat as cancelled
|
|
Debug.WriteLine("Countdown form closed. Treating as cancelled.");
|
|
WindowsShutdown.AbortShutdown();
|
|
CancelTimedShutdown();
|
|
isShutdownRequested = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ExecuteWindowsShutdown(int sec){
|
|
if (IsRunningInDebuggerOrIDE()){
|
|
Debug.WriteLine("Running from Debug/IDE. Bypassing actual OS shutdown!");
|
|
isShutdownRequested = true;
|
|
lastTickShutdownRequested = Environment.TickCount;
|
|
MessageBox.Show("[Debug Mode] Windows shutdown would execute now.", "Debug Shutdown", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
bool b = WindowsShutdown.TimedShutdown(false, sec, "EVXIO UPS Net shutdown activated.");
|
|
//non invasive shutdown
|
|
if (!b) b = WindowsShutdown.Shutdown(WindowsShutdown.ShutdownMode.Shutdown);
|
|
if (!b) b = WindowsShutdown.CmdShutdown(WindowsShutdown.ShutdownMode.Shutdown, sec);
|
|
if (b){
|
|
isShutdownRequested = true;
|
|
lastTickShutdownRequested = Environment.TickCount;
|
|
}else{
|
|
isShutdownRequested = false;
|
|
}
|
|
}
|
|
|
|
public static void SendTimedShutdown(){
|
|
string url = "http://" + deviceAddress + "/state?shutdown=30";
|
|
fetcher.SetApiUrlOnce(url);
|
|
}
|
|
|
|
public static void CancelTimedShutdown(){
|
|
isShutdownCancelledByUser = true;
|
|
string url = "http://" + deviceAddress + "/state?shutdown=0";
|
|
fetcher.SetApiUrlOnce(url);
|
|
}
|
|
|
|
public static void ManageShutdownRoutine(){
|
|
if (UPSApp.fetcher.IsReachable){
|
|
// Update last known state
|
|
lastKnownLineFrom = UPSApp.fetcher.UPSLineFrom;
|
|
lastKnownBatteryCritical = UPSApp.fetcher.BatteryCritical;
|
|
lastKnownChargeLevel = UPSApp.fetcher.ChargeLevel;
|
|
|
|
if (UPSApp.fetcher.OnTimedShutdown){
|
|
Debug.WriteLine("UPS going to shutdown: " + UPSApp.fetcher.TimedShutdownRemaining);
|
|
//checking if pc already flagged to shutdown
|
|
if (!IsGoingToShutdown() && !isShutdownCancelledByUser){
|
|
int tMax = UPSApp.fetcher.TimedShutdownRemaining - 10; // Cadangan waktu 10 detik agar PC sempat mati sebelum listrik UPS putus
|
|
if (tMax<1) tMax = 1;
|
|
|
|
int t = UPSApp.fetcher.BatteryCritical ? 1 : tMax;
|
|
RequestForShutdown(t);//request shutdown
|
|
}
|
|
}else{
|
|
//checking requirement
|
|
int realMode = 0;
|
|
bool isForced = UPSApp.shutdownMode>0;
|
|
if (!isForced){
|
|
realMode = UPSApp.fetcher.ShutdownSuggestMode;
|
|
}else{
|
|
realMode = UPSApp.shutdownMode-1;
|
|
}
|
|
|
|
bool shutFlag = false;
|
|
|
|
if (isForced){
|
|
//check setting requirmenet flag before decide to shutdown
|
|
bool onBattery = (UPSApp.fetcher.UPSLineFrom == 2);
|
|
int battLv = UPSApp.fetcher.ChargeLevel;
|
|
bool isCritical = UPSApp.fetcher.BatteryCritical;
|
|
ShutdownMode mode = (ShutdownMode) realMode;
|
|
|
|
// Calculate normalized percentage (0 to 100)
|
|
double percentage = 100.0;
|
|
if (UPSApp.fetcher.BattMaxLevel > 0) {
|
|
percentage = (double)battLv * 100.0 / UPSApp.fetcher.BattMaxLevel;
|
|
} else {
|
|
percentage = battLv > 4 ? battLv : (battLv * 25.0);
|
|
}
|
|
|
|
if (onBattery){
|
|
switch (mode){
|
|
case ShutdownMode.Disabled:{
|
|
//do nothing
|
|
}break;
|
|
case ShutdownMode.OnBattery:{
|
|
shutFlag = true;
|
|
}break;
|
|
case ShutdownMode.OnBattery75:{
|
|
shutFlag = percentage <= 75.0;
|
|
}break;
|
|
case ShutdownMode.OnBattery50:{
|
|
shutFlag = percentage <= 50.0;
|
|
}break;
|
|
case ShutdownMode.OnBattery25:{
|
|
shutFlag = percentage <= 25.0;
|
|
}break;
|
|
case ShutdownMode.OnBatteryCritical:{
|
|
shutFlag = (percentage <= 10.0) || isCritical;
|
|
}break;
|
|
}
|
|
}
|
|
|
|
}else{
|
|
//follow UPS flag
|
|
if (UPSApp.fetcher.ShutdownSuggested){
|
|
shutFlag = true;
|
|
}
|
|
}
|
|
|
|
if (UPSApp.fetcher.UPSLineFrom == 1 || UPSApp.fetcher.IsChargingOrBlink) {
|
|
isShutdownCancelledByUser = false;
|
|
}
|
|
|
|
if (shutFlag && !isShutdownCancelledByUser){
|
|
Debug.WriteLine("UPS Suggest to shutdown!");
|
|
//request timed shutdown in 120 seconds, next cycle just wait for flagged shutdown
|
|
SendTimedShutdown();
|
|
}
|
|
}
|
|
}else{
|
|
// Handle unreachable (offline) state:
|
|
// If the connection is lost while the UPS was on battery, or the battery was critical, or if the user's setting was triggered
|
|
double lastPercentage = 100.0;
|
|
lastPercentage = lastKnownChargeLevel > 4 ? lastKnownChargeLevel : (lastKnownChargeLevel * 25.0);
|
|
|
|
bool wasOnBattery = (lastKnownLineFrom == 2);
|
|
bool wasCritical = lastKnownBatteryCritical || (lastPercentage <= 25.0);
|
|
|
|
if (wasOnBattery && wasCritical){
|
|
if (!IsGoingToShutdown() && !isShutdownCancelledByUser){
|
|
Debug.WriteLine("UPS connection lost while on battery and battery is critical. Requesting shutdown!");
|
|
RequestForShutdown(30); // 30 seconds warning countdown
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |