
Bei der Entwicklung von Softwarelösungen gibt es immer wieder die Anforderung SMS vom System verschicken zu lassen. Meistens während dem Ablauf eines Eskalationsmodells oder zum Benachrichtigen bei außergewöhnlichen Vorkommnissen oder Fehlern im System (z.B. einem unerwarteten Stillstand einer Produktionsanlage).
Um eine SMS Lösung zu realisieren muss man nicht immer auf teuere GSM Modems / SMS Gateways zurückgreifen. Es reicht schon wenn man einen Surfstick oder Web’n'Walk Stick zu verfügung hat. Diese kann man auch sehr günstig beziehen.
Ich möchte hier erklären, was man beachten muss um einen Surfstick aus einer eigenen Applikation heraus zu steuern, sodass man ihn als GSM Modem zum Verschicken von SMS benutzen kann.
Voraussetzungen
Damit man per Surfstick SMS verschicken kann müssen zwei Voraussetzungen erfüllt sein
- SIM Karte mit der man SMS versenden kann
eine Kombikarte von T-Mobile ist evtl. nicht dazu geeignet um SMS zu schicken. Meistens sind diese Karten nur zum Surfen geeignet.
- Treiber für Surfstick müssen installiert sein
Meistens muss dazu ein Programm zum benutzen des Sticks installiert werden (Modem Software). Diese Software darf nicht gestartet sein, wenn man per selbstgeschriebenem Programm SMS versenden möchte, da sie COM Ports blockiert.
Herausfinden welchen COM Port der Stick benutzt
Nachdem man die Treiber für den Stick installiert hat, findet man in dem Geräte-Manager mind. 1 neues serielles Gerät (bei meinem blau.de Surfstick werden 2 Geräte installiert, das GSM Modem und ein Service/Diagnose Anschluss).

Um herauszufinden welcher COM Port benutzt werden muss, öffnet man den COM Port per Telnet Programm und schickt eine Testnachricht hin. Am einfachsten benutzt man dazu PuTTY.
Als Connection Type Serial benutzen und COM Port angeben.
Wenn die Sitzung geöffnet ist schickt man die Nachricht AT. Danach sollte man die Antwort OK erhalten und weiß somit auf welchem Port das GSM Modem läuft
.

