-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathArduinoPCMonitor.py
297 lines (238 loc) · 9.22 KB
/
ArduinoPCMonitor.py
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import os
import time
from urllib.error import URLError, HTTPError
from urllib.request import Request, urlopen
import serial
import serial.tools.list_ports
serial_debug = False
show_gpu_mem = None
gpu_fan_rpm = None
def space_pad(number, length):
"""
Return a number as a string, padded with spaces to make it the given length
:param number: the number to pad with spaces (can be int or float)
:param length: the specified length
:returns: the number padded with spaces as a string
"""
number_length = len(str(number))
spaces_to_add = length - number_length
return (' ' * spaces_to_add) + str(number)
def get_local_json_contents(json_filename):
"""
Returns the contents of a (local) JSON file
:param json_filename: the filename (as a string) of the local JSON file
:returns: the data of the JSON file
"""
try:
with open(json_filename) as json_file:
try:
data = json.load(json_file)
except ValueError:
print('Contents of "' + json_filename + '" are not valid JSON')
raise
except IOError:
print('An error occurred while reading "' + json_filename + '"')
raise
return data
def get_json_contents(json_url):
"""
Return the contents of a (remote) JSON file
:param json_url: the url (as a string) of the remote JSON file
:returns: the data of the JSON file
"""
data = None
req = Request(json_url)
try:
response = urlopen(req).read()
except HTTPError as e:
print('HTTPError ' + str(e.code))
except URLError as e:
print('URLError ' + str(e.reason))
else:
try:
data = json.loads(response.decode('utf-8'))
except ValueError:
print('Invalid JSON contents')
return data
def find_in_data(ohw_data, name):
"""
Search in the OpenHardwareMonitor data for a specific node, recursively
:param ohw_data: OpenHardwareMonitor data object
:param name: Name of node to search for
:returns: The found node, or -1 if no node was found
"""
if ohw_data == -1:
raise Exception('Couldn\'t find value ' + name + '!')
if ohw_data['Text'] == name:
# The node we are looking for is this one
return ohw_data
elif len(ohw_data['Children']) > 0:
# Look at the node's children
for child in ohw_data['Children']:
if child['Text'] == name:
# This child is the one we're looking for
return child
else:
# Look at this children's children
result = find_in_data(child, name)
if result != -1:
# Node with specified name was found
return result
# When this point is reached, nothing was found in any children
return -1
def get_temperature_number(temp_str):
"""
Given a temperature string of the form "48.8 °C", return the first number
(in this example, 48). Also handles strings of the form "48,8 °C" that
apparently can exist (at least when reading the response from a file)
:param temp_str: Temperature string
:return: Temperature number (in string form)
"""
if 'Â' in temp_str:
return temp_str[:-6]
else:
return temp_str[:-5]
def get_hardware_info(ohw_ip, ohw_port, cpu_name, gpu_name, gpu_mem_size):
"""
Get hardware info from OpenHardwareMonitor's web server and format it
"""
global show_gpu_mem
global gpu_fan_rpm
global serial_debug
# Init arrays
my_info = {}
gpu_info = {}
cpu_core_temps = []
# Get data
if serial_debug:
# Read data from response.json file (for debugging)
data_json = get_local_json_contents('response.json')
else:
# Get actual OHW data
ohw_json_url = 'http://' + ohw_ip + ':' + ohw_port + '/data.json'
data_json = get_json_contents(ohw_json_url)
# Get info for CPU
cpu_data = find_in_data(data_json, cpu_name)
cpu_temps = find_in_data(cpu_data, 'Temperatures')
cpu_load = find_in_data(cpu_data, 'CPU Total')
# Look for CPU temperatures. For all children of the CPU temp. section...
for core_temp in cpu_temps['Children']:
# Check that "Core" is in the name, to prevent using Intel's
# "CPU Package" temperature, and should work with AMD too.
if 'Core' in core_temp['Text']:
# Remove '.0 °C' from end of value
cpu_core_temps.append(get_temperature_number(core_temp['Value']))
my_info['cpu_temps'] = cpu_core_temps
# Get CPU total load, and remove ".0 %" from the end
cpu_load_value = cpu_load['Value'][:-4]
my_info['cpu_load'] = cpu_load_value
# Get info for GPU
gpu_data = find_in_data(data_json, gpu_name)
gpu_clocks = find_in_data(gpu_data, 'Clocks')
gpu_load = find_in_data(gpu_data, 'Load')
gpu_core_clock = find_in_data(gpu_clocks, 'GPU Core')
gpu_mem_clock = find_in_data(gpu_clocks, 'GPU Memory')
gpu_temp = find_in_data(find_in_data(gpu_data, 'Temperatures'), 'GPU Core')
gpu_core_load = find_in_data(gpu_load, 'GPU Core')
fan_percent = find_in_data(find_in_data(gpu_data, 'Controls'), 'GPU Fan')
# Check if the GPU has fan RPM info or just PWM info (percentage)
if gpu_fan_rpm is None:
gpu_fans = find_in_data(gpu_data, 'Fans')
gpu_fan_rpm = (gpu_fans != -1)
# Get data for GPU fans (or PWM percentage)
if gpu_fan_rpm:
gpu_fans = find_in_data(gpu_data, 'Fans')
else:
gpu_fans = find_in_data(gpu_data, 'Controls')
# Get GPU Fan RPM info (check both Fans > GPU and Fans > GPU Fan)
fan_rpm = find_in_data(gpu_fans, 'GPU')
if fan_rpm == -1:
fan_rpm = find_in_data(gpu_fans, 'GPU Fan')
# Check if the GPU has used memory information, and remember it
if show_gpu_mem is None:
gpu_mem_percent = find_in_data(gpu_load, 'GPU Memory')
show_gpu_mem = (gpu_mem_percent != -1)
# Get GPU Memory percentage if it exists, otherwise GPU voltage
if show_gpu_mem:
# Get GPU memory percentage
gpu_mem_percent = find_in_data(gpu_load, 'GPU Memory')
# Calculate used MBs of GPU memory based on the percentage
used_percentage = float(
gpu_mem_percent['Value'][:-2].replace(",", "."))
used_mb = int((gpu_mem_size * used_percentage) / 100)
# Add to GPU info object
gpu_info['used_mem'] = used_mb
else:
# Get GPU voltage
voltages = find_in_data(gpu_data, 'Voltages')
core_voltage = find_in_data(voltages, 'GPU Core')
gpu_info['voltage'] = core_voltage['Value'][:-2]
# Add rest of GPU info to GPU object
gpu_info['temp'] = get_temperature_number(gpu_temp['Value'])
gpu_info['load'] = gpu_core_load['Value'][:-4]
gpu_info['core_clock'] = gpu_core_clock['Value'][:-4]
# Memory clock divided by 2 so it is the same as GPU-Z reports
gpu_info['mem_clock'] = int(int(gpu_mem_clock['Value'][:-4]) / 2)
gpu_info['fan_percent'] = fan_percent['Value'][:-4]
gpu_info['fan_rpm'] = fan_rpm['Value'][:-4]
# Add GPU info to my_info
my_info['gpu'] = gpu_info
return my_info
def main():
global serial_debug
# Load config JSON
cd = os.path.join(os.getcwd(), os.path.dirname(__file__))
__location__ = os.path.realpath(cd)
config = get_local_json_contents(os.path.join(__location__, 'config.json'))
# Connect to the specified serial port
serial_port = config['serial_port']
if serial_port == 'TEST':
serial_debug = True
else:
ser = serial.Serial(serial_port)
while True:
# Get current info
my_info = get_hardware_info(
config['ohw_ip'],
config['ohw_port'],
config['cpu_name'],
config['gpu_name'],
config['gpu_mem_size']
)
# Prepare CPU string
cpu_temps = my_info['cpu_temps']
cpu = space_pad(int(my_info['cpu_load']), 3) + '% '
for index, temp in enumerate(cpu_temps):
if index >= 4:
# Can't fit more than 4 temperatures in Arduino screen
break
cpu += space_pad(int(temp), 2) + 'C '
# Prepare GPU strings
gpu_info = my_info['gpu']
gpu1 = \
space_pad(int(gpu_info['load']), 3) + '% ' + \
space_pad(int(gpu_info['temp']), 2) + 'C '
if 'used_mem' in gpu_info:
gpu1 += space_pad(int(gpu_info['used_mem']), 4) + 'MB'
else:
gpu1 += str(gpu_info['voltage']) + 'V'
gpu2 = \
space_pad(int(gpu_info['fan_percent']), 3) + '% F ' + \
space_pad(int(gpu_info['fan_rpm']), 4) + ' RPM'
gpu3 = \
space_pad(int(gpu_info['core_clock']), 4) + '/' + \
space_pad(int(gpu_info['mem_clock']), 4)
# Send the strings via serial to the Arduino
arduino_str = \
'C' + cpu + '|G' + gpu1 + '|F' + gpu2 + '|g' + gpu3 + '|'
if serial_debug:
print(arduino_str)
else:
ser.write(arduino_str.encode())
# Wait until refreshing Arduino again
time.sleep(2.5)
if __name__ == '__main__':
main()