lmsensors 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,193 @@
1
+ # lib/lmsensors/lm_constants.rb
2
+
3
+ # Require the base implementation
4
+ require_relative "../lmsensors_base/lmsensors_base"
5
+
6
+ # :nodoc: Constants for the LmSensors module
7
+ module LmSensors
8
+ ##
9
+ # The default feature map method maps the feature
10
+ # type to the subclass that will be used to handle
11
+ # formatting and analytic post-processing.
12
+ DEF_FMAP = lambda do |name, f_obj|
13
+ case f_obj[:type]
14
+ when SF_IN
15
+ Feature::Voltage.new name, f_obj
16
+ when SF_CURR
17
+ Feature::Current.new name, f_obj
18
+ when SF_POWER
19
+ Feature::Power.new name, f_obj
20
+ when SF_TEMP
21
+ Feature::Temp.new name, f_obj
22
+ when SF_FAN
23
+ Feature::Fan.new name, f_obj
24
+ when SF_HUMIDITY
25
+ Feature::Humidity.new name, f_obj
26
+ when SF_INTRUSION
27
+ Feature::Alarm.new name, f_obj
28
+ when SF_BEEP_ENABLE
29
+ Feature::Beep.new name, f_obj
30
+ else
31
+ Feature::GenFeature.new name, f_obj
32
+ end
33
+ end # End default feature mapper
34
+
35
+ ##
36
+ # CHK_BEEP uses the same formatting as the print_chip_beep_enable
37
+ # from the sensors program chips.c.
38
+ CHK_BEEP = lambda { |v| v < 0.5 ? "disabled" : "enabled" }
39
+
40
+ ##
41
+ # CHK_ALARM uses the same formatting as the print_chip_intrusion
42
+ # from the sensors program chips.c.
43
+ CHK_ALARM = lambda { |v| v.zero? ? "OK" : "ALARM" }
44
+
45
+ ##
46
+ # Index of units -- this will map the expected
47
+ # default units to any features. These are taken from
48
+ # the 'lm-sensors' headers, and I have exported the
49
+ # constants from the C-code to here. The names DIRECTLY
50
+ # correspond to the 'sensors_feature_type' enum, but
51
+ # they have been shortened from 'SENSORS_FEATURE_'
52
+ # to 'SF_'.
53
+ #
54
+ # This is, to the best of my ability, taken from the
55
+ # chips.c file, which has a header you can't seem to
56
+ # access from sensors.h.
57
+ UNITS = {
58
+ SF_IN => "V", # Volts
59
+ SF_FAN => "RPM", # RPM (fan)
60
+ SF_TEMP => "°C", # Degrees Celsius
61
+ SF_POWER => "W", # Watts
62
+ SF_ENERGY => "J", # Energy, Joules
63
+ SF_CURR => "A", # Current, Amps
64
+ SF_HUMIDITY => "% RH", # Humidity, percent
65
+ SF_MAX_MAIN => "",
66
+ # Skips here
67
+ SF_VID => "V", # Vid -- this is in Volts
68
+ SF_INTRUSION => CHK_ALARM, # Intrusion
69
+ SF_MAX_OTHER => "",
70
+ # Skips here, again
71
+ SF_BEEP_ENABLE => CHK_BEEP, # Beep enabled, true if gte 0.5
72
+ SF_MAX => "",
73
+ SF_UNKNOWN => "",
74
+ } # End default units enum
75
+
76
+ ##
77
+ # This hash maps commonly-used limit types from
78
+ # the SENSORS_SUBFEATURE_* constants.
79
+ #
80
+ # I have not included ALL the subfeatures types,
81
+ # because there are around 100 of them, and most of
82
+ # them provide no post-processing value. I have, however,
83
+ # included the following subfeature groups for
84
+ # the _POWER_, _IN_, _CURR_, and _FAN_ categories:
85
+ #
86
+ # MIN, MAX, a couple HYST (hysteresis) types, INPUT,
87
+ # and a couple AVERAGE types.
88
+ #
89
+ # NOTE: For the MOST part, the below list is provided
90
+ # for convenience, only. You can use it to create your
91
+ # own mappers, if you are not sure what subtypes are
92
+ # included in the normal feature objects.
93
+ #
94
+ # I had actually considered removing everything below,
95
+ # but I realized that simply swapping the keys with the
96
+ # values actually provides a simpler way for others to
97
+ # access the types and provide some default key types
98
+ # they might want to use in their subclasses, even if it's
99
+ # somewhat redundant.
100
+ SSF_SUBTYPES = {
101
+ # Voltages
102
+ SF_IN => {
103
+ # Inputs
104
+ SSF_IN_INPUT => :input,
105
+
106
+ # Edge readings
107
+ SSF_IN_LOW => :lowest,
108
+ SSF_IN_HIGH => :highest,
109
+
110
+ # Minima and maxima
111
+ SSF_IN_MIN => :min,
112
+ SSF_IN_MAX => :max,
113
+
114
+ # Critical levels
115
+ SSF_IN_LCRIT => :lcrit,
116
+ SSF_IN_CRIT => :crit,
117
+
118
+ # Averages
119
+ SSF_IN_AVG => :average,
120
+ },
121
+ # Currents
122
+ SF_CURR => {
123
+ # Inputs
124
+ SSF_CURR_INPUT => :input,
125
+
126
+ # Edge readings
127
+ SSF_CURR_LOW => :lowest,
128
+ SSF_CURR_HIGH => :highest,
129
+
130
+ # Minima and maxima
131
+ SSF_CURR_MIN => :min,
132
+ SSF_CURR_MAX => :max,
133
+
134
+ # Critical levels
135
+ SSF_CURR_LCRIT => :lcrit,
136
+ SSF_CURR_CRIT => :crit,
137
+
138
+ # Averages
139
+ SSF_CURR_AVG => :average,
140
+ },
141
+ # Power
142
+ SF_POWER => {
143
+ # Inputs
144
+ SSF_POWER_INPUT => :input,
145
+ SSF_POWER_INPUT_LOW => :input_low,
146
+ SSF_POWER_INPUT_HIGH => :input_high,
147
+
148
+ # Minima and maxima
149
+ SSF_POWER_MIN => :min,
150
+ SSF_POWER_MAX => :max,
151
+
152
+ # Critical levels
153
+ SSF_POWER_LCRIT => :lcrit,
154
+ SSF_POWER_CRIT => :crit,
155
+
156
+ # Averages
157
+ SSF_POWER_AVG => :average,
158
+ SSF_POWER_AVG_LOW => :average_low,
159
+ SSF_POWER_AVG_HIGH => :average_high,
160
+
161
+ # Caps
162
+ SSF_POWER_CAP => :cap,
163
+ SSF_POWER_CAP_HYST => :cap_hyst,
164
+ },
165
+
166
+ # Temperatures
167
+ SF_TEMP => {
168
+ # Inputs
169
+ SSF_TEMP_INPUT => :input,
170
+
171
+ # Edge readings
172
+ SSF_TEMP_LOW => :lowest,
173
+ SSF_TEMP_HIGH => :highest,
174
+
175
+ # Minima and maxima + hysteresis
176
+ SSF_TEMP_MIN => :min,
177
+ SSF_TEMP_MIN_HYST => :min_hyst,
178
+ SSF_TEMP_MAX => :max,
179
+ SSF_TEMP_MAX_HYST => :max_hyst,
180
+
181
+ # Critical and emergency levels + hysteresis
182
+ SSF_TEMP_LCRIT => :lcrit,
183
+ SSF_TEMP_LCRIT_HYST => :lcrit_hyst,
184
+ SSF_TEMP_CRIT => :crit,
185
+ SSF_TEMP_CRIT_HYST => :crit_hyst,
186
+ SSF_TEMP_EMERG => :emergency,
187
+ SSF_TEMP_EMERG_HYST => :emergency_hyst,
188
+ },
189
+ # Fans
190
+ # Fan sensors have a simple structure that is self-explanatory.
191
+ SF_FAN => { SSF_FAN_INPUT => :input, SSF_FAN_MIN => :min, SSF_FAN_MAX => :max, },
192
+ } # End SSF subtypes
193
+ end # End LmSensors constants maps
@@ -0,0 +1,33 @@
1
+ # lib/lmsensors/lmsensors.rb
2
+
3
+ # Base requires
4
+ require_relative "../lmsensors_base/lmsensors_base"
5
+ require_relative "./lm_constants"
6
+
7
+ require_relative "./sensors/sensorspawner"
8
+ require_relative "./sensors/sensor"
9
+
10
+ require_relative "./feature"
11
+
12
+ ##
13
+ # LmSensors is the wrapping module for the entire
14
+ # lm-sensors library. This module provides the ability
15
+ # to initialize for and clean up resources from it, to
16
+ # generate Sensor objects that can be used to monitor
17
+ # specific pieces of hardware, as desired, and to generate
18
+ # a SensorSpawner to enumerate what sensors are available.
19
+ module LmSensors
20
+ ##
21
+ # Wraps the 'pro_init' function to initialize the sensors
22
+ def self.init(filename=nil) self.pro_init(filename) end
23
+
24
+ ##
25
+ # Wraps the 'pro_cleanup' function to release the resources
26
+ def self.cleanup() self.pro_cleanup end
27
+
28
+ ##
29
+ # For compatibility purposes, as this was
30
+ # being tested by some people, Sensors is now
31
+ # purely an alias of SensorSpawner.
32
+ Sensors = LmSensors::SensorSpawner
33
+ end # End LmSensors module
@@ -0,0 +1,151 @@
1
+ # lib/lmsensors/sensors/abssensors.rb
2
+
3
+ # Requires
4
+ require_relative "../../lmsensors_base/lmsensors_base"
5
+ require_relative "../lm_constants"
6
+ require_relative "../feature"
7
+
8
+ # :nodoc: This module will house the abstract implementation
9
+ # :nodoc: of a general sensor-type object.
10
+ module LmSensors
11
+ ##
12
+ # AbsSensor is an abstract representation of
13
+ # a sensor-type object and provides a base interface
14
+ # for required functions (operates like a trait).
15
+ class AbsSensor < LmSensors::SensorsBase
16
+ ##
17
+ # AbsSensor :chip_name is the name sensors returns for the chip
18
+ attr_reader :chip_name
19
+ ##
20
+ # AbsSensor :filters are the selected feature types to return for your use case
21
+ attr_reader :filters
22
+ ##
23
+ # AbsSensor :fmap is a custom formatter map or the default DEF_FMAP
24
+ attr_reader :fmap
25
+ ##
26
+ # AbsSensor :fmt is a boolean of whether or not to return the data formatted
27
+ attr_reader :fmt
28
+ ##
29
+ # AbsSensor :subs is whether to return the subfeatures or just the feature
30
+ # and its type
31
+ attr_reader :subs
32
+
33
+ ##
34
+ # Constructor
35
+ def initialize
36
+ pro_initialize
37
+ unset_filters
38
+ @fmt = @subs = false
39
+ @fmap = LmSensors::DEF_FMAP
40
+ end # End constructor
41
+
42
+ ##
43
+ # Get the chip_name
44
+ def name() @chip_name end
45
+
46
+ ##
47
+ # Set the sensor's filters, the types that
48
+ # will be returned on '.features'.
49
+ def set_filters(arr)
50
+ if Array === arr then
51
+ @filters = arr.select { |item| LmSensors::UNITS.include? item } .sort
52
+ else STDERR.puts "::Sensors ERROR:: Filters must be an array" end
53
+ end # End set filters
54
+
55
+ ##
56
+ # Unset the filters for this sensor
57
+ def unset_filters() @filters = [] end
58
+
59
+ ##
60
+ # Toggle whether this sensor also returns
61
+ # subfeatures on a feature list request.
62
+ def toggle_subs() @subs = !@subs end
63
+
64
+ ##
65
+ # Toggle if the sensor will report with
66
+ # formatted output or unformatted. Default
67
+ # is to report unformatted.
68
+ def toggle_fmt() @fmt = !@fmt end
69
+
70
+ ##
71
+ # Set the format mapper for the different types.
72
+ # This method should receive a proc that maps to different
73
+ # feature formatters.
74
+ def set_fmap(map)
75
+ # Only accept a proc/lambda for format mapping
76
+ if Proc === map then
77
+ if map.arity != 2 then
78
+ if !$LmSensorsIgnArity then
79
+ STDERR.puts <<~EMSG
80
+ ::SensorSpawner WARNING:: @fmap arity should be 2 and will
81
+ NOT WORK without being overridden in a custom subclass.
82
+
83
+ If you are receiving this message, and you have already
84
+ overridden the subclasses and format handling, you should
85
+ set the global variable $LmSensorsIgnArity to 'true'.
86
+
87
+ This is only a warning and will not stop the map from
88
+ being set, even if it is invalid.
89
+
90
+ This warning will NOT be displayed again in this run.
91
+ EMSG
92
+ $LmSensorsIgnArity = true
93
+ end
94
+ @fmap = map
95
+ end
96
+ else # If it's not a proc, spit out an error
97
+ STDERR.puts "::Sensors ERROR:: Format map must be a proc/lambda"
98
+ end
99
+ end # End format mapper
100
+
101
+ ##
102
+ # Reset the formatting map
103
+ def reset_fmap() @fmap = LmSensors::DEF_FMAP end
104
+
105
+ ##
106
+ # Abstract enum must be implemented
107
+ # by subclasses
108
+ def enum(*args)
109
+ STDERR.puts "::AbsSensor ERROR:: Abstract 'enum' not implemented for #{self.class}"
110
+ []
111
+ end # End abstract enum
112
+
113
+ ##
114
+ # Get the number of sensors available
115
+ # for the current name (or the total number
116
+ # of sensors available).
117
+ def count
118
+ data = enum
119
+ if ([Hash, Array].include?(data.class)) then
120
+ # If the data is good
121
+ data.count
122
+ else return nil end
123
+
124
+ end # End count of sensors
125
+ alias :count_s :count # Add alias for backwards compatibility
126
+
127
+ # Protected methods
128
+ protected
129
+
130
+ ##
131
+ # Validate a path -- protected
132
+ def path_valid?(path)
133
+ # Check that the path argument is a string
134
+ if (!path.is_a?(String)) then
135
+ STDERR.puts "::Sensor ERROR:: Path must be string!"
136
+ return nil
137
+ end # End check for correct path
138
+ # Clean the path input
139
+ dir = path.strip.gsub(/\/*$/, "")
140
+
141
+ # Check if path is a directory, else exit early.
142
+ # Do not need to check File.exist? first, b/c this
143
+ # already returns nil, if it doesn't.
144
+ if !File.directory?(dir) then
145
+ STDERR.puts "::Sensors ERROR:: Path is either invalid or not a directory!"
146
+ return nil
147
+ end # End directory check
148
+ dir # Return the correct path
149
+ end # End path validation
150
+ end # End Sensors class
151
+ end # End LmSensors append
@@ -0,0 +1,148 @@
1
+ # lib/lmsensors/sensors/sensors.rb
2
+
3
+ # Require the abstract sensor
4
+ require_relative "./abssensor"
5
+
6
+ # :nodoc: This module will house the concrete implementation
7
+ # :nodoc: of the actual Sensor object types.
8
+ module LmSensors
9
+ ##
10
+ # Sensor is a concrete implementation
11
+ # of the AbsSensor. This implementation
12
+ # is a specific object dedicated to an
13
+ # individual sensor chip.
14
+ class Sensor < LmSensors::AbsSensor
15
+ ##
16
+ # Sensor :path is the '/sys/class/hwmon/' path for this specific sensor
17
+ attr_reader :path
18
+ # Sensor :adapter is the adapter type, like the PCI or ISA adapter
19
+ attr_reader :adapter
20
+
21
+ ##
22
+ # Find a device sensors by its path, in such
23
+ # a case where the app already has a device from
24
+ # sysfs or similar and needs to attach them together.
25
+ #
26
+ # Sets the name of the sensor, as well, so this should
27
+ # be used as an instance.
28
+ def locate(path)
29
+ # Validate the path
30
+ dir = path_valid?(path)
31
+ if !dir then return nil end
32
+ # Determine if it's a valid sensor, since the
33
+ # sensors here use the path as the index.
34
+ # If so, set it as this object's name
35
+ raw = pro_enum(nil, nil)
36
+ # Check if the enum value is valid
37
+ if (![Hash, Array].include?(raw.class)) then
38
+ STDERR.puts raw
39
+ return nil
40
+ end # End enum check
41
+ raw.then do |sensors|
42
+ # Return early, if it's not in the keys
43
+ if !sensors.keys.include?(dir) then
44
+ STDERR.puts "Path does not have an associated sensor"
45
+ return nil
46
+ end # End check for valid sensor path
47
+
48
+ # Set the data and return the chip name
49
+ data = sensors[dir]
50
+ @chip_name = data[:name]
51
+ @adapter = data[:adapter]
52
+ @path = data[:path]
53
+ @chip_name
54
+ end # End enumeration handler
55
+ end # End locate by path
56
+
57
+ ##
58
+ # Return the core chip data. This does NOT include
59
+ # the features and subfeatures, just the information
60
+ # on the chip itself.
61
+ def info() { name: name, adapter: @adapter, path: @path, } end
62
+
63
+ ##
64
+ # Stat will return the values and names
65
+ # of all the features associated with the
66
+ # currently-selected chip. This has replaced
67
+ # :features, which is now just an alias for :stat.
68
+ #
69
+ # 2021-May-31 (pre-release): Now respects @filters
70
+ def stat
71
+ # For each chip, split it by key and value
72
+ raw = read
73
+ # Check that the data is still valid
74
+ if raw.nil? then return nil end
75
+ data = raw.collect do |feature, keys|
76
+ # Is the filter set?
77
+ no_filter = @filters.empty?
78
+ # Is the feature of a type included in the filter?
79
+ included = @filters.include?(keys[:type])
80
+
81
+ # If the filter is NOT set, OR the keys IS included
82
+ # in the filter, proceed.
83
+ if no_filter or included then
84
+ # If the subfeature option is set, return
85
+ # the subfeature data and the unit.
86
+ if @subs then
87
+ @fmap.call(feature, keys)
88
+ # If the subfeature option is not set, just return
89
+ # the feature type.
90
+ else { feature => keys[:type] } end
91
+ # If filtered and not included, return empty array
92
+ else [] end
93
+ end.flatten # Remove any empty units
94
+ # If format is set, format the output
95
+ if (@fmt and @subs) then data.collect { |item| item.fmt } else data end
96
+ end
97
+ # Pseudo-alias abstract enum to stat, for this class
98
+ def enum(*args) stat end
99
+
100
+ ##
101
+ # :features is now an alias for :stat,
102
+ # as :stat did not previously respect @filters
103
+ alias :features :stat
104
+
105
+ ##
106
+ # Get the number of features available for
107
+ # the current name (or total features to be
108
+ # read for all sensors). This overrides the
109
+ # abstract ``.count`` method.
110
+ def count
111
+ data = read
112
+ if !data then return nil end
113
+ # If the data is good
114
+ data.count
115
+ end # End count method
116
+
117
+ ##
118
+ # Get the number of subfeatures available for
119
+ # the current name (or total features to be
120
+ # read for all sensors).
121
+ def count_sf
122
+ total = 0 # Accumulator
123
+ data = read
124
+ if !data then return nil end
125
+ # If the data is good, continue
126
+ data.each do |_, v|
127
+ # Ignore the :type symbol
128
+ total += (v.select { |item| :type != item } .count)
129
+ end
130
+ total # Return accumulator
131
+ end # End count of subfeatures
132
+
133
+ ##
134
+ # Raw chip data -- this is unfiltered and unformatted.
135
+ #
136
+ # This has replaced the previous version of :stat, as
137
+ # :stat unintentionally did not respect the @filters.
138
+ def read
139
+ c = pro_enum(true, @chip_name)
140
+ if Hash === c then
141
+ c[@path][:stat]
142
+ else
143
+ STDERR.puts c
144
+ return nil
145
+ end
146
+ end # End read
147
+ end # End Sensor class
148
+ end # End LmSensors append