SIP Announcement Player Code Documentation

Goal

The aim of this python code is to establish (register) a SIP call to signal the output of audio data (e.g. an Announcement/Music etc.) on the target device (receiver).

The Session Initiation Protocol (SIP) is a protocol which is used for establishing a two-party or multy-party connection (session) to send media (i.e. voice/video).

Quick Start

To get started as fast as possible, only 3 steps are necessary:

  1. Obtain SIP registry, Auth ID and secret from linuquick
  2. Update the config.json file with your new credentials (path /mnt/data/package/config.json)
  3. Change SOUND_F_PATH variable in line 9 of callbacks.py so that it points to the desired audio (.wav) file

In-Depth Explanation

All the action happens in the main script run.py. From here, all other files or scripts will be called.

Let's dive into the run.py file and analyse what is happening:

(i)

First, we need to import all packages and (our own) classes we want to use. These are already pre-installed on the device, so we don't need to take care of that.

Python
Copy

(ii)

Secondly, we define some (constant) paths, file names and variables we will need throughout the script:

Python
Copy
VariableDescription
PROCESS_ID_PATHPath where the current process ID will be saved
CONFIG_PATHPath to the file where the linuquick user identity is saved
LOG_LEVEL
Input verbosity level of callback log

(iii)

Next, some helper functions we will need later.

Python
Copy
FunctionDescription
daemonize_app()

Accesses the current process ID and saves it into a file, which path is defined in the beginning of the script (PROCESS_ID_PATH, see (ii) above).


    get_linu_config()Reads the linuquick config parameters from the config.json file and stores them in the variable config, which will be returned in the end.
    log_cb(level, str, len)

    Logging function for later callbacks.


    (iv)

    After this is done, we can analyse the execution logic of the script. We jump to the end of the script (lines 91-92):

    Python
    Copy

    This is the first part that will start the script execution. Since we are running the run.py script as the main program, the "name == "main" condition evaluates to true.

    As you can see, the script continues with invoking the main() function, which definition starts in line 31.

    (v)

    In a first step, 2 helper functions from above (see (ii)) will be called:

    Python
    Copy

    (vi)

    Next, we create a pjsua library instance

    Python
    Copy

    and invoke the call_update function from the *get_update.py *script.

    Python
    Copy

    (Please see Apx I **below for more information about the *get_update.py*** script.)

    (vii)

    We initialize the library, create a UDP transport and set user identity information as well as a callback for the account (see Apx II).

    Python
    Copy

    (For a closer look into the MyAccountCallback class, please see (Apx II) below.)

    (viii)

    Python
    Copy

    This is the main loop of our script. We create a loop that will run forever unless we stop the program and close the connection/lib instance (pressing Ctrl+c). Since we put the pass keyword here, no further code will be executed inside the body of the loop.

    All the action is happening inside our callback classes, which will handle the events from the ongoing call.

    (Apx I - get_update.py)

    This script is updating the json.config parameters and will change the value of the audio output.

    Again, first the necessary imports.

    Python
    Copy

    Followed by the declaration of the (constant) variables we will need in the script.

    Python
    Copy
    VariableDescription
    REG_PATHPath to the regID
    SERV_F_PATHPath to service.json file
    CONFIG_PATHPath of current working directory
    UPDATE_INTERVALUpdate step for next call
    LINU_Q_URLURL to linuquick api

    Let's take a look at the execution logic from the run.py viewpoint. The function call_update() (which is part of the get_update.py script) gets invoked in line 98 of run.py.

    Python
    Copy

    The function is defined between lines 63-74 of get_update.py.

    Python
    Copy

    First, a global variable next_call is define (accessible from everywhere in the script). This variable is used to schedule the update intervall for the next call. Next, we load the regID and store it in the variable regID. We initialize the next_call variable with the current time, defined in seconds (see here). Now we increment the next_call variable by the value of UPDATE_INTERVAL (i.e. 1 second).

    Finally, we can create a thread that executes the function get_update() after a specified time interval (i.e. next_call - time.time()) has passed.

    To follow the execution logic, we now take a look at the get_update() function, starting in line 17. Make variables accessible for the whole script (global keyword).

    Python
    Copy

    Load service.json file.

    Python
    Copy

    POST request to url.

    Python
    Copy

    Get state of the request.

    Python
    Copy

    If config state of request is "new", send POST request with configUrl and regID.

    Python
    Copy

    Save new config parameters to config.json file.

    Python
    Copy

    Finally, set new volume value for the audio output.

    Python
    Copy

    (See Apx III for control_volume.py description.)

    (Apx II - callbacks.py)

    Again, imports, parameter and variable declarations in the beginning.

    Python
    Copy

    Here we define the path to the desired audio file (.wav) as well as a flag to check if a call is ongoing.

    Python
    Copy

    Here, we define a function to handle the output of the audio. The function will wait till the audio has finished.

    Going on, MyAccountCallback class is next.

    This is a callback class for the pjsua SIP account. It will handle incoming notifications from account events. Hence, it inherits attributes from the pjsua AccountCallback class.

    At the top, we declare a placeholder for the semaphore variable we will need later.

    Python
    Copy

    The class has 4 methods:

    MethodDescription
    init(self, account=None)Class constructor, will be called after a new class instance has been created.
    wait(self)
    Initialize the semaphore variable; increment it by 1 (self.sem.acquire()).
    on_reg_state(self)Decrement semaphore if account registration was successful.
    on_incoming_call(self, call)Gets invoked if there is an incoming call. First, check if there is an ongoing call. If not, we answer the call and enable the playback of the audio (i.e. play_file()). Finally, we hangup the call.

    The last class is defined at the bottom of the script, MyCallCallback.

    This is a callback class for the call itself. It will handle the status of our call. It inherits attributes from the pjsua CallCallback class.** **

    Python
    Copy
    MethodDescription
    init(self, call=None)
    Class constructor, will be called after a new class instance has been created.
    on_state(self)Notifies when call state has changed, handels call status if call gets disconnected (current_call = None)

    (**Apx III - control_volume.py)**

    Script to control output volume.

    Python
    Copy

    Create a mixer instance and set its volume.

    Type to search, ESC to discard
    Type to search, ESC to discard
    Type to search, ESC to discard