Blog traitant d'informatique, de programmation, de python, de golang, de 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 :-)