/
linearisation.go
199 lines (182 loc) · 8.11 KB
/
linearisation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package ipmi
import (
"errors"
"fmt"
"math"
)
const (
LinearisationLinear Linearisation = iota
LinearisationLn
LinearisationLog10
LinearisationLog2
LinearisationE
LinearisationExp10
LinearisationExp2
LinearisationInverse
LinearisationSqr
LinearisationCube
LinearisationSqrt
LinearisationCubeRt
LinearisationNonLinear
// 0x71 through 0x7f are reserved for non-linear, OEM defined
// linearisations. It is unclear why these cannot use
// LinearisationNonLinear, as being non-linear, they do not have a
// linearisation formula. Waiting for a use case to emerge rather than
// implementing a questionably useful RegisterLineariser() function.
)
var (
// ErrNotLinearised is returned if Lineariser() is called on a linear or
// non-linear linearisation. Linear sensors' values do not require any
// transformation by virtue of the sensor already being linear. If the sensor
// is non-linear, the conversion factors returned by Get Sensor Reading
// Factors are all that are needed to obtain a real value: by being unique
// to the raw sensor reading, there is no need for a separate linearisation
// formula.
//
// Linearise() could return a no-op lineariser, however the current
// implementation should never ask for one on a non-linearised sensor, so
// instead we return an error to flag up a possible bug.
ErrNotLinearised = errors.New(
"only linearised sensors have a linearisation formula")
linearisationDescriptions = map[Linearisation]string{
LinearisationLinear: "Linear",
LinearisationLn: "ln",
LinearisationLog10: "log10",
LinearisationLog2: "log2",
LinearisationE: "e",
LinearisationExp10: "exp10",
LinearisationExp2: "exp2",
LinearisationInverse: "1/x",
LinearisationSqr: "sqr(x)",
LinearisationCube: "cube(x)",
LinearisationSqrt: "sqrt(x)",
LinearisationCubeRt: "x^(1/3)",
LinearisationNonLinear: "Non-linear",
}
// linearisationLinearisers allows us to find out what linearisation formula
// needs to be applied to the converted output of a linearised sensor, to
// produce a real value. Note that linear and non-linear linearisations do
// not appear here as they don't need a linearisation formula.
linearisationLinearisers = map[Linearisation]Lineariser{
LinearisationLn: LineariserFunc(math.Log),
LinearisationLog10: LineariserFunc(math.Log10),
LinearisationLog2: LineariserFunc(math.Log2),
LinearisationE: LineariserFunc(math.Exp),
LinearisationExp10: LineariserFunc(func(f float64) float64 {
// cannot use math.Pow10 as that takes an int
return math.Pow(10, f)
}),
LinearisationExp2: LineariserFunc(math.Exp2),
LinearisationInverse: LineariserFunc(func(f float64) float64 {
return math.Pow(f, -1)
}),
LinearisationSqr: LineariserFunc(func(f float64) float64 {
return math.Pow(f, 2)
}),
LinearisationCube: LineariserFunc(func(f float64) float64 {
return math.Pow(f, 3)
}),
LinearisationSqrt: LineariserFunc(math.Sqrt),
LinearisationCubeRt: LineariserFunc(func(f float64) float64 {
return math.Pow(f, 1./3)
}),
}
)
// Linearisation indicates whether a sensor is linear, linearised, or
// non-linear. Values are specified in the Full Sensor Record wire format table
// in 37-1 and 43-1 of v1.5 and v2.0 respectively.
//
// Linear sensors are the easiest to deal with. The sensor's raw readings are
// converted into real readings (e.g. Celsius) with a linear formula. Accuracy
// and resolution are constant in real terms across the entire range of values
// produced by the sensor.
//
// Linearised are slightly more challenging. The same linear formula is applied
// as for linear sensors, however a final "linearisation formula" is applied to
// obtain the real reading. This transformation is one of 11 defined in the
// spec, e.g. log or sqrt, and obviously does not have to be linear itself. The
// tolerance (the spec misuses accuracy as a synonym) of linearised sensors is
// also constant for all values. This is possible despite the existence of the
// linearisation formula turning raw values into disproportionate real values,
// as tolerance is expressed relative to 0. This assumes the sensor's tolerance
// does not diminish in real, absolute terms at extreme values (positive or
// negative), as there is no way of representing it (you'd have to resort to
// declaring it a non-linear sensor). Note that tolerance can only be expressed
// in half-raw value increments, which is in itself quite coarse. Regarding
// resolution, this will vary with reading due to the linearisation formula. The
// recommended way to calculate it is to retrieve and calculate the real values
// (with the help of Get Sensor Reading Factors as necessary) corresponding to
// the raw values below and above the actual raw value observed. Subtracting the
// real reading for the raw value below the observed raw value from the real
// reading for the observed value gives the negative resolution, and the process
// is equivalent for the positive resolution using the raw value one above.
//
// All consistency bets are off with non-linear sensors. Not only does
// resolution vary by reading (calculated in the same was as for linearised
// sensors), but so does tolerance. Get Sensor Reading Factors must be sent with
// each raw reading; applying the linear formula using the returned conversion
// factors yields the real reading, and can the same factors can be plugged into
// the tolerance and resolution formulae to calculate them.
type Linearisation uint8
// IsLinear returns whether the underlying sensor is linear. Calling
// Lineariser() will return an error, as there is no linearisation formula (it
// is effectively a no-op). Only the linear formula in the spec needs be applied
// to obtain a real reading.
func (l Linearisation) IsLinear() bool {
return l == LinearisationLinear
}
// IsLinearised returns whether the underlying sensor is linearised, meaning the
// value after conversion needs to be fed through a linearisation formula as a
// final step before being used. A suitable implementation of this function is
// returned by the Lineariser() method.
func (l Linearisation) IsLinearised() bool {
return l > LinearisationLinear && l < LinearisationNonLinear
}
// IsNonLinear returns whether the underlying sensor is not consistent enough
// for the constraints of linear and linearised. As for linear sensors,
// attempting to retrieve a Lineariser will return an error. Readings from these
// sensors require Get Sensor Reading Factors to convert them into usable
// values.
func (l Linearisation) IsNonLinear() bool {
return l >= LinearisationNonLinear
}
// Lineariser returns a suitable Lineariser implementation that will turn the
// converted raw value produced by the underlying sensor into a usable value. If
// the sensor is already linear, or non-linear, this will return
// ErrNotLinearised.
func (l Linearisation) Lineariser() (Lineariser, error) {
if lineariser, ok := linearisationLinearisers[l]; ok {
return lineariser, nil
}
return nil, ErrNotLinearised
}
func (l Linearisation) Description() string {
if desc, ok := linearisationDescriptions[l]; ok {
return desc
}
if l >= 0x71 && l <= 0x7f {
return "Non-linear OEM"
}
return "Unknown"
}
func (l Linearisation) String() string {
return fmt.Sprintf("%#v(%v)", uint8(l), l.Description())
}
// Lineariser is implemented by formulae that can linearise a value returned by
// the Get Sensor Reading command that has gone through the linear formula
// containing M, B, K1 and K2, used for all sensors.
type Lineariser interface {
// Linearise applies a linearisation formula to a converted value, returning
// the final value in the correct unit. This is the last step in the "Sensor
// Reading Conversion Formula" described in section 30.3 of IPMI v1.5 and
// v2.0.
Linearise(float64) float64
}
// LineariserFunc is the type of the function in the Lineariser interface. It
// allows us to create stateless Lineariser implementations from raw functions,
// including those in the math package.
type LineariserFunc func(float64) float64
// Linearise invokes the wrapped function, passing through the input and result.
func (l LineariserFunc) Linearise(f float64) float64 {
return l(f)
}