mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Add Repetier-Server Component (#21658)
* Bumped to version 2.0 * Updated requirements * Updated requirements and coveragerc * Removed long lines, changes to coveragerc and requirements * Fixed under-indented lines * Fixed invalid syntax * Updated .coveragerc to include repetier/__init__.py and sensor.py * Module update * Rebased to latest dev * Blank lines fix * Add missing manifest.json * Update requirements * Bumped component to new API module style * Removed whitespaces and line feeds * Added missing newline * Added missing heated chamber sensor * Fixed wrong indentation * Various fixes * Various build fixes * Clean up * Load platform only once * Sort imports * Add printer api * Clean up * Build out sensor classes * Clarify temperature sensor variable names * Move constants * Clean up name * Run script/gen_requirements_all.py * Working code, missing auto add of new sensors * Updated code to return proper device_class and timestamp * Removed unnessecary code * Renamed elapsed and remaining sensors * Dynamically adding sensors as they become available * Rebased .coveragerc due to conflicts * Code changes and cleanup * Removed whitespace and code simplification
This commit is contained in:
committed by
Charles Garwood
parent
015c8811a5
commit
85dfea1642
@ -488,6 +488,8 @@ omit =
|
||||
homeassistant/components/reddit/*
|
||||
homeassistant/components/rejseplanen/sensor.py
|
||||
homeassistant/components/remember_the_milk/__init__.py
|
||||
homeassistant/components/repetier/__init__.py
|
||||
homeassistant/components/repetier/sensor.py
|
||||
homeassistant/components/remote_rpi_gpio/*
|
||||
homeassistant/components/rest/binary_sensor.py
|
||||
homeassistant/components/rest/notify.py
|
||||
|
@ -193,6 +193,7 @@ homeassistant/components/qwikswitch/* @kellerza
|
||||
homeassistant/components/raincloud/* @vanstinator
|
||||
homeassistant/components/rainmachine/* @bachya
|
||||
homeassistant/components/random/* @fabaff
|
||||
homeassistant/components/repetier/* @MTrab
|
||||
homeassistant/components/rfxtrx/* @danielhiversen
|
||||
homeassistant/components/rmvtransport/* @cgtobi
|
||||
homeassistant/components/roomba/* @pschmitt
|
||||
|
248
homeassistant/components/repetier/__init__.py
Executable file
248
homeassistant/components/repetier/__init__.py
Executable file
@ -0,0 +1,248 @@
|
||||
"""Support for Repetier-Server sensors."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY, CONF_HOST, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PORT,
|
||||
CONF_SENSORS, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
from homeassistant.util import slugify as util_slugify
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'RepetierServer'
|
||||
DOMAIN = 'repetier'
|
||||
REPETIER_API = 'repetier_api'
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
UPDATE_SIGNAL = 'repetier_update_signal'
|
||||
|
||||
TEMP_DATA = {
|
||||
'tempset': 'temp_set',
|
||||
'tempread': 'state',
|
||||
'output': 'output',
|
||||
}
|
||||
|
||||
|
||||
API_PRINTER_METHODS = {
|
||||
'bed_temperature': {
|
||||
'offline': {'heatedbeds': None, 'state': 'off'},
|
||||
'state': {'heatedbeds': 'temp_data'},
|
||||
'temp_data': TEMP_DATA,
|
||||
'attribute': 'heatedbeds',
|
||||
},
|
||||
'extruder_temperature': {
|
||||
'offline': {'extruder': None, 'state': 'off'},
|
||||
'state': {'extruder': 'temp_data'},
|
||||
'temp_data': TEMP_DATA,
|
||||
'attribute': 'extruder',
|
||||
},
|
||||
'chamber_temperature': {
|
||||
'offline': {'heatedchambers': None, 'state': 'off'},
|
||||
'state': {'heatedchambers': 'temp_data'},
|
||||
'temp_data': TEMP_DATA,
|
||||
'attribute': 'heatedchambers',
|
||||
},
|
||||
'current_state': {
|
||||
'offline': {'state': None},
|
||||
'state': {
|
||||
'state': 'state',
|
||||
'activeextruder': 'active_extruder',
|
||||
'hasxhome': 'x_homed',
|
||||
'hasyhome': 'y_homed',
|
||||
'haszhome': 'z_homed',
|
||||
'firmware': 'firmware',
|
||||
'firmwareurl': 'firmware_url',
|
||||
},
|
||||
},
|
||||
'current_job': {
|
||||
'offline': {'job': None, 'state': 'off'},
|
||||
'state': {
|
||||
'done': 'state',
|
||||
'job': 'job_name',
|
||||
'jobid': 'job_id',
|
||||
'totallines': 'total_lines',
|
||||
'linessent': 'lines_sent',
|
||||
'oflayer': 'total_layers',
|
||||
'layer': 'current_layer',
|
||||
'speedmultiply': 'feed_rate',
|
||||
'flowmultiply': 'flow',
|
||||
'x': 'x',
|
||||
'y': 'y',
|
||||
'z': 'z',
|
||||
},
|
||||
},
|
||||
'job_end': {
|
||||
'offline': {
|
||||
'job': None, 'state': 'off', 'start': None, 'printtime': None},
|
||||
'state': {
|
||||
'job': 'job_name',
|
||||
'start': 'start',
|
||||
'printtime': 'print_time',
|
||||
'printedtimecomp': 'from_start',
|
||||
},
|
||||
},
|
||||
'job_start': {
|
||||
'offline': {
|
||||
'job': None,
|
||||
'state': 'off',
|
||||
'start': None,
|
||||
'printedtimecomp': None
|
||||
},
|
||||
'state': {
|
||||
'job': 'job_name',
|
||||
'start': 'start',
|
||||
'printedtimecomp': 'from_start',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def has_all_unique_names(value):
|
||||
"""Validate that printers have an unique name."""
|
||||
names = [util_slugify(printer[CONF_NAME]) for printer in value]
|
||||
vol.Schema(vol.Unique())(names)
|
||||
return value
|
||||
|
||||
|
||||
SENSOR_TYPES = {
|
||||
# Type, Unit, Icon
|
||||
'bed_temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer',
|
||||
'_bed_'],
|
||||
'extruder_temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer',
|
||||
'_extruder_'],
|
||||
'chamber_temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer',
|
||||
'_chamber_'],
|
||||
'current_state': ['state', None, 'mdi:printer-3d', ''],
|
||||
'current_job': ['progress', '%', 'mdi:file-percent', '_current_job'],
|
||||
'job_end': ['progress', None, 'mdi:clock-end', '_job_end'],
|
||||
'job_start': ['progress', None, 'mdi:clock-start', '_job_start'],
|
||||
}
|
||||
|
||||
SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_PORT, default=3344): cv.port,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
|
||||
})], has_all_unique_names),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Repetier Server component."""
|
||||
import pyrepetier
|
||||
|
||||
hass.data[REPETIER_API] = {}
|
||||
|
||||
for repetier in config[DOMAIN]:
|
||||
_LOGGER.debug("Repetier server config %s", repetier[CONF_HOST])
|
||||
|
||||
url = "http://{}".format(repetier[CONF_HOST])
|
||||
port = repetier[CONF_PORT]
|
||||
api_key = repetier[CONF_API_KEY]
|
||||
|
||||
client = pyrepetier.Repetier(
|
||||
url=url,
|
||||
port=port,
|
||||
apikey=api_key)
|
||||
|
||||
printers = client.getprinters()
|
||||
|
||||
if not printers:
|
||||
return False
|
||||
|
||||
sensors = repetier[CONF_SENSORS][CONF_MONITORED_CONDITIONS]
|
||||
api = PrinterAPI(hass, client, printers, sensors,
|
||||
repetier[CONF_NAME], config)
|
||||
api.update()
|
||||
track_time_interval(hass, api.update, SCAN_INTERVAL)
|
||||
|
||||
hass.data[REPETIER_API][repetier[CONF_NAME]] = api
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class PrinterAPI:
|
||||
"""Handle the printer API."""
|
||||
|
||||
def __init__(self, hass, client, printers, sensors, conf_name, config):
|
||||
"""Set up instance."""
|
||||
self._hass = hass
|
||||
self._client = client
|
||||
self.printers = printers
|
||||
self.sensors = sensors
|
||||
self.conf_name = conf_name
|
||||
self.config = config
|
||||
self._known_entities = set()
|
||||
|
||||
def get_data(self, printer_id, sensor_type, temp_id):
|
||||
"""Get data from the state cache."""
|
||||
printer = self.printers[printer_id]
|
||||
methods = API_PRINTER_METHODS[sensor_type]
|
||||
for prop, offline in methods['offline'].items():
|
||||
state = getattr(printer, prop)
|
||||
if state == offline:
|
||||
# if state matches offline, sensor is offline
|
||||
return None
|
||||
|
||||
data = {}
|
||||
for prop, attr in methods['state'].items():
|
||||
prop_data = getattr(printer, prop)
|
||||
if attr == 'temp_data':
|
||||
temp_methods = methods['temp_data']
|
||||
for temp_prop, temp_attr in temp_methods.items():
|
||||
data[temp_attr] = getattr(prop_data[temp_id], temp_prop)
|
||||
else:
|
||||
data[attr] = prop_data
|
||||
return data
|
||||
|
||||
def update(self, now=None):
|
||||
"""Update the state cache from the printer API."""
|
||||
for printer in self.printers:
|
||||
printer.get_data()
|
||||
self._load_entities()
|
||||
dispatcher_send(self._hass, UPDATE_SIGNAL)
|
||||
|
||||
def _load_entities(self):
|
||||
sensor_info = []
|
||||
for pidx, printer in enumerate(self.printers):
|
||||
for sensor_type in self.sensors:
|
||||
info = {}
|
||||
info['sensor_type'] = sensor_type
|
||||
info['printer_id'] = pidx
|
||||
info['name'] = printer.slug
|
||||
info['printer_name'] = self.conf_name
|
||||
|
||||
known = '{}-{}'.format(printer.slug, sensor_type)
|
||||
if known in self._known_entities:
|
||||
continue
|
||||
|
||||
methods = API_PRINTER_METHODS[sensor_type]
|
||||
if 'temp_data' in methods['state'].values():
|
||||
prop_data = getattr(printer, methods['attribute'])
|
||||
if prop_data is None:
|
||||
continue
|
||||
for idx, _ in enumerate(prop_data):
|
||||
info['temp_id'] = idx
|
||||
sensor_info.append(info)
|
||||
else:
|
||||
info['temp_id'] = None
|
||||
sensor_info.append(info)
|
||||
|
||||
self._known_entities.add(known)
|
||||
|
||||
if not sensor_info:
|
||||
return
|
||||
load_platform(self._hass, 'sensor', DOMAIN, sensor_info, self.config)
|
10
homeassistant/components/repetier/manifest.json
Executable file
10
homeassistant/components/repetier/manifest.json
Executable file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"domain": "repetier",
|
||||
"name": "Repetier Server",
|
||||
"documentation": "https://www.home-assistant.io/components/repetier",
|
||||
"requirements": [
|
||||
"pyrepetier==3.0.5"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@MTrab"]
|
||||
}
|
215
homeassistant/components/repetier/sensor.py
Executable file
215
homeassistant/components/repetier/sensor.py
Executable file
@ -0,0 +1,215 @@
|
||||
"""Support for monitoring Repetier Server Sensors."""
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import time
|
||||
|
||||
from homeassistant.const import DEVICE_CLASS_TIMESTAMP
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import REPETIER_API, SENSOR_TYPES, UPDATE_SIGNAL
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the available Repetier Server sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
sensor_map = {
|
||||
'bed_temperature': RepetierTempSensor,
|
||||
'extruder_temperature': RepetierTempSensor,
|
||||
'chamber_temperature': RepetierTempSensor,
|
||||
'current_state': RepetierSensor,
|
||||
'current_job': RepetierJobSensor,
|
||||
'job_end': RepetierJobEndSensor,
|
||||
'job_start': RepetierJobStartSensor,
|
||||
}
|
||||
|
||||
entities = []
|
||||
for info in discovery_info:
|
||||
printer_name = info['printer_name']
|
||||
api = hass.data[REPETIER_API][printer_name]
|
||||
printer_id = info['printer_id']
|
||||
sensor_type = info['sensor_type']
|
||||
temp_id = info['temp_id']
|
||||
name = info['name']
|
||||
if temp_id is not None:
|
||||
name = '{}{}{}'.format(
|
||||
name, SENSOR_TYPES[sensor_type][3], temp_id)
|
||||
else:
|
||||
name = '{}{}'.format(name, SENSOR_TYPES[sensor_type][3])
|
||||
sensor_class = sensor_map[sensor_type]
|
||||
entity = sensor_class(api, temp_id, name, printer_id, sensor_type)
|
||||
entities.append(entity)
|
||||
|
||||
add_entities(entities, True)
|
||||
|
||||
|
||||
class RepetierSensor(Entity):
|
||||
"""Class to create and populate a Repetier Sensor."""
|
||||
|
||||
def __init__(self, api, temp_id, name, printer_id, sensor_type):
|
||||
"""Init new sensor."""
|
||||
self._api = api
|
||||
self._attributes = {}
|
||||
self._available = False
|
||||
self._temp_id = temp_id
|
||||
self._name = name
|
||||
self._printer_id = printer_id
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return sensor attributes."""
|
||||
return self._attributes
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
return SENSOR_TYPES[self._sensor_type][1]
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Icon to use in the frontend."""
|
||||
return SENSOR_TYPES[self._sensor_type][2]
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return False as entity is updated from the component."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return sensor state."""
|
||||
return self._state
|
||||
|
||||
@callback
|
||||
def update_callback(self):
|
||||
"""Get new data and update state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Connect update callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, UPDATE_SIGNAL, self.update_callback)
|
||||
|
||||
def _get_data(self):
|
||||
"""Return new data from the api cache."""
|
||||
data = self._api.get_data(
|
||||
self._printer_id, self._sensor_type, self._temp_id)
|
||||
if data is None:
|
||||
_LOGGER.debug(
|
||||
"Data not found for %s and %s",
|
||||
self._sensor_type, self._temp_id)
|
||||
self._available = False
|
||||
return None
|
||||
self._available = True
|
||||
return data
|
||||
|
||||
def update(self):
|
||||
"""Update the sensor."""
|
||||
data = self._get_data()
|
||||
if data is None:
|
||||
return
|
||||
state = data.pop('state')
|
||||
_LOGGER.debug("Printer %s State %s", self._name, state)
|
||||
self._attributes.update(data)
|
||||
self._state = state
|
||||
|
||||
|
||||
class RepetierTempSensor(RepetierSensor):
|
||||
"""Represent a Repetier temp sensor."""
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return sensor state."""
|
||||
if self._state is None:
|
||||
return None
|
||||
return round(self._state, 2)
|
||||
|
||||
def update(self):
|
||||
"""Update the sensor."""
|
||||
data = self._get_data()
|
||||
if data is None:
|
||||
return
|
||||
state = data.pop('state')
|
||||
temp_set = data['temp_set']
|
||||
_LOGGER.debug(
|
||||
"Printer %s Setpoint: %s, Temp: %s",
|
||||
self._name, temp_set, state)
|
||||
self._attributes.update(data)
|
||||
self._state = state
|
||||
|
||||
|
||||
class RepetierJobSensor(RepetierSensor):
|
||||
"""Represent a Repetier job sensor."""
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return sensor state."""
|
||||
if self._state is None:
|
||||
return None
|
||||
return round(self._state, 2)
|
||||
|
||||
|
||||
class RepetierJobEndSensor(RepetierSensor):
|
||||
"""Class to create and populate a Repetier Job End timestamp Sensor."""
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class."""
|
||||
return DEVICE_CLASS_TIMESTAMP
|
||||
|
||||
def update(self):
|
||||
"""Update the sensor."""
|
||||
data = self._get_data()
|
||||
if data is None:
|
||||
return
|
||||
job_name = data['job_name']
|
||||
start = data['start']
|
||||
print_time = data['print_time']
|
||||
from_start = data['from_start']
|
||||
time_end = start + round(print_time, 0)
|
||||
self._state = datetime.utcfromtimestamp(time_end).isoformat()
|
||||
remaining = print_time - from_start
|
||||
remaining_secs = int(round(remaining, 0))
|
||||
_LOGGER.debug(
|
||||
"Job %s remaining %s",
|
||||
job_name, time.strftime('%H:%M:%S', time.gmtime(remaining_secs)))
|
||||
|
||||
|
||||
class RepetierJobStartSensor(RepetierSensor):
|
||||
"""Class to create and populate a Repetier Job Start timestamp Sensor."""
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class."""
|
||||
return DEVICE_CLASS_TIMESTAMP
|
||||
|
||||
def update(self):
|
||||
"""Update the sensor."""
|
||||
data = self._get_data()
|
||||
if data is None:
|
||||
return
|
||||
job_name = data['job_name']
|
||||
start = data['start']
|
||||
from_start = data['from_start']
|
||||
self._state = datetime.utcfromtimestamp(start).isoformat()
|
||||
elapsed_secs = int(round(from_start, 0))
|
||||
_LOGGER.debug(
|
||||
"Job %s elapsed %s",
|
||||
job_name, time.strftime('%H:%M:%S', time.gmtime(elapsed_secs)))
|
@ -1290,6 +1290,9 @@ pyrainbird==0.1.6
|
||||
# homeassistant.components.recswitch
|
||||
pyrecswitch==1.0.2
|
||||
|
||||
# homeassistant.components.repetier
|
||||
pyrepetier==3.0.5
|
||||
|
||||
# homeassistant.components.ruter
|
||||
pyruter==1.1.0
|
||||
|
||||
|
Reference in New Issue
Block a user