Skip to content

Module __init__

Psycho edited this page Nov 15, 2019 · 11 revisions

When you create a module, you need an init method in it. This method has to call the super init function like with at least the intent your module supports such as:

from core.base.model.Intent import Intent

class AliceCore(Module):

    _INTENT_MY_FIRST_TRY = Intent('MyFirstTry')

    def __init__(self):
        self._SUPPORTED_INTENTS = [
            self._INTENT_MY_FIRST_TRY
        ]
        super().__init__(self._SUPPORTED_INTENTS)

You can add two parameters to this super init call:

  • authOnlyIntents dict[intent: Intent, level: str]: A dictionary of intents that are only for authenticated users. Exemple:
self._AUTH_ONLY_INTENTS = {
    self._INTENT_ADD_USER: AccessLevel.ADMIN
}

super().__init__(self._SUPPORTED_INTENTS, self._AUTH_ONLY_INTENTS)
  • databaseSchema dict: A dictionary containing the schema of the database tables your module uses. The database manager will take care of creating those tables if needed, but also to update them if you add columns or drop any. Exemple:
class Ifttt(Module):
    DATABASE = {
        'ifttt': [
            'id integer PRIMARY KEY',
            'user TEXT NOT NULL',
            'key TEXT NOT NULL'
        ]
    }
    def __init__(self):
        self._SUPPORTED_INTENTS	= []
        super().__init__(self._SUPPORTED_INTENTS, self.DATABASE)

We also made it easy to map intents to functions directly! And even map them to different functions under different states! This is a bit more advanced stuff but really helps maintaining a readable code.

Let's take a dialog as exemple, like asking the user for his name and confirming we understood it correctly. The dialog logic would be:

  1. Do you know me?
    • Yep, You're Psycho! (Stops here)
    • Nope, sorry. What's your name? (Continue to 2.)
  2. I'm Psycho
  3. Did I understand correctly? You said your name is Psycho?
    • Yes
      • Cool, nice to meet you Psycho, I'm Alice!
    • No
      • Ok, sorry, what's your name then? (Answer goes back to 3.)

Let's see what we have here. We have the intent, the user asking "Do you know me?". A first answer by Alice in the dialog where either she knows you or doesn't. The user responding with his name, a second intent followed by Alice asking a confirmation for the name to which the user answers with a third intent, a YesOrNo intent.

So, now we could write all this in onMessage. For a small module, why not. AliceCore modules was over 800 lines before we me the intent mapping. It's now still pretty long, but subdividing onMessage into functions really helped! This is how to map intents to functions. You will modify the way you write self._SUPPORTED_INTENTS.

Declaring the intent as class variable as usual:

_INTENT_ANSWER_YES_OR_NO = Intent('AnswerYesOrNo', isProtected=True)
  1. Simply declaring an intent, without linked function or condition:
self._SUPPORTED_INTENTS = [
    self._INTENT_ANSWER_YES_OR_NO
]

When the module catches the 'AnswerYesOrNo' intent it triggers the modules's "onMessage"

  1. Simply linking an intent to a fonction, without any condition:
self._INTENTS = [
    (self._INTENT_ANSWER_YES_OR_NO, self.onAnsweringYesOrNo)
]

When the module catches the 'AnswerYesOrNo' it triggers the modules's "onAnsweringYesOrNo" function automagically, passing intent: Intent and session: DialogSession as parameters

  1. Add a mapping to an intent
self._INTENTS = [
    self._INTENT_ANSWER_YES_OR_NO
]

self._ANSWER_YES_OR_NO.dialogMapping = {
    'confirmingUserDeletion':    self.onUserDeleteConfirmed,
    'confirmingReboot:           self.onRebootConfirmed,
    'confirmingMainDoorOpening': self.onDoorOpenConfirmed,
    'confirmingUsername':        self.onUsernameConfirmed
}

When the module catches the 'AnswerYesOrNo' it first checks the dialog state to decide what function to call. If the dialog state is confirmingUserDeletionit will call onUserDeleteConfirmed, if the state is confirmingReboot it will trigger onRebootConfirmed, etc etc, passing intent: Intent and session: DialogSession as parameters. Of course you can mix the direct mapping with conditional mapping!

self._INTENTS = [
    (self._INTENT_ANSWER_YES_OR_NO, onAnswerYesOrNo)
]

self._ANSWER_YES_OR_NO.dialogMapping = {
    'confirmingUserDeletion':    self.onUserDeleteConfirmed,
    'confirmingReboot:           self.onRebootConfirmed,
    'confirmingMainDoorOpening': self.onDoorOpenConfirmed,
    'confirmingUsername':        self.onUsernameConfirmed
}

Same as above, but if no state is met, it will call onAnswerYesOrNo

  1. How to set a dialog state? Well, the MqttManager ask and continueDialog accept the parameter currentDialogState: str that will set the dialogState for you. Cases where the answer is not understand are handled by keeping the state ensuring a correct dialog flow.

  2. You might want to check the AliceCore module to see some implemented code for this advanced part