Skip to content

Remote control phones for automated testing of call scenarios

License

Notifications You must be signed in to change notification settings

p4irin/remote_phone_control

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Remote Phone Control - v0.1.0

Some SIP hardphone brands support the features Action URIs and Action URLs on some or all of their phone models. Action URIs are used to remote control a phone by sending it HTTP GET requests. E.g., place a call. Action URLs are used to communicate events occuring on a phone to an HTTP service. E.g. an incoming call.

This Python package primarily serves as a remote controller and a listener for the call related commands to and events from a phone. Its main intent and focus is to automate the testing of call scenarios.

Supported phones

In principle, Remote Phone Control, could be extended to support any phone that provides the Action URI and Action URL features. Currently, only the SNOM models noted under the Stack heading are tested and supported.

Action URIs and methods

These are HTTP GET requests with query parameters to control a phone, make it do something. remote_phone_control hides the URIs and query parameters in a phone class object, e.g. Snom, and its methods. The methods are mapped to and use corresponding URI and query parameters. The table below lists currently implemented methods for Snom class objects. The method names are quite self explanatory. A description is provided if more clarification is needed. Consult SNOM Remote phone control via http for reference.

Method Arguments Returns Description
callout number: str bool
pickup None bool
hangup None bool
hangup_onhook None bool
hangup_all None bool
senddtmf digits: str bool Send in-band DTMF
senddtmf_sipinfo digits: str bool Send DTMF using SIP-INFO
expect event: Literal['incoming', 'connect', 'disconnect'], timeout: int = event_wait_timeout bool Returns True if your expectation was correct. The default timeout to wait for the event is 15 seconds
clear_events None None Call this together with hangup_all in case a test goes awry and phones are left with dangling, standing calls.
set_disable_speaker value: Literal['on', 'off'] bool Turn audible cues/ the speaker on or off
set_setting setting: str, value: str bool setting is the name of the phone setting you want to set to value. For settings name reference browse to the phone's settings page: http://<phone's ip-address>/settings.htm
press_px x: str bool Simulates pressing a free programmable function key
transfer None bool
reboot None bool
stop None None When you're done consuming Snom, you MUST call stop to stop the Snom's Action Server

Action URLs and handled events

A phone will send an HTTP GET request to the Action URL when triggered by an event. The remote_phone_control package is the endpoint, target, of the request. It will parse and process the request.

Event to Action URL mappings

The events currently handled by remote_phone_control are per the following table.

Event Description Action URL used by the phone
Incoming call There is an incomming call, the phone is ringing http://<remote_phone_control ip-address>:<port>/event?ip=$phone_ip&event=incoming&local=$local&remote=$remote
On Connected Fired when a call is established http://<remote_phone_control ip-address>:<port>/event?ip=$phone_ip&event=connected&local=$local&remote=$remote
On Disconnected A call is ended http://<remote_phone_control ip-address>:<port>/event?ip=$phone_ip&event=disconnected&local=$local&remote=$remote

With remote_phone_control you can use these events as verification points when testing a call. E.g., when a phone is called, we can verify if there is an incoming call. When the call is answered, we can check if there's an established call.

For further reference on Action URL s consult SNOM Action URLs

Action URL, format and query parameters

The Action URL is configured on the phone. remote_phone_control will configure this automatically on the phone given the ip-address of the phone. See Usage for an example.

Below, the format used by remote_phone_control to set the Action URL on the phone.

http://<Remote Phone Controller ip-address>:<port>/event?ip=$phone_ip&event=<event>&local=$local&remote=$remote
  • <Remote Phone Controller ip-address>: The ip-address of the node where a remote_phone_control package is run.
  • <port>: The Action server port served by remote_phone_control
  • $phone_ip: A phone variable that will be replaced by the phone's ip-address
  • <event>: indicates to remote_phone_control what event triggered the request
    • Currently remote_phone_control parses and handles the following <event>s
      • incoming
      • connected
      • disconnected
  • $local: A phone variable that will be replaced by the local sip-id related to the event
  • $remote: A phone variable that will be replaced by the remote sip-id related to the event

Stack

This was used to develop and test the package. Not to say that other combinations of, e.g., Python, Ubuntu, phone models, phone firmware might also work. The SNOM models used are not very recent though. End of life even. So, no guarantees that the package will work for newer models unmodified. But my first impression after skimming through the documentation of recent SNOM models is that the Action URI and Action URL support looks very similar.

  • Python: 3.8.10
  • Ubuntu: 20.04.6 LTS
  • This Python package was tested with the following SIP hardphones
    • SNOM

      Model Firmware
      300 8.7.3.19, 8.7.3.25 8.7.5.17
      320 8.7.3.19, 8.7.3.25 8.7.5.17
      360 8.7.3.19, 8.7.3.25

