21 from abc
import ABC, abstractmethod
22 from enum
import Enum, unique
23 from functools
import total_ordering
24 from itertools
import count
26 from mycroft
import MycroftSkill
39 Indefinitely return the next number in sequence from 0. 41 This can be replaced with enum.auto when we no longer 42 need to support python3.4. 49 This class contains some strings used to identify 50 messages on the messagebus. They are used in in 51 CommonIoTSkill and the IoTController skill, but 52 are not intended to be used elsewhere. 55 TRIGGER = BASE +
":trigger" 56 RESPONSE = TRIGGER +
".response" 58 REGISTER = BASE +
"register" 59 CALL_FOR_REGISTRATION = REGISTER +
".request" 65 This class represents 'Things' which may be controlled 66 by IoT Skills. This is intended to be used with the 67 IoTRequest class. See that class for more details. 83 This class represents 'Attributes' of 'Things'. 85 This may also grow to encompass states, e.g. 86 'locked' or 'unlocked'. 96 This class represents 'Actions' that can be applied to 97 'Things,' e.d. a LIGHT can be turned ON. It is intended 98 to be used with the IoTRequest class. See that class 114 Enum indicating support IoTRequest fields 116 This class allows us to extend the request without 117 requiring that all existing skills are updated to 118 handle the new fields. Skills will simply not respond 119 to requests that contain fields they are not aware of. 121 CommonIoTSkill subclasses should override 122 CommonIoTSkill.supported_request_version to indicate 123 their level of support. For backward compatibility, 126 Note that this is an attempt to avoid false positive 127 matches (i.e. prevent skills from reporting that they 128 can handle a request that contains fields they don't 129 know anything about). To avoid any possibility of 130 false negatives, however, skills should always try to 131 support the latest version. 133 Version to supported fields (provided only for reference - always use the 134 latest version available, and account for all fields): 136 V1 = {'action', 'thing', 'attribute', 'entity', 'scene'} 140 return self.name < other.name
142 V1 = {
'action',
'thing',
'attribute',
'entity',
'scene'}
148 This class represents a request from a user to control 149 an IoT device. It contains all of the information an IoT 150 skill should need in order to determine if it can handle 151 a user's request. The information is supplied as properties 152 on the request. At present, those properties are: 154 action (see the Action enum above) 155 thing (see the Thing enum above) 160 The 'action' is mandatory, and will always be not None. The 161 other fields may be None. 163 The 'entity' is intended to be used for user-defined values 164 specific to a skill. For example, in a skill controlling Lights, 165 an 'entity' might represent a group of lights. For a smart-lock 166 skill, it might represent a specific lock, e.g. 'front door.' 168 The 'scene' value is also intended to to be used for user-defined 169 values. Skills that extend CommonIotSkill are expected to register 170 their own scenes. The controller skill will have the ability to 171 trigger multiple skills, so common scene names may trigger many 172 skills, for a coherent experience. 174 The 'value' property will be a number value. This is intended to 175 be used for requests such as "set the heat to 70 degrees" and 176 "set the lights to 50% brightness." 178 Skills that extend CommonIotSkill will be expected to register 179 their own entities. See the documentation in CommonIotSkill for 186 attribute: Attribute =
None,
191 if not thing
and not entity
and not scene:
192 raise Exception(
"At least one of thing," 193 " entity, or scene must be present!")
203 template = (
'IoTRequest(' 206 ' attribute={attribute},' 211 return template.format(
215 entity=
'"{}"'.format(self.
entity)
if self.
entity else None,
216 scene=
'"{}"'.format(self.
scene)
if self.
scene else None,
217 value=
'"{}"'.format(self.
value)
if self.
value is not None else None 222 if self.
value is not None:
223 return IoTRequestVersion.V2
224 return IoTRequestVersion.V1
228 'action': self.action.name,
229 'thing': self.thing.name
if self.
thing else None,
230 'attribute': self.attribute.name
if self.
attribute else None,
239 data[
'action'] = Action[data[
'action']]
240 if data.get(
'thing')
not in (
None,
''):
241 data[
'thing'] = Thing[data[
'thing']]
242 if data.get(
'attribute')
not in (
None,
''):
243 data[
'attribute'] = Attribute[data[
'attribute']]
250 Skills that want to work with the CommonIoT system should 251 extend this class. Subclasses will be expected to implement 252 two methods, `can_handle` and `run_request`. See the 253 documentation for those functions for more details on how 254 they are expected to behave. 256 Subclasses may also register their own entities and scenes. 257 See the register_entities and register_scenes methods for 260 This class works in conjunction with a controller skill. 261 The controller registers vocabulary and intents to capture 262 IoT related requests. It then emits messages on the messagebus 263 that will be picked up by all skills that extend this class. 264 Each skill will have the opportunity to declare whether or not 265 it can handle the given request. Skills that acknowledge that 266 they are capable of handling the request will be considered 267 candidates, and after a short timeout, a winner, or winners, 268 will be chosen. With this setup, a user can have several IoT 269 systems, and control them all without worry that skills will 275 Overrides MycroftSkill.bind. 277 This is called automatically during setup, and 278 need not otherwise be used. 286 self.add_event(_BusKeys.RUN + self.skill_id, self.
_run_request)
287 self.add_event(_BusKeys.CALL_FOR_REGISTRATION,
292 Given a message, determines if this skill can 293 handle the request. If it can, it will emit 294 a message on the bus indicating that. 300 request = IoTRequest.from_dict(data[IoTRequest.__name__])
305 can_handle, callback_data = self.
can_handle(request)
307 data.update({
"skill_id": self.skill_id,
308 "callback_data": callback_data})
309 self.bus.emit(message.response(data))
313 Given a message, extracts the IoTRequest and 314 callback_data and sends them to the run_request 320 request = IoTRequest.from_dict(message.data[IoTRequest.__name__])
321 callback_data = message.data[
"callback_data"]
326 Register this skill's scenes and entities when requested. 329 _: Message. This is ignored. 335 Emit a message to the controller skill to register vocab. 337 Emits a message on the bus containing the type and 338 the words. The message will be picked up by the 339 controller skill, and the vocabulary will be registered 347 self.bus.emit(
Message(_BusKeys.REGISTER,
348 data={
"skill_id": self.skill_id,
350 "words": list(words)}))
354 This method will register this skill's scenes and entities. 356 This should be called in the skill's `initialize` method, 357 at some point after `get_entities` and `get_scenes` can 358 be expected to return correct results. 367 Get the supported IoTRequestVersion 369 By default, this returns IoTRequestVersion.V1. Subclasses 370 should override this to indicate higher levels of support. 372 The documentation for IoTRequestVersion provides a reference 373 indicating which fields are included in each version. Note 374 that you should always take the latest, and account for all 377 return IoTRequestVersion.V1
381 Get a list of custom entities. 383 This is intended to be overridden by subclasses, though it 384 it not required (the default implementation will return an 387 The strings returned by this function will be registered 388 as ENTITY values with the intent parser. Skills should provide 389 group names, user aliases for specific devices, or anything 390 else that might represent a THING or a set of THINGs, e.g. 391 'bedroom', 'lamp', 'front door.' This allows commands that 392 don't explicitly include a THING to still be handled, e.g. 393 "bedroom off" as opposed to "bedroom lights off." 399 Get a list of custom scenes. 401 This method is intended to be overridden by subclasses, though 402 it is not required. The strings returned by this function will 403 be registered as SCENE values with the intent parser. Skills 404 should provide user defined scene names that they are aware of 405 and capable of handling, e.g. "relax," "movie time," etc. 412 Determine if an IoTRequest can be handled by this skill. 414 This method must be implemented by all subclasses. 416 An IoTRequest contains several properties (see the 417 documentation for that class). This method should return 418 True if and only if this skill can take the appropriate 419 'action' when considering _all other properties 420 of the request_. In other words, a partial match, one in which 421 any piece of the IoTRequest is not known to this skill, 422 and is not None, this should return (False, None). 427 Returns: (boolean, dict) 428 True if and only if this skill knows about all the 429 properties set on the IoTRequest, and a dict containing 430 callback_data. If this skill is chosen to handle the 431 request, this dict will be supplied to `run_request`. 433 Note that the dictionary will be sent over the bus, and thus 434 must be JSON serializable. 441 Handle an IoT Request. 443 All subclasses must implement this method. 445 When this skill is chosen as a winner, this function will be called. 446 It will be passed an IoTRequest equivalent to the one that was 447 supplied to `can_handle`, as well as the `callback_data` returned by
def supported_request_version(self)
def _handle_call_for_registration
def register_entities_and_scenes(self)