Package: remote-phone-control
Remote Phone Control - v0.1.0Some SIP hardphone brands support the featuresAction URIsandAction URLson some or all of their phone models.Action URIsare used toremote controla phone by sending itHTTP GETrequests. E.g., place a call.Action URLsare used tocommunicate eventsoccuring on a phone to anHTTP service. E.g. an incoming call.This Python package primarily serves as aremote controllerand alistenerfor thecall relatedcommands toandevents froma phone. Its main intent and focus isto automate the testing of call scenarios.Supported phonesIn principle,Remote Phone Control, could be extended to support any phone that provides theAction URIandAction URLfeatures. Currently, only the SNOM models noted under theStackheading are tested and supported.Action URIs and methodsThese areHTTP GETrequests withquery parametersto control a phone, make it do something.remote_phone_controlhides the URIs and query parameters in a phone class object, e.g.Snom, and its methods. Themethodsare mapped to and usecorresponding URI and query parameters. The table below lists currently implemented methods forSnomclass objects. The method names are quite self explanatory. A description is provided if more clarification is needed. ConsultSNOM Remote phone control via httpfor reference.MethodArgumentsReturnsDescriptioncalloutnumber: strboolpickupNoneboolhangupNoneboolhangup_onhookNoneboolhangup_allNoneboolsenddtmfdigits: strboolSend in-band DTMFsenddtmf_sipinfodigits: strboolSend DTMF using SIP-INFOexpectevent: Literal['incoming', 'connect', 'disconnect'], timeout: int = event_wait_timeoutboolReturnsTrueif your expectation was correct. The default timeout to wait for the event is 15 secondsclear_eventsNoneNoneCall this together withhangup_allin case a test goes awry and phones are left with dangling, standing calls.set_disable_speakervalue: Literal['on', 'off']boolTurn audible cues/ the speaker on or offset_settingsetting: str, value: strboolsettingis the name of the phone setting you want to set tovalue. For settings name reference browse to the phone's settings page:http://<phone's ip-address>/settings.htmpress_pxx: strboolSimulates pressing a free programmable function keytransferNoneboolrebootNoneboolstopNoneNoneWhen you're done consumingSnom, you MUST callstopto stop theSnom'sAction ServerAction URLs and handled eventsA phone will send anHTTP GETrequest to theAction URLwhen triggered by an event. Theremote_phone_controlpackage is the endpoint, target, of the request. It will parse and process the request.Event to Action URL mappingsThe events currently handled byremote_phone_controlare per the following table.EventDescriptionAction URL used by the phoneIncoming callThere is an incomming call, the phone is ringinghttp://<remote_phone_control ip-address>:<port>/event?ip=$phone_ip&event=incoming&local=$local&remote=$remoteOn ConnectedFired when a call is establishedhttp://<remote_phone_control ip-address>:<port>/event?ip=$phone_ip&event=connected&local=$local&remote=$remoteOn DisconnectedA call is endedhttp://<remote_phone_control ip-address>:<port>/event?ip=$phone_ip&event=disconnected&local=$local&remote=$remoteWithremote_phone_controlyou 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 onAction URLs consultSNOM Action URLsAction URL, format and query parametersTheAction URLis configured on the phone.remote_phone_controlwill configure this automatically on the phone given the ip-address of the phone. SeeUsagefor an example.Below, the format used byremote_phone_controlto set theAction URLon 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 aremote_phone_controlpackage is run.<port>: TheAction serverport served byremote_phone_control$phone_ip: A phone variable that will be replaced by the phone's ip-address<event>: indicates toremote_phone_controlwhat event triggered the requestCurrentlyremote_phone_controlparses and handles the following<event>sincomingconnecteddisconnected$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 eventStackThis 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 theAction URIandAction URLsupport looks very similar.Python: 3.8.10Ubuntu: 20.04.6 LTSThis Python package was tested with the following SIP hardphonesSNOMModelFirmware3008.7.3.19, 8.7.3.25 8.7.5.173208.7.3.19, 8.7.3.25 8.7.5.173608.7.3.19, 8.7.3.25InstallationFrom PyPI(venv)$pipinstallremote-phone-controlFrom GibHub(venv)$pipinstallgit+https://github.com/p4irin/remote_phone_control.gitVerify(venv)$python
Python3.8.10(default,May262023,14:05:08)[GCC9.4.0]onlinux
Type"help","copyright","credits"or"license"formoreinformation.
>>>importremote_phone_control
>>>remote_phone_control.__version__'0.0.1'>>>importremote_phone_control.snom
>>>dir(remote_phone_control.snom)['ActionServer','Literal','Snom','__builtins__','__cached__','__doc__','__file__','__loader__','__name__','__package__','__spec__','requests']>>>UsagePreconditionsA list of what to account for in order to remote control phones for test automation.NetworkingPreferably use a setup where the node, on whichremote_phone_controlis running, the phones, the registrar and the call proxy are on the same subnetIfremote_phone_control, the phones, the registrar and the call proxyareon different subnets, make sure they can see, reach each other. E.g., look at you're routing configuration.No firewall betweenremote_phone_controland phonesNo firewall onremote_phone_control's node, Ubuntu, as mentioned underStackNo NAT betweenremote_phone_controland phonesThe phones MUST be provisioned and workingAre registeredCan call each otherCan, optionally, call out to PSTNCan, optionally, receive incoming calls originating from the PSTNWhat data you needip addresses of the phonesthe username and password of the phonesallocate, list anAction serverport 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 anAction URLip address ofremote_phone_control's node. This will be used as theAction 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 examplesThe examples given are included in thepackage's source as unit tests, in thetestssub directory. Thepackage's tests use Python's standard library unit testing framework. For simplicity's sake, the examples below do not.Imports andSnominstancesThese imports andSnominstantiations must precede all the standalone examples.importtimefromremote_phone_control.snomimportSnomsnom_a=Snom("nnn.nnn.nnn.nnn",# ip-address of a SNOM phone"nnn.nnn.nnn.nnn",# Action Server's ip-addressnnnn,# 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.TheAction Server's ip-address is the ip-address of the node whereremote_phone_controlis used. Remember, theAction Serveris served byremote_phone_control.TheAction Server'slistening portMUST be unique for eachSnominstanceTheoutgoing URIis thesip accountused for callingExample: Call out to PSTNE.g., set your mobile phone toauto answerand call it.assertsnom_a.callout('PSTN number to call')==Trueassertsnom_a.expect('connect')==Truetime.sleep(5)# Represents a 5 seconds conversationassertsnom_a.hangup()==Trueassertsnom_a.expect('disconnect')==Truesnom_a.stop()SNOM A calls SNOM B, B answers, call is established, B ends the callsnom_a.callout("Extension of SNOM B")assertsnom_b.expect('incoming')==Truesnom_b.pickup()assertsnom_b.expect('connect')==Truetime.sleep(5)# Represents a 5 seconds conversationsnom_b.hangup()assertsnom_b.expect('disconnect')==Truesnom_a.stop()snom_b.stop()A calls B, B rejects the callsnom_a.callout("Extension of SNOM B")assertsnom_b.expect('incoming')==True# Reject the incoming callsnom_b.hangup()assertsnom_a.expect('disconnect')snom_a.stop()snom_b.stop()An actual use caseTo 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.ReferenceSNOMRemote phone control via httpAction URLshttp://<phone's ip-address>/settings.htmYealinkAction URIHow to dial or place a call using remote control?PythonRequests: HTTP for Humans