1 """Support for monitoring OctoPrint 3D printers."""
3 from __future__
import annotations
6 from typing
import cast
9 from pyoctoprintapi
import OctoprintClient
10 import voluptuous
as vol
18 CONF_MONITORED_CONDITIONS,
26 EVENT_HOMEASSISTANT_STOP,
37 from .const
import CONF_BAUDRATE, DOMAIN, SERVICE_CONNECT
38 from .coordinator
import OctoprintDataUpdateCoordinator
40 _LOGGER = logging.getLogger(__name__)
44 """Validate that printers have an unique name."""
45 names = [util_slugify(printer[
"name"])
for printer
in value]
46 vol.Schema(vol.Unique())(names)
51 """Validate the path, ensuring it starts and ends with a /."""
52 vol.Schema(cv.string)(value)
60 PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CAMERA, Platform.SENSOR]
61 DEFAULT_NAME =
"OctoPrint"
62 CONF_NUMBER_OF_TOOLS =
"number_of_tools"
65 BINARY_SENSOR_TYPES = [
70 BINARY_SENSOR_SCHEMA = vol.Schema(
73 CONF_MONITORED_CONDITIONS, default=
list(BINARY_SENSOR_TYPES)
74 ): vol.All(cv.ensure_list, [vol.In(BINARY_SENSOR_TYPES)]),
75 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
87 SENSOR_SCHEMA = vol.Schema(
89 vol.Optional(CONF_MONITORED_CONDITIONS, default=
list(SENSOR_TYPES)): vol.All(
90 cv.ensure_list, [vol.In(SENSOR_TYPES)]
92 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
96 CONFIG_SCHEMA = vol.Schema(
98 cv.deprecated(DOMAIN),
105 vol.Required(CONF_API_KEY): cv.string,
106 vol.Required(CONF_HOST): cv.string,
107 vol.Optional(CONF_SSL, default=
False): cv.boolean,
108 vol.Optional(CONF_PORT, default=80): cv.port,
109 vol.Optional(CONF_PATH, default=
"/"): ensure_valid_path,
112 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
114 CONF_NUMBER_OF_TOOLS, default=0
116 vol.Optional(CONF_BED, default=
False): cv.boolean,
117 vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
119 CONF_BINARY_SENSORS, default={}
120 ): BINARY_SENSOR_SCHEMA,
124 has_all_unique_names,
128 extra=vol.ALLOW_EXTRA,
131 SERVICE_CONNECT_SCHEMA = vol.Schema(
133 vol.Required(CONF_DEVICE_ID): cv.string,
134 vol.Optional(CONF_PROFILE_NAME): cv.string,
135 vol.Optional(CONF_PORT): cv.string,
136 vol.Optional(CONF_BAUDRATE): cv.positive_int,
141 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
142 """Set up the OctoPrint component."""
143 if DOMAIN
not in config:
146 domain_config = config[DOMAIN]
148 for conf
in domain_config:
149 hass.async_create_task(
150 hass.config_entries.flow.async_init(
152 context={
"source": SOURCE_IMPORT},
154 CONF_API_KEY: conf[CONF_API_KEY],
155 CONF_HOST: conf[CONF_HOST],
156 CONF_PATH: conf[CONF_PATH],
157 CONF_PORT: conf[CONF_PORT],
158 CONF_SSL: conf[CONF_SSL],
167 """Set up OctoPrint from a config entry."""
169 if DOMAIN
not in hass.data:
170 hass.data[DOMAIN] = {}
172 if CONF_VERIFY_SSL
not in entry.data:
173 data = {**entry.data, CONF_VERIFY_SSL:
True}
174 hass.config_entries.async_update_entry(entry, data=data)
176 connector = aiohttp.TCPConnector(
179 if not entry.data[CONF_VERIFY_SSL]
182 session = aiohttp.ClientSession(connector=connector)
185 def _async_close_websession(event: Event) ->
None:
186 """Close websession."""
189 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_close_websession)
191 client = OctoprintClient(
192 host=entry.data[CONF_HOST],
194 port=entry.data[CONF_PORT],
195 ssl=entry.data[CONF_SSL],
196 path=entry.data[CONF_PATH],
199 client.set_api_key(entry.data[CONF_API_KEY])
203 await coordinator.async_config_entry_first_refresh()
205 hass.data[DOMAIN][entry.entry_id] = {
206 "coordinator": coordinator,
210 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
212 async
def async_printer_connect(call: ServiceCall) ->
None:
213 """Connect to a printer."""
215 await client.connect(
216 printer_profile=call.data.get(CONF_PROFILE_NAME),
217 port=call.data.get(CONF_PORT),
218 baud_rate=call.data.get(CONF_BAUDRATE),
221 if not hass.services.has_service(DOMAIN, SERVICE_CONNECT):
222 hass.services.async_register(
225 async_printer_connect,
226 schema=SERVICE_CONNECT_SCHEMA,
233 """Unload a config entry."""
234 unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
237 hass.data[DOMAIN].pop(entry.entry_id)
243 hass: HomeAssistant, call: ServiceCall
244 ) -> OctoprintClient:
245 """Get the client related to a service call (by device ID)."""
246 device_id = call.data[CONF_DEVICE_ID]
247 device_registry = dr.async_get(hass)
249 if device_entry := device_registry.async_get(device_id):
250 for entry_id
in device_entry.config_entries:
251 if data := hass.data[DOMAIN].
get(entry_id):
252 return cast(OctoprintClient, data[
"client"])
255 translation_domain=DOMAIN,
256 translation_key=
"missing_client",
257 translation_placeholders={
258 "device_id": device_id,
web.Response get(self, web.Request request, str config_key)
def ensure_valid_path(value)
bool async_setup(HomeAssistant hass, ConfigType config)
def has_all_unique_names(value)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
OctoprintClient async_get_client_for_service_call(HomeAssistant hass, ServiceCall call)
ssl.SSLContext get_default_context()
ssl.SSLContext get_default_no_verify_context()