use sensors.py library from Pavel Rojtberg instead of PySensors. fix #781
paulfantom committed
Aug 14, 2016 at 19:51 UTC
1779a2b46a9f6115201f6dd8fdad57a9d21d015d
3 files changed
+303
-351
LICENSE.md
+3
-3
@@ -138,10 +138,10 @@ connectivity is not available.
138
Copyright 2015, Joseph Huckaby
139
[MIT License](https://github.com/jhuckaby/pixl-xml)
140
141
-- [PySensors](https://bitbucket.org/blackjack/pysensors)
141
+- [sensors](https://github.com/paroj/sensors.py)
142
143
- Copyright 2014, Marc 'BlackJack' Rintsch
144
- [LGPL 2.1 License](https://bitbucket.org/blackjack/pysensors)
143
+ Copyright 2014, Pavel Rojtberg
144
+ [LGPL 2.1 License](http://opensource.org/licenses/LGPL-2.1)
145
146
- [PyYAML](https://bitbucket.org/blackjack/pysensors)
147
python.d/python_modules/lm_sensors.py
+251
-303
@@ -1,309 +1,257 @@
1
-#!/usr/bin/env python
2
-# -*- coding: utf-8 -*-
3
-import os
4
-
5
-from ctypes import CDLL, c_char_p, c_int, c_void_p, c_uint, c_double, byref, Structure, get_errno,\
6
- POINTER, c_short, c_size_t, create_string_buffer
7
-from ctypes.util import find_library
8
-
9
-version_info = (0, 0, 3)
10
-
11
-__version__ = '.'.join(map(str, version_info))
12
-__date__ = '2014-08-17'
13
-__author__ = "Marc 'BlackJack' Rintsch"
14
-__contact__ = 'marc@rintsch.de'
15
-__license__ = 'LGPL v2.1'
16
-
17
-API_VERSION = 4
18
-DEFAULT_CONFIG_FILENAME = '/etc/sensors3.conf'
19
-
20
-LIB_FILENAME = os.environ.get('SENSORS_LIB') or find_library('sensors')
21
-SENSORS_LIB = CDLL(LIB_FILENAME)
22
-VERSION = c_char_p.in_dll(SENSORS_LIB, 'libsensors_version').value
23
-MAJOR_VERSION = version_info[0]
24
-STDC_LIB = CDLL(find_library('c'), use_errno=True)
25
-
26
-TYPE_DICT = {
27
- 0: 'voltage',
28
- 1: 'fan',
29
- 2: 'temperature',
30
- 3: 'power',
31
- 4: 'energy',
32
- 5: 'current',
33
- 6: 'humidity',
34
- 7: 'max_main',
35
- 16: 'vid',
36
- 17: 'intrusion',
37
- 18: 'max_other',
38
- 24: 'beep_enable'
39
-}
40
-
41
-class SensorsError(Exception):
42
- def __init__(self, message, error_number=None):
43
- Exception.__init__(self, message)
44
- self.error_number = error_number
45
-
46
-
47
-def _error_check(result, _func, _arguments):
48
- if result < 0:
49
- raise SensorsError(_strerror(result), result)
50
- return result
51
-
52
-_strerror = SENSORS_LIB.sensors_strerror
53
-_strerror.argtypes = [c_int]
54
-_strerror.restype = c_char_p
55
-
56
-_init = SENSORS_LIB.sensors_init
57
-_init.argtypes = [c_void_p]
58
-_init.restype = c_int
59
-_init.errcheck = _error_check
60
-
61
-cleanup = SENSORS_LIB.sensors_cleanup
62
-cleanup.argtypes = None
63
-cleanup.restype = None
64
-
65
-
66
-def init(config_filename=DEFAULT_CONFIG_FILENAME):
67
- file_p = STDC_LIB.fopen(config_filename.encode('utf-8'), b'r')
68
- if file_p is None:
69
- error_number = get_errno()
70
- raise OSError(error_number, os.strerror(error_number), config_filename)
71
- try:
72
- _init(file_p)
73
- finally:
74
- STDC_LIB.fclose(file_p)
75
-
76
-
77
-class Subfeature(Structure):
78
- _fields_ = [
79
- ('name', c_char_p),
80
- ('number', c_int),
81
- ('type', c_int),
82
- ('mapping', c_int),
83
- ('flags', c_uint),
84
- ]
85
-
86
- def __repr__(self):
87
- return '<%s name=%r number=%d type=%d mapping=%d flags=%08x>' % (
88
- self.__class__.__name__,
89
- self.name,
90
- self.number,
91
- self.type,
92
- self.mapping,
93
- self.flags
94
- )
95
-
96
- def get_value(self):
97
- result = c_double()
98
- _get_value(byref(self.parent.chip), self.number, byref(result))
99
- return result.value
100
-
101
-SUBFEATURE_P = POINTER(Subfeature)
102
-
103
-
104
-class Feature(Structure):
105
- _fields_ = [
106
- ('name', c_char_p),
107
- ('number', c_int),
108
- ('type', c_int),
109
- ('_first_subfeature', c_int),
110
- ('_padding1', c_int),
111
- ]
112
-
113
- def __repr__(self):
114
- return '<%s name=%r number=%r type=%r>' % (
115
- self.__class__.__name__,
116
- self.name,
117
- self.number,
118
- self.type
119
- )
1
+"""
2
+@package sensors.py
3
+Python Bindings for libsensors3
4
+
5
+use the documentation of libsensors for the low level API.
6
+see example.py for high level API usage.
7
+
8
+@author: Pavel Rojtberg (http://www.rojtberg.net)
9
+@see: https://github.com/paroj/sensors.py
10
+@copyright: LGPLv2 (same as libsensors) <http://opensource.org/licenses/LGPL-2.1>
11
+"""
12
+
13
+from ctypes import *
14
+import ctypes.util
15
+
16
+_libc = cdll.LoadLibrary(ctypes.util.find_library("c"))
17
+# see https://github.com/paroj/sensors.py/issues/1
18
+_libc.free.argtypes = [c_void_p]
19
+_hdl = cdll.LoadLibrary(ctypes.util.find_library("sensors"))
20
+
21
+version = c_char_p.in_dll(_hdl, "libsensors_version").value.decode("ascii")
22
+
23
+
24
+class bus_id(Structure):
25
+ _fields_ = [("type", c_short),
26
+ ("nr", c_short)]
27
+
28
+
29
+class chip_name(Structure):
30
+ _fields_ = [("prefix", c_char_p),
31
+ ("bus", bus_id),
32
+ ("addr", c_int),
33
+ ("path", c_char_p)]
34
+
35
+
36
+class feature(Structure):
37
+ _fields_ = [("name", c_char_p),
38
+ ("number", c_int),
39
+ ("type", c_int)]
40
+
41
+ # sensors_feature_type
42
+ IN = 0x00
43
+ FAN = 0x01
44
+ TEMP = 0x02
45
+ POWER = 0x03
46
+ ENERGY = 0x04
47
+ CURR = 0x05
48
+ HUMIDITY = 0x06
49
+ MAX_MAIN = 0x7
50
+ VID = 0x10
51
+ INTRUSION = 0x11
52
+ MAX_OTHER = 0x12
53
+ BEEP_ENABLE = 0x18
54
+
55
+
56
+class subfeature(Structure):
57
+ _fields_ = [("name", c_char_p),
58
+ ("number", c_int),
59
+ ("type", c_int),
60
+ ("mapping", c_int),
61
+ ("flags", c_uint)]
62
+
63
+
64
+_hdl.sensors_get_detected_chips.restype = POINTER(chip_name)
65
+_hdl.sensors_get_features.restype = POINTER(feature)
66
+_hdl.sensors_get_all_subfeatures.restype = POINTER(subfeature)
67
+_hdl.sensors_get_label.restype = c_void_p # return pointer instead of str so we can free it
68
+_hdl.sensors_get_adapter_name.restype = c_char_p # docs do not say whether to free this or not
69
+_hdl.sensors_strerror.restype = c_char_p
70
+
71
+### RAW API ###
72
+MODE_R = 1
73
+MODE_W = 2
74
+COMPUTE_MAPPING = 4
75
+
76
+
77
+def init(cfg_file=None):
78
+ file = _libc.fopen(cfg_file.encode("utf-8"), "r") if cfg_file is not None else None
79
+
80
+ if _hdl.sensors_init(file) != 0:
81
+ raise Exception("sensors_init failed")
82
+
83
+ if file is not None:
84
+ _libc.fclose(file)
85
+
86
+
87
+def cleanup():
88
+ _hdl.sensors_cleanup()
89
+
90
+
91
+def parse_chip_name(orig_name):
92
+ ret = chip_name()
93
+ err = _hdl.sensors_parse_chip_name(orig_name.encode("utf-8"), byref(ret))
94
+
95
+ if err < 0:
96
+ raise Exception(strerror(err))
97
+
98
+ return ret
99
+
100
+
101
+def strerror(errnum):
102
+ return _hdl.sensors_strerror(errnum).decode("utf-8")
103
+
104
+
105
+def free_chip_name(chip):
106
+ _hdl.sensors_free_chip_name(byref(chip))
107
+
108
+
109
+def get_detected_chips(match, nr):
110
+ """
111
+ @return: (chip, next nr to query)
112
+ """
113
+ _nr = c_int(nr)
114
+
115
+ if match is not None:
116
+ match = byref(match)
117
+
118
+ chip = _hdl.sensors_get_detected_chips(match, byref(_nr))
119
+ chip = chip.contents if bool(chip) else None
120
+ return chip, _nr.value
121
+
122
+
123
+def chip_snprintf_name(chip, buffer_size=200):
124
+ """
125
+ @param buffer_size defaults to the size used in the sensors utility
126
+ """
127
+ ret = create_string_buffer(buffer_size)
128
+ err = _hdl.sensors_snprintf_chip_name(ret, buffer_size, byref(chip))
129
+
130
+ if err < 0:
131
+ raise Exception(strerror(err))
132
+
133
+ return ret.value.decode("utf-8")
134
+
135
+
136
+def do_chip_sets(chip):
137
+ """
138
+ @attention this function was not tested
139
+ """
140
+ err = _hdl.sensors_do_chip_sets(byref(chip))
141
+ if err < 0:
142
+ raise Exception(strerror(err))
143
+
144
+
145
+def get_adapter_name(bus):
146
+ return _hdl.sensors_get_adapter_name(byref(bus)).decode("utf-8")
147
+
148
+
149
+def get_features(chip, nr):
150
+ """
151
+ @return: (feature, next nr to query)
152
+ """
153
+ _nr = c_int(nr)
154
+ feature = _hdl.sensors_get_features(byref(chip), byref(_nr))
155
+ feature = feature.contents if bool(feature) else None
156
+ return feature, _nr.value
157
+
158
+
159
+def get_label(chip, feature):
160
+ ptr = _hdl.sensors_get_label(byref(chip), byref(feature))
161
+ val = cast(ptr, c_char_p).value.decode("utf-8")
162
+ _libc.free(ptr)
163
+ return val
164
+
165
+
166
+def get_all_subfeatures(chip, feature, nr):
167
+ """
168
+ @return: (subfeature, next nr to query)
169
+ """
170
+ _nr = c_int(nr)
171
+ subfeature = _hdl.sensors_get_all_subfeatures(byref(chip), byref(feature), byref(_nr))
172
+ subfeature = subfeature.contents if bool(subfeature) else None
173
+ return subfeature, _nr.value
174
+
175
+
176
+def get_value(chip, subfeature_nr):
177
+ val = c_double()
178
+ err = _hdl.sensors_get_value(byref(chip), subfeature_nr, byref(val))
179
+ if err < 0:
180
+ raise Exception(strerror(err))
181
+ return val.value
182
+
183
+
184
+def set_value(chip, subfeature_nr, value):
185
+ """
186
+ @attention this function was not tested
187
+ """
188
+ val = c_double(value)
189
+ err = _hdl.sensors_set_value(byref(chip), subfeature_nr, byref(val))
190
+ if err < 0:
191
+ raise Exception(strerror(err))
192
+
193
+
194
+### Convenience API ###
195
+class ChipIterator:
196
+ def __init__(self, match=None):
197
+ self.match = parse_chip_name(match) if match is not None else None
198
+ self.nr = 0
199
200
def __iter__(self):
122
- number = c_int(0)
123
- while True:
124
- result_p = _get_all_subfeatures(
125
- byref(self.chip),
126
- byref(self),
127
- byref(number)
128
- )
129
- if not result_p:
130
- break
131
- result = result_p.contents
132
- result.chip = self.chip
133
- result.parent = self
134
- yield result
135
-
136
- @property
137
- def label(self):
138
- #
139
- # TODO Maybe this is a memory leak!
140
- #
141
- return _get_label(byref(self.chip), byref(self)).decode('utf-8')
142
-
143
- def get_value(self):
144
- #
145
- # TODO Is the first always the correct one for all feature types?
146
- #
147
- return next(iter(self)).get_value()
148
-
149
-FEATURE_P = POINTER(Feature)
150
-
151
-
152
-class Bus(Structure):
153
- TYPE_ANY = -1
154
- NR_ANY = -1
155
-
156
- _fields_ = [
157
- ('type', c_short),
158
- ('nr', c_short),
159
- ]
160
-
161
- def __str__(self):
162
- return (
163
- '*' if self.type == self.TYPE_ANY
164
- else _get_adapter_name(byref(self)).decode('utf-8')
165
- )
166
-
167
- def __repr__(self):
168
- return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.nr)
169
-
170
- @property
171
- def has_wildcards(self):
172
- return self.type == self.TYPE_ANY or self.nr == self.NR_ANY
173
-
174
-BUS_P = POINTER(Bus)
175
-
176
-
177
-class Chip(Structure):
178
- #
179
- # TODO Move common stuff into `AbstractChip` class.
180
- #
181
- _fields_ = [
182
- ('prefix', c_char_p),
183
- ('bus', Bus),
184
- ('addr', c_int),
185
- ('path', c_char_p),
186
- ]
187
-
188
- PREFIX_ANY = None
189
- ADDR_ANY = -1
190
-
191
- def __new__(cls, *args):
192
- result = super(Chip, cls).__new__(cls)
193
- if args:
194
- _parse_chip_name(args[0].encode('utf-8'), byref(result))
195
- return result
196
-
197
- def __init__(self, *_args):
198
- Structure.__init__(self)
199
- #
200
- # Need to bind the following to the instance so it is available in
201
- # `__del__()` when the interpreter shuts down.
202
- #
203
- self._free_chip_name = _free_chip_name
204
- self.byref = byref
201
+ return self
202
+
203
+ def __next__(self):
204
+ chip, self.nr = get_detected_chips(self.match, self.nr)
205
+
206
+ if chip is None:
207
+ raise StopIteration
208
+
209
+ return chip
210
211
def __del__(self):
207
- if self._b_needsfree_:
208
- self._free_chip_name(self.byref(self))
209
-
210
- def __repr__(self):
211
- return '<%s prefix=%r bus=%r addr=%r path=%r>' % (
212
- (
213
- self.__class__.__name__,
214
- self.prefix,
215
- self.bus,
216
- self.addr,
217
- self.path
218
- )
219
- )
220
-
221
- def __str__(self):
222
- buffer_size = 200
223
- result = create_string_buffer(buffer_size)
224
- used = _snprintf_chip_name(result, len(result), byref(self))
225
- assert used < buffer_size
226
- return result.value.decode('utf-8')
212
+ if self.match is not None:
213
+ free_chip_name(self.match)
214
+
215
+ def next(self): # python2 compability
216
+ return self.__next__()
217
+
218
+
219
+class FeatureIterator:
220
+ def __init__(self, chip):
221
+ self.chip = chip
222
+ self.nr = 0
223
+
224
+ def __iter__(self):
225
+ return self
226
+
227
+ def __next__(self):
228
+ feature, self.nr = get_features(self.chip, self.nr)
229
+
230
+ if feature is None:
231
+ raise StopIteration
232
+
233
+ return feature
234
+
235
+ def next(self): # python2 compability
236
+ return self.__next__()
237
+
238
+
239
+class SubFeatureIterator:
240
+ def __init__(self, chip, feature):
241
+ self.chip = chip
242
+ self.feature = feature
243
+ self.nr = 0
244
245
def __iter__(self):
229
- number = c_int(0)
230
- while True:
231
- result_p = _get_features(byref(self), byref(number))
232
- if not result_p:
233
- break
234
- result = result_p.contents
235
- result.chip = self
236
- yield result
237
-
238
- @property
239
- def adapter_name(self):
240
- return str(self.bus)
241
-
242
- @property
243
- def has_wildcards(self):
244
- return (
245
- self.prefix == self.PREFIX_ANY
246
- or self.addr == self.ADDR_ANY
247
- or self.bus.has_wildcards
248
- )
249
-
250
-CHIP_P = POINTER(Chip)
251
-
252
-
253
-_parse_chip_name = SENSORS_LIB.sensors_parse_chip_name
254
-_parse_chip_name.argtypes = [c_char_p, CHIP_P]
255
-_parse_chip_name.restype = c_int
256
-_parse_chip_name.errcheck = _error_check
257
-
258
-_free_chip_name = SENSORS_LIB.sensors_free_chip_name
259
-_free_chip_name.argtypes = [CHIP_P]
260
-_free_chip_name.restype = None
261
-
262
-_snprintf_chip_name = SENSORS_LIB.sensors_snprintf_chip_name
263
-_snprintf_chip_name.argtypes = [c_char_p, c_size_t, CHIP_P]
264
-_snprintf_chip_name.restype = c_int
265
-_snprintf_chip_name.errcheck = _error_check
266
-
267
-_get_adapter_name = SENSORS_LIB.sensors_get_adapter_name
268
-_get_adapter_name.argtypes = [BUS_P]
269
-_get_adapter_name.restype = c_char_p
270
-
271
-_get_label = SENSORS_LIB.sensors_get_label
272
-_get_label.argtypes = [CHIP_P, FEATURE_P]
273
-_get_label.restype = c_char_p
274
-
275
-_get_value = SENSORS_LIB.sensors_get_value
276
-_get_value.argtypes = [CHIP_P, c_int, POINTER(c_double)]
277
-_get_value.restype = c_int
278
-_get_value.errcheck = _error_check
279
-
280
-#
281
-# TODO sensors_set_value()
282
-# TODO sensors_do_chip_sets()
283
-#
284
-
285
-_get_detected_chips = SENSORS_LIB.sensors_get_detected_chips
286
-_get_detected_chips.argtypes = [CHIP_P, POINTER(c_int)]
287
-_get_detected_chips.restype = CHIP_P
288
-
289
-_get_features = SENSORS_LIB.sensors_get_features
290
-_get_features.argtypes = [CHIP_P, POINTER(c_int)]
291
-_get_features.restype = FEATURE_P
292
-
293
-_get_all_subfeatures = SENSORS_LIB.sensors_get_all_subfeatures
294
-_get_all_subfeatures.argtypes = [CHIP_P, FEATURE_P, POINTER(c_int)]
295
-_get_all_subfeatures.restype = SUBFEATURE_P
296
-
297
-#
298
-# TODO sensors_get_subfeature() ?
299
-#
300
-
301
-
302
-def iter_detected_chips(chip_name='*-*'):
303
- chip = Chip(chip_name)
304
- number = c_int(0)
305
- while True:
306
- result = _get_detected_chips(byref(chip), byref(number))
307
- if not result:
308
- break
309
- yield result.contents
246
+ return self
247
+
248
+ def __next__(self):
249
+ subfeature, self.nr = get_all_subfeatures(self.chip, self.feature, self.nr)
250
+
251
+ if subfeature is None:
252
+ raise StopIteration
253
+
254
+ return subfeature
255
+
256
+ def next(self): # python2 compability
257
+ return self.__next__()
\ No newline at end of file
python.d/sensors.chart.py
+49
-45
@@ -3,7 +3,7 @@
3
# Author: Pawel Krupa (paulfantom)
4
5
from base import SimpleService
6
-import lm_sensors as sensors
6
+import new_sensors as sensors
7
8
# default module values (can be overridden per job in `config`)
9
# update_every = 2
@@ -49,6 +49,21 @@ CHARTS = {
49
]}
50
}
51
52
+TYPE_MAP = {
53
+ 0: 'voltage',
54
+ 1: 'fan',
55
+ 2: 'temperature',
56
+ 3: 'power',
57
+ 4: 'energy',
58
+ 5: 'current',
59
+ 6: 'humidity',
60
+ 7: 'max_main',
61
+ 16: 'vid',
62
+ 17: 'intrusion',
63
+ 18: 'max_other',
64
+ 24: 'beep_enable'
65
+}
66
+
67
68
class Service(SimpleService):
69
def __init__(self, configuration=None, name=None):
@@ -60,11 +75,14 @@ class Service(SimpleService):
75
def _get_data(self):
76
data = {}
77
try:
63
- for chip in sensors.iter_detected_chips():
64
- prefix = '_'.join(str(chip.path.decode()).split('/')[3:])
65
- lines = {}
66
- for feature in chip:
67
- data[prefix + "_" + str(feature.name.decode())] = feature.get_value() * 1000
78
+ for chip in sensors.ChipIterator():
79
+ prefix = sensors.chip_snprintf_name(chip)
80
+ for feature in sensors.FeatureIterator(chip):
81
+ sfi = sensors.SubFeatureIterator(chip, feature)
82
+ for sf in sfi:
83
+ val = sensors.get_value(chip, sf.number)
84
+ break
85
+ data[prefix + "_" + str(feature.name.decode())] = int(val * 1000)
86
except Exception as e:
87
self.error(e)
88
return None
@@ -74,57 +92,43 @@ class Service(SimpleService):
92
return data
93
94
def _create_definitions(self):
95
+ prev_chip = ""
96
for type in ORDER:
78
- for chip in sensors.iter_detected_chips():
79
- prefix = '_'.join(str(chip.path.decode()).split('/')[3:])
80
- name = ""
81
- lines = []
82
- pref = str(chip.prefix.decode())
83
- if len(self.chips) != 0 and not any([ex.startswith(pref) for ex in self.chips]):
97
+ for chip in sensors.ChipIterator():
98
+ chip_name = sensors.chip_snprintf_name(chip)
99
+ if len(self.chips) != 0 and not any([chip_name.startswith(ex) for ex in self.chips]):
100
continue
85
- pref = pref + '_' + str(chip.addr)
86
- for feature in chip:
87
- try:
88
- float(feature.get_value())
89
- except ValueError:
90
- continue
91
- if feature.get_value() < 0:
101
+ for feature in sensors.FeatureIterator(chip):
102
+ sfi = sensors.SubFeatureIterator(chip, feature)
103
+ vals = [sensors.get_value(chip, sf.number) for sf in sfi]
104
+ if vals[0] == 0:
105
continue
93
- if sensors.TYPE_DICT[feature.type] == type:
94
- name = pref + "_" + sensors.TYPE_DICT[feature.type]
95
- if name not in self.order:
96
- options = list(CHARTS[type]['options'])
97
- options[1] = pref + options[1]
98
- self.definitions[name] = {'options': options}
99
- self.definitions[name]['lines'] = []
100
- self.order.append(name)
106
+ if TYPE_MAP[feature.type] == type:
107
+ # create chart
108
+ if chip_name != prev_chip:
109
+ name = chip_name + "_" + TYPE_MAP[feature.type]
110
+ if name not in self.order:
111
+ self.order.append(name)
112
+ chart_def = list(CHARTS[type]['options'])
113
+ chart_def[1] = chip_name + chart_def[1]
114
+ self.definitions[name] = {'options': chart_def}
115
+ self.definitions[name]['lines'] = []
116
line = list(CHARTS[type]['lines'][0])
102
- line[0] = prefix + "_" + str(feature.name.decode())
103
- line[1] = str(feature.label)
117
+ line[0] = chip_name + "_" + str(feature.name.decode())
118
+ line[1] = sensors.get_label(chip, feature)
119
self.definitions[name]['lines'].append(line)
120
+ prev_chip = chip_name
121
122
def check(self):
107
- try:
108
- self.chips = list(self.configuration['chips'])
109
- except (KeyError, TypeError):
110
- self.error("No path to log specified. Using all chips.")
111
- try:
112
- global ORDER
113
- ORDER = list(self.configuration['types'])
114
- except (KeyError, TypeError):
115
- self.error("No path to log specified. Using all sensor types.")
123
try:
124
sensors.init()
125
except Exception as e:
126
self.error(e)
127
return False
128
+
129
try:
130
self._create_definitions()
123
- except:
124
- return False
125
-
126
- if len(self.definitions) == 0:
127
- self.error("No sensors found")
131
+ except Exception as e:
132
+ self.error(e)
133
return False
129
-
130
- return True
134
+ return True
\ No newline at end of file