lmsensors 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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