package sensor import ( "errors" "time" "git.mial.net/mokhtar/x1208-exporter/internal/max17040" "git.mial.net/mokhtar/x1208-exporter/internal/pld" ) // Hardware composes the fuel gauge and the AC-present line into one Sensor. // // Each device owns its own reacquire-on-error policy, so a fault on one bus // does not discard a healthy handle on the other. type Hardware struct { gauge *max17040.Gauge line *pld.Line now func() time.Time } // NewHardware returns a Sensor backed by the real HAT. Neither device is opened // until the first Read. func NewHardware(i2cDevice, gpioChip string, gpioLine int) *Hardware { return &Hardware{ gauge: max17040.New(i2cDevice), line: pld.New(gpioChip, gpioLine), now: time.Now, } } // Read returns a complete sample or an error. It never returns a partially // populated Reading: a sample that is missing a channel cannot be rendered or // acted on, so there is nothing useful to pass upwards. // // Both channels are attempted even when the first fails, so one tick reports // every fault rather than hiding the second behind the first. func (h *Hardware) Read() (Reading, error) { acPresent, acErr := h.line.Read() volts, soc, batErr := h.gauge.Read() if err := errors.Join(acErr, batErr); err != nil { return Reading{}, err } return Reading{ ACPresent: acPresent, Volts: volts, SOC: soc, At: h.now(), }, nil } // Close releases both devices, reporting every failure. func (h *Hardware) Close() error { return errors.Join(h.line.Close(), h.gauge.Close()) }