mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
fix(temperature): parse string sensor values without zeroing readings (#1224)
This commit is contained in:
parent
0d6fffbb1c
commit
cedf0c8f0f
4 changed files with 151 additions and 7 deletions
|
|
@ -712,8 +712,8 @@ func extractTempInput(sensorMap map[string]interface{}) float64 {
|
|||
case int:
|
||||
return float64(v)
|
||||
case string:
|
||||
if f, err := strconv.ParseFloat(v, 64); err == nil {
|
||||
return f
|
||||
if parsed, ok := parseStringTemperature(v); ok {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -721,6 +721,26 @@ func extractTempInput(sensorMap map[string]interface{}) float64 {
|
|||
return math.NaN()
|
||||
}
|
||||
|
||||
func parseStringTemperature(value string) (float64, bool) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
parsed, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
if _, scanErr := fmt.Sscanf(value, "%f", &parsed); scanErr != nil {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
if math.Abs(parsed) >= 1000 {
|
||||
parsed = parsed / 1000.0
|
||||
}
|
||||
|
||||
return parsed, true
|
||||
}
|
||||
|
||||
// extractCoreNumber extracts the core number from a sensor name like "Core 0"
|
||||
func extractCoreNumber(name string) int {
|
||||
parts := strings.Fields(name)
|
||||
|
|
|
|||
|
|
@ -119,6 +119,43 @@ func TestTemperatureCollector_ParseSensorsJSON_Complex(t *testing.T) {
|
|||
assert.Equal(t, 60.5, temp.CPUPackage) // Tctl mapped to package
|
||||
}
|
||||
|
||||
func TestExtractTempInput_StringValues(t *testing.T) {
|
||||
t.Run("parses celsius strings with suffix", func(t *testing.T) {
|
||||
got := extractTempInput(map[string]interface{}{
|
||||
"temp1_input": "+44.5°C",
|
||||
})
|
||||
assert.InDelta(t, 44.5, got, 0.0001)
|
||||
})
|
||||
|
||||
t.Run("parses millidegree strings", func(t *testing.T) {
|
||||
got := extractTempInput(map[string]interface{}{
|
||||
"temp1_input": "39000",
|
||||
})
|
||||
assert.InDelta(t, 39.0, got, 0.0001)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemperatureCollector_ParseSensorsJSON_StringTemps(t *testing.T) {
|
||||
tc := &TemperatureCollector{}
|
||||
jsonStr := `{
|
||||
"coretemp-isa-0000": {
|
||||
"Package id 0": { "temp1_input": "+47.0°C" },
|
||||
"Core 0": { "temp2_input": "45.0" }
|
||||
},
|
||||
"nvme-pci-0100": {
|
||||
"Composite": { "temp1_input": "42000" }
|
||||
}
|
||||
}`
|
||||
|
||||
temp, err := tc.parseSensorsJSON(jsonStr)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, temp)
|
||||
assert.True(t, temp.Available)
|
||||
assert.InDelta(t, 47.0, temp.CPUPackage, 0.0001)
|
||||
require.Len(t, temp.NVMe, 1)
|
||||
assert.InDelta(t, 42.0, temp.NVMe[0].Temp, 0.0001)
|
||||
}
|
||||
|
||||
func TestTemperatureCollector_HelperMethods(t *testing.T) {
|
||||
// extractCoreNumber
|
||||
// Private methods are hard to test directly from separate package if using _test,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
|
@ -279,11 +280,8 @@ func extractTempInput(sensorMap map[string]interface{}) float64 {
|
|||
case int:
|
||||
return float64(v)
|
||||
case string:
|
||||
// Raspberry Pi reports in millidegrees as string
|
||||
var milliTemp float64
|
||||
if _, err := fmt.Sscanf(v, "%f", &milliTemp); err == nil {
|
||||
// Convert from millidegrees to degrees
|
||||
return milliTemp / 1000.0
|
||||
if parsed, ok := parseStringTemperature(v); ok {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -291,6 +289,32 @@ func extractTempInput(sensorMap map[string]interface{}) float64 {
|
|||
return math.NaN()
|
||||
}
|
||||
|
||||
// parseStringTemperature parses numeric string temperature values.
|
||||
// It preserves normal degree values (e.g., "45.0", "+45.0C") and only converts
|
||||
// probable millidegree values (e.g., "42000") down to degrees.
|
||||
func parseStringTemperature(value string) (float64, bool) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
parsed, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
// Fall back to parsing numeric prefixes such as "+45.0°C".
|
||||
if _, scanErr := fmt.Sscanf(value, "%f", &parsed); scanErr != nil {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// lm-sensors fallback on some platforms can report millidegrees as raw strings.
|
||||
// Convert only when the magnitude strongly indicates millidegrees.
|
||||
if math.Abs(parsed) >= 1000 {
|
||||
parsed = parsed / 1000.0
|
||||
}
|
||||
|
||||
return parsed, true
|
||||
}
|
||||
|
||||
// parseFansAndOther extracts fan speeds and other temperature readings from a sensor chip.
|
||||
// This captures DDR5/RAM temps, motherboard temps, additional NVMe sensors, fan speeds, etc.
|
||||
func parseFansAndOther(chipName string, chipMap map[string]interface{}, data *TemperatureData) {
|
||||
|
|
|
|||
|
|
@ -100,3 +100,66 @@ func TestExtractNVMeCompositeTemp(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtractTempInput_StringTemperatures(t *testing.T) {
|
||||
t.Run("preserves normal celsius values", func(t *testing.T) {
|
||||
sensorMap := map[string]interface{}{
|
||||
"temp1_input": "+45.5°C",
|
||||
}
|
||||
|
||||
got := extractTempInput(sensorMap)
|
||||
if math.Abs(got-45.5) > 0.0001 {
|
||||
t.Fatalf("extractTempInput() = %v, want 45.5", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("converts millidegree strings", func(t *testing.T) {
|
||||
sensorMap := map[string]interface{}{
|
||||
"temp1_input": "42000",
|
||||
}
|
||||
|
||||
got := extractTempInput(sensorMap)
|
||||
if math.Abs(got-42.0) > 0.0001 {
|
||||
t.Fatalf("extractTempInput() = %v, want 42.0", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParse_WithStringTemperatureValues(t *testing.T) {
|
||||
input := `{
|
||||
"coretemp-isa-0000": {
|
||||
"Package id 0": {
|
||||
"temp1_input": "+50.0°C"
|
||||
},
|
||||
"Core 0": {
|
||||
"temp2_input": "48.0"
|
||||
}
|
||||
},
|
||||
"nvme-pci-0100": {
|
||||
"Composite": {
|
||||
"temp1_input": "39000"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
data, err := Parse(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error: %v", err)
|
||||
}
|
||||
|
||||
if !data.Available {
|
||||
t.Fatal("Expected Available to be true")
|
||||
}
|
||||
|
||||
if math.Abs(data.CPUPackage-50.0) > 0.0001 {
|
||||
t.Fatalf("CPUPackage = %v, want 50.0", data.CPUPackage)
|
||||
}
|
||||
|
||||
if math.Abs(data.Cores["Core 0"]-48.0) > 0.0001 {
|
||||
t.Fatalf("Core 0 = %v, want 48.0", data.Cores["Core 0"])
|
||||
}
|
||||
|
||||
if math.Abs(data.NVMe["nvme0"]-39.0) > 0.0001 {
|
||||
t.Fatalf("nvme0 = %v, want 39.0", data.NVMe["nvme0"])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue