Eklablog Tous les blogs
Editer l'article Suivre ce blog Administration + Créer mon blog
MENU

Blog traitant d'informatique, de programmation, de python, de golang, de linux...

Publicité

batteryCheck : pour vous aider à préserver votre batterie de portable sous Linux

1/ Le code :

    1 package main
    2 
    3 import (
    4 	"bufio"
    5 	"fmt"
    6 	"os"
    7 	"os/exec"
    8 	"strconv"
    9 	"strings"
   10 	"time"
   11 )
   12 
   13 var values []int64
   14 
   15 func main() {
   16 	updateStats()
   17 	var lowBatt, maxBatt int64
   18 	if len(os.Args) == 3 {
   19 		low, errl := strconv.ParseInt(os.Args[1], 10, 32)
   20 		high, errh := strconv.ParseInt(os.Args[2], 10, 32)
   21 		if errl == nil && errh == nil {
   22 			if high-low < 10 || high > 100 || low < 8 {
   23 				showHelp()
   24 				return
   25 			}
   26 		} else {
   27 			showHelp()
   28 			return
   29 		}
   30 		lowBatt, maxBatt = low, high
   31 	} else {
   32 		showHelp()
   33 		return
   34 	}
   35 	prevMode := "None"
   36 	delay := time.Second * 60 // 60 secondes entre chaque analyse de la batterie
   37 	percentS := ""
   38 	zenCommand := "zenity"
   39 	zenArg1 := "--notification"
   40 	command := "acpi"
   41 	commandArg := "-b"
   42 
   43 	for {
   44 		cmd := exec.Command(command, commandArg)
   45 		stdout, err := cmd.Output()
   46 		if err != nil {
   47 			fmt.Println(err.Error())
   48 			return
   49 		}
   50 		extract := strings.Split(string(stdout), ": ")[1]
   51 		mode := strings.Split(extract, "ing, ")[0]
   52 		if strings.HasPrefix(mode, "Dis") { // Batterie en décharge
   53 			//
   54 			endLine := strings.Split(string(stdout), "Discharging, ")[1]
   55 			percentS = strings.Split(endLine, "%, ")[0]
   56 			percent, _ := strconv.ParseInt(percentS, 10, 64)
   57 			dataMess := "Batterie en décharge => Charge restante : " + strconv.Itoa(int(percent)) + "%  /  Limite basse : " + strconv.Itoa(int(lowBatt)) + "%"
   58 			fmt.Println(dataMess)
   59 			if prevMode != mode {
   60 				values = nil // Vider values
   61 			}
   62 			prevMode = mode
   63 			writeCurrentRate("Discharging", percent)
   64 			if percent <= lowBatt {
   65 				//Afficher message pour brancher sur secteur
   66 				message := "La batterie a atteint le seuil de charge minimum autorisé (" + strconv.Itoa(int(lowBatt)) + "%)\nBranchez l'ordinateur sur secteur !"
   67 				fmt.Println(message)
   68 				zenArg2 := "--text=" + message
   69 				go func() {
   70 					zcmd := exec.Command(zenCommand, zenArg1, zenArg2)
   71 					_, err := zcmd.Output()
   72 					if err != nil {
   73 						return
   74 					}
   75 				}()
   76 			}
   77 
   78 		} else { // Batterie en charge
   79 			//
   80 			endLine := strings.Split(string(stdout), "Charging, ")[1]
   81 			percentS = strings.Split(endLine, "%, ")[0]
   82 			percent, _ := strconv.ParseInt(percentS, 10, 64)
   83 			dataMess := "Batterie en charge => Charge courante : " + strconv.Itoa(int(percent)) + "%  /  Limite haute : " + strconv.Itoa(int(maxBatt)) + "%"
   84 			fmt.Println(dataMess)
   85 			if prevMode != mode {
   86 				values = nil // Vider values
   87 			}
   88 			prevMode = mode
   89 			writeCurrentRate("Charging", percent)
   90 			if percent >= maxBatt {
   91 				//Afficher message pour débrancher du secteur
   92 				message := "La batterie a atteint la charge demandée (" + strconv.Itoa(int(maxBatt)) + "%)\nDébranchez le secteur !"
   93 				fmt.Println(message)
   94 				zenArg2 := "--text=" + message
   95 				go func() {
   96 					zcmd := exec.Command(zenCommand, zenArg1, zenArg2)
   97 					_, err := zcmd.Output()
   98 					if err != nil {
   99 						return
  100 					}
  101 				}()
  102 			}
  103 		}
  104 		time.Sleep(delay) // On vérifiera l'état de la batterie toutes les minutes
  105 	}
  106 }
  107 
  108 //
  109 // showHelp() va afficher une aide succinte à l'utilisation de batteryCheck
  110 //
  111 func showHelp() {
  112 	fmt.Println("\nbatteryCheck doit être démarré avec 2 arguments :")
  113 	fmt.Println("batteryCheck lowPercent highPercent")
  114 	fmt.Println("ex : \"batteryCheck 40 80\" pour une alerte basse à 40% (où il faudra rebrancher le secteur)")
  115 	fmt.Println("     et une alerte haute à 80% (pour débrancher le PC du secteur)")
  116 	fmt.Println("\nlowPercent devra impérativement être >= 8%")
  117 	fmt.Println("highPercent devra impérativement être <= 100%")
  118 	fmt.Println("L'écart entre lowPercent et highPercent devra être au moins égal à 10%")
  119 	fmt.Println("Il ne faut pas mettre le signe % dans les paramètres passés, uniquement la valeur.")
  120 	fmt.Println("")
  121 }
  122 
  123 //
  124 // writeCurrentRate() va écrire les fichiers current.charging et current.discharging à chaque minute.
  125 // Ces 2 fichiers contiendront le dernier taux de charge ou de décharge calculé dans la
  126 // mesure où ce taux de charge ou de décharge est pertinent (>0)
  127 //
  128 func writeCurrentRate(mode string, value int64) {
  129 	currentTime := time.Now()
  130 	year, week := currentTime.ISOWeek()
  131 	strWeek := strconv.Itoa(week)
  132 	if week < 10 {
  133 		strWeek = "0" + strWeek
  134 	}
  135 	values = append(values, value)
  136 	if len(values) > 1 {
  137 		var diffSlice []int64
  138 		for n := 1; n < len(values); n++ {
  139 			if mode == "Charging" {
  140 				diffSlice = append(diffSlice, values[n]-values[n-1])
  141 			} else { // Discharging
  142 				diffSlice = append(diffSlice, values[n-1]-values[n])
  143 			}
  144 		}
  145 		var sum int64
  146 		for _, d := range diffSlice {
  147 			sum += d
  148 		}
  149 		fmoyPerHour := 60.0 * (float32(sum) / float32(len(diffSlice)))
  150 		if mode == "Charging" {
  151 			strVal := strconv.Itoa(int(fmoyPerHour))
  152 			fmt.Println("Taux de charge :", (strVal + "%/Heure"))
  153 			if fmoyPerHour > 0 {
  154 				rawchardataFilename := "current.charging"
  155 				rc, _ := os.OpenFile(rawchardataFilename, os.O_WRONLY|os.O_CREATE, 0644)
  156 				rc.WriteString(strconv.Itoa(year) + "_" + strWeek + "," + strconv.Itoa(int(value)) + "," + strVal + "\n")
  157 			}
  158 		} else { // mode  = "Discharging"
  159 			strVal := strconv.Itoa(int(fmoyPerHour))
  160 			fmt.Println("Taux de décharge :", strVal+"%/Heure")
  161 			if fmoyPerHour > 0 {
  162 				rawdisdataFilename := "current.discharging"
  163 				rd, _ := os.OpenFile(rawdisdataFilename, os.O_WRONLY|os.O_CREATE, 0644)
  164 				rd.WriteString(strconv.Itoa(year) + "_" + strWeek + "," + strconv.Itoa(int(value)) + "," + strVal + "\n")
  165 			}
  166 		}
  167 	}
  168 }
  169 
  170 //
  171 // updateStats() va regarder si un fichier current.charging ou current.discharging existe
  172 // Si c'est le cas, il va récuperer le dernier taux de charge et/ou de décharge, le stocker
  173 // dans le fichier CStat ou DStat de la semaine courante puis effacer les fichiers
  174 // current.charging et current.discharging
  175 //
  176 func updateStats() {
  177 	var lastData string
  178 	if _, err := os.Stat("current.charging"); err == nil { // Le fichier "current.charging" existe !
  179 		currentFile, _ := os.Open("current.charging")
  180 		fileScanner := bufio.NewScanner(currentFile)
  181 		for fileScanner.Scan() {
  182 			if len(fileScanner.Text()) > 0 {
  183 				lastData = fileScanner.Text()
  184 			}
  185 		}
  186 		currentFile.Close()
  187 		cStatFilename := "cStat.csv"
  188 		statF, _ := os.OpenFile(cStatFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
  189 		statF.WriteString(lastData + "\n")
  190 		os.Remove("current.charging")
  191 	}
  192 	if _, err := os.Stat("current.discharging"); err == nil {
  193 		currentFile, _ := os.Open("current.discharging")
  194 		fileScanner := bufio.NewScanner(currentFile)
  195 		for fileScanner.Scan() {
  196 			if len(fileScanner.Text()) > 0 {
  197 				lastData = fileScanner.Text()
  198 			}
  199 		}
  200 		currentFile.Close()
  201 		dStatFilename := "dStat.csv"
  202 		statF, _ := os.OpenFile(dStatFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
  203 		statF.WriteString(lastData + "\n")
  204 		os.Remove("current.discharging")
  205 	}
  206 }
  207 
  208 // Help sources :
  209 //
  210 // For Time & date format, see :
  211 // https://www.golangprograms.com/get-current-date-and-time-in-various-format-in-golang.html
  212 
  213 // Loop patterns
  214 // https://yourbasic.org/golang/for-loop/
  215 
  216 // Clear Slice
  217 // https://yourbasic.org/golang/clear-slice/
  218 
  219 //Lecture de fichier
  220 //https://www.delftstack.com/fr/howto/go/how-to-read-a-file-line-by-line-in-go/

 

En plus de gérer le niveau de la batterie, batteryCheck va mémoriser les taux de charge et de décharge, en % par heure et les stocker dans 2 fichiers csv : dStat.csv pour la décharge, et cStat.csv pour la charge. Cela permettra de voir au bout de plusieurs semaines/mois/années si la batterie perd en capacité ou non.

La 1ère fonction appelée au début du programme est updateStats() (voir ligne 176). On va regarder ici si un fichier nommé current.charging et/ou un fichier current.discharging existe. Si c'est le cas, on va lire ces fichiers, et reporter leur contenu (qui se limite à une seul ligne) respectivement dans les fichiers cStat.csv et dStat.csv . On va ensuite effacer les fichiers current.charging et current.discharging.

Reprenons à la ligne 18, dans le main. On va s'assurer ici que les paramètres passés au lancement du programme sont bien au nombre de 2, que le 1er est inférieur au 2nd avec un écart d'au moins 10, que le 1er est >=8 et le 2nd est <=100. Ce sont nos limites basse (déchargement max autorisé) et haute (chargement max autorisé). Si ces paramètres passés sont manquants ou incorrects, la fonction showHelp() est appelé pour afficher une aide succinte -voir ligne 111),  puis on sort du programme avec un return.

On a une courte séquence d'initialisation de variables des lignes 35 à 42 - on va ici initialiser les commandes à passer (acpi -b pour récupérer l'état de la batterie, et zenity --notification pour afficher le message de notification quand la charge ou la décharge sera arrivé à son max/min) -, puis on entame une boucle infinie dans laquelle on va scruter l'état de la batterie toutes les minutes.

 Les if ligne 52 et else ligne 78 agiront en fonction du retour de la commande acpi -b exécutée en ligne 44.