Sollte keine Eingabe möglich sein oder kein OK zurückgegeben werden muss man einen anderen COM Port öffnen.
AT Commands
Handys und GSM Modems arbeiten mit AT Commands um auf die Mobilfunkfunktionen zuzugreifen. AT steht dabei für Attention.
Ein Command ist sehr simple aufgebaut: AT+Command Parameter.
Als Antwort bekommt man immer ein OK zurück geliefert bzw ein ERROR wenn etwas schief ging.
Ein einfaches Beispiel:
AT+CPIN="1234" Mit diesem Befehl gibt man seine PIN ein.
Zum senden von SMS benötigt man die folgenden Befehle:
- AT
Prüfen ob Modem bereit ist
- AT+CPIN=?
Prüfen ob PIN, PUK, PIN2 oder PUK2 benötigt wird
- AT+CPIN="1234"
Eingabe von PIN, PUK, PIN2 oder PUK2
- AT+CMGF=1
Setzt das SMS Format in den Text Modus
- AT+CMGS="+yyyyy" [Enter]
> Das ist eine Nachricht [STRG-Z]
Sendet eine SMS Nachricht ohne sie im Speicher zu hinterlegen
Diese Befehle kann man auch super in einer Telnet Session ausprobieren
Wer mehr Informationen zu AT Commands haben möchte, dem kann ich diese Ressourcen ans Herz legen:
SMS mit .NET versenden
Jetzt sind alle nötigen Informationen gesammelt und man kann mittels .NET einen SerielPort öffnen. Die Einstellungen sind prizipiell egal und können auf dem Stardard stehen bleiben. Einzigst der COM Port, der benutzt werden soll muss eingestellt werden.
// using System.IO.Ports; nicht vergessen in die Klasse aufzunehmen
SerialPort comport = new SerialPort("COM5");
Als nächstes schickt man dann den Befehl AT um zu sehen ob das GSM Modem bereit ist.
Da man warten muss bis das Modem eine Antwort zurück gibt, lässt man den Thread in dem der Code läuft ein paar ms warten.
comPort.Write("AT" + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
Die Methode ReadComPort( ) vereinfacht das lesen und löscht die Puffer des SerialPort, damit man nicht immer alles per Code wiederholen muss.
private string ReadComPort()
{
try
{
return comPort.ReadExisting();
}
catch
{
return "ERROR";
}
finally
{
comPort.DiscardInBuffer();
comPort.DiscardOutBuffer();
}
}
Ein weiteres kleines Helferlein ist die Struktur SpacialCharacter mit der ich benötigte Schriftzeichen an den SerialPort schicken kann.
public struct SpecialCharacter
{
public static string CtrlZ = ((char)26).ToString();
public static string Return = Environment.NewLine;
}
Nun muss man die PIN Nummer eingeben. Dazu schickt man den Befehl AT+CPIN="1234" ab. Ich gehe jetzt davon aus, dass der PIN 1234 lautet
.
comPort.Write("AT+CPIN="1234"" + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
Gibt das GSM Modem in diesem Schritt wieder OK zurück, kommen wir zum dritten Schritt.
Das setzen des Text Modus mittels AT+CMGF=1.
comPort.Write("AT+CMGF=1" + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
Quittiert das Modem den Befehl wiedermal mit OK kommt der finale Schritt. Das senden einer SMS!!!
Der Befehl zum Senden einer SMS ohne die SMS im Speicher zu hinterlegen lautet AT+CMGS="+yyyyy" bei der man die Nummer des Empfängers angibt. Nach dem Absenden des Befehls erwartet das Modem eine weiter Eingabe, die Nachricht, die mit STRG+z bestätigt werden muss.
comPort.Write("AT+CMGS="" + PhoneNumber + """ + SpecialCharacter.Return);
Thread.Sleep(10);
comPort.Write(Message + SpecialCharacter.CtrlZ);
Thread.Sleep(50);
string answer = ReadComPort();
Ist die Antwort OK hat man eine SMS verschickt und kann sich auf den Empfang freuen
Schön verpacken
Damit der gesamte Ablauf etwas handlicher wird, sollte man den kompletten Vorgang um eine SMS zu verschicken in eine Klasse.
Bei mir kam folgendes heraus:
using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
namespace EasySms
{
public class GsmModem : IDisposable
{
private string comPortName;
private int baudRate;
private Parity parity;
private int dataBits;
private StopBits stopBits;
private SerialPort comPort;
public GsmModem()
{
this.comPortName = string.IsNullOrEmpty(SerialPort.GetPortNames()[0]) ? "COM1" : SerialPort.GetPortNames()[0];
this.baudRate = 115200;
this.parity = Parity.None;
this.dataBits = 8;
this.stopBits = StopBits.One;
this.SetUpComport();
this.comPort.Open();
}
public GsmModem(string ComPortName)
{
this.comPortName = ComPortName;
this.baudRate = 115200;
this.parity = Parity.None;
this.dataBits = 8;
this.stopBits = StopBits.One;
this.SetUpComport();
this.comPort.Open();
}
public GsmModem(string ComPortName, int BaudRate, Parity ComParity, int DataBits, StopBits ComStopBits)
{
this.comPortName = ComPortName;
this.baudRate = BaudRate;
this.parity = ComParity;
this.dataBits = DataBits;
this.stopBits = ComStopBits;
this.SetUpComport();
this.comPort.Open();
}
public bool ModemReady
{
get
{
comPort.Write("AT" + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
if (answer.ToLower().Contains("ok"))
return true;
else
return false;
}
}
public bool SimReady
{
get
{
comPort.Write("AT+CPIN=?" + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
if (answer.ToLower().Contains("ready"))
return true;
else
return false;
}
}
public SimState ModemSimState
{
get
{
comPort.Write("AT+CPIN=?" + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
string pre = "+cpin: ";
if (answer.ToLower().Contains(pre + "ready"))
return SimState.READY;
else if (answer.ToLower().Contains(pre + "sim pin"))
return SimState.SIM_PIN;
else if (answer.ToLower().Contains(pre + "sim puk"))
return SimState.SIM_PUK;
else if (answer.ToLower().Contains(pre + "sim pin2"))
return SimState.SIM_PIN2;
else if (answer.ToLower().Contains(pre + "sim puk2"))
return SimState.SIM_PUK2;
else if (answer.ToLower().Contains(pre + "ph-sim pin"))
return SimState.PH_SIM_PIN;
else if (answer.ToLower().Contains(pre + "blocked"))
return SimState.BLOCKED;
else
return SimState.ERROR;
}
}
public Encoding ModemEncoding
{
get { return this.comPort.Encoding; }
}
private string ReadComPort()
{
try
{
return comPort.ReadExisting();
}
catch
{
return "ERROR";
}
finally
{
comPort.DiscardInBuffer();
comPort.DiscardOutBuffer();
}
}
private void SetUpComport()
{
this.comPort = new SerialPort(this.comPortName, this.baudRate, this.parity, this.dataBits, this.stopBits);
this.comPort.ErrorReceived += new SerialErrorReceivedEventHandler(comPort_ErrorReceived);
this.comPort.Encoding = Encoding.Default;
this.comPort.NewLine = SpecialCharacter.Return;
this.comPort.ReadTimeout = 2500;
this.comPort.WriteTimeout = 2500;
}
private void comPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
throw new GsmModemException("Error on " + this.comPortName, e.EventType);
}
public string ExecuteATCommand(string Command)
{
comPort.Write(Command);
Thread.Sleep(50);
return ReadComPort();
}
public ModemAnswer SetTextMode(SmsTextMode TextMode)
{
comPort.Write("AT+CMGF=" + ((int)TextMode).ToString() + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
if (answer.ToLower().Contains("ok"))
return ModemAnswer.OK;
else
return ModemAnswer.ERROR;
}
public ModemAnswer SetPin(string Pin)
{
comPort.Write("AT+CPIN="" + Pin + """ + SpecialCharacter.Return);
Thread.Sleep(50);
string answer = ReadComPort();
if (answer.ToLower().Contains("ok"))
return ModemAnswer.OK;
else
return ModemAnswer.ERROR;
}
public ModemAnswer SendSms(string PhoneNumber, string Message)
{
comPort.Write("AT+CMGS="" + PhoneNumber + """ + SpecialCharacter.Return);
Thread.Sleep(10);
comPort.Write(Message + SpecialCharacter.CtrlZ);
Thread.Sleep(50);
string answer = ReadComPort();
if (answer.ToLower().Contains("ok"))
return ModemAnswer.OK;
else
return ModemAnswer.ERROR;
}
public ModemAnswer SendSms(string PhoneNumber, string Message, out string CompleteModemAnswer)
{
comPort.Write("AT+CMGS="" + PhoneNumber + """ + SpecialCharacter.Return);
Thread.Sleep(10);
comPort.Write(Message + SpecialCharacter.CtrlZ);
Thread.Sleep(50);
string answer = ReadComPort();
CompleteModemAnswer = answer;
if (answer.ToLower().Contains("ok"))
return ModemAnswer.OK;
else
return ModemAnswer.ERROR;
}
#region IDisposable Member
public void Dispose()
{
if (this.comPort != null && this.comPort.IsOpen)
{
this.comPort.Close();
}
this.comPort.Dispose();
}
#endregion
}
}
namespace EasySms
{
public enum ModemAnswer
{
OK,
ERROR
}
public enum SmsTextMode
{
PDU = 0,
TEXT = 1
}
public enum SimState
{
READY,
SIM_PIN,
SIM_PUK,
SIM_PIN2,
SIM_PUK2,
PH_SIM_PIN,
BLOCKED,
ERROR
}
}
Durch diese kleine Klasse vereinfacht sich das SMS senden auf folgende paar Zeilen:
using (GsmModem modem = new GsmModem("COM11"))
{
if (modem.ModemReady)
{
if (modem.ModemSimState == SimState.READY)
{
string modemAnswer;
ModemAnswer answer;
modem.SetTextMode(SmsTextMode.TEXT);
answer = modem.SendSms("+49160xxxxxxx", "Test von meiner Assembly", out modemAnswer);
if (answer == ModemAnswer.OK)
{
Console.WriteLine("yeah!!!");
Console.WriteLine(modemAnswer);
}
else
{
Console.WriteLine("och nööö!!!!");
Console.WriteLine(modemAnswer);
}
}
}
}
Das komplette Projekt kann man sich hier herunterladen:
EasySms.zip
Ich denke es ist eine gute Basis und kann leicht erweitet werden.