temp.go (2299B)
1 package temp 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os/exec" 8 "strings" 9 ) 10 11 // Sensor describes a single temperature sensor to be read. 12 type Sensor struct { 13 Label string `json:"label"` 14 Path []string `json:"path"` 15 } 16 17 // Config holds the configuration for the temp block. 18 type Config struct { 19 Sensors []Sensor `json:"sensors"` 20 } 21 22 type Block struct { 23 config Config 24 } 25 26 func New(c Config) *Block { 27 return &Block{config: c} 28 } 29 30 func (b *Block) Name() string { 31 return "temp" 32 } 33 34 func (b *Block) Render(ctx context.Context) (string, error) { 35 out, err := exec.CommandContext(ctx, "sensors", "-j").Output() 36 if err != nil { 37 return "", fmt.Errorf("exec sensors failed: %v", err) 38 } 39 if len(b.config.Sensors) == 0 { 40 return "", nil 41 } 42 43 var parts = make([]string, 0, len(b.config.Sensors)) 44 for _, sensor := range b.config.Sensors { 45 temp, err := parse(out, sensor.Path) 46 if err != nil { 47 return "", fmt.Errorf( 48 "failed to get temp for %v: %w", 49 sensor.Path, err, 50 ) 51 } 52 53 var renderedSensor string 54 if sensor.Label != "" { 55 renderedSensor = fmt.Sprintf( 56 "%s %.0f℃", sensor.Label, temp, 57 ) 58 } else { 59 renderedSensor = fmt.Sprintf("%.0f℃", temp) 60 } 61 parts = append(parts, renderedSensor) 62 } 63 64 return strings.Join(parts, " "), nil 65 } 66 67 // parse traverses the json data using the given path to find a temperature 68 // value. 69 func parse(data []byte, path []string) (float64, error) { 70 if len(path) == 0 { 71 return 0, fmt.Errorf("sensor path cannot be empty") 72 } 73 74 // We start with the full JSON output. 75 var current = json.RawMessage(data) 76 77 // For each key in the path, we dive one level deeper into the JSON 78 // structure. 79 for i, key := range path { 80 var m map[string]json.RawMessage 81 if err := json.Unmarshal(current, &m); err != nil { 82 return 0, fmt.Errorf( 83 "failed to unmarshal object at path segment %d ('%s'): %w", 84 i, key, err, 85 ) 86 } 87 88 var ok bool 89 current, ok = m[key] 90 if !ok { 91 return 0, fmt.Errorf( 92 "key not found at path segment %d ('%s')", 93 i, key, 94 ) 95 } 96 } 97 98 // After the loop, 'current' holds the raw JSON for the temperature 99 // value. 100 var temp float64 101 if err := json.Unmarshal(current, &temp); err != nil { 102 return 0, fmt.Errorf( 103 "final value at path %v is not a number: %w", path, err, 104 ) 105 } 106 107 return temp, nil 108 }