... | ... |
@@ -57,16 +57,24 @@ class ina260: |
57 | 57 |
# register. |
58 | 58 |
def __init__(self, sAddr=DEFAULT_BUS_ADDRESS, |
59 | 59 |
sbus=DEFAULT_BUS_NUMBER, |
60 |
- config=DEFAULT_CONFIG): |
|
60 |
+ config=DEFAULT_CONFIG, |
|
61 |
+ debug=False): |
|
61 | 62 |
# Instantiate a smbus object. |
62 | 63 |
self.sensorAddr = sAddr |
63 | 64 |
self.bus = smbus.SMBus(sbus) |
65 |
+ self.debugMode = debug |
|
66 |
+ |
|
64 | 67 |
# Initialize INA260 sensor. |
65 | 68 |
initData = [(config >> 8), (config & 0x00FF)] |
66 | 69 |
self.bus.write_i2c_block_data(self.sensorAddr, CONFIG_REG, initData) |
70 |
+ |
|
71 |
+ if self.debugMode: |
|
72 |
+ data = self.getInfo() |
|
73 |
+ print("manufacturer ID: %s %s\n"\ |
|
74 |
+ "configuration register: %s %s\n" % data) |
|
67 | 75 |
## end def |
68 | 76 |
|
69 |
- def status(self): |
|
77 |
+ def getInfo(self): |
|
70 | 78 |
# Read manufacture identification data. |
71 | 79 |
mfcid = self.bus.read_i2c_block_data(self.sensorAddr, ID_REG, 2) |
72 | 80 |
mfcidB1 = format(mfcid[0], "08b") |
... | ... |
@@ -91,7 +99,7 @@ class ina260: |
91 | 99 |
# Get the current data from the sensor. |
92 | 100 |
# INA260 returns the data in two bytes formatted as follows |
93 | 101 |
# ------------------------------------------------- |
94 |
- # bit | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | |
|
102 |
+ # bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|
95 | 103 |
# ------------------------------------------------- |
96 | 104 |
# byte 1 | d15 | d14 | d13 | d12 | d11 | d10 | d9 | d8 | |
97 | 105 |
# ------------------------------------------------- |
... | ... |
@@ -100,6 +108,12 @@ class ina260: |
100 | 108 |
# The current is returned in d15-d0, a two's complement, |
101 | 109 |
# 16 bit number. This means that d15 is the sign bit. |
102 | 110 |
data=self.bus.read_i2c_block_data(self.sensorAddr, CUR_REG, 2) |
111 |
+ |
|
112 |
+ if self.debugMode: |
|
113 |
+ dataB1 = format(data[0], "08b") |
|
114 |
+ dataB2 = format(data[1], "08b") |
|
115 |
+ print("current register: %s %s" % (dataB1, dataB2)) |
|
116 |
+ |
|
103 | 117 |
# Format into a 16 bit word. |
104 | 118 |
bdata = data[0] << 8 | data[1] |
105 | 119 |
# Convert from two's complement to integer. |
... | ... |
@@ -126,7 +140,7 @@ class ina260: |
126 | 140 |
# Get the voltage data from the sensor. |
127 | 141 |
# INA260 returns the data in two bytes formatted as follows |
128 | 142 |
# ------------------------------------------------- |
129 |
- # bit | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | |
|
143 |
+ # bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|
130 | 144 |
# ------------------------------------------------- |
131 | 145 |
# byte 1 | d15 | d14 | d13 | d12 | d11 | d10 | d9 | d8 | |
132 | 146 |
# ------------------------------------------------- |
... | ... |
@@ -134,6 +148,12 @@ class ina260: |
134 | 148 |
# ------------------------------------------------- |
135 | 149 |
# The voltage is returned in d15-d0 as an unsigned integer. |
136 | 150 |
data=self.bus.read_i2c_block_data(self.sensorAddr, VOLT_REG, 2) |
151 |
+ |
|
152 |
+ if self.debugMode: |
|
153 |
+ dataB1 = format(data[0], "08b") |
|
154 |
+ dataB2 = format(data[1], "08b") |
|
155 |
+ print("voltage register: %s %s" % (dataB1, dataB2)) |
|
156 |
+ |
|
137 | 157 |
# Convert data to volts. |
138 | 158 |
volts = (data[0] << 8 | data[1]) * 0.00125 # LSB is 1.25 mV |
139 | 159 |
return volts |
... | ... |
@@ -143,7 +163,7 @@ class ina260: |
143 | 163 |
# Get the wattage data from the sensor. |
144 | 164 |
# INA260 returns the data in two bytes formatted as follows |
145 | 165 |
# ------------------------------------------------- |
146 |
- # bit | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | |
|
166 |
+ # bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|
147 | 167 |
# ------------------------------------------------- |
148 | 168 |
# byte 1 | d15 | d14 | d13 | d12 | d11 | d10 | d9 | d8 | |
149 | 169 |
# ------------------------------------------------- |
... | ... |
@@ -151,6 +171,12 @@ class ina260: |
151 | 171 |
# ------------------------------------------------- |
152 | 172 |
# The wattage is returned in d15-d0 as an unsigned integer. |
153 | 173 |
data=self.bus.read_i2c_block_data(self.sensorAddr, PWR_REG, 2) |
174 |
+ |
|
175 |
+ if self.debugMode: |
|
176 |
+ dataB1 = format(data[0], "08b") |
|
177 |
+ dataB2 = format(data[1], "08b") |
|
178 |
+ print("power register: %s %s" % (dataB1, dataB2)) |
|
179 |
+ |
|
154 | 180 |
# Convert data to milliWatts. |
155 | 181 |
mW = (data[0] << 8 | data[1]) * 10.0 # LSB is 10.0 mW |
156 | 182 |
return mW |
... | ... |
@@ -159,15 +185,10 @@ class ina260: |
159 | 185 |
|
160 | 186 |
def test(): |
161 | 187 |
# Initialize the smbus and INA260 sensor. |
162 |
- pwr1 = ina260(0x40, 1) |
|
163 |
- # Read the INA260 configuration register and manufacturer's ID. |
|
164 |
- data = pwr1.status() |
|
165 |
- print("manufacturer ID: %s %s\nconfiguration register: %s %s\n" % data) |
|
188 |
+ pwr1 = ina260(0x40, 1, debug=True) |
|
166 | 189 |
# Print out sensor values. |
167 | 190 |
while True: |
168 |
- print("current register: %s %s" % pwr1.getCurrentReg()) |
|
169 | 191 |
print("%6.2f mA" % pwr1.getCurrent()) |
170 |
- print("volt register: %s %s" % pwr1.getVoltageReg()) |
|
171 | 192 |
print("%6.2f V" % pwr1.getVoltage()) |
172 | 193 |
print("%6.2f mW\n" % pwr1.getPower()) |
173 | 194 |
time.sleep(2) |
... | ... |
@@ -29,6 +29,8 @@ |
29 | 29 |
# |
30 | 30 |
# Revision History |
31 | 31 |
# * v10 released 01 June 2021 by J L Owrey; first release |
32 |
+# * v11 released 02 July 2021 by J L Owrey; improved sensor fault |
|
33 |
+# handling; improved code readability |
|
32 | 34 |
# |
33 | 35 |
#2345678901234567890123456789012345678901234567890123456789012345678901234567890 |
34 | 36 |
|
... | ... |
@@ -55,7 +57,7 @@ _PWR_SENSOR_ADDR = 0X40 |
55 | 57 |
_BAT_TMP_SENSOR_ADDR = 0x48 |
56 | 58 |
_AMB_TMP_SENSOR_ADDR = 0x4B |
57 | 59 |
# Set bus selector. |
58 |
-_BUS_SEL = 1 |
|
60 |
+_BUS_NUMBER = 1 |
|
59 | 61 |
|
60 | 62 |
### FILE AND FOLDER LOCATIONS ### |
61 | 63 |
|
... | ... |
@@ -71,9 +73,12 @@ _RRD_FILE = "/home/%s/database/powerData.rrd" % _USER |
71 | 73 |
### GLOBAL CONSTANTS ### |
72 | 74 |
|
73 | 75 |
# sensor data request interval in seconds |
74 |
-_DEFAULT_DATA_REQUEST_INTERVAL = 2 |
|
76 |
+_DEFAULT_SENSOR_POLLING_INTERVAL = 2 |
|
75 | 77 |
# rrdtool database update interval in seconds |
76 | 78 |
_DATABASE_UPDATE_INTERVAL = 30 |
79 |
+# max number of failed attempts to get sensor data |
|
80 |
+_MAX_FAILED_DATA_REQUESTS = 2 |
|
81 |
+ |
|
77 | 82 |
# chart update interval in seconds |
78 | 83 |
_CHART_UPDATE_INTERVAL = 600 |
79 | 84 |
# standard chart width in pixels |
... | ... |
@@ -85,19 +90,23 @@ _AVERAGE_LINE_COLOR = '#006600' |
85 | 90 |
|
86 | 91 |
### GLOBAL VARIABLES ### |
87 | 92 |
|
88 |
-# debug output options |
|
93 |
+# turns on or off extensive debugging messages |
|
89 | 94 |
debugMode = False |
90 | 95 |
verboseMode = False |
91 | 96 |
|
92 | 97 |
# frequency of data requests to sensors |
93 |
-dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL |
|
98 |
+dataRequestInterval = _DEFAULT_SENSOR_POLLING_INTERVAL |
|
94 | 99 |
# how often charts get updated |
95 | 100 |
chartUpdateInterval = _CHART_UPDATE_INTERVAL |
101 |
+# number of failed attempts to get sensor data |
|
102 |
+failedUpdateCount = 0 |
|
103 |
+# sensor status |
|
104 |
+deviceOnline = False |
|
96 | 105 |
|
97 |
-# Define each sensor. This also initialzes each sensor. |
|
98 |
-pwr = ina260.ina260(_PWR_SENSOR_ADDR, _BUS_SEL) |
|
99 |
-btmp = tmp102.tmp102(_BAT_TMP_SENSOR_ADDR, _BUS_SEL) |
|
100 |
-atmp = tmp102.tmp102(_AMB_TMP_SENSOR_ADDR, _BUS_SEL) |
|
106 |
+# Create sensor objects. This also initialzes each sensor. |
|
107 |
+power = ina260.ina260(_PWR_SENSOR_ADDR, _BUS_NUMBER) |
|
108 |
+battemp = tmp102.tmp102(_BAT_TMP_SENSOR_ADDR, _BUS_NUMBER) |
|
109 |
+ambtemp = tmp102.tmp102(_AMB_TMP_SENSOR_ADDR, _BUS_NUMBER) |
|
101 | 110 |
|
102 | 111 |
### PRIVATE METHODS ### |
103 | 112 |
|
... | ... |
@@ -127,21 +136,37 @@ def getEpochSeconds(sTime): |
127 | 136 |
return tSeconds |
128 | 137 |
## end def |
129 | 138 |
|
130 |
-def terminateAgentProcess(signal, frame): |
|
131 |
- """ |
|
132 |
- Send a message to the log when the agent process gets killed |
|
133 |
- by the operating system. Inform downstream clients |
|
134 |
- by removing output data files. |
|
135 |
- Parameters: |
|
136 |
- signal, frame - dummy parameters |
|
137 |
- Returns: nothing |
|
139 |
+def setStatusToOffline(): |
|
140 |
+ """Set the detected status of the device to |
|
141 |
+ "offline" and inform downstream clients by removing input |
|
142 |
+ and output data files. |
|
143 |
+ Parameters: none |
|
144 |
+ Returns: nothing |
|
138 | 145 |
""" |
146 |
+ global deviceOnline |
|
147 |
+ |
|
139 | 148 |
# Inform downstream clients by removing output data file. |
140 | 149 |
if os.path.exists(_OUTPUT_DATA_FILE): |
141 | 150 |
os.remove(_OUTPUT_DATA_FILE) |
142 |
- print('%s terminating npw agent process' % getTimeStamp()) |
|
151 |
+ # If the sensor or device was previously online, then send |
|
152 |
+ # a message that we are now offline. |
|
153 |
+ if deviceOnline: |
|
154 |
+ print('%s device offline' % getTimeStamp()) |
|
155 |
+ deviceOnline = False |
|
156 |
+##end def |
|
157 |
+ |
|
158 |
+def terminateAgentProcess(signal, frame): |
|
159 |
+ """Send a message to log when the agent process gets killed |
|
160 |
+ by the operating system. Inform downstream clients |
|
161 |
+ by removing input and output data files. |
|
162 |
+ Parameters: |
|
163 |
+ signal, frame - dummy parameters |
|
164 |
+ Returns: nothing |
|
165 |
+ """ |
|
166 |
+ print('%s terminating agent process' % getTimeStamp()) |
|
167 |
+ setStatusToOffline() |
|
143 | 168 |
sys.exit(0) |
144 |
-## end def |
|
169 |
+##end def |
|
145 | 170 |
|
146 | 171 |
### PUBLIC METHODS ### |
147 | 172 |
|
... | ... |
@@ -156,11 +181,11 @@ def getSensorData(dData): |
156 | 181 |
dData["time"] = getTimeStamp() |
157 | 182 |
|
158 | 183 |
try: |
159 |
- dData["current"] = pwr.getCurrent() |
|
160 |
- dData["voltage"] = pwr.getVoltage() |
|
161 |
- dData["power"] = pwr.getPower() |
|
162 |
- dData["battemp"] = btmp.getTempF() |
|
163 |
- dData["ambtemp"] = atmp.getTempF() |
|
184 |
+ dData["current"] = power.getCurrent() |
|
185 |
+ dData["voltage"] = power.getVoltage() |
|
186 |
+ dData["power"] = power.getPower() |
|
187 |
+ dData["battemp"] = battemp.getTempF() |
|
188 |
+ dData["ambtemp"] = ambtemp.getTempF() |
|
164 | 189 |
except Exception as exError: |
165 | 190 |
print("%s sensor error: %s" % (getTimeStamp(), exError)) |
166 | 191 |
return False |
... | ... |
@@ -212,6 +237,35 @@ def writeOutputFile(dData): |
212 | 237 |
return True |
213 | 238 |
## end def |
214 | 239 |
|
240 |
+def setStatus(updateSuccess): |
|
241 |
+ """Detect if device is offline or not available on |
|
242 |
+ the network. After a set number of attempts to get data |
|
243 |
+ from the device set a flag that the device is offline. |
|
244 |
+ Parameters: |
|
245 |
+ updateSuccess - a boolean that is True if data request |
|
246 |
+ successful, False otherwise |
|
247 |
+ Returns: nothing |
|
248 |
+ """ |
|
249 |
+ global failedUpdateCount, deviceOnline |
|
250 |
+ |
|
251 |
+ if updateSuccess: |
|
252 |
+ failedUpdateCount = 0 |
|
253 |
+ # Set status and send a message to the log if the device |
|
254 |
+ # previously offline and is now online. |
|
255 |
+ if not deviceOnline: |
|
256 |
+ print('%s device online' % getTimeStamp()) |
|
257 |
+ deviceOnline = True |
|
258 |
+ else: |
|
259 |
+ # The last attempt failed, so update the failed attempts |
|
260 |
+ # count. |
|
261 |
+ failedUpdateCount += 1 |
|
262 |
+ |
|
263 |
+ if failedUpdateCount >= _MAX_FAILED_DATA_REQUESTS: |
|
264 |
+ # Max number of failed data requests, so set |
|
265 |
+ # device status to offline. |
|
266 |
+ setStatusToOffline() |
|
267 |
+##end def |
|
268 |
+ |
|
215 | 269 |
def updateDatabase(dData): |
216 | 270 |
""" |
217 | 271 |
Update the rrdtool database by executing an rrdtool system command. |
... | ... |
@@ -439,12 +493,13 @@ def main(): |
439 | 493 |
Parameters: none |
440 | 494 |
Returns: nothing |
441 | 495 |
""" |
442 |
- global dataRequestInterval |
|
443 |
- |
|
444 | 496 |
signal.signal(signal.SIGTERM, terminateAgentProcess) |
445 | 497 |
signal.signal(signal.SIGINT, terminateAgentProcess) |
446 | 498 |
|
447 |
- print('%s starting up node power agent process' % getTimeStamp()) |
|
499 |
+ # Log agent process startup time. |
|
500 |
+ print '===================\n'\ |
|
501 |
+ '%s starting up node power agent process' % \ |
|
502 |
+ (getTimeStamp()) |
|
448 | 503 |
|
449 | 504 |
# last time output JSON file updated |
450 | 505 |
lastDataRequestTime = -1 |
... | ... |
@@ -488,6 +543,8 @@ def main(): |
488 | 543 |
## Update the round robin database with the parsed data. |
489 | 544 |
result = updateDatabase(dData) |
490 | 545 |
|
546 |
+ setStatus(result) |
|
547 |
+ |
|
491 | 548 |
# At the chart generation interval, generate charts. |
492 | 549 |
if currentTime - lastChartUpdateTime > chartUpdateInterval: |
493 | 550 |
lastChartUpdateTime = currentTime |
... | ... |
@@ -57,17 +57,25 @@ class tmp102: |
57 | 57 |
# register. |
58 | 58 |
def __init__(self, sAddr=DEFAULT_BUS_ADDRESS, |
59 | 59 |
sbus=DEFAULT_BUS_NUMBER, |
60 |
- config=DEFAULT_CONFIG): |
|
60 |
+ config=DEFAULT_CONFIG, |
|
61 |
+ debug=False): |
|
61 | 62 |
# Instantiate a smbus object |
62 | 63 |
self.sensorAddr = sAddr |
63 | 64 |
self.bus = smbus.SMBus(sbus) |
65 |
+ self.debugMode = debug |
|
66 |
+ |
|
64 | 67 |
# Initialize TMP102 sensor. |
65 | 68 |
initData = [(config >> 8), (config & 0x00FF)] |
66 | 69 |
self.bus.write_i2c_block_data(self.sensorAddr, CONFIG_REG, initData) |
70 |
+ |
|
71 |
+ if self.debugMode: |
|
72 |
+ # Read the TMP102 configuration register. |
|
73 |
+ data = self.getInfo() |
|
74 |
+ print("configuration register: %s %s\n" % data) |
|
67 | 75 |
## end def |
68 | 76 |
|
69 | 77 |
# Reads the configuration register (two bytes). |
70 |
- def status(self): |
|
78 |
+ def getInfo(self): |
|
71 | 79 |
# Read configuration data |
72 | 80 |
config = self.bus.read_i2c_block_data(self.sensorAddr, CONFIG_REG, 2) |
73 | 81 |
configB1 = format(config[0], "08b") |
... | ... |
@@ -99,6 +107,12 @@ class tmp102: |
99 | 107 |
# The temperature is returned in d11-d0, a two's complement, |
100 | 108 |
# 12 bit number. This means that d11 is the sign bit. |
101 | 109 |
data=self.bus.read_i2c_block_data(self.sensorAddr, TEMP_REG, 2) |
110 |
+ |
|
111 |
+ if self.debugMode: |
|
112 |
+ dataB1 = format(data[0], "08b") |
|
113 |
+ dataB2 = format(data[1], "08b") |
|
114 |
+ print("Temperature Reg: %s %s" % (dataB1, dataB2)) |
|
115 |
+ |
|
102 | 116 |
# Format into a 12 bit word. |
103 | 117 |
bData = ( data[0] << 8 | data[1] ) >> 4 |
104 | 118 |
# Convert from two's complement to integer. |
... | ... |
@@ -121,24 +135,18 @@ class tmp102: |
121 | 135 |
|
122 | 136 |
def testclass(): |
123 | 137 |
# Initialize the smbus and TMP102 sensor. |
124 |
- ts1 = tmp102(0x48, 1) |
|
125 |
- # Read the TMP102 configuration register. |
|
126 |
- data = ts1.status() |
|
127 |
- print "configuration register: %s %s\n" % data |
|
138 |
+ ts1 = tmp102(0x48, 1, debug=True) |
|
128 | 139 |
# Print out sensor values. |
129 | 140 |
bAl = False |
130 | 141 |
while True: |
131 |
- regdata = ts1.getTempReg() |
|
132 | 142 |
tempC = ts1.getTempC() |
133 | 143 |
tempF = ts1.getTempF() |
134 | 144 |
if bAl: |
135 | 145 |
bAl = False |
136 |
- print("\033[42;30mTemperature Reg: %s %s\033[m" % regdata) |
|
137 |
- print("\033[42;30m%6.2f%sC %6.2f%s \033[m" % \ |
|
146 |
+ print("\033[42;30m%6.2f%sC %6.2f%sF \033[m" % \ |
|
138 | 147 |
(tempC, DEGSYM, tempF, DEGSYM)) |
139 | 148 |
else: |
140 | 149 |
bAl = True |
141 |
- print("Temperature Reg: %s %s" % regdata) |
|
142 | 150 |
print("%6.2f%sC %6.2f%sF" % \ |
143 | 151 |
(tempC, DEGSYM, tempF, DEGSYM)) |
144 | 152 |
time.sleep(2) |