Openhardwaremonitor (#8056)

* Open Hardware Monitor sensor

Platform which is able to connect to the JSON API of Open Hardware Monitor and adds sensors for the devices.

* Remove copyright in header, not needed.

* - Removed old code
- Fixed typo’s in comments
- Removed log spamming
- Removed code that was unnecessary
- Use requests instead of urllib
- Moved sensor update functionality to data handler, to remove unwanted constructor parameters

* Fixed typo in comment
Added tests

* Added default fixture, to stabilize tests

* - Fix for values deeper than 4 levels, no longer relies on fixed level
- Fixed tests

* Removed timer in preference of helper methods

* Moved update functionality back to Entity….
Updated SCAN INTERVAL

* Added timeout to request
Removed retry when Open Hardware Monitor API is not reachable
Fixed naming of sensors
Flow optimalisations
Fixed tests to use states

* Remove unused import
pull/8217/head
Wim Haanstra 2017-06-25 22:48:05 +02:00 committed by Paulus Schoutsen
parent 8358542ce0
commit 2f2952e0ec
3 changed files with 794 additions and 0 deletions

View File

@ -0,0 +1,183 @@
"""Support for Open Hardware Monitor Sensor Platform."""
from datetime import timedelta
import logging
import requests
import voluptuous as vol
from homeassistant.util.dt import utcnow
from homeassistant.const import CONF_HOST, CONF_PORT
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
STATE_MIN_VALUE = 'minimal_value'
STATE_MAX_VALUE = 'maximum_value'
STATE_VALUE = 'value'
STATE_OBJECT = 'object'
CONF_INTERVAL = 'interval'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=15)
SCAN_INTERVAL = timedelta(seconds=30)
RETRY_INTERVAL = timedelta(seconds=30)
OHM_VALUE = 'Value'
OHM_MIN = 'Min'
OHM_MAX = 'Max'
OHM_CHILDREN = 'Children'
OHM_NAME = 'Text'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=8085): cv.port
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Open Hardware Monitor platform."""
data = OpenHardwareMonitorData(config, hass)
add_devices(data.devices, True)
class OpenHardwareMonitorDevice(Entity):
"""Device used to display information from OpenHardwareMonitor."""
def __init__(self, data, name, path, unit_of_measurement):
"""Initialize an OpenHardwareMonitor sensor."""
self._name = name
self._data = data
self.path = path
self.attributes = {}
self._unit_of_measurement = unit_of_measurement
self.value = None
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def state(self):
"""Return the state of the device."""
return self.value
@property
def state_attributes(self):
"""Return the state attributes of the sun."""
return self.attributes
def update(self):
"""Update the device from a new JSON object."""
self._data.update()
array = self._data.data[OHM_CHILDREN]
_attributes = {}
for path_index in range(0, len(self.path)):
path_number = self.path[path_index]
values = array[path_number]
if path_index == len(self.path) - 1:
self.value = values[OHM_VALUE].split(' ')[0]
_attributes.update({
'name': values[OHM_NAME],
STATE_MIN_VALUE: values[OHM_MIN].split(' ')[0],
STATE_MAX_VALUE: values[OHM_MAX].split(' ')[0]
})
self.attributes = _attributes
return
else:
array = array[path_number][OHM_CHILDREN]
_attributes.update({
'level_%s' % path_index: values[OHM_NAME]
})
class OpenHardwareMonitorData(object):
"""Class used to pull data from OHM and create sensors."""
def __init__(self, config, hass):
"""Initialize the Open Hardware Monitor data-handler."""
self.data = None
self._config = config
self._hass = hass
self.devices = []
self.initialize(utcnow())
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Hit by the timer with the configured interval."""
if self.data is None:
self.initialize(utcnow())
else:
self.refresh()
def refresh(self):
"""Download and parse JSON from OHM."""
data_url = "http://%s:%d/data.json" % (
self._config.get(CONF_HOST),
self._config.get(CONF_PORT))
try:
response = requests.get(data_url, timeout=30)
self.data = response.json()
except requests.exceptions.ConnectionError:
_LOGGER.error("ConnectionError: Is OpenHardwareMonitor running?")
def initialize(self, now):
"""Initial parsing of the sensors and adding of devices."""
self.refresh()
if self.data is None:
return
self.devices = self.parse_children(self.data, [], [], [])
def parse_children(self, json, devices, path, names):
"""Recursively loop through child objects, finding the values."""
result = devices.copy()
if len(json[OHM_CHILDREN]) > 0:
for child_index in range(0, len(json[OHM_CHILDREN])):
child_path = path.copy()
child_path.append(child_index)
child_names = names.copy()
if len(path) > 0:
child_names.append(json[OHM_NAME])
obj = json[OHM_CHILDREN][child_index]
added_devices = self.parse_children(
obj, devices, child_path, child_names)
result = result + added_devices
return result
if json[OHM_VALUE].find(' ') == -1:
return result
unit_of_measurement = json[OHM_VALUE].split(' ')[1]
child_names = names.copy()
child_names.append(json[OHM_NAME])
fullname = ' '.join(child_names)
dev = OpenHardwareMonitorDevice(
self,
fullname,
path,
unit_of_measurement
)
result.append(dev)
return result

