SMS küldés PC-ről Bizonyos esetekben szükség lehet arra, hogy a számítógépről küldjünk SMS-t. Ilyen helyzet lehet az, amikor egy bank szeretné értesíteni az ügyfelét a számlamozgásairól, vagy meg akar bizonyosodni arról, hogy valóban az ügyfele, és nem valaki más kezdeményezett egy tranzakciót. Szükségét érezhetjük egy ilyen technológiai megoldásnak akkor is, amikor viszonylag sok szöveget, esetleg sok címzettnek akarunk elküldeni mi magunk SMS-ként. Erre kínál egy megoldást ez az esszé. Nem közöl kész programot, hanem egy lehetséges program részletein keresztül ismertet egy megoldást. Tegyük fel, hogy rendelkezésünkre állnak a következők: - Egy olyan mobiltelefon, mely támogatja az AT parancsokat (Sony Ericsson T630); - Windows XP operációs rendszerű számítógép, fölszerelve Microsoft Visual C# 2005 Express Editionnal; - Csatlakozási lehetőség a telefonhoz soros porton keresztül. Ezt megoldhatjuk vezetékkel, infravörös vagy bluetooth kapcsolattal. Csatlakoztassuk a telefont a PC-hez, és tudjuk meg, hogy melyik soros portokra csatlakozott a telefon. Válasszuk azt a portot, amely (a telefon felé) kimenő adatforgalomra való. A továbbiakban ezt a portot hívjuk COM38-nak. Ismerkedés gyanánt, mielőtt nekilátnánk a program áttekintésének, tegyünk egy próbát, próbáljunk a HyperTerminalról küldeni egy SMS-t! Nyissuk meg a HyperTerminal programot (Start menü >> Kellékek >> Kommunikáció >> HyperTerminal), és kapcsolódjunk a COM38- hoz. Miután létrejött a kapcsolat, a következőképpenn tudjuk letesztelni. (Az általunk kiadandó parancsot vastag betűvel, a telefon válaszát normál betűvel szedtem.) AT OK AT+CMGF=1 OK // A telefon készen áll a kommunikációra. Ezt a parancsot // nem fogjuk a továbbiakban kiadni, amikor már biztosak // vagyunk benne, hogy működik. // Szöveges módra kapcsoljuk a telefont. AT+CSCA="+36309888000" // Ez a T-Mobile SMS szolgáltatója. OK // Pannon: +36209300099, Vodafone: +36709996500 AT+CMGS="1234567" TESZTuzenet // A telefonszám, ahová az SMS-t küldjük. Enter // után kezdhetjük gépelni az SMS szövegét, // melyet control+z -vel (fájlvége jellel) // indíthatunk el. Most pedig lássuk neki a program megírásának. A.NET 2.0 biztosít egy eléggé egyszerű felületet a soros portokkal való kommunikációhoz. C# nyelven a következőképpen érdemes eljárnunk. A jelek helyén tetszőlegesen sok, további kód állhat. A példa nem foglalkozik a telefon üzeneteinek fogadásával, sem a lehetséges program más részeivel, hanem kizárólag a
küldést mutatja be. A telefon válaszainak figyelése fontos lehet. Ezt a követkeőképpen tehetjük meg. Miután megnyitottuk a portot adjunk egy SerialDataReceivedEventHandler-t a porthoz: sp.datareceived += new SerialDataReceivedEventHandler(AsynchronousReader); A következőképpen kezelhetjük az üzeneteket: private void AsynchronousReader(object sender, SerialDataReceivedEventArgs e) // reads all the bytes available in the SerialPort, // and puts it on a string inbuffer = inbuffer + sp.readexisting(); Most pedig következzen az SMS küldése! using System.IO.Ports; SerialPort sp; String message; // ebben a stringeben van az elküldendő üzenet // Ezzel a fügvénnyel gondoskodhatunk róla, // hogy ne legyenek ékezetek a szövegben message = withoutaccents(message); // A függelékben megtalálható a messagepdu osztály. // Az a feladata, hogy a feladatot átalakítsa PDU formátumuvá. // Ezáltal több típusú telefonnal fog együttműködni a programunk. int actuallength; string messageinpdu = SubmitPdu.GetPdu(number, message, servicecenter, out actuallength); try sp = new SerialPort("COM38", 4800, Parity.None, 8, StopBits.One); sp.open(); sp.readtimeout = 500; // Átváltunk PDU módba, ha esetleg text módban let volna a telefon. // Majdnem minden parancsot a kocsivissza karakterrel zárunk. sp.write("at+cmgf=0\r"); // Kiadjuk a "Küld SMS-t parancsot", ezúttal PDU módban. // Ekkor nem a telefonszámot kell a AT+CMGS= után írnunk, // mint a Hyperterminal-ban, hanem az üzenet hosszát. A // PDU formátumú üzenet tartalmazza a telefonszámot is. sp.write("at+cmgs=" + actuallength +"\r"); // Kicsit altatjuk a programot, hogy legyen ideje a // telefonnak feldolgozni a parancsot. System.Threading.Thread.Sleep(1000); // Most kiküldjük magát az üzenetet, amely actuallength hosszúságú. sp.write(messageinpdu); System.Threading.Thread.Sleep(1500); // Ezt a parancsot nem kocsivissza, hanem fájlvége // karakterrel kell lezárnunk. byte[] b = 0x1A ;
sp.write(b, 0, 1); System.Threading.Thread.Sleep(1500); sp.close(); catch (System.Exception ex) Console.Out.WriteLine("Hiba tortent sms kudes kozben: " + ex); Répássy László Irodalom http://msdn2.microsoft.com/en-us/library/30swa673(vs.80).aspx http://forums.microsoft.com/msdn/ Függelék Az SMS üzenet PDU formátummá alakítása using System; using System.Collections.Generic; using System.Collections; using System.Text; namespace smssend public class SubmitPdu /* * Encodes an SMS text message into the PDU format. * * <param name="number">the recipient's phone number (destination address)</param> * <param name="message">the message text (user data text)</message> * <param name="smscaddress">number of the service center. If an * empty string, address stored in the phone will be used.</param> * <param name="actuallength">receives the actual length of the PDU data.</param> * <returns>the encoded message</returns> */ public static string GetPdu(string number, string message, string smscaddress, out int actuallength) const byte internationalformat = 0x91; // telephone/isdn, international format const byte unknownformat = 0x81; // telephone/isdn, unknown format // Determine destination address type byte numberaddresstype; string usenumber; if (number.startswith("+")) numberaddresstype = internationalformat; usenumber = number.substring(1); // strip initial "+" character else numberaddresstype = unknownformat; usenumber = number; // Determine SMSC address type byte smscaddresstype; string usesmscaddress; if (smscaddress.startswith("+"))
character smscaddresstype = internationalformat; usesmscaddress = smscaddress.substring(1); // strip initial "+" else smscaddresstype = unknownformat; usesmscaddress = smscaddress; // Encode the user data text byte datalength; byte[] encodedtext = EncodeText(message, out datalength); // Encode the SMSC address string encodedsmsc = EncodeSmscAddress(useSmscAddress, smscaddresstype); // Create the PDU string StringBuilder builder = new StringBuilder(); builder.append(inttohex(0x11)); // message flags: // msg type=sms-submit-pdu, // validity=relative builder.append(inttohex(0x00)); // Message Reference, 0=set by device builder.append(encodedestinationaddress(usenumber, numberaddresstype)); builder.append(inttohex(0x00)); // Protocol ID, 0=SME-to-SME builder.append(inttohex(0x00)); // Data Coding Scheme, 0=7-Bit default builder.append(inttohex(0xa7)); // Relative Validity, 0xA7=24 hrs. builder.append(inttohex(datalength)); builder.append(inttohex(encodedtext)); actuallength = builder.length / 2; builder.insert(0, encodedsmsc); return builder.tostring(); private static string EncodeSmscAddress(string address, byte addresstype) if (address.length == 0) return IntToHex(0); string encoded = EncodeSemiOctets(address); // count bytes byte lenbytes = (byte)((encoded.length / 2) + 1); // plus prefix byte return IntToHex(lenBytes) + IntToHex(addressType) + encoded; private static string EncodeDestinationAddress(string address, byte addresstype) if (address.length == 0) // special case: specify length 0 BUT include the type! return IntToHex(0) + IntToHex(addressType); // count characters byte lenchars = (byte)address.length; // count original characters string encoded = EncodeSemiOctets(address); return IntToHex(lenChars) + IntToHex(addressType) + encoded; private static byte[] EncodeText(string data, out byte datalength) const int maxseptets = 160; data = StringTo7Bit(data); int length = data.length; if (data.length > maxseptets) throw new ArgumentException("Text is too long. A maximum of " + maxseptets.tostring() + " septets is allowed. " + length.tostring() + " were passed."); datalength = (byte)length; return SeptetsToOctetsInt(data);
// BCD operations /// Swaps the semi-octets of a BCD encoded string. /// <param name="data">the string to convert.</param> /// <para>if the string is not of even length, it is padded with a /// hexadecimal "F" before converting.</para> /// <para>this method does not verify the actual contents of the string.</para> /// <example> /// <param>a string containing "12345678" will become "21436587".</param> /// <param>a string containing "1234567" will become "214365F7".</param> /// </example> private static string EncodeSemiOctets(string data) if (data.length % 2!= 0) data += "F"; // Pad address with an "F" to make it even length string swapped = string.empty; for (int i = 0; i < data.length; i += 2) swapped += data.substring(i + 1, 1) + data.substring(i, 1); return swapped; // Some numeric conversions private static char[] hexdigits = '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'; /// Converts a byte array into its hexadecimal representation (BCD encoding). /// <param name="bytes">the byte array to convert.</param> private static string IntToHex(byte[] bytes) char[] chars = new char[bytes.length * 2]; for (int i = 0; i < bytes.length; i++) int b = bytes[i]; chars[i * 2] = hexdigits[b >> 4]; chars[i * 2 + 1] = hexdigits[b & 0xF]; return new string(chars); /// Converts a byte into its BCD (hexadecimal) representation. /// <param name="b">the byte to convert.</param> private static string IntToHex(byte b) return hexdigits[b >> 4].ToString() + hexdigits[b & 0xF].ToString(); /// Converts a bit string into a byte. /// <param name="s">the string to convert.</param> private static byte BinToInt(string s) return Convert.ToByte(s, 2); /// Converts a byte into a bit string.
/// <param name="b">the byte to convert.</param> /// <param name="size"> /// The final length the string should have. If the resulting string is /// shorter than this value, it is padded with leading zeroes. /// </param> private static string IntToBin(byte b, byte size) return Convert.ToString(b, 2).PadLeft(size, '0'); // Text data conversion /// Converts a character from the ISO-8859-1 character set /// into the corresponding character of the GSM "7-bit default alphabet" /// character set. /// <param name="c">the character to convert.</param> /// <returns>a string containing the converted character.</returns> /// A string is returned instead of a character because some characters /// must be escaped, and consist then of two characters instead of one. private static string CharTo7Bit(char c) byte retval; bool escape = false; switch (c) // Characters not listed here are equal to those in the // ISO-8859-1 charset OR not present in it. case '@': retval = 0; break; case ' ': retval = 1; break; case '$': retval = 2; break; case ' ': retval = 3; break; case 'è': retval = 4; break; case 'é': retval = 5; break; case 'ú': retval = 6; break; case 'ì': retval = 7; break; case 'ò': retval = 8; break; case 'Ç': retval = 9; break; case 'Ø': retval = 11; break; case 'ø': retval = 12; break; case 'Å': retval = 14; break; case 'å': retval = 15; break; case '_': retval = 17; break; case 'Æ': retval = 28; break; case 'æ': retval = 29; break; case 'ß': retval = 30; break; case 'É': retval = 31; break; case ' ': retval = 36; break; // 164 in ISO-8859-1 case ' ': retval = 64; break; // 65-90 capital letters case 'Ä': retval = 91; break; case 'Ö': retval = 92; break; case 'Ñ': retval = 93; break; case 'Ü': retval = 94; break; case ' ': retval = 95; break; case ' ': retval = 96; break; // 97-122 small letters case 'ä': retval = 123; break; case 'ö': retval = 124; break; case 'ñ': retval = 125; break; case 'ü': retval = 126; break; case 'à': retval = 127; break; // extension table case '\f': retval = 10; escape = true; break; // form feed, 0x0C case '^': retval = 20; escape = true; break; case '': retval = 40; escape = true; break; case '': retval = 41; escape = true; break;
case '\\': retval = 47; escape = true; break; case '[': retval = 60; escape = true; break; case '~': retval = 61; escape = true; break; case ']': retval = 62; escape = true; break; case ' ': retval = 64; escape = true; break; case ' ': retval = 101; escape = true; break; // 164 in ISO-8859-15 default: retval = (byte)c; break; return (escape? Convert.ToChar(0x1B).ToString() : "") + Convert.ToChar(retval).ToString(); /// Converts a string consisting of characters from the ISO-8859-1 /// character set into a string of corresponding characters of the /// GSM "7-bit default alphabet" character set. /// <param name="s">the string to convert.</param> /// <returns>the converted string.</returns> /// Note that the converted string does not need to have the same /// length as the original one because some characters may be escaped. private static string StringTo7Bit(string s) string newstring = string.empty; for (int i = 0; i < s.length; i++) newstring += CharTo7Bit(s.Substring(i, 1)[0]); return newstring; /// Compacts a string of septets into octets. /// <par>when only 7 of 8 available bits of a character are used, 1 bit is /// wasted per character. This method compacts a string of characters /// which consist solely of such 7-bit characters.</par> /// <par>effectively, every 8 bytes of the original string are packed into /// 7 bytes in the resulting string.</par> private static byte[] SeptetsToOctetsInt(string data) ArrayList output = new ArrayList(); string octetsecond = string.empty; for (int i = 0; i < data.length; i++) string current = IntToBin((byte)data[i], 7); if (i!= 0 && i % 8!= 0) string octetfirst = current.substring(7 - i % 8); string currentoctet = octetfirst + octetsecond; output.add(bintoint(currentoctet)); octetsecond = current.substring(0, 7 - i % 8); if (i == data.length - 1 && octetsecond!= string.empty) output.add(bintoint(octetsecond)); byte[] array = new byte[output.count]; output.copyto(array); return array;