Handling Planned Actions for the SUSI Smart Speaker

Handling Planned Actions for the SUSI Smart Speaker

Handling Planned Actions for the SUSI Smart Speaker 

Planned action is the latest feature added to the SUSI Smart Speaker, The user now has the option to set timed actions such as-  settings alarms, countdown timer etc. So the user now can say “SUSI, Set an alarm in one minute” and after one minute the smart speaker will notify you.

The following flowchart represents the workflow for the working of a planned action:

Planned Action Response

The SUSI Server accepts the planned action query and sends a multi-action response which looks like this: 

“actions”: [
        “language”: “en”,
        “type”: “answer”,
        “expression”: “alarm set for in 1 minute”
        “expression”: “ALARM”,
        “language”: “en”,
        “type”: “answer”,
        “plan_delay”: 60003,
        “plan_date”: “2019-08-19T22:28:44.283Z”

Here we can see that we have two actions in the server response. The first action is of the type “answer” and is executed by the SUSI Linux client immediately, the other response has the `plan_date` and `plan_delay` keys which tells the BUSY State of the SUSI Linux client that this is a planned action and is then sent to the scheduler.

Parsing Planned Action Response From The Server 

The SUSI python wrapper is responsible for parsing the response from the server and making it understandable to the SUSI Linux client. In SUSI Python we have classes which represent the different types of actions possible. The SUSI Python takes all the actions sent by the server and parses them into objects of different action types. To enable handling planned actions we add two more attributes to the base action class – `planned_date` and `planned_delay`.

class BaseAction:
    def __init__(self, plan_delay = None, plan_date = None):
        self.plan_delay = plan_delay
        self.plan_date = plan_date

class AnswerAction(BaseAction):
    def __init__(self, expression, plan_delay = None, plan_date = None):
        self.expression = expression

Here we can see, All the action types which can be planned actions call the base class’ constructor to set the value for planned_delay and planned_date attributes. 

The next step is to parse the different action type object and generate the final result

def generate_result(response):
    result = dict()
    actions = response.answer.actions
    data = response.answer.data
    result[“planned_actions”] = []
    for action in actions:
        data = dict()
        if isinstance(action, AnswerAction):
            if action.plan_delay != None and action.plan_date != None:
                data[‘answer’] = action.expression
                data[‘plan_delay’] = action.plan_delay
                data[‘plan_date’] = action.plan_date
                result[‘answer’] = action.expression
        if data != {}:

Here if the action object has a non none value for the planned attributes, the action object’s values are added to a planned actions list.

Listening to Planned Actions in the SUSI Linux Client

In the busy state, we see if the payload coming from the IDLE state is a query or a planned response coming from the scheduler. If the payload is a query, the query is sent to the server, otherwise the payload is executed directly

if isinstance(payload, str):
logger.debug(“Sending payload to susi server: %s”, payload)
reply = self.components.susi.ask(payload)
else :
logger.debug(“Executing planned action response”, payload)
reply = payload

If the payload was a query and the server replies with a planned action response, then 

The server response is sent to the scheduler.

if ‘planned_actions’ in reply.keys():
    for plan in reply[‘planned_actions’]:        self.components.action_schduler.add_event(int(plan[‘plan_delay’])/1000,plan)

The scheduler then schedules the event and send the payload to the IDLE state with the required delay. To trigger planned actions we implemented an event based observer using RxPy. The listener resides in the idle state of the SUSI State Machine. 

        if self.components.action_schduler is not None:
                on_next=lambda x: self.transition_busy(x))

The observer in the IDLE state on receiving an event sends the payload to the busy state where it is processed. This is done by the transition_busy method which uses the allowedStateTransitions method.

    def transition_busy(self,reply):
            ‘busy’), payload=reply)


Understanding the SUSI State Machine – https://blog.fossasia.org/implementing-susi-linux-app-as-a-finite-state-machine/

Audio Structure of SUSI Smart Speaker – https://blog.fossasia.org/audio-structure-of-susi-smart-speaker/

Reactive Python Documentation – https://rxpy.readthedocs.io/en/latest/



Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.