283 lines
6.7 KiB
Go
283 lines
6.7 KiB
Go
// battery
|
|
// Copyright (C) 2016-2017,2019,2023 Karol 'Kenji Takahashi' Woźniak
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
package battery
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func sysctl(mib []int32, out unsafe.Pointer, n uintptr) unix.Errno {
|
|
_, _, e := unix.Syscall6(
|
|
unix.SYS___SYSCTL,
|
|
uintptr(unsafe.Pointer(&mib[0])),
|
|
uintptr(len(mib)),
|
|
uintptr(out),
|
|
uintptr(unsafe.Pointer(&n)),
|
|
uintptr(unsafe.Pointer(nil)),
|
|
0,
|
|
)
|
|
return e
|
|
}
|
|
|
|
var errValueNotFound = fmt.Errorf("Value not found")
|
|
|
|
func setErr(err *error, newErr error) {
|
|
if *err == errValueNotFound {
|
|
*err = newErr
|
|
}
|
|
}
|
|
|
|
var sensorW = [4]int32{
|
|
2, // SENSOR_VOLTS_DC (uV)
|
|
5, // SENSOR_WATTS (uW)
|
|
7, // SENSOR_WATTHOUR (uWh)
|
|
10, // SENSOR_INTEGER
|
|
}
|
|
|
|
const (
|
|
sensorA = 6 // SENSOR_AMPS (uA)
|
|
sensorAH = 8 // SENSOR_AMPHOUR (uAh)
|
|
)
|
|
|
|
type sensorStatus int32
|
|
|
|
const (
|
|
unspecified sensorStatus = iota
|
|
ok
|
|
warning
|
|
critical
|
|
unknown
|
|
)
|
|
|
|
type sensor struct {
|
|
desc [32]byte
|
|
tv [16]byte // struct timeval
|
|
value int64
|
|
typ [4]byte // enum sensor_type
|
|
status sensorStatus
|
|
numt int32
|
|
flags int32
|
|
}
|
|
|
|
func (s *sensor) readValue(div float64) (float64, error) {
|
|
if s.status == unknown {
|
|
return 0, fmt.Errorf("Unknown value received")
|
|
}
|
|
|
|
return float64(s.value) / div, nil
|
|
}
|
|
|
|
func (s *sensor) handleA(factor float64, field *float64, fieldErr *error) {
|
|
*field, *fieldErr = s.readValue(1000)
|
|
if *fieldErr == nil {
|
|
*field *= factor
|
|
}
|
|
}
|
|
|
|
func (s *sensor) handleAH(factor float64, factorErr error, field *float64, fieldErr *error) {
|
|
if factorErr != nil {
|
|
*fieldErr = factorErr
|
|
return
|
|
}
|
|
s.handleA(factor, field, fieldErr)
|
|
}
|
|
|
|
type sensordev struct {
|
|
num int32
|
|
xname [16]byte
|
|
maxnumt [23]int32
|
|
sensors_count int32
|
|
}
|
|
|
|
func (sd *sensordev) get() (*Battery, error) {
|
|
var battery Battery
|
|
err := ErrPartial{
|
|
Design: errValueNotFound,
|
|
Full: errValueNotFound,
|
|
Current: errValueNotFound,
|
|
ChargeRate: errValueNotFound,
|
|
State: errValueNotFound,
|
|
Voltage: errValueNotFound,
|
|
DesignVoltage: errValueNotFound,
|
|
}
|
|
|
|
mib := []int32{unix.CTL_HW, 11, sd.num, 0, 0}
|
|
var s sensor
|
|
|
|
iter := func(maxnumtIdx int32, cb func(string)) {
|
|
mib[3] = maxnumtIdx
|
|
|
|
for i := int32(0); i < sd.maxnumt[maxnumtIdx]; i++ {
|
|
mib[4] = i
|
|
|
|
if errno := sysctl(mib, unsafe.Pointer(&s), unsafe.Sizeof(s)); errno != 0 {
|
|
setErr(&err.Design, errno)
|
|
setErr(&err.Full, errno)
|
|
setErr(&err.Current, errno)
|
|
setErr(&err.ChargeRate, errno)
|
|
setErr(&err.Voltage, errno)
|
|
setErr(&err.DesignVoltage, errno)
|
|
setErr(&err.State, errno)
|
|
}
|
|
|
|
// Convert 0-terminated C-string to a Go string
|
|
cb(string(s.desc[:bytes.IndexByte(s.desc[:], 0)]))
|
|
}
|
|
}
|
|
|
|
for _, w := range sensorW {
|
|
mib[3] = w
|
|
|
|
iter(w, func(desc string) {
|
|
if strings.HasPrefix(desc, "battery ") {
|
|
battery.State.specific, err.State = desc, nil
|
|
|
|
switch desc[8:] {
|
|
case "unknown":
|
|
battery.State.Raw = Unknown
|
|
case "full":
|
|
battery.State.Raw = Full
|
|
case "charging":
|
|
battery.State.Raw = Charging
|
|
case "discharging":
|
|
battery.State.Raw = Discharging
|
|
case "idle":
|
|
battery.State.Raw = Idle
|
|
case "critical":
|
|
battery.State.Raw = Empty
|
|
default:
|
|
battery.State.Raw = Undefined
|
|
}
|
|
return
|
|
}
|
|
switch desc {
|
|
case "rate":
|
|
battery.ChargeRate, err.ChargeRate = s.readValue(1000)
|
|
case "design capacity":
|
|
battery.Design, err.Design = s.readValue(1000)
|
|
case "last full capacity":
|
|
battery.Full, err.Full = s.readValue(1000)
|
|
case "remaining capacity":
|
|
battery.Current, err.Current = s.readValue(1000)
|
|
case "current voltage":
|
|
battery.Voltage, err.Voltage = s.readValue(1000_000)
|
|
case "voltage":
|
|
battery.DesignVoltage, err.DesignVoltage = s.readValue(1000_000)
|
|
}
|
|
})
|
|
}
|
|
|
|
if err.DesignVoltage != nil && err.Voltage == nil {
|
|
battery.DesignVoltage, err.DesignVoltage = battery.Voltage, nil
|
|
}
|
|
if err.ChargeRate == errValueNotFound {
|
|
if err.Voltage == nil {
|
|
iter(sensorA, func(desc string) {
|
|
if desc == "rate" {
|
|
s.handleA(battery.Voltage, &battery.ChargeRate, &err.ChargeRate)
|
|
}
|
|
})
|
|
} else {
|
|
err.ChargeRate = err.Voltage
|
|
}
|
|
}
|
|
if err.Design == errValueNotFound || err.Full == errValueNotFound || err.Current == errValueNotFound {
|
|
iter(sensorAH, func(desc string) {
|
|
switch desc {
|
|
case "design capacity":
|
|
s.handleAH(battery.DesignVoltage, err.DesignVoltage, &battery.Design, &err.Design)
|
|
case "last full capacity":
|
|
s.handleAH(battery.Voltage, err.Voltage, &battery.Full, &err.Full)
|
|
case "remaining capacity":
|
|
s.handleAH(battery.Voltage, err.Voltage, &battery.Current, &err.Current)
|
|
}
|
|
})
|
|
}
|
|
|
|
return &battery, err
|
|
}
|
|
|
|
func getBatteryAtMIBIndex(i int32) (*Battery, int32, error) {
|
|
mib := []int32{
|
|
unix.CTL_HW,
|
|
11, // HW_SENSORS
|
|
0,
|
|
}
|
|
var sd sensordev
|
|
for ; ; i++ {
|
|
mib[2] = i
|
|
|
|
errno := sysctl(mib, unsafe.Pointer(&sd), unsafe.Sizeof(sd))
|
|
if errno == unix.ENOENT {
|
|
break
|
|
}
|
|
if errno != 0 {
|
|
continue
|
|
}
|
|
if bytes.HasPrefix(sd.xname[:], []byte("acpibat")) {
|
|
battery, err := sd.get()
|
|
return battery, i + 1, err
|
|
}
|
|
}
|
|
return nil, i, nil
|
|
}
|
|
|
|
func systemGet(idx int) (*Battery, error) {
|
|
var i int32
|
|
for idxCurr := 0; ; idxCurr++ {
|
|
battery, iNext, err := getBatteryAtMIBIndex(i)
|
|
// If this is the index we look for, grab it.
|
|
// Otherwise just move on, regardless of errors etc.
|
|
if idxCurr == idx {
|
|
return battery, err
|
|
}
|
|
|
|
i = iNext
|
|
}
|
|
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
func systemGetAll() ([]*Battery, error) {
|
|
var batteries []*Battery
|
|
var errors Errors
|
|
|
|
var i int32
|
|
for {
|
|
battery, iNext, err := getBatteryAtMIBIndex(i)
|
|
if battery == nil && err == nil { // no more batteries
|
|
break
|
|
}
|
|
batteries = append(batteries, battery)
|
|
errors = append(errors, err)
|
|
|
|
i = iNext
|
|
}
|
|
|
|
return batteries, errors
|
|
}
|