Installation

From PyPI

(venv) $ pip install remote-phone-control

From GibHub

(venv) $ pip install git+https://github.com/p4irin/remote_phone_control.git

Verify

(venv) $ python
Python 3.8.10 (default, May 26 2023, 14:05:08) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import remote_phone_control
>>> remote_phone_control.__version__
'0.0.1'
>>> import remote_phone_control.snom
>>> dir(remote_phone_control.snom)
['ActionServer', 'Literal', 'Snom', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'requests']
>>>

Usage

Preconditions

A list of what to account for in order to remote control phones for test automation.

  • Networking
    • Preferably use a setup where the node, on which remote_phone_control is running, the phones, the registrar and the call proxy are on the same subnet
    • If remote_phone_control, the phones, the registrar and the call proxy are on different subnets, make sure they can see, reach each other. E.g., look at you're routing configuration.
    • No firewall between remote_phone_control and phones
    • No firewall on remote_phone_control 's node, Ubuntu, as mentioned under Stack
    • No NAT between remote_phone_control and phones
  • The phones MUST be provisioned and working
    • Are registered
    • Can call each other
    • Can, optionally, call out to PSTN
    • Can, optionally, receive incoming calls originating from the PSTN
  • What data you need
    • ip addresses of the phones
    • the username and password of the phones
    • allocate, list an Action server port for each phone. Each phone will be controlled by a separate instance of a phone class object, e.g. Snom, that will also handle a phone's events through an Action URL
    • ip address of remote_phone_control 's node. This will be used as the Action server's ip-address for each phone object as you can control several phones from the same node.
    • a sip-id (outgoing URI) on the phone to use for call operations.

Basic examples

The examples given are included in the package's source as unit tests, in the tests sub directory. The package's tests use Python's standard library unit testing framework. For simplicity's sake, the examples below do not.

Imports and Snom instances

These imports and Snom instantiations must precede all the standalone examples.

import time
from remote_phone_control.snom import Snom


snom_a = Snom(
  "nnn.nnn.nnn.nnn", # ip-address of a SNOM phone
  "nnn.nnn.nnn.nnn", # Action Server's ip-address
  nnnn, # Action Server's listening port
  "username", # username to access the SNOM
  "password", # password to access the SNOM
  "outgoing URI", # a sip-id configured on the SNOM
  "extension" # the SNOM's extension
)

snom_b = Snom(
  "nnn.nnn.nnn.nnn",
  "nnn.nnn.nnn.nnn",
  nnnn,
  "username",
  "password",
  "outgoing URI",
  "extension"
)

N.B.

  • The Action Server's ip-address is the ip-address of the node where remote_phone_control is used. Remember, the Action Server is served by remote_phone_control.
  • The Action Server's listening port MUST be unique for each Snom instance
  • The outgoing URI is the sip account used for calling

Example: Call out to PSTN

E.g., set your mobile phone to auto answer and call it.

assert snom_a.callout('PSTN number to call') == True
assert snom_a.expect('connect') == True
time.sleep(5) # Represents a 5 seconds conversation
assert snom_a.hangup() == True
assert snom_a.expect('disconnect') == True

snom_a.stop()

SNOM A calls SNOM B, B answers, call is established, B ends the call

snom_a.callout("Extension of SNOM B")
assert snom_b.expect('incoming') == True
snom_b.pickup()
assert snom_b.expect('connect') == True
time.sleep(5) # Represents a 5 seconds conversation
snom_b.hangup()
assert snom_b.expect('disconnect') == True

snom_a.stop()
snom_b.stop()

A calls B, B rejects the call

snom_a.callout("Extension of SNOM B")
assert snom_b.expect('incoming') == True
# Reject the incoming call
snom_b.hangup()
assert snom_a.expect('disconnect')

snom_a.stop()
snom_b.stop()

An actual use case

To get a feel of how you can use this package. It proofed very useful in automating end to end regression tests of a cloud hosted call center web app where an agent is tied to a browser interface, the GUI, and a SIP phone. Call actions displayed and performed on the GUI should be in sync with the actual state of the phone and vice versa. Using Selenium, call actions are executed on the GUI and the state of the phone checked against the expectation using the package. Using the package, call actions are executed on the phone and the GUI state checked using Selenium. Inbound calls, with an external origin, i.e. PSTN, and several other scenarios are checked against both the state of the GUI and the phone. Gluing it all together with Python's standard library unittest framework.

Reference

Releases

No releases published

Packages

No packages published

Languages