0
  • i am a complete c# noob (i know Java though)
  • i have a c# script i modified. I want to run it at startup using a daemon and systemctl (the original is this NZXT automatic fan controller script)
  • the code created a repeating timer to execute a piece of code every 4 seconds and blocks exiting the script using Console.ReadLine();
  • i compiled and tested the script using msc and mono. it works as intended in a terminal (it is NOT exiting directly)
  • when run as a daemon, Console.ReadLine(); doesn't wait and the script directly completes the main method and exits
  • NOTES:

    • i am also a linux moob, so please point out if i am doing something wrong in the daemon. My goal is having a background script that i can start and stop, which the system starts from me on boot
    • please find all code attached below

main code:

using System;
using System.IO;
using System.Timers;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Program
{

    private static string root;
    private static string confFile = "/home/mohammadah/scripts/grid+v2/conf.d";
    private static System.Timers.Timer aTimer;

    private static int maxSpeed = 100;
    private static int minSpeed = 35;
    // do not change this. all fan speeds have to rounded to the nearest multiple of 5
    private static int minimumRound = 5;
    private static int timerTime = 4000;
    private static int defaultFanSpeed = 40;

    // fans: 
    // 1: empty
    // 2: empty
    // 3: bottom
    // 4: top
    // 5: CPU
    // 6: Back
    private static int[] gpuFans;
    private static int gpuMaxTemp;
    private static int gpuMinTemp;

    private static int[] cpuFans;
    private static int cpuMaxTemp;
    private static int cpuMinTemp;

    private static float lastCPUSpeed = 0;
    private static float lastGPUSpeed = 0;

    public static void Main()
    {
    //    Console.WriteLine(System.IO.Path.GetDirectoryName(Application.ExecutablePath));
       ParseConfigFile(confFile);


        if(!Regex.IsMatch(DoGridCommand("get fan 1"), @"Fan 1: \d{1,5} RPM")){
            DoGridCommand("init");
        }

        DoGridCommand("set fans all speed " + defaultFanSpeed);

        

        SetTimer();
        Console.WriteLine("\nPress the Enter key to exit the application...\n");
        // Process.GetCurrentProcess().WaitForExit();

        Console.ReadLine();
        aTimer.Stop();
        aTimer.Dispose();
        Console.WriteLine("Terminating the application...");
    }

    private static float GetGPUTemp(){
        ProcessStartInfo procStartInfo = new ProcessStartInfo("/bin/bash", "-c nvidia-smi -q -d temperature");
        procStartInfo.RedirectStandardOutput = true;
        procStartInfo.UseShellExecute = false;
        procStartInfo.CreateNoWindow = true;

        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo = procStartInfo;
        proc.Start();
        string result = proc.StandardOutput.ReadToEnd();
        
        char first = '-';
        char second = '-';
        string temp = "";

        foreach (char c in result)
        {
            if(c=='1' || c == '2'|| c == '3'|| c == '4'|| c == '5'|| c == '6'|| c == '7'|| c == '8'|| c == '9'|| c == '0'|| c == 'C'){
                if(first == '-'){
                    first = c;
                }else if(second == '-'){
                    second = c;
                }else if(c == 'C'){
                    temp = "" + first + second;
                    break;
                }
            }else{
                first = '-';
                second = '-';
            }
        }
        return float.Parse(temp);
    }

    private static int GetCPUTemp(){

        string tempLineStart = "Packageid0:+";

        ProcessStartInfo procStartInfo = new ProcessStartInfo("/bin/bash", "-c sensors");
        procStartInfo.RedirectStandardOutput = true;
        procStartInfo.UseShellExecute = false;
        procStartInfo.CreateNoWindow = true;

        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo = procStartInfo;
        proc.Start();

        int temp = 0;
        string line = "";
        while ((line = proc.StandardOutput.ReadLine()) != null) {
            line = Regex.Replace(line, @"\s+", "");
            if(Regex.IsMatch(line, tempLineStart)){
                line = line.Trim().Replace(tempLineStart.Trim(), "");
                line = line.Substring(0, 6);
                line = Regex.Replace(line, "[^0-9.]", "");
                float t = float.Parse(line);
                temp = (int)t;
                break;
            }
        }

        return temp;
    }

    private static void SetTimer()
    {
        // Create a timer with a two second interval.
        aTimer = new System.Timers.Timer(timerTime);
        // Hook up the Elapsed event for the timer. 
        aTimer.Elapsed += OnTimedEvent;
        aTimer.AutoReset = true;
        aTimer.Enabled = true;
    }    

    private static void ParseConfigFile(string fileName){
        string gridfanLocation = "GridfanLocation:";
        string gpuFanLineStart = "GPUFans:";
        string gpuMaxLineStart = "GPUMaxTemp:";
        string gpuMinLineStart = "GPUMinTemp:";
        string cpuFanLineStart = "CPUFans:";
        string cpuMinLineStart = "CPUMinTemp:";
        string cpuMaxLineStart = "CPUMaxTemp:";
    
        StreamReader reader = File.OpenText(fileName);
        string line;    
        while ((line = reader.ReadLine()) != null) {

            line = line.Trim();
            // Regex.Replace(line, @"\s+", "");
            // ignore commented out (starts with "//") or empty lines
            if(line.StartsWith("//") || line.Equals("")) continue;
            // set gpu temp at which gpu fans will be set at 100% speed
            else if(Regex.IsMatch(line, gridfanLocation)){
                root = line.Trim().Replace(gridfanLocation, "");
            // set gpu fan list using the numbering found on gridfan git repo
            }else if(Regex.IsMatch(line, gpuFanLineStart)){
                line = line.Trim().Replace(gpuFanLineStart, "");
                string[] fans = line.Split(",");
                gpuFans = new int[fans.Length];
                for (int i =0;i<fans.Length;i++){
                    gpuFans[i] = int.Parse(fans[i].Trim());
                }
            // set gpu temp at which gpu fans will be set at 100% speed
            } else if(Regex.IsMatch(line, gpuMaxLineStart)){
                line = line.Trim().Replace(gpuMaxLineStart, "");
                gpuMaxTemp = int.Parse(line);
            // set gpu temp at which gpu fans will be set at lowest speed
            } else if(Regex.IsMatch(line, gpuMinLineStart)){
                line = line.Trim().Replace(gpuMinLineStart, "");
                gpuMinTemp = int.Parse(line);
            // set cpu fan list using the numbering found on gridfan git repo
            } else if(Regex.IsMatch(line, cpuFanLineStart)){
                line = line.Trim().Replace(cpuFanLineStart, "");
                string[] fans = line.Split(",");
                cpuFans = new int[fans.Length];
                for (int i =0;i<fans.Length;i++){
                    cpuFans[i] = int.Parse(fans[i].Trim());
                }
            // set cpu temp at which gpu fans will be set at 100% speed
            } else if(Regex.IsMatch(line, cpuMaxLineStart)){
                line = line.Trim().Replace(cpuMaxLineStart, "");
                cpuMaxTemp = int.Parse(line);
            // set cpu temp at which gpu fans will be set at lowest speed
            } else if(Regex.IsMatch(line, cpuMinLineStart)){
                line = line.Trim().Replace(cpuMinLineStart, "");
                cpuMinTemp = int.Parse(line);
            }
            else throw new Exception ("Something went wrong while parsing config file \"./conf.d\"");
        }
    }

    private static void OnTimedEvent(Object source, ElapsedEventArgs e)
    {
        Console.WriteLine("====================================================");
        float gpuTemp = GetGPUTemp();
        Console.WriteLine("Graphics (GPU) temp: " + gpuTemp+"`C");
        float gpuPercentage = 100 * ((gpuTemp - gpuMinTemp) / (gpuMaxTemp - gpuMinTemp));
        Console.WriteLine("Graphics (GPU) percentage: " + gpuPercentage+"`%");
        float gpuFanSpeed = gpuPercentage > maxSpeed ? maxSpeed : gpuPercentage < minSpeed ? minSpeed : gpuPercentage;
        gpuFanSpeed = (int) Math.Ceiling(gpuFanSpeed / minimumRound) * minimumRound;

        if(lastGPUSpeed != gpuFanSpeed){
            if(!Regex.IsMatch(DoGridCommand("get fan 1"), @"Fan 1: \d{1,5} RPM")){
                DoGridCommand("init");
            }
            foreach (int gpuFan in gpuFans){
                DoGridCommand("set fans " + gpuFan + " speed " + gpuFanSpeed);
            }
        }
        float cpuTemp = GetCPUTemp();
        Console.WriteLine("Central (CPU) temp: " + cpuTemp+"`C");
        float cpuPercentage = 100 * ((cpuTemp - cpuMinTemp) / (cpuMaxTemp - cpuMinTemp));
        Console.WriteLine("Central (CPU) percentage: " + cpuPercentage+"`%");
        Console.WriteLine("");
        float cpuFanSpeed = (cpuPercentage > maxSpeed ? maxSpeed : cpuPercentage < minSpeed ? minSpeed : cpuPercentage);
        cpuFanSpeed = (int) Math.Ceiling(cpuFanSpeed / minimumRound) * minimumRound;

        if(lastCPUSpeed != gpuFanSpeed){
            if(!Regex.IsMatch(DoGridCommand("get fan 1"), @"Fan 1: \d{1,5} RPM")){
                DoGridCommand("init");
            }

            foreach (int cpuFan in cpuFans){
                DoGridCommand("set fans " + cpuFan + " speed " + cpuFanSpeed);
            }
        }
        lastCPUSpeed = cpuFanSpeed;
        lastGPUSpeed = gpuFanSpeed;
        Console.WriteLine("====================================================");
        Console.WriteLine();
    }

    private static string DoGridCommand(string command){
        ProcessStartInfo procStartInfo = new ProcessStartInfo("/bin/bash", root + "/gridfan " + command);
        procStartInfo.RedirectStandardOutput = true;
        procStartInfo.UseShellExecute = false;
        procStartInfo.CreateNoWindow = true;

        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo = procStartInfo;
        proc.Start();
        string result = proc.StandardOutput.ReadToEnd();
        Console.WriteLine("Command result: " + result.Trim());
        // System.Threading.Thread.Sleep(500);
        return result;
    }
}