Selon le mode dans lequel la batterie se trouve (charge ou décharge) la fonction writeCurrentRate() sera appelée (lignes 63 et 89). Dans cette fonction (ligne 128), on va récupérer le taux de charge/décharge courant de la batterie, regarder les taux précédents (pris N minutes avant), et calculer le taux de charge ou de décharge sur tout le temps de fonctionnement depuis le lancement de l'application.

Ce taux sera ensuite écrit, selon le mode (charge ou décharge) dans un fichier current.charging ou current.discharging. La ligne de données écrite est au format : [ANNÉE_SEMAINE],[Charge de la batterie],[Taux de charge ou de décharge en % / heure].

En retournant dans le main, juste après l'appel de la fonction writeCurrentRate(), on va vérifier si le taux min pour la décharge ou le taux max pour la charge est atteint, auquel cas on va lancer une goroutine go func(){} pour l'affichage du message indiquant le besoin de brancher ou débrancher le secteur.

En bas, ligne 104, juste avant de reboucler sur le for, on va faire une pause d'une minute, avant de recommencer le process, lire l'état de la batterie, etc...

Voilà, c'est tout pour cette fois, en espérant que ces quelques explications auront suffit à éclaircir le fonctionnement de ce programme. J'ai laissé vonlontairement quelques lignes de commentaires à la fin du fichier source pour indiquer les liens vers quelques sites où j'ai pu trouver réponses à quelques problèmes rencontrés lors de la mise au point de ce programme...

Dernière choses, pour télécharger le code, c'est ici

Tschüss :-)

 

Publicité
Retour à l'accueil
Partager cet article
Repost0
Pour être informé des derniers articles, inscrivez vous :
Commenter cet article