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.
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.
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 |
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.
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
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
- Currently remote_phone_control parses and handles the following
$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
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
-
(venv) $ pip install remote-phone-control
(venv) $ pip install git+https://github.com/p4irin/remote_phone_control.git
(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']
>>>
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.
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.
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
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.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()
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()
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.
- SNOM
- Remote phone control via http
- Action URLs
http://<phone's ip-address>/settings.htm
- Yealink
- Python