View File

@ -0,0 +1,40 @@
"""The tests for the Open Hardware Monitor platform."""
import unittest
import requests_mock
from homeassistant.setup import setup_component
from tests.common import load_fixture, get_test_home_assistant
class TestOpenHardwareMonitorSetup(unittest.TestCase):
"""Test the Open Hardware Monitor platform."""
def setUp(self):
"""Initialize values for this testcase class."""
self.hass = get_test_home_assistant()
self.config = {
'sensor': {
'platform': 'openhardwaremonitor',
'host': 'localhost',
'port': 8085
}
}
def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started."""
self.hass.stop()
@requests_mock.Mocker()
def test_setup(self, mock_req):
"""Test for successfully setting up the platform."""
mock_req.get('http://localhost:8085/data.json',
text=load_fixture('openhardwaremonitor.json'))
self.assertTrue(setup_component(self.hass, 'sensor', self.config))
entities = self.hass.states.async_entity_ids('sensor')
self.assertEqual(len(entities), 38)
state = self.hass.states.get(
'sensor.testpc_intel_core_i77700_clocks_bus_speed')
self.assertIsNot(state, None)
self.assertEqual(state.state, '100')

571
tests/fixtures/openhardwaremonitor.json vendored Normal file
View File

@ -0,0 +1,571 @@
{
"id": 0,
"Text": "Sensor",
"Children": [
{
"id": 1,
"Text": "TEST-PC",
"Children": [
{
"id": 2,
"Text": "ASUS PRIME Z270-P",
"Children": [],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/mainboard.png"
},
{
"id": 3,
"Text": "Intel Core i7-7700",
"Children": [
{
"id": 4,
"Text": "Clocks",
"Children": [
{
"id": 5,
"Text": "Bus Speed",
"Children": [],
"Min": "100 MHz",
"Value": "100 MHz",
"Max": "100 MHz",
"ImageURL": "images/transparent.png"
},
{
"id": 6,
"Text": "CPU Core #1",
"Children": [],
"Min": "800 MHz",
"Value": "800 MHz",
"Max": "4200 MHz",
"ImageURL": "images/transparent.png"
},
{
"id": 7,
"Text": "CPU Core #2",
"Children": [],
"Min": "800 MHz",
"Value": "800 MHz",
"Max": "4200 MHz",
"ImageURL": "images/transparent.png"
},
{
"id": 8,
"Text": "CPU Core #3",
"Children": [],
"Min": "800 MHz",
"Value": "800 MHz",
"Max": "4200 MHz",
"ImageURL": "images/transparent.png"
},
{
"id": 9,
"Text": "CPU Core #4",
"Children": [],
"Min": "800 MHz",
"Value": "800 MHz",
"Max": "4200 MHz",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/clock.png"
},
{
"id": 10,
"Text": "Temperatures",
"Children": [
{
"id": 11,
"Text": "CPU Core #1",
"Children": [],
"Min": "29.0 °C",
"Value": "31.0 °C",
"Max": "60.0 °C",
"ImageURL": "images/transparent.png"
},
{
"id": 12,
"Text": "CPU Core #2",
"Children": [],
"Min": "29.0 °C",
"Value": "30.0 °C",
"Max": "61.0 °C",
"ImageURL": "images/transparent.png"
},
{
"id": 13,
"Text": "CPU Core #3",
"Children": [],
"Min": "28.0 °C",
"Value": "29.0 °C",
"Max": "58.0 °C",
"ImageURL": "images/transparent.png"
},
{
"id": 14,
"Text": "CPU Core #4",
"Children": [],
"Min": "29.0 °C",
"Value": "31.0 °C",
"Max": "57.0 °C",
"ImageURL": "images/transparent.png"
},
{
"id": 15,
"Text": "CPU Package",
"Children": [],
"Min": "30.0 °C",
"Value": "31.0 °C",
"Max": "61.0 °C",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/temperature.png"
},
{
"id": 16,
"Text": "Load",
"Children": [
{
"id": 17,
"Text": "CPU Total",
"Children": [],
"Min": "0.0 %",
"Value": "1.0 %",
"Max": "42.2 %",
"ImageURL": "images/transparent.png"
},
{
"id": 18,
"Text": "CPU Core #1",
"Children": [],
"Min": "0.0 %",
"Value": "1.6 %",
"Max": "50.8 %",
"ImageURL": "images/transparent.png"
},
{
"id": 19,
"Text": "CPU Core #2",
"Children": [],
"Min": "0.0 %",
"Value": "1.6 %",
"Max": "52.0 %",
"ImageURL": "images/transparent.png"
},
{
"id": 20,
"Text": "CPU Core #3",
"Children": [],
"Min": "0.0 %",
"Value": "0.0 %",
"Max": "52.2 %",
"ImageURL": "images/transparent.png"
},
{
"id": 21,
"Text": "CPU Core #4",
"Children": [],
"Min": "0.0 %",
"Value": "0.8 %",
"Max": "51.8 %",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/load.png"
},
{
"id": 22,
"Text": "Powers",
"Children": [
{
"id": 23,
"Text": "CPU Package",
"Children": [],
"Min": "4.4 W",
"Value": "12.1 W",
"Max": "44.6 W",
"ImageURL": "images/transparent.png"
},
{
"id": 24,
"Text": "CPU Cores",
"Children": [],
"Min": "0.9 W",
"Value": "1.0 W",
"Max": "33.5 W",
"ImageURL": "images/transparent.png"
},
{
"id": 25,
"Text": "CPU Graphics",
"Children": [],
"Min": "0.0 W",
"Value": "0.0 W",
"Max": "0.0 W",
"ImageURL": "images/transparent.png"
},
{
"id": 26,
"Text": "CPU DRAM",
"Children": [],
"Min": "1.0 W",
"Value": "1.0 W",
"Max": "2.4 W",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/power.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/cpu.png"
},
{
"id": 27,
"Text": "Generic Memory",
"Children": [
{
"id": 28,
"Text": "Load",
"Children": [
{
"id": 29,
"Text": "Memory",
"Children": [],
"Min": "13.1 %",
"Value": "13.6 %",
"Max": "14.5 %",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/load.png"
},
{
"id": 30,
"Text": "Data",
"Children": [
{
"id": 31,
"Text": "Used Memory",
"Children": [],
"Min": "4.2 GB",
"Value": "4.3 GB",
"Max": "4.6 GB",
"ImageURL": "images/transparent.png"
},
{
"id": 32,
"Text": "Available Memory",
"Children": [],
"Min": "27.2 GB",
"Value": "27.5 GB",
"Max": "27.7 GB",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/power.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/ram.png"
},
{
"id": 33,
"Text": "NVIDIA GeForce GTX 1080",
"Children": [
{
"id": 34,
"Text": "Clocks",
"Children": [
{
"id": 35,
"Text": "GPU Core",
"Children": [],
"Min": "215 MHz",
"Value": "215 MHz",
"Max": "1683 MHz",
"ImageURL": "images/transparent.png"
},
{
"id": 36,
"Text": "GPU Memory",
"Children": [],
"Min": "405 MHz",
"Value": "405 MHz",
"Max": "5006 MHz",
"ImageURL": "images/transparent.png"
},
{
"id": 37,
"Text": "GPU Shader",
"Children": [],
"Min": "430 MHz",
"Value": "430 MHz",
"Max": "3366 MHz",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/clock.png"
},
{
"id": 38,
"Text": "Temperatures",
"Children": [
{
"id": 39,
"Text": "GPU Core",
"Children": [],
"Min": "38.0 °C",
"Value": "39.0 °C",
"Max": "42.0 °C",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/temperature.png"
},
{
"id": 40,
"Text": "Load",
"Children": [
{
"id": 41,
"Text": "GPU Core",
"Children": [],
"Min": "0.0 %",
"Value": "0.0 %",
"Max": "19.0 %",
"ImageURL": "images/transparent.png"
},
{
"id": 42,
"Text": "GPU Memory Controller",
"Children": [],
"Min": "0.0 %",
"Value": "0.0 %",
"Max": "2.0 %",
"ImageURL": "images/transparent.png"
},
{
"id": 43,
"Text": "GPU Video Engine",
"Children": [],
"Min": "0.0 %",
"Value": "0.0 %",
"Max": "0.0 %",
"ImageURL": "images/transparent.png"
},
{
"id": 44,
"Text": "GPU Memory",
"Children": [],
"Min": "3.9 %",
"Value": "3.9 %",
"Max": "4.1 %",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/load.png"
},
{
"id": 45,
"Text": "Fans",
"Children": [
{
"id": 46,
"Text": "GPU",
"Children": [],
"Min": "0 RPM",
"Value": "0 RPM",
"Max": "0 RPM",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/fan.png"
},
{
"id": 47,
"Text": "Controls",
"Children": [
{
"id": 48,
"Text": "GPU Fan",
"Children": [],
"Min": "0.0 %",
"Value": "0.0 %",
"Max": "0.0 %",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/control.png"
},
{
"id": 49,
"Text": "Data",
"Children": [
{
"id": 50,
"Text": "GPU Memory Free",
"Children": [],
"Min": "7854.8 MB",
"Value": "7873.1 MB",
"Max": "7873.1 MB",
"ImageURL": "images/transparent.png"
},
{
"id": 51,
"Text": "GPU Memory Used",
"Children": [],
"Min": "318.9 MB",
"Value": "318.9 MB",
"Max": "337.2 MB",
"ImageURL": "images/transparent.png"
},
{
"id": 52,
"Text": "GPU Memory Total",
"Children": [],
"Min": "8192.0 MB",
"Value": "8192.0 MB",
"Max": "8192.0 MB",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/power.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/nvidia.png"
},
{
"id": 53,
"Text": "Generic Hard Disk",
"Children": [
{
"id": 54,
"Text": "Load",
"Children": [
{
"id": 55,
"Text": "Used Space",
"Children": [],
"Min": "74.6 %",
"Value": "75.3 %",
"Max": "75.6 %",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/load.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/hdd.png"
},
{
"id": 56,
"Text": "WDC WD30EZRZ-00Z5HB0",
"Children": [
{
"id": 57,
"Text": "Temperatures",
"Children": [
{
"id": 58,
"Text": "Temperature",
"Children": [],
"Min": "30.0 °C",
"Value": "30.0 °C",
"Max": "32.0 °C",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/temperature.png"
},
{
"id": 59,
"Text": "Load",
"Children": [
{
"id": 60,
"Text": "Used Space",
"Children": [],
"Min": "14.4 %",
"Value": "14.4 %",
"Max": "14.4 %",
"ImageURL": "images/transparent.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/load.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/hdd.png"
}
],
"Min": "",
"Value": "",
"Max": "",
"ImageURL": "images_icon/computer.png"
}
],
"Min": "Min",
"Value": "Value",
"Max": "Max",
"ImageURL": ""
}