Sigapy: a failed attempt to create a product in Paraguay

A little more than a year ago, I created a service which main intention was to provide my fellow Paraguayans living abroad, an easy way to call home using their smartphones. It was not like Viber or Tango app, you could actually call to land lines and mobile phones, and of course between Sigapy users.

There’s no specialized service like this in Paraguay, and I wanted to fill that void with something new and easy to use, and so Sigapy was born. It was conceived as an academical project but soon it started to appear exploitable as a commercial service, but the environment for this kind of business in Paraguay isn’t the best (actually, it’s horrible) and I had to shut it down for my own juridical sake.

Today, I decided someone else can give it a try, because it’s a more or less mature product and can easily be adapted to someone else’s needs.

Check out the videos and the landing page I created. The videos are in Spanish, but the flow can be understood by anyone.

DWG2000 API Windows demo

Finally a full-featured and executable demo is available for downloading and testing.

It runs on any Windows box with dotNet framework 2.0 installed on it. Personally, I tested the app on Windows XP and Windows 7.

To try it, decompress the zip file and paste the only two DLL files into %windir%System32 folder. Then, run the executable file.

This is a very early demo application and it may contain bugs. In case of that, please report it to my personal email so I can fix it.

dwg2000-demo

C# bindings for DWG2000 SMS API

Yes, it’s done and as I promised in my previous post, I am releasing it to the public.

This project started when a local company hired me to make the API support for Linux. It was a success and the sms-client started to work right away. It was not until 2 or 3 months after finishing the project that the idea of making the source code public came to my mind and despite this project may not be a great contribution to the community, it is the first of (hopefully) many.

This particular branch of the project might be specially useful for writing quick VAS software on top of the gateway. It is much easier to write business logic algorithms in C# than in C and in my case, that’s exactly what happened.

Get a copy from my github account

Dinstar SMS API update

