2024-09-27 11:40:06 +10:00
// Adapted from https://github.com/periph/cmd/blob/main/ina219/main.go
//
// Copyright 2018 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
2024-10-18 13:05:25 +11:00
package main // import "drjosh.dev/ina219-exporter"
2024-09-27 11:40:06 +10:00
import (
"flag"
"fmt"
2024-09-27 12:50:13 +10:00
"log"
"net/http"
2024-09-27 11:40:06 +10:00
"os"
"os/signal"
"regexp"
"strconv"
"syscall"
"time"
2024-10-18 13:05:25 +11:00
"drjosh.dev/jmetrics"
2024-09-27 12:50:13 +10:00
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
2024-09-27 11:40:06 +10:00
"periph.io/x/conn/v3/i2c/i2creg"
"periph.io/x/conn/v3/physic"
"periph.io/x/devices/v3/ina219"
"periph.io/x/host/v3"
)
2024-11-06 09:26:14 +11:00
func main ( ) {
2024-09-27 12:50:13 +10:00
httpAddr := flag . String ( "http-address" , ":9455" , "Listen addr for HTTP handler" )
i2cAddress := flag . Int ( "i2c-address" , 0x45 , "I²C address" )
i2cBus := flag . String ( "i2c-bus" , "" , "I²C bus (/dev/i2c-1)" )
2024-09-27 11:40:06 +10:00
senseResistor := flag . String ( "sense-resistor" , "10m" , "Resistance of the shunt resistor in ohms (optionally with suffix m = milli, {µ,mu} = micro, n = nano)" )
maxCurrent := flag . String ( "max-current" , "8" , "Maximum current through the device in amps (optional suffix as for --sense-resistor)" )
flag . Parse ( )
resistor , err := parseNanoSI [ physic . ElectricResistance ] ( * senseResistor )
if err != nil {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "parsing --sense-resistor: %w" , err )
2024-09-27 11:40:06 +10:00
}
2024-09-27 11:56:06 +10:00
if resistor <= 0 {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "--sense-resistor must be positive" )
2024-09-27 11:56:06 +10:00
}
2024-09-27 11:40:06 +10:00
current , err := parseNanoSI [ physic . ElectricCurrent ] ( * maxCurrent )
if err != nil {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "parsing --max-current: %w" , err )
2024-09-27 11:40:06 +10:00
}
2024-09-27 11:56:06 +10:00
if current <= 0 {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "--max-current must be positive" )
2024-09-27 11:56:06 +10:00
}
2024-09-27 11:40:06 +10:00
if _ , err := host . Init ( ) ; err != nil {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "host.Init() error: %v" , err )
2024-09-27 11:40:06 +10:00
}
// Open default I²C bus.
2024-09-27 12:50:13 +10:00
bus , err := i2creg . Open ( * i2cBus )
2024-09-27 11:40:06 +10:00
if err != nil {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "i2creg.Open(%v): %v" , * i2cBus , err )
2024-09-27 11:40:06 +10:00
}
defer bus . Close ( )
sensor , err := ina219 . New ( bus , & ina219 . Opts {
2024-09-27 12:50:13 +10:00
Address : * i2cAddress ,
2024-09-27 11:40:06 +10:00
SenseResistor : resistor ,
MaxCurrent : current ,
} )
if err != nil {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "ina219.New(...): %v" , err )
2024-09-27 11:40:06 +10:00
}
2024-09-27 13:05:07 +10:00
constLabels := prometheus . Labels { "i2c_addr" : fmt . Sprintf ( "0x%x" , * i2cAddress ) }
2024-09-27 12:50:13 +10:00
busVoltageHist := promauto . NewHistogram ( prometheus . HistogramOpts {
Namespace : "ina219" ,
Name : "bus_voltage" ,
Help : "Bus voltage (V)" ,
ConstLabels : constLabels ,
NativeHistogramBucketFactor : 1.001 ,
NativeHistogramZeroThreshold : 0.001 ,
} )
2024-10-01 14:44:35 +10:00
busVoltageSumm := jmetrics . NewLiteGaugeSummary ( jmetrics . LiteGaugeSummaryOpts {
Namespace : "ina219" ,
Subsystem : "summ" ,
Name : "bus_voltage" ,
Help : "Bus voltage (V)" ,
ConstLabels : constLabels ,
} )
prometheus . MustRegister ( busVoltageSumm )
2024-09-27 12:50:13 +10:00
busCurrentHist := promauto . NewHistogram ( prometheus . HistogramOpts {
Namespace : "ina219" ,
Name : "bus_current" ,
Help : "Bus current (A)" ,
ConstLabels : constLabels ,
NativeHistogramBucketFactor : 1.001 ,
NativeHistogramZeroThreshold : 0.001 ,
} )
2024-10-01 14:44:35 +10:00
busCurrentSumm := jmetrics . NewLiteGaugeSummary ( jmetrics . LiteGaugeSummaryOpts {
Namespace : "ina219" ,
Subsystem : "summ" ,
Name : "bus_current" ,
Help : "Bus current (A)" ,
ConstLabels : constLabels ,
} )
prometheus . MustRegister ( busCurrentSumm )
2024-09-27 12:50:13 +10:00
busPowerHist := promauto . NewHistogram ( prometheus . HistogramOpts {
Namespace : "ina219" ,
Name : "bus_power" ,
Help : "Bus power (W)" ,
ConstLabels : constLabels ,
NativeHistogramBucketFactor : 1.001 ,
NativeHistogramZeroThreshold : 0.001 ,
} )
2024-10-01 14:44:35 +10:00
busPowerSumm := jmetrics . NewLiteGaugeSummary ( jmetrics . LiteGaugeSummaryOpts {
Namespace : "ina219" ,
Subsystem : "summ" ,
Name : "bus_power" ,
Help : "Bus power (W)" ,
ConstLabels : constLabels ,
} )
prometheus . MustRegister ( busPowerSumm )
2024-09-27 12:50:13 +10:00
shuntVoltageHist := promauto . NewHistogram ( prometheus . HistogramOpts {
Namespace : "ina219" ,
Name : "shunt_voltage" ,
Help : "Shunt voltage (V)" ,
ConstLabels : constLabels ,
NativeHistogramBucketFactor : 1.001 ,
NativeHistogramZeroThreshold : 0.001 ,
} )
2024-10-01 14:44:35 +10:00
shuntVoltageSumm := jmetrics . NewLiteGaugeSummary ( jmetrics . LiteGaugeSummaryOpts {
Namespace : "ina219" ,
Subsystem : "summ" ,
Name : "shunt_voltage" ,
Help : "Shunt voltage (V)" ,
ConstLabels : constLabels ,
} )
prometheus . MustRegister ( shuntVoltageSumm )
2024-09-27 12:50:13 +10:00
go func ( ) {
http . Handle ( "/metrics" , promhttp . Handler ( ) )
log . Fatal ( http . ListenAndServe ( * httpAddr , nil ) )
} ( )
2024-09-27 11:40:06 +10:00
// Read values from sensor every second.
everySecond := time . NewTicker ( time . Second ) . C
2024-09-27 12:50:13 +10:00
halt := make ( chan os . Signal , 1 )
2024-09-27 11:40:06 +10:00
signal . Notify ( halt , syscall . SIGTERM )
signal . Notify ( halt , syscall . SIGINT )
for {
select {
case <- everySecond :
p , err := sensor . Sense ( )
if err != nil {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "sensor.Sense() error: %v" , err )
2024-09-27 11:40:06 +10:00
}
2024-10-08 16:18:56 +11:00
// sanity check sensor outputs:
// current = shunt / resistor, but resistor is fixed,
// so if current == 0 if and only if shunt == 0
if ( p . Current == 0 ) != ( p . Shunt == 0 ) {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "Ohm's Law violation: current = %v but shunt = %v" , p . Current , p . Shunt )
2024-10-08 16:18:56 +11:00
}
// power = current * voltage, similar logic
if ( p . Power == 0 ) != ( p . Current == 0 || p . Voltage == 0 ) {
2024-11-06 09:26:14 +11:00
log . Fatalf ( "Joule's First Law violation: power = %v but current = %v and voltage = %v" , p . Power , p . Current , p . Voltage )
2024-10-08 16:18:56 +11:00
}
2024-10-01 14:44:35 +10:00
busVolts := float64 ( p . Voltage ) / float64 ( physic . Volt )
busVoltageHist . Observe ( busVolts )
busVoltageSumm . Observe ( busVolts )
busAmps := float64 ( p . Current ) / float64 ( physic . Ampere )
busCurrentHist . Observe ( busAmps )
busCurrentSumm . Observe ( busAmps )
busWatts := float64 ( p . Power ) / float64 ( physic . Watt )
busPowerHist . Observe ( busWatts )
busPowerSumm . Observe ( busWatts )
shuntVolts := float64 ( p . Shunt ) / float64 ( physic . Volt )
shuntVoltageHist . Observe ( shuntVolts )
shuntVoltageSumm . Observe ( shuntVolts )
2024-09-27 12:50:13 +10:00
2024-09-27 11:40:06 +10:00
case <- halt :
2024-11-06 09:26:14 +11:00
return
2024-09-27 11:40:06 +10:00
}
}
}
var siSmallRE = regexp . MustCompile ( ` ^(\d+)(m|mu|µ|n)?$ ` )
func parseNanoSI [ U ~ int64 ] ( s string ) ( U , error ) {
submatches := siSmallRE . FindStringSubmatch ( s )
if len ( submatches ) < 2 {
return 0 , fmt . Errorf ( "value %q did not match %q" , s , siSmallRE )
}
valueInt , err := strconv . Atoi ( submatches [ 1 ] )
if err != nil {
return 0 , err
}
value := U ( valueInt )
switch len ( submatches ) {
case 2 :
value *= 1_000_000_000
case 3 :
2024-09-27 11:56:06 +10:00
switch suffix := submatches [ 2 ] ; suffix {
case "" :
value *= 1_000_000_000
2024-09-27 11:40:06 +10:00
case "m" :
value *= 1_000_000
case "mu" , "µ" :
value *= 1_000
case "n" :
value *= 1
default :
2024-09-27 11:56:06 +10:00
return 0 , fmt . Errorf ( "invalid suffix %q" , suffix )
2024-09-27 11:40:06 +10:00
}
}
return value , nil
}