conf.d used in main code (not really important, but for completeness):

// gridFan repo = https://github.com/CapitalF/gridfan
// root of gridFan script location
GridfanLocation:/home/mohammadah/scripts/grid+v2
// GPU fans id/ports as per the schema in gridFan repo readme
GPUFans: 3
// GPU temp at which GPU fans will be set at 100% speed
GPUMaxTemp: 75
// GPU temp at which GPU fans will be set at lowest speed
GPUMinTemp: 35
// GPU fans id/port as per the schema in gridFan repo readme
CPUFans: 4, 5, 6
// CPU temp at which CPU fans will be set at 100% speed
CPUMaxTemp: 75
// CPU temp at which CPU fans will be set at lowest speed
CPUMinTemp: 35

daemon .service file

[Unit]
Description=Grid+ v2 automatic fan controller

[Service]
ExecStart=/usr/bin/mono /home/mohammadah/scripts/grid+v2/LinuxGrid-v2Controller.exe
Restart=on-failure

[Install]
WantedBy=multi-user.target

1 Answer 1

0

I don't know c#, but I suspect the issue is because when launched as a systemd service, your program's STDIN is connected to /dev/null, so when Console.ReadLine(); tries to read, it immediately gets a result (a 0-byte read) and returns.

You can reproduce the same thing manually by doing

myprog < /dev/null

If you want your program to not exit, you need a different mechanism for doing so.

3
  • i understand that much, but how can i make the script wait Nov 24, 2020 at 21:15
  • I don't know. What do you want it to wait for? Programming should be over on StackOverflow anyway, not U&L. However if you're saying you've got a "main" thread that's not doing anything, then good practice would be to have it monitor all the other threads that it fired off and wait for them to die/exit.
    – phemmer
    Nov 25, 2020 at 1:16
  • my program doesn't need any input.... it is completely self contained and should never exit. It controls my computer's fans, so it is critical that it doesn't shut down. My only issue is with the daemon shutting down. i think it falls more under the linux context the the programming context Nov 25, 2020 at 13:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.