I’ve been working very hard in the past few months that updating my blog was relegated to the least of my priorities. It’s a pity, but I had no choice :(.

Back on the track, I came here to present an update of the Dinstar’s SMS API implementation which I started writing some time ago. I paused the project several times mostly because I had no economic reason to support my time on it , but luckily, the situation changed and today the great majority of the API is implemented and working.

I don’t maintain a changelog other than the one on github commit list, but basically, this is what changed:

– Support for API 1.4 and 2.0
– Support for authentication mechanism. (This is partial, check source code)
– Support for RSSI packages
– Support for Unicode strings
– The project is no longer a standalone application, now is a shared library.
– Several bug fixes

The C# bindings are also ready. I will publish the code probably next week.

Check the repo on github

Dialer: tracking calls with Asterisk/AMI

Long story short: I was asked to write a real time dialer callable from a web page that takes the number from the parameters received, make the call, and play a pre-recorded message when the called picks up. This post is not about the dialer itself, it’s about tracking the call originated by it, detect the “answer” event, detect the “hangup” event and save a CDR.

The approach I chose involves AMI to generate and track the calls. It offers a really nice interface based on a text protocol and the most important thing, the API can be mapped in almost any programming language like C# (Asterisk.NET).

Some of the protocol internals

  1. The connection is made via TCP/IP sockets using user/password authentication.
  2. Once logged in, you receive every event on the system, even if that event doesn’t belong to you. This could be a real mess because it introduces a lot of noise into the dialer and the software must support some kind of filtering mechanism.
  3. Every event comes with certain data, including a field named Uniqueid which relates the events to a transaction.
  4. The order the events were received not necessarily match the order they were generated.

The native way to generate a call is through the OriginateAction but the problem is that OriginateAction doesn’t reply with the uniqueID of the transaction, it just tells you whether the call was successfully queued or not. Then, you start receiving the events with the uniqueIDs but you have no way to know if that call belongs to you, to another dialer instance, to a human making a call, etc.

Searching the web I found what I thought was the solution: use asynchronous origination.  The usual OriginateAction is a blocking call that returns only if success or failure. With async origination the answer to the request is delivered through the OriginateResponse event and with the parameters you receive, the so wanted uniqueID. I built the dialer on top of this “fix” just to later discover that if the call fails, the uniqueID is empty. Once again, you have no way to know which call failed and for what reason.

I was so pissed off because something as basic as this wasn’t correctly available, or at least, there is no documentation to expose an standard solution. I started considering parallel approaches to abandon AMI but reading the input parameters for OriginateAction a dirty workaround came to my mind.

OriginateAction takes as input a field named CallerID, this field is always available and always filled with information. I used this field to uniquely identify the call whether it succeeded or not. In my particular case, the calls goes to a GSM network so overwriting the callerID has no effect and the call follows its normal destination.

Bellow is part of the dialer class. If you can read C#, it would explain itself.

[csharp]

public class Dialer
{
ManagerConnection _managerConnection;
Timer _timer;
public delegate void CallFinalizedCallback(Dialer sender);
string _uniqueID = string.Empty;
bool _wasOriginated = false;
object _locker = new object();

List<HangupEvent> _hangupList = new List<HangupEvent>();

CallFinalizedCallback _callFinalized;
public CallFinalizedCallback CallFinalized
{
get {
return this._callFinalized;
}
set {
_callFinalized = value;
}
}

CallInfo _callInfo;
public CallInfo CallInfo
{
get
{
return this._callInfo;
}
}

public Dialer()
{
_managerConnection = new ManagerConnection(Configuration.ManagerHost,
Configuration.ManagerPort,
Configuration.ManagerUserName,
Configuration.ManagerPassword);

//_timer = new Timer(Handle_timeoutCallback, Configuration.CallNotificationTimeout);

AddEventHandlers();
}

void Handle_managerConnectionOriginateResponse (object sender, OriginateResponseEvent e)

{
if (!e.CallerIdNum.Equals(_callInfo.CallId.ToString()))
return;

Logger.Print(string.Format(“Call to {0}-{1} was originated”, _callInfo.CallId, e.Exten));

bool succeeded = e.Response.ToLower().Equals(“success”);

if (succeeded)
UpdateDialoutQueue(CallStatus.Established);
else
UpdateDialoutQueue(CallStatus.FinishedWithError);

lock(_locker)
{
_wasOriginated = true;
_uniqueID = _callInfo.CallId.ToString();
}

foreach(HangupEvent hEvent in _hangupList)
{
if (hEvent.CallerIdNum.Equals(e.CallerIdNum)) // the call failed for some reason
{
Handle_managerConnectionHangup(this, hEvent);
break;
}
}
_hangupList.Clear();

}

public Dialer(CallInfo callInfo, CallFinalizedCallback callback) : this()
{
_callInfo = callInfo;

if (callback == null)
throw new ArgumentException(“CallFinalizedCallback cannot be null”);

_callFinalized = callback;
}

void AddEventHandlers()
{
_managerConnection.Hangup += Handle_managerConnectionHangup;
_managerConnection.OriginateResponse += Handle_managerConnectionOriginateResponse;
}

public void DisconnectFromAMI()
{
_managerConnection.Hangup -= Handle_managerConnectionHangup;
_managerConnection.OriginateResponse -= Handle_managerConnectionOriginateResponse;
}

void Handle_timeoutCallback()
{
try
{
Logger.Print(string.Format(“Call {0}-{1} forced to finalize after timeout”, _callInfo.CallId, _callInfo.Extension));

UpdateDialoutQueue(CallStatus.Timedout);
//_timer.CancelAlarm();

if (_callFinalized != null)
_callFinalized(this);

DisconnectFromAMI();
}
catch(Exception ex)
{
Logger.Error(string.Format(“Handle_timeoutCallback: {0}”, ex.Message));
DisconnectFromAMI();
}
}

void Handle_managerConnectionHangup(object sender, HangupEvent e)
{
try
{
lock(_locker)
if (!_wasOriginated || !_uniqueID.Equals(e.CallerIdNum))
{
_hangupList.Add(e); // register this event in case it was fired before the OriginateAction event
return;
}

UpdateDialoutQueue(CallStatus.Finished, e.Cause, e.CauseTxt);
//_timer.CancelAlarm();

Logger.Print(string.Format(“Call {0}-{1} was hung up”, _callInfo.CallId, e.Channel));

if (_callFinalized != null)
_callFinalized(this);

DisconnectFromAMI();
}
catch(Exception ex)
{
Logger.Error(string.Format(“Handle_managerConnectionHangup: {0}”, ex.Message));
DisconnectFromAMI();
}
}

public void Call()
{
Call(_callInfo);
}

public void Call(CallInfo callInfo)
{
try
{
OriginateAction originate = new OriginateAction();

originate.Channel = callInfo.Channel;
originate.Priority = callInfo.Priority;
originate.Context = callInfo.Context;
originate.Exten = callInfo.Extension;
originate.CallerId = callInfo.CallId.ToString(); // <——–
originate.Timeout = 500000;
originate.Async = true;

_managerConnection.Login();

MarkCallAsProcessed();

//_timer.SetAlarm();

UpdateDialoutQueue(CallStatus.Trying);
ManagerResponse r = _managerConnection.SendAction(originate, originate.Timeout);
}
catch(Exception ex)
{
Logger.Error(string.Format(“Call: {0}”, ex.Message));

//_timer.CancelAlarm();

if (ex is Asterisk.NET.Manager.TimeoutException)
{
UpdateDialoutQueue(CallStatus.Timedout);
DisconnectFromAMI();
}
else
UpdateDialoutQueue(CallStatus.FinishedWithError);
}
}
}

[/csharp]

 

Software development, times and costs

Time estimation in software development is probably the most difficult part for me when it comes to starting projects independently.

I studied this subject back in university but the classes were never accurate enough to reflex real world examples and I just can blame the teacher for using a 20 years old book talking about failures of WINWORD 1.0, IBM OS/2 and what the hell. The tools has changed, the people has changed and the programming paradigms too. I don’t know if I was doing something wrong but I never found useful any of these so called “techniques” to estimate cost in building software.

Every time a potential client comes to me asking for a solution I analyse the complexity of the project, the possible time I would have to spend in order to get it done, the time he’s giving me and the situation of the marketplace for the kind of software he’s asking for. Clients always come with “I have this problem, I need it fixed, how much for that?”, and they expect an answer immediately and you just have no time to open your spreadsheet and fill it with the variables.

All the techniques I learned at university doesn’t work as I expected. They are just too old or were designed for major software companies with dozens of developers and not for the rest of the people working independently or with a team of 2 or 3 people as much.

My algorithm has some failures. The client sometimes makes last time changes and since a commitment contract was not signed and was all verbally agreed, you can’t say no because you won’t get paid. It is almost a 100% sure that if you, an independent software developer living at Paraguay, try to make your client sign an agreement paper, he will run away :D.

I have a full time job but I also work as an external consultant in my spare time and I have to deal with these kind of situations very often.