def listener(q, port, protocol): # Signal that we have spawned then loop until terminated, pulling messages # of the UDP port and putting them back onto the queue. q.put("Started") log_debug("Listen on port %d, protocol %s", port, protocol) if protocol == "udp": sock_type = socket.SOCK_DGRAM else: sock_type = socket.SOCK_STREAM sock = socket.socket(socket.AF_INET, sock_type) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', int(port))) if protocol == "tcp": log_debug("Set socket to listen") sock.listen(1) (sock, address) = sock.accept() log_debug( "Accepted incoming connection from address %s on protocol %s", pp.pformat(address), protocol) while (True): log_debug("Waiting for data on protocol %s", protocol) (message, address) = sock.recvfrom(1024) log_debug("Received data %s from address %s on protocol %s", message, pp.pformat(address), protocol) q.put(message)
def __init__(self, manufacturer, device, use_S3): self.manufacturer = manufacturer self.device = device self.device_details = {} self.use_S3 = use_S3 log_debug("Creating Device object for manufacturer %s/device %s", manufacturer, device) log_debug("Using S3 rather than static files? %s", self.use_S3)
def handle_non_discovery(request, command_sequences, device_power_map, device_state): # We have received a directive for some capability interface, which we have # to now act on. # The command_sequences structure is a dict telling us what to do. It # is a nested dict with the following structure: # # { endpoint: # { capability: # { directive: # [ list of specific commands ] # } # } # } # # The device_power_map dict tells us which devices are needed for # which endpoints, plus for each device whether or not it has separate # on/off commands or (evil) a single power toggle. We use the combo of # current state, device involvement and toggle vs. on/off to decide # (a) whether to skip any commmands and (b) any additional commands # to send for devices that should be switched off. # Extract the key fields from the request and check it's one we recognise capability, directive, payload, endpoint_id = unpack_request(request) verify_request(command_sequences, endpoint_id, capability, directive) log_info("Received directive %s on capability %s for endpoint %s", directive, capability, endpoint_id) # If this is a PowerController capability we need to figure out # what to turn on/off if capability == "PowerController": log_debug("Turn things on/off") new_device_status, status_changed = set_power_states( directive, endpoint_id, device_state, device_power_map, PAUSE_BETWEEN_COMMANDS, payload) else: new_device_status = {} status_changed = False # Get the list of commands we need to respond to this directive commands_list = command_sequences[endpoint_id][capability][directive] for command_tuple in commands_list: for verb in command_tuple: run_command(verb, command_tuple[verb], PAUSE_BETWEEN_COMMANDS, payload) time.sleep(PAUSE_BETWEEN_COMMANDS) response = construct_response(request) log_info("Did device power on/off status change? %s", status_changed) return response, new_device_status, status_changed
def __init__(self, user_id): try: self.use_S3 = not (os.environ['USE_STATIC_FILES'] == "Y") except KeyError: self.use_S3 = True self.user_id = user_id self.user_details = {} self.model = {} self.device_status = {} self.devicesDB = {} log_debug("Create a User object for user %s", user_id) log_debug("Using S3 for storage? %s", self.use_S3)
def get_model(self): if not self.model: if self.use_S3: log_debug("Retrieve model from S3") self.model = read_S3state(BUCKET_USERDB, self.user_id + KEY_USER_MODEL) else: global G_MODEL self.model = copy.deepcopy(G_MODEL) log_debug("Retrieved model from memory: %s", pp.pformat(self.model)) return self.model
def extract_token_from_request(request): # Find the OAuth2 token from either a discovery or directive request. locations = [ 'endpoint', 'payload' ] token = None for l in locations: if l in request['directive']: if 'scope' in request['directive'][l]: token = request['directive'][l]['scope']['token'] log_debug("Token passed in request = %s", token) return token
def get_device_status(self): if not self.device_status: if self.use_S3: log_debug("Retrieve device status from S3") self.device_status = read_S3state( BUCKET_USERDB, self.user_id + KEY_USER_DEVICE_STATUS) else: global G_DEVICE_STATUS self.device_status = copy.deepcopy(G_DEVICE_STATUS) log_debug("Retrieved device status from memory: %s", pp.pformat(self.device_status)) return self.device_status
def set_device_status(self, device_status): log_info("Set device status for user %s to be %s", self.user_id, pp.pformat(device_status)) self.device_status = device_status if self.use_S3: log_debug("Secure device status to S3") write_S3state(BUCKET_USERDB, self.user_id + KEY_USER_DEVICE_STATUS, device_status) else: global G_DEVICE_STATUS G_DEVICE_STATUS = copy.deepcopy(device_status) log_debug("Secured device status to memory: %s", pp.pformat(G_DEVICE_STATUS))
def SendToKIRA(target, mesg, repeat, repeatDelay): log_info("Send to %s with repeat %d, delay %.3f; message %s", target, repeat, repeatDelay, mesg) host, port = target.split(":") sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('', int(port))) for i in range(repeat + 1): log_debug("Sending %s", mesg.encode('utf-8')) sock.sendto(mesg.encode('utf-8'), (host, int(port))) if i < repeat: time.sleep(repeatDelay) sock.close()
def read_S3state(bucket, key): state = {} blob, version = read_object(BUCKET_ROOT + bucket, KEY_ROOT + key) if version != S3_SCHEMA_VERSION: log_error("Schema mismatch: read %s, code at %s", version, S3_SCHEMA_VERSION) else: state = pickle.loads(blob) log_debug("Read %s/%s state from S3: %s", bucket, key, pp.pformat(state)) return state
def find_target(device, targets): # Check which target is associated with this device if 'target' in device: target = device['target'] log_debug("This device is associated with target %s", target) else: target = 'primary' log_debug("No target specified - assume primary") if target in targets: t = targets[target] else: t = None return t
def listener(q, port): # Signal that we have spawned then loop until terminated, pulling messages # of the UDP port and putting them back onto the queue. q.put("Started") log_debug("Listen on port %d", port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', int(port))) while (True): (message, address) = sock.recvfrom(1024) q.put(message)
def StepIRCommands(device, capability, command_list): cap_to_check = which_capability(capability) device_name = device['friendly_name'] device_logname = device['log_name'] device_details = device['details'] target = device['target'] output_cmd = {} if cap_to_check in device_details['supports']: log_debug("Device %s supports the %s capability", device_name, capability) output_cmd['StepIRCommands'] = {} found = False log_debug("StepIRCommands should check for key %s", command_list['key']) output_cmd['StepIRCommands']['key'] = command_list['key'] for i in ['+ve', '-ve']: found = False for command in command_list[i]: if not found: log_debug("Look for command %s", command) if command in device_details['IRcodes']: log_debug("Device %s supports command %s", device_name, command) # Only want to find one command e.g. PowerOn or PowerToggle # We include the device name as we will need that when # later figuring out what to turn on/off output_cmd['StepIRCommands'][ i] = construct_specific_IR_command( device_details, command, target, device_name, device_logname) found = True else: log_debug("...doesn't support this capability") return output_cmd
def extract_user(request): user = "******" if 'TEST_USER' in os.environ: # Currently we are testing with a hard-coded user name user = os.environ['TEST_USER'] log_debug("User name passed as env var = %s", user) else: # User name must be retrieved from a token, either passed in as an env # var or extracted from the real request. if 'TEST_TOKEN' in os.environ: OAuth2_token = os.environ['TEST_TOKEN'] log_debug("Token passed as env var = %s", OAuth2_token) else: # Real request. Extract token. OAuth2_token = extract_token_from_request(request) user = get_user_from_token(OAuth2_token) return user
def read_object(bucket_name, key_name): blob = b'' version = "" s3 = boto3.client('s3') # Check if bucket exists try: response = s3.get_object(Bucket=bucket_name, Key=key_name) blob = response['Body'].read() version = response['Metadata']['schema_version'] log_debug( "Returned %d bytes of schema version %s reading object %s from bucket %s", len(blob), version, key_name, bucket_name) except botocore.exceptions.ClientError as e: log_error("Error %s reading object %s from bucket %s", pp.pformat(e), key_name, bucket_name) return blob, version
def create_model(self): # Create a model for this user, plus initialise the device status to # 'all devices off' log_debug("Create model for user %s", self.user_id) self.get_details() user_devices = self.user_details['devices'] device_state = {} for user_device in user_devices: manufacturer = user_device['manufacturer'] model = user_device['model'] d = Device(manufacturer, model, self.use_S3) if manufacturer not in self.devicesDB: self.devicesDB[manufacturer] = {} self.devicesDB[manufacturer][model] = d.get() device_state[user_device['friendly_name']] = False self.model = model_user_and_devices(self.user_details, self.devicesDB) if self.use_S3: log_debug("Secure model to S3") write_S3state(BUCKET_USERDB, self.user_id + KEY_USER_MODEL, self.model) else: global G_MODEL G_MODEL = copy.deepcopy(self.model) log_debug("Secured model to memory: %s", pp.pformat(G_MODEL)) self.set_device_status(device_state)
def write_object(bucket_name, key_name, blob, version): s3 = boto3.client('s3') # Check if bucket exists try: s3.head_bucket(Bucket=bucket_name) log_debug("Bucket %s exists", bucket_name) except botocore.exceptions.ClientError as e: error_code = int(e.response['Error']['Code']) if error_code == 403: log_error("Denied access to bucket %s", bucket_name) elif error_code == 404: log_debug("Bucket %s does not exist - creating", bucket_name) bucket = s3.create_bucket( ACL='public-read-write', Bucket=bucket_name, CreateBucketConfiguration={'LocationConstraint': REGION}) else: log_error("Error %d checking bucket %s", error_code, bucket_name) try: metadata = {"schema_version": version} s3.put_object(Bucket=bucket_name, Key=key_name, Body=blob, ACL='public-read-write', Metadata=metadata) log_debug("Written object %s to bucket %s", key_name, bucket_name) except botocore.exceptions.ClientError as e: error_code = int(e.response['Error']['Code']) log_error("Error %d writing object %s to bucket %s", error_code, key_name, bucket_name)
def InputChoice(device, capability, command_list): device_name = device['friendly_name'] device_logname = device['log_name'] device_details = device['details'] target = device['target'] output_cmd = {} # Add commands to set all the devices in the chain to the correct input # values. if 'required_input' in device: log_debug("Need to set %s to input %s", device_name, device['required_input']) log_debug("device_details: %s", pp.pformat(device_details['IRcodes'])) output_cmd['SingleIRCommand'] = {} output_cmd['SingleIRCommand'][ 'single'] = construct_specific_IR_command(device_details, device['required_input'], target, device_name, device_logname) return output_cmd
def verify_request(primitives, endpoint, capability, directive): # Check we know about this endpoint if endpoint in primitives: log_debug("Recognise endpoint %s", endpoint) if capability in primitives[endpoint]: log_debug("Recognise capability %s", capability) if directive in primitives[endpoint][capability]: log_debug("Recognise directive %s", directive) else: log_error("Don't recognise directive %s", directive) else: log_error("Don't recognise capability %s", capability) else: log_error("Don't recognise endpoint %s", endpoint)
def get_user_from_token(token): log_debug("Token is %s", token) url = LWA_PROFILE_URL + urllib.parse.urlencode({'access_token': token}) r = requests.get(url=url) if r.status_code == 200: log_debug("Amazon profile returned is:") log_debug(json.dumps(r.json(), indent=4)) body = r.json() user = body['user_id'] else: log_error("Amazon look up returned an error %d", r.status_code) log_error(json.dumps(r.json(), indent=4)) user = "******" return user
def verify_devices(devices, database): # Check we recognise the list of devices the user has. As we'er running # as a lambda, don't worry too much about raising and catching exceptions; # the important thing is to log it. log_debug("Validate user devices exist in DB") bad_device = False for device in devices: log_debug("Check user device %s", device['friendly_name']) manu = device['manufacturer'] model = device['model'] if manu in database: if model in database[manu]: log_debug("Manu %s, model %s found OK", manu, model) else: log_error("Device %s with manu %s has incorrect model %s", device['friendly_name'], manu, model) bad_device = True else: log_error("Device %s has incorrect manu %s", device['friendly_name'], manu) bad_device = True
def lambda_handler(request, context): # Main lambda handler. We simply switch on the directive type. log_info("Received request") log_info(json.dumps(request, indent=4)) user_id = extract_user(request) log_info("Request is for user %s", user_id) u = User(user_id) if is_discovery(request): # On discovery requests we always re-model the user. This is the # natural point to model, as it is the only mechanism to report to # Alexa any changes in a user's devices. # Note that creating the model also resets the device status to # 'all off'. log_debug("Discovery: model the user") u.create_model() model = u.get_model() response = handle_discovery(model['discovery_response']) else: log_debug("Normal directive: retrieve the model") model = u.get_model() log_debug("Model is %s", pp.pformat(model)) device_status = u.get_device_status() response, new_device_status, status_changed = handle_non_discovery( request, model['command_sequences'], model['device_power_map'], device_status) if status_changed: log_info("Device status changed - updating") u.set_device_status(new_device_status) log_info("Response:") log_info(json.dumps(response, indent=4, sort_keys=True)) #validate_message(response) return response
def construct_power_map(user_details, global_database): # We need to understand (a) which devices are active in which endpoints and # (b) whether they have sensible PowerOn/Off commands or just support the # useless PowerToggle (why?) which can result in us getting out of sync. # We will later combine this with the current device status to ensure that # we (a) turn off now-unused devices when switching between endpoints and # (b) get the power polarity correct. # device_power_map is a dict with form # { 'device': { # 'room': <room>, # which room this device is # 'toggle': 'True/False', # Is PowerToggle used? # 'endpoints': { # '<endpoint>' : 'True/False', # Is device used in this endpoint? # }, # 'commands': {} # Set of commands corresponding to # power on/off/toggle # } # } # # We fill in the power toggle status here; endpoints are added as we find # the capabilities. log_debug("Construct device power map") user_targets = user_details['targets'] user_devices = user_details['devices'] device_power_map = {} for this_device in user_devices: friendly_name = this_device['friendly_name'] log_debug("Examine device %s", friendly_name) device_power_map[friendly_name] = {} this_device_map = device_power_map[friendly_name] this_device_map['room'] = this_device['room'] this_device_map['endpoints'] = {} device_details = find_user_device_in_DB(this_device, global_database) # Does it use PowerToggle? if 'PowerToggle' in device_details['IRcodes']: log_debug("Uses PowerToggle") this_device_map['toggle'] = True else: log_debug("Does not use PowerToggle") this_device_map['toggle'] = False # Store the set of commands corresponding to the power directives. this_device_map['commands'] = {} chain = [{ "friendly_name": this_device['friendly_name'], "log_name": this_device['manufacturer'] + " " + this_device['model'], "details": device_details, "target": find_target(this_device, user_targets) }] power_directives = CAPABILITY_DIRECTIVES_TO_COMMANDS[ 'DevicePowerController'] for directive in power_directives: log_debug("Find specific commands corresponding to %s", directive) specific_commands = construct_command_sequence( chain, "DevicePowerController", power_directives[directive]) this_device_map['commands'][directive] = specific_commands return device_power_map
def SingleIRCommand(device, capability, command_list): log_debug("Have list of single IR commands to check: %s", pp.pformat(command_list)) cap_to_check = which_capability(capability) log_debug("Checking for capability %s", capability) device_name = device['friendly_name'] device_logname = device['log_name'] device_details = device['details'] target = device['target'] output_cmd = {} log_debug("Checking device details: %s", pp.pformat(device_details)) if cap_to_check in device_details['supports']: log_debug("Device %s supports the %s capability", device_name, capability) output_cmd['SingleIRCommand'] = {} found = False for command in command_list: if not found: log_debug("Look for command %s", command) if command in device_details['IRcodes']: log_debug("Device %s supports command %s", device_name, command) # Only want to find one command e.g. PowerOn or PowerToggle # We include the device name as we will need that when # later figuring out what to turn on/off output_cmd['SingleIRCommand'][ 'single'] = construct_specific_IR_command( device_details, command, target, device_name, device_logname) found = True else: log_debug("...doesn't support this capability") return output_cmd
def get_connected_device(user_devices, global_database, device): next_device_name = device['connected_to']['next_device'] log_debug("Next connected device is %s", next_device_name) device = find_device_from_friendly_name(user_devices, next_device_name) device_details = find_user_device_in_DB(device, global_database) return next_device_name, device, device_details
def write_S3state(bucket, key, state): blob = pickle.dumps(state) write_object(BUCKET_ROOT + bucket, KEY_ROOT + key, blob, S3_SCHEMA_VERSION) log_debug("Wrote %s/%s state to S3: %s", bucket, key, pp.pformat(state))
def construct_endpoint_chain(user_details, root_device, global_database): # Identify the chain of devices included in an endpoint chain plus the set of # capabilities it supports. # This is the union of all capabilities supported by the devices we are # aggregating to form this endpoint. user_targets = user_details['targets'] user_devices = user_details['devices'] log_debug("Find the set of capabilities for the activity rooted in %s", root_device['friendly_name']) endpoint = new_endpoint(root_device['friendly_name'].replace(" ", ""), root_device['manufacturer'], root_device['description']) capabilities = {} chain = [] device = root_device device_details = find_user_device_in_DB(device, global_database) is_audio = ('A_source' in device_details['roles']) log_debug("Is the activity audio only? %d", is_audio) reached_end = False required_input = None while not reached_end: for capability in device_details['supports']: log_debug("Activity supports %s capability via device %s", capability, device['friendly_name']) capabilities[capability] = 'supported' this_link = { "friendly_name": device['friendly_name'], "log_name": device['manufacturer'] + " " + device['model'], "details": device_details, "target": find_target(device, user_targets) } if required_input != None: this_link['required_input'] = required_input if 'connected_to' in device: old_device = device device_friendly_name, device, device_details = get_connected_device( user_devices, global_database, device) if is_audio and ('display' in device_details['roles']): log_debug( "Connected to a display, but audio only source - end of chain" ) reached_end = True else: required_input = old_device['connected_to']['input'] else: reached_end = True log_debug("Reached end of activity chain") chain.append(this_link) log_debug("List of capabilities for this activity:\n%s", pp.pformat(capabilities)) log_debug("Device chain involved in this activity:\n%s", pp.pformat(chain)) return endpoint, capabilities, chain
def model_user_and_devices(user_details, device_database): # Here we model the user's devices and activities. # # It is important to understand: # - the inputs to this model # - the Alexa data model # - how we map user devices -> Alexa objects # - why we have to treat power differently # - the set of models we return from here. # # Inputs to this model # -------------------- # # There are two inputs to this modelling exercise. # # 1. A global database of devices. This is a dict, structured by # manufacturer then device, which for each device contains # - what real-world roles it can play (e.g. audio source, or audio + video) # - which Alexa capabilities it can support (see below) # - a map of IR command names -> IR codes. # This database is stored in S3. # # 2. A user's details. This has two key pieces of info. # - The set of KIRA targets to send commands to (IP addresses + ports); we # support multiple devices per-user). # - A list of the user's devices, including what the user wants to call # them, how they are linked together (e.g. what TV input a Blu-ray player # is connected to) and how they are grouped into rooms. # User details are stored in S3. # # Alexa data model # ---------------- # # The key Alexa concepts are endpoints, capabilities and directives. # # Endpoints are things like TV or Blu-ray. # # Capabilities (aka interfaces) are groups of related features such as a # ChannelController or PowerController. We model each physical # device as supporting a set of capabilities; the capabilities we # return for an endpoint is the union of all capabilities supported # by the devices being aggregated into that endpoint. # # Directives are the individual commands within each capability e.g. # Play or AdjustVolume. # # Mapping user devices -> Alexa data model # ---------------------------------------- # # Alexa "expects" endpoints to be 1-1 with physical devices, but instead # we aggregate multiple physical devices into a single endpoint so that # they can all be controlled simultaneously. So our model is that any # devices which the user has which are audio or audio+video sources are # mapped to an endpoint, and we then follow the chain of connectivity # from those sources to create a list of each device included in that # endpoint. # # We then model the capabilities supported by the endpoint as being the # union of the capabilities supported by each device included in the # endpoint. # # For each directive of each capability, we then create a list of the # specific commands we must send to implement that directive on each of # the devices supporting that capability (typically, there will only be # one device in the chain doing so e.g. only one device supporting # StepSpeaker for volume control). We do this via a dict representing the # Alexa schema; for each directive of each capability it has a list of # command names to search for in the device database for the appropriate # devices e.g. for the AdjustVolume directive of the StepSpeaker capability # we search for commands called VolumeUp and VolumeDown. # # The commands we extract may be simple unconditional "send this IR seq" # commands, or more complex commands that are parameterised by values # in the directive e.g. the ChangeChannel directive of the # ChannelController capability extracts the IR sequences corresponding to # digits 0-9; the value channel passed in the payload of the directive # then determines which IR sequences are sent. # # Why power is different # ---------------------- # # The scheme outlined above works well for everything apart from power # commands, for two reasons. # # - We potentially have to send commands to devices *not* in the endpoint # the user command nominally addresses. For example, if the user issues # "Turn on TV" then "Turn on CD", then (assuming the CD is being played # back through speakers not the TV) we should turn off the TV as part of # handling the latter command. That means we have to have awareness of # the power state of all devices. # # - Some evil device manufacturers just support a "power toggle" IR command # rather than separate "power on" and "power off". This makes awareness # of state essential, as the commands are not idempotent e.g. if we simply # mapped "Turn on TV" to sending "power toggle" to the TV, then if the user # issued successive "Turn on TV" commands, the second one would turn *off* # the TV. # # Models # ------ # # Given the above, we model and return the following. # # discovery_response # # This is a dict corresponding to the JSON structure to return to Alexa in # response to Discovery commands, and includes the full set of endpoints # and their supported capabilities. # # command_sequence # # This is a dict indexed by endpoint then capability then directive, # containing a structure corresponding to the set of IR commands to send # for that directive of that capability for that endpoint. The lambda # handler then simply sends that sequence of IR commands, parameterised as # necessary by values in the directive payload. # # device_power_map # # This is a dict indexed by device which includes info on # - which endpoints the device participates in # - whether it is a "toggle" or "on/off" device # - the set of IR commands corresponding to power toggle/on/off commands. # The lambda handler then uses this whenever it receives a directive for # the PowerController capability. In conjunction with the device state, # it works out which devices need to be turned off. Power manipulation # (and associated setting of inputs) for devices in the endpoint is handled # by the normal command sequence processing. log_info("Auto-generating model") # Extract the lists of targets and devices from the user details, and check # they're not duff. user_targets = user_details['targets'] user_devices = user_details['devices'] verify_devices(user_devices, device_database) discovery_response = [] command_sequences = {} # We can't construct the full power map until we have the list of endpoints # but we can extract whether each device is a toggle or on/off plus the # list of its IR commands. device_power_map = construct_power_map(user_details, device_database) # Now construct the list of endpoints, and use to flesh out the discovery # response, command sequence and power map. for this_device in user_devices: log_debug("User has device %s", this_device['friendly_name']) device_details = find_user_device_in_DB(this_device, device_database) is_video_source = ('AV_source' in device_details['roles']) is_audio_source = ('A_source' in device_details['roles']) is_source = (is_video_source or is_audio_source) if is_source: log_debug("It's a source; map it to an endpoint") # We now need to find the chain of devices in this endpoint chain, # plus the union of their capabilities. endpoint, capabilities, chain = construct_endpoint_chain(user_details, this_device, device_database) endpoint_id = endpoint['endpointId'] for link in chain: log_debug("Marking device %s involved in endpoint %s", link['friendly_name'], endpoint_id) device_power_map[link['friendly_name']]['endpoints'][endpoint_id] = True # Now go through the capabilities, and as well as constructing the # appropriate discovery response construct the set of commands for # each primitive. command_sequences[endpoint_id] = {} for capability in capabilities: log_debug("Add capability %s to endpoint response", capability) # Append the section of the discovery response for this # capability for this endpoint. This is the set of directives # we support for this capability, and is taken direct from the # Alexa schema. endpoint['capabilities'].append(CAPABILITY_DISCOVERY_RESPONSES[capability]) # Now construct the set of IR commands for each directive of # this capability. # The schema includes the set of command names to look for, # for each directive of each capability. command_sequences[endpoint_id][capability] = {} directives_to_commands = CAPABILITY_DIRECTIVES_TO_COMMANDS[capability] for directive in directives_to_commands: specific_commands = construct_command_sequence(chain, capability, directives_to_commands[directive]) command_sequences[endpoint_id][capability][directive] = specific_commands # Add the constructed endpoint info to what we return discovery_response.append(endpoint) log_debug("Device power map = %s", pp.pformat(device_power_map)) model = { 'discovery_response': discovery_response, 'command_sequences': command_sequences, 'device_power_map': device_power_map } return model
def construct_command_sequence(device_chain, capability, generic_commands): # Construct the sequence of commands corresponding to a particular # directive of a particular capability, for example "Play" for a # "PlaybackController". # # The generic_commands is a dictionary of commands to be interpreted when a # directive is received, each consisting of a primitive plus parameters as # follows. # # SingleIRCommands - Send a single IR command. Primitive has a list of # command names to search for in each device; use the # first match found for each relevant device # # StepIRCommands - Directive is of form increase/decrease by N. # Primitive is a dict with +ve and -ve command lists, # interpreted as above. # # DigitsIRComamnds - Directive is a number to be converted to a sequence # of IR commands for each decimal digit. Primitives # is a dict with list of possible commands for each # digit. # # InputChoice - Send IRCommands to set all the devices in the # activity to the correct input setting. Typically # used in "power on". # # Pause - Pause before sending anything further. Typically # used to wait for kit to switch on. Primitive is # length of pause in seconds. # # We map this to the specific sequence of commands for this users devices # by substituting from the global database of devices. # # Note that we rely on dictionary ordering - not true for Python 2, true # for Python 3.6 onwards. # # We do this by looping through all the devices in the activity, and for # those which support this capability, looking for matches for the set of # instructions. commands = [] log_debug( "Converting generic primitives %s to specific commands for capability %s for device chain %s", pp.pformat(generic_commands), capability, pp.pformat(device_chain)) for primitive in generic_commands: log_debug("This primitive is %s", primitive) for link in device_chain: device = link['friendly_name'] device_logname = link['log_name'] device_details = link['details'] target = link['target'] log_debug("Check device %s", device) commands.append(globals()[primitive](link, capability, generic_commands[primitive])) log_debug("Commands for this directive:\n%s", pp.pformat(commands)) return commands
def set_details(self, details): log_debug("Set user details for user %s", self.user_id) self.user_details = details if self.use_S3: write_S3state(BUCKET_USERDB, self.user_id + KEY_USER_DETAILS, details)