By Shane Bryldt, Senior Software Engineer
I’d like to start by introducing myself: My name is Shane Bryldt, and I have been a software engineer for over 20 years. I am proud to currently be a Senior Software Engineer for SignalWire, Inc with the opportunity to work on many different parts of the system with such a talented group of people. I am honored to have the chance to share my knowledge with you, the community, through these articles.
Maybe your telecommunication needs are someone else’s problem or you’ve been using other communication services, or maybe you’ve just been living off the grid. Whatever the reason, you might be thinking to yourself, “what exactly is SignalWire?” There is no short one-liner that can speak to the scope of what SignalWire is, or what it can do for you. It is the most advanced and scalable cloud communications solution available, leveraging the most widely deployed communications software across the planet, and built by the most talented Original Geeks the universe has to offer.
Starting with this article, I will be explaining what SignalWire can do, providing examples, and diving into the APIs and features of the platform. I’m also going to be offering my personal engineering insight and ideas on how you can leverage some of the more obscure options you can set.
We hope you like Bohemian Rhapsody because you’ve just entered...
Shane’s World – Installment I
Let’s take a moment to look at one of the SignalWire mission statements: “Develop. Deploy. Disrupt.” These powerful words give much insight into the mindset of the entire team at SignalWire; everything we do can be tied back to this mission statement.
1 - The engineers are in direct contact with customers and the community to get feedback on what we should be focusing on developing.
2 - We offer many of these services in a variety of deployment scenarios, tailored to fit your needs.
3 - SignalWire is disrupting the status quo in everything from the incredibly low pricing to the wide variety of services that are offered.
It’s Relay time, excellent!
Let’s dive into some features.
Today we’re going to talk about the real-time RELAY system, and since this is the first installment, I will be introducing more than one API to give a more complete start on the basics. We will cover the Calling.DialPhone API, and it’s counterparts Calling.NewPhoneCall and Call.Dial. We will also briefly cover Call.Hangup, and a little bit about the Consumer philosophy itself.
I will start with a few links to the documentation, please review these for relevant information:
Let’s start with some example code in C#, and then I will discuss what the code is doing:
The first thing we see is that there is a type called ExampleConsumer which inherits a base type called Consumer. The Consumer type is part of the SDK and provides the foundation on which most applications should be built. This type facilitates a number of operations; such operations include the availability of overridable methods for some important callbacks including Setup and Ready.
The Setup method is called during the creation of the Consumer, and provides the ability to configure the consumer from any configuration source you may want to use. In this example, we are assigning some hardcoded strings for simplicity, but you could obtain those values from environment variables, a configuration file, or by using the Microsoft Extensions for Configuration to combine a bunch of options. At the very least, a Project and a Token must be configured for authentication to the services, these are assigned to the Consumer object for quick and easy configuration.
After calling Setup, the Consumer will attempt to connect to the network, and once established the Ready method will be called. This Ready method is called from a new thread each time, which means you are free to use blocking API calls directly from this method without worrying too much about threading. This is where you can start doing your own SignalWire operations. The example shows that we have accessed a Client variable from the base Consumer type that was established during connecting. This Client is how you can get access to the API objects which expose some of the actual API methods, while other API’s are on specific objects like the Call object.
In the example, we are using Client.Calling.DialPhone to start a new phone (PSTN) call with two required parameters which are the to and from phone numbers respectively. This will begin immediately and will block until the call has been answered or fails for some reason (such as receiving a busy signal.) When it has completed, it will return a DialResult which will contain whether or not the call was Successful. If Successful is true then the call was answered. Anything else results in Successful being false, if more information on the reason for failure is desired you can look into the Event field of the result which contains the last event that resulted in completion of the operation.
Advanced Note: There is an additional timeout parameter on the DialPhone API which is optional and defaults to 30 seconds. Changing this parameter will change how long a call will attempt to ring before giving up.
With this example, if we have any failure of the dial, it will Stop the consumer which will cause the Consumer to terminate and ultimately the application will exit. Otherwise, if successful, the other field that is available in the DialResult is the Call field which is a Call object that will be assigned if the call was successfully answered. This Call object has a variety of additional API’s that can be used in the context of that call.
If the call was dialed and answered successfully, then we call the Call.Hangup API to gracefully terminate the call that was just established without doing anything too exciting. Sorry... It’ll be more interesting next time! Once again, this API will block until the call is successfully hung up, and then we call the Stop on the consumer to let the Consumer terminate and exit the application by returning from Run.
This is a good time to mention how your own Consumer is run. As you can see in the example, the Main program entry point has a short one line of code that allows you to create your Consumer and then call Run on it which will block until the Stop method is called on the Consumer. It is more likely that you will leave your applications running for extended periods, so Stop is usually used for graceful shutdown or restarting.
There is also another way a call can be started. While the above is the preferred approach, you may also create a new call by using Client.Calling.NewPhoneCall to create the call object without immediately starting the call. Using this method, it is expected that you need to hook some event callbacks on the call object for more advanced control before the call is started. After hooking those callbacks you can call Call.Dial to start the call. The helper methods exist to provide the majority use case, but we still expose the more advanced controlling options for those who want to dive in. Below is a shorter piece of code to demonstrate this approach:
In addition to these calls, there is also often an XXXXAsync version such as Client.Calling.DialPhoneAsync which provides immediate return without blocking, and provides an XXXAction object upon return. This object can be used to monitor the status of the operation, without blocking and waiting for it to complete while doing other things. After Completed is true the Result field of the Action will give you the same thing the blocking call returns. These are typically used in more advanced scenarios, but I wanted to mention them here to remind everyone that most API’s have an Async version for more advanced needs.
As a final parting piece of wisdom, the Run call blocks remember this when attempting to build something like a Windows Service. Attempting to call Run from the Windows Service OnStart will result in a failure of the service startup because the Run will block indefinitely. In cases like this there is a simple solution: create and start a new Thread and call Run from there instead. This could look something like:
new Thread(new ThreadStart(new TestConsumer().Run)).Start();
This allows the Windows Service OnStart to complete quickly.
Thank you for reading this article and I hope you find it helpful. The best way to learn about SignalWire is to tinker around with it, so sign up now to receive $5 in free credit and try it out yourself. You can also join us on our community Slack channel, where you can ask me any questions that you have about SignalWire. Stay tuned as we dive down the rabbit hole together into more of SignalWire, on the next installment of Shane’s World!