diff --git a/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs b/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs index 9ed155b..3386fab 100644 --- a/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs +++ b/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs @@ -2,7 +2,8 @@ using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Events; using HeboTech.ATLib.Modems; -using HeboTech.ATLib.Modems.D_LINK; +using HeboTech.ATLib.Modems.Cinterion; +using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System; using System.Collections.Generic; @@ -19,7 +20,7 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) using AtChannel atChannel = AtChannel.Create(stream); //atChannel.EnableDebug((string line) => Console.WriteLine(line)); - using IModem modem = new DWM222(atChannel); + using IMC55i modem = new MC55i(atChannel); atChannel.Open(); await atChannel.ClearAsync(); @@ -32,23 +33,23 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) modem.ErrorReceived += Modem_ErrorReceived; modem.GenericEvent += Modem_GenericEvent; - // Configure modem with required settings - await modem.SetRequiredSettingsAsync(); - - await modem.SetSmsMessageFormatAsync(smsTextFormat); + // Configure modem with required settings before PIN + var requiredSettingsBeforePin = await modem.SetRequiredSettingsBeforePinAsync(); + Console.WriteLine($"Successfully set required settings before PIN: {requiredSettingsBeforePin}"); + await Task.Delay(TimeSpan.FromSeconds(2)); var simStatus = await modem.GetSimStatusAsync(); Console.WriteLine($"SIM Status: {simStatus}"); - await modem.ReInitializeSimAsync(); + //await modem.ReInitializeSimAsync(); simStatus = await modem.GetSimStatusAsync(); Console.WriteLine($"SIM Status: {simStatus}"); - if (simStatus.IsSuccess && simStatus.Result == SimStatus.SIM_READY) + if (simStatus.Success && simStatus.Result == SimStatus.SIM_READY) { } - else if (simStatus.IsSuccess && simStatus.Result == SimStatus.SIM_PIN) + else if (simStatus.Success && simStatus.Result == SimStatus.SIM_PIN) { var simPinStatus = await modem.EnterSimPinAsync(new PersonalIdentificationNumber(pin)); Console.WriteLine($"SIM PIN Status: {simPinStatus}"); @@ -57,9 +58,9 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) { simStatus = await modem.GetSimStatusAsync(); Console.WriteLine($"SIM Status: {simStatus}"); - if (simStatus.IsSuccess && simStatus.Result == SimStatus.SIM_READY) + if (simStatus.Success && simStatus.Result == SimStatus.SIM_READY) break; - await Task.Delay(TimeSpan.FromMilliseconds(1000)); + await Task.Delay(TimeSpan.FromSeconds(1)); } } else @@ -68,21 +69,33 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) return; } + await Task.Delay(TimeSpan.FromSeconds(2)); + for (int i = 0; i < 10; i++) { var imsi = await modem.GetImsiAsync(); Console.WriteLine($"IMSI: {imsi}"); - if (imsi.IsSuccess) + if (imsi.Success) break; await Task.Delay(TimeSpan.FromMilliseconds(1000)); } + // Configure modem with required settings after PIN + var requiredSettingsAfterPin = await modem.SetRequiredSettingsAfterPinAsync(); + Console.WriteLine($"Successfully set required settings after PIN: {requiredSettingsAfterPin}"); + + var smsMessageFormat = await modem.SetSmsMessageFormatAsync(smsTextFormat); + Console.WriteLine($"Setting SMS message format: {smsMessageFormat}"); + var signalStrength = await modem.GetSignalStrengthAsync(); Console.WriteLine($"Signal Strength: {signalStrength}"); var batteryStatus = await modem.GetBatteryStatusAsync(); Console.WriteLine($"Battery Status: {batteryStatus}"); + //var mc55iBatteryStatus = await modem.MC55i_GetBatteryStatusAsync(); + //Console.WriteLine($"MC55i Battery Status: {mc55iBatteryStatus}"); + var productInfo = await modem.GetProductIdentificationInformationAsync(); Console.WriteLine($"Product Information:{Environment.NewLine}{productInfo}"); @@ -92,34 +105,21 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) var dateTime = await modem.GetDateTimeAsync(); Console.WriteLine($"Date and time: {dateTime}"); - var newSmsIndicationResult = await modem.SetNewSmsIndication(2, 1, 0, 0, 0); + var newSmsIndicationResult = await modem.SetNewSmsIndication(2, 1, 0, 0, 1); Console.WriteLine($"Setting new SMS indication: {newSmsIndicationResult}"); var supportedStorages = await modem.GetSupportedPreferredMessageStoragesAsync(); Console.WriteLine($"Supported storages:{Environment.NewLine}{supportedStorages}"); var currentStorages = await modem.GetPreferredMessageStoragesAsync(); Console.WriteLine($"Current storages:{Environment.NewLine}{currentStorages}"); - var setPreferredStorages = await modem.SetPreferredMessageStorageAsync("ME", "ME", "ME"); + var setPreferredStorages = await modem.SetPreferredMessageStorageAsync(MessageStorage.SM, MessageStorage.SM, MessageStorage.SM); Console.WriteLine($"Storages set:{Environment.NewLine}{setPreferredStorages}"); - //var singleSms = await modem.ReadSmsAsync(2, smsTextFormat); - //Console.WriteLine($"Single SMS: {singleSms}"); - - var smss = await modem.ListSmssAsync(SmsStatus.ALL); - if (smss.IsSuccess) - { - foreach (var sms in smss.Result) - { - Console.WriteLine($"SMS: {sms}"); - var smsDeleteStatus = await modem.DeleteSmsAsync(sms.Index); - Console.WriteLine($"Delete SMS #{sms.Index} - {smsDeleteStatus}"); - } - } - - Console.WriteLine("Done. Press 'a' to answer call, 'd' to dial, 'h' to hang up, 's' to send SMS, 'r' to read an SMS, 'u' to send USSD code, '+' to enable debug, '-' to disable debug and 'q' to exit..."); + Console.WriteLine("Done. Press 'a' to answer call, 'd' to dial, 'h' to hang up, 's' to send SMS, 'r' to read an SMS, 'l' to list all SMSs, 'u' to send USSD code, '+' to enable debug, '-' to disable debug and 'q' to exit..."); ConsoleKey key; while ((key = Console.ReadKey().Key) != ConsoleKey.Q) { + Console.WriteLine(); switch (key) { case ConsoleKey.A: @@ -186,6 +186,13 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) var ussdResult = await modem.SendUssdAsync(ussd); Console.WriteLine($"USSD Status: {ussdResult}"); break; + case ConsoleKey.L: + Console.WriteLine("List all SMSs:"); + var smss = await modem.ListSmssAsync(SmsStatus.ALL); + if (smss.Success) + foreach (var sms in smss.Result) + Console.WriteLine($"SMS: {sms}"); + break; case ConsoleKey.OemPlus: atChannel.EnableDebug((string line) => Console.WriteLine(line)); Console.WriteLine("Debug enabled"); @@ -210,7 +217,8 @@ private static void Modem_ErrorReceived(object sender, ErrorEventArgs e) private static void Modem_UssdResponseReceived(object sender, UssdResponseEventArgs e) { - Console.WriteLine($"USSD Response: {e.Status} - {e.Response} - ({e.CodingScheme})"); + if (e != null) + Console.WriteLine($"USSD Response: {e.Status} - {e.Response} - ({e.CodingScheme})"); } private static void Modem_CallEnded(object sender, CallEndedEventArgs e) diff --git a/src/HeboTech.ATLib.TestConsole/Program.cs b/src/HeboTech.ATLib.TestConsole/Program.cs index 2d4cbad..38e515e 100644 --- a/src/HeboTech.ATLib.TestConsole/Program.cs +++ b/src/HeboTech.ATLib.TestConsole/Program.cs @@ -34,22 +34,22 @@ static async Task Main(string[] args) /* ######## UNCOMMENT THIS SECTION TO USE SERIAL PORT ######## */ - using SerialPort serialPort = new("COM1", 9600, Parity.None, 8, StopBits.One) - { - Handshake = Handshake.RequestToSend - }; - serialPort.Open(); - Console.WriteLine("Serialport opened"); - Stream stream; - stream = serialPort.BaseStream; + //using SerialPort serialPort = new("COM1", 9600, Parity.None, 8, StopBits.One) + //{ + // Handshake = Handshake.RequestToSend + //}; + //serialPort.Open(); + //Console.WriteLine("Serialport opened"); + //Stream stream; + //stream = serialPort.BaseStream; /* ######## UNCOMMENT THIS SECTION TO USE NETWORK SOCKET ######## */ - //using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - //socket.Connect("192.168.1.144", 7000); - //Console.WriteLine("Network socket opened"); - //Stream stream; - //stream = new NetworkStream(socket); + using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.Connect("192.168.1.144", 7000); + Console.WriteLine("Network socket opened"); + Stream stream; + stream = new NetworkStream(socket); // ### Choose what to run diff --git a/src/HeboTech.ATLib.TestConsole/StressTest.cs b/src/HeboTech.ATLib.TestConsole/StressTest.cs index 109d988..0e76688 100644 --- a/src/HeboTech.ATLib.TestConsole/StressTest.cs +++ b/src/HeboTech.ATLib.TestConsole/StressTest.cs @@ -64,7 +64,7 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) Console.WriteLine($"Single SMS: {singleSms}"); var smss = await modem.ListSmssAsync(SmsStatus.ALL); - if (smss.IsSuccess) + if (smss.Success) { foreach (var sms in smss.Result) { diff --git a/src/HeboTech.ATLib/CodingSchemes/UCS2.cs b/src/HeboTech.ATLib/CodingSchemes/UCS2.cs index b45078f..ac8974c 100644 --- a/src/HeboTech.ATLib/CodingSchemes/UCS2.cs +++ b/src/HeboTech.ATLib/CodingSchemes/UCS2.cs @@ -12,8 +12,6 @@ namespace HeboTech.ATLib.CodingSchemes /// public class UCS2 { - public const CodingScheme DataCodingSchemeCode = CodingScheme.UCS2; - /// /// Encode to UCS2 /// diff --git a/src/HeboTech.ATLib/DTOs/PreferredMessageStorage.cs b/src/HeboTech.ATLib/DTOs/PreferredMessageStorage.cs index bc463b8..6ed2df6 100644 --- a/src/HeboTech.ATLib/DTOs/PreferredMessageStorage.cs +++ b/src/HeboTech.ATLib/DTOs/PreferredMessageStorage.cs @@ -1,9 +1,16 @@ -using System; +using HeboTech.ATLib.Modems.Generic; +using System; namespace HeboTech.ATLib.DTOs { public class PreferredMessageStorages { + /// + /// + /// + /// Storage area to be used when reading or deleting SMS messages + /// Storage area to be used when sending SMS messages from message storage or writing SMS messages + /// Storage area to be used when storing newly received SMS messages public PreferredMessageStorages( PreferredMessageStorage storage1Name, PreferredMessageStorage storage2Name, @@ -27,22 +34,31 @@ public override string ToString() } } + public class PreferredMessageStorage { - public PreferredMessageStorage(string storage1Name, int storage1Messages, int storage1MessageLocations) + /// + /// SM: SIM card storage area + /// ME: Modem storage area + /// MT: All storage combined + /// BM: Broadcast message storage area + /// SR: Status report storage area + /// TA: Terminal adaptor storage area + /// + public PreferredMessageStorage(MessageStorage storageName, int storageMessages, int storageMessageLocations) { - Storage1Name = storage1Name; - Storage1Messages = storage1Messages; - Storage1MessageLocations = storage1MessageLocations; + StorageName = storageName; + StorageMessages = storageMessages; + StorageMessageLocations = storageMessageLocations; } - public string Storage1Name { get; } - public int Storage1Messages { get; } - public int Storage1MessageLocations { get; } + public MessageStorage StorageName { get; } + public int StorageMessages { get; } + public int StorageMessageLocations { get; } public override string ToString() { - return $"Name:{Storage1Name}, Messages:{Storage1Messages}, Locations:{Storage1MessageLocations}"; + return $"Name:{StorageName}, Messages:{StorageMessages}, Locations:{StorageMessageLocations}"; } } } diff --git a/src/HeboTech.ATLib/DTOs/SmsStatus.cs b/src/HeboTech.ATLib/DTOs/SmsStatus.cs index 84444eb..ed22813 100644 --- a/src/HeboTech.ATLib/DTOs/SmsStatus.cs +++ b/src/HeboTech.ATLib/DTOs/SmsStatus.cs @@ -5,11 +5,11 @@ namespace HeboTech.ATLib.DTOs { public enum SmsStatus { - REC_UNREAD, - REC_READ, - STO_UNSENT, - STO_SENT, - ALL + REC_UNREAD = 0, + REC_READ = 1, + STO_UNSENT = 2, + STO_SENT = 3, + ALL = 4 } public static class SmsStatusHelpers @@ -23,11 +23,6 @@ public static class SmsStatusHelpers { SmsStatus.STO_UNSENT, "STO UNSENT" }, }; - public static SmsStatus ToSmsStatus(int statusCode) - { - return LUT.First(x => (int)x.Key == statusCode).Key; - } - public static SmsStatus ToSmsStatus(string text) { return LUT.First(x => x.Value == text).Key; diff --git a/src/HeboTech.ATLib/Events/UssdResponseEventArgs.cs b/src/HeboTech.ATLib/Events/UssdResponseEventArgs.cs index da0abbb..2b38229 100644 --- a/src/HeboTech.ATLib/Events/UssdResponseEventArgs.cs +++ b/src/HeboTech.ATLib/Events/UssdResponseEventArgs.cs @@ -17,7 +17,7 @@ public UssdResponseEventArgs(int status, string response, int codingScheme) public static UssdResponseEventArgs CreateFromResponse(string response) { - var match = Regex.Match(response, @"\+CUSD:\s(?\d),""(?(?s).*)"",(?\d+)"); + var match = Regex.Match(response, @"\+CUSD:\s(?\d)(,""(?(?s).*)"",(?\d+))?"); if (match.Success) { int status = int.Parse(match.Groups["status"].Value); diff --git a/src/HeboTech.ATLib/Exceptions/CmeException.cs b/src/HeboTech.ATLib/Exceptions/CmeException.cs deleted file mode 100644 index 5651abe..0000000 --- a/src/HeboTech.ATLib/Exceptions/CmeException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace HeboTech.ATLib.Exceptions -{ - public class CmeException : Exception - { - public CmeException(string message) : base(message) - { - } - } -} diff --git a/src/HeboTech.ATLib/HeboTech.ATLib.csproj b/src/HeboTech.ATLib/HeboTech.ATLib.csproj index 6adb44b..af9dd13 100644 --- a/src/HeboTech.ATLib/HeboTech.ATLib.csproj +++ b/src/HeboTech.ATLib/HeboTech.ATLib.csproj @@ -4,8 +4,8 @@ netstandard2.1 HeboTech HeboTech ATLib - 7.0.0-RC1 - 7.0.0-RC1 + 7.0.0-RC2 + 7.0.0-RC2 7.0.0.0 7.0.0.0 HeboTech.ATLib diff --git a/src/HeboTech.ATLib/Modems/Adafruit/Fona3G.cs b/src/HeboTech.ATLib/Modems/Adafruit/Fona3G.cs index 9a7f155..f7c2564 100644 --- a/src/HeboTech.ATLib/Modems/Adafruit/Fona3G.cs +++ b/src/HeboTech.ATLib/Modems/Adafruit/Fona3G.cs @@ -1,9 +1,18 @@ -using HeboTech.ATLib.Modems.SIMCOM; +using HeboTech.ATLib.CodingSchemes; +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using HeboTech.ATLib.Modems.SIMCOM; using HeboTech.ATLib.Parsers; +using HeboTech.ATLib.PDU; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System; +using System.Linq; namespace HeboTech.ATLib.Modems.Adafruit { - public class Fona3G : SIM5320, IModem + public class Fona3G : SIM5320, IModem, IFona3G { /// /// Based on SIMCOM SIM5320 chipset @@ -11,9 +20,165 @@ public class Fona3G : SIM5320, IModem /// Serial port settings: /// 9600 8N1 Handshake.None /// - public Fona3G(AtChannel channel) + public Fona3G(IAtChannel channel) : base(channel) { } + + //public override async Task> ReadSmsAsync(int index, SmsTextFormat smsTextFormat) + //{ + // switch (smsTextFormat) + // { + // case SmsTextFormat.PDU: + // AtResponse pduResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); + + // if (pduResponse.Success) + // { + // string line1 = pduResponse.Intermediates[0]; + // var line1Match = Regex.Match(line1, @"\+CMGR:\s(?\d),(""(?\w*)"")*,(?\d+)"); + // string line2 = pduResponse.Intermediates[1]; + // var line2Match = Regex.Match(line2, @"(?[0-9A-Z]*)"); + // if (line1Match.Success && line2Match.Success) + // { + // int statusCode = int.Parse(line1Match.Groups["status"].Value); + // SmsStatus status = (SmsStatus)statusCode; + + // string pdu = line2Match.Groups["status"].Value; + // SmsDeliver pduMessage = SmsDeliverDecoder.Decode(pdu.ToByteArray()); + + // return ModemResponse.IsResultSuccess(new Sms(status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); + // } + // if (AtErrorParsers.TryGetError(pduResponse.FinalResponse, out Error pduError)) + // return ModemResponse.HasResultError(pduError); + // } + // break; + // case SmsTextFormat.Text: + // AtResponse textResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); + + // if (textResponse.Success && textResponse.Intermediates.Count > 0) + // { + // string line = textResponse.Intermediates.First(); + // var match = Regex.Match(line, @"\+CMGR:\s""(?[A-Z\s]+)"",""(?\+\d+)"","""",""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"",(?\d+),(?\d),(?\d),(?\d),(?""\+\d+""),(?\d+),(?\d+)"); + // if (match.Success) + // { + // SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); + // PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); + // int year = int.Parse(match.Groups["year"].Value); + // int month = int.Parse(match.Groups["month"].Value); + // int day = int.Parse(match.Groups["day"].Value); + // int hour = int.Parse(match.Groups["hour"].Value); + // int minute = int.Parse(match.Groups["minute"].Value); + // int second = int.Parse(match.Groups["second"].Value); + // int zone = int.Parse(match.Groups["zone"].Value); + + // int addressType = int.Parse(match.Groups["addressType"].Value); + // int tpduFirstOctet = int.Parse(match.Groups["tpduFirstOctet"].Value); + // int protocolIdentifier = int.Parse(match.Groups["pid"].Value); + // int dataCodingScheme = int.Parse(match.Groups["dcs"].Value); + // string serviceCenterAddress = match.Groups["serviceCenterAddress"].Value; + // int serviceCenterAddressType = int.Parse(match.Groups["serviceCenterAddressType"].Value); + // int length = int.Parse(match.Groups["length"].Value); + + // DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + // string message = textResponse.Intermediates.Last(); + + // CodingScheme dcs = (CodingScheme)dataCodingScheme; + // if (dcs == CodingScheme.UCS2) + // message = UCS2.Decode(message); + + // return ModemResponse.IsResultSuccess(new Sms(status, sender, received, message)); + // } + // } + // if (AtErrorParsers.TryGetError(textResponse.FinalResponse, out Error textError)) + // return ModemResponse.HasResultError(textError); + // break; + // default: + // throw new NotSupportedException("The format is not supported"); + // } + // return ModemResponse.HasResultError(); + //} + + //public override async Task>> ListSmssAsync(SmsStatus smsStatus) + //{ + // string command = currentSmsTextFormat switch + // { + // CurrentSmsTextFormat.Text => $"AT+CMGL=\"{SmsStatusHelpers.ToString(smsStatus)}\"", + // CurrentSmsTextFormat.PDU => $"AT+CMGL={(int)smsStatus}", + // _ => throw new Exception("Unknown SMS Text Format") + // }; + + // AtResponse response = await channel.SendMultilineCommand(command, null); + + // List smss = new List(); + // if (response.Success) + // { + // switch (currentSmsTextFormat) + // { + // case CurrentSmsTextFormat.PDU: + // if ((response.Intermediates.Count % 2) != 0) + // return ModemResponse.HasResultError>(); + + // for (int i = 0; i < response.Intermediates.Count; i += 2) + // { + // string metaDataLine = response.Intermediates[i]; + // string messageLine = response.Intermediates[i + 1]; + // var match = Regex.Match(metaDataLine, @"\+CMGL:\s(?\d+),(?\d+),"""",(?\d+)"); + // if (match.Success) + // { + // int index = int.Parse(match.Groups["index"].Value); + // SmsStatus status = (SmsStatus)int.Parse(match.Groups["status"].Value); + + // // Sent when AT+CSDH=1 is set + // int length = int.Parse(match.Groups["length"].Value); + + // SmsDeliver sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); + // smss.Add(new SmsWithIndex(index, status, sms.SenderNumber, sms.Timestamp, sms.Message)); + // } + // } + // break; + // case CurrentSmsTextFormat.Text: + // if ((response.Intermediates.Count % 2) != 0) + // return ModemResponse.HasResultError>(); + + // for (int i = 0; i < response.Intermediates.Count; i += 2) + // { + // string metaDataLine = response.Intermediates[i]; + // string messageLine = response.Intermediates[i + 1]; + // var match = Regex.Match(metaDataLine, @"\+CMGL:\s(?\d+),""(?[A-Z\s]+)"",""(?\+*\d+)"","""",""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"",(?\d+),(?\d),(?\d),(?\d),(?""\+\d+""),(?\d+),(?\d+)"); + // if (match.Success) + // { + // int index = int.Parse(match.Groups["index"].Value); + // SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); + // PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); + // int year = int.Parse(match.Groups["year"].Value); + // int month = int.Parse(match.Groups["month"].Value); + // int day = int.Parse(match.Groups["day"].Value); + // int hour = int.Parse(match.Groups["hour"].Value); + // int minute = int.Parse(match.Groups["minute"].Value); + // int second = int.Parse(match.Groups["second"].Value); + // int zone = int.Parse(match.Groups["zone"].Value); + + // // Sent when AT+CSDH=1 is set + // int addressType = int.Parse(match.Groups["addressType"].Value); + // int dataLength = int.Parse(match.Groups["length"].Value); + + // DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + + // string message = messageLine; + // if (messageLine.Length != dataLength) + // message = UCS2.Decode(messageLine); + + // smss.Add(new SmsWithIndex(index, status, sender, received, message)); + // } + // } + // break; + // case CurrentSmsTextFormat.Unknown: + // break; + // default: + // break; + // } + // } + // return ModemResponse.IsResultSuccess(smss); + //} } } diff --git a/src/HeboTech.ATLib/Modems/Adafruit/IFona3G.cs b/src/HeboTech.ATLib/Modems/Adafruit/IFona3G.cs new file mode 100644 index 0000000..da0fb60 --- /dev/null +++ b/src/HeboTech.ATLib/Modems/Adafruit/IFona3G.cs @@ -0,0 +1,6 @@ +namespace HeboTech.ATLib.Modems.Adafruit +{ + public interface IFona3G : IModem + { + } +} diff --git a/src/HeboTech.ATLib/Modems/Cinterion/IMC55i.cs b/src/HeboTech.ATLib/Modems/Cinterion/IMC55i.cs new file mode 100644 index 0000000..d33c38a --- /dev/null +++ b/src/HeboTech.ATLib/Modems/Cinterion/IMC55i.cs @@ -0,0 +1,14 @@ +using HeboTech.ATLib.Parsers; +using System.Threading.Tasks; + +namespace HeboTech.ATLib.Modems.Cinterion +{ + public interface IMC55i : IModem + { + /// + /// Get the current battery status + /// + /// Command status with battery status + Task> MC55i_GetBatteryStatusAsync(); + } +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs b/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs new file mode 100644 index 0000000..d9e6c67 --- /dev/null +++ b/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs @@ -0,0 +1,209 @@ +using HeboTech.ATLib.CodingSchemes; +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using HeboTech.ATLib.Modems.Generic; +using HeboTech.ATLib.Parsers; +using HeboTech.ATLib.PDU; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnitsNet; +using UnitsNet.Units; + +namespace HeboTech.ATLib.Modems.Cinterion +{ + public class MC55i : ModemBase, IModem, IMC55i + { + /// + /// Cinterion MC55i chipset + /// + /// Serial port settings: + /// 115200 8N1 Handshake.None + /// + public MC55i(IAtChannel channel) + : base(channel) + { + } + + public override async Task>> SendSmsInPduFormatAsync(PhoneNumber phoneNumber, string message) + { + if (phoneNumber is null) + throw new ArgumentNullException(nameof(phoneNumber)); + if (message is null) + throw new ArgumentNullException(nameof(message)); + + IEnumerable pdus = SmsSubmitEncoder.Encode(new SmsSubmitRequest(phoneNumber, message) { IncludeEmptySmscLength = true }); + List> references = new List>(); + foreach (string pdu in pdus) + { + string cmd1 = $"AT+CMGS={(pdu.Length - 2) / 2}"; // Subtract 2 (one octet) for SMSC. + string cmd2 = pdu; + AtResponse response = await channel.SendSmsAsync(cmd1, cmd2, "+CMGS:", TimeSpan.FromSeconds(30)); + + if (response.Success) + { + string line = response.Intermediates.First(); + var match = Regex.Match(line, @"\+CMGS:\s(?\d+)"); + if (match.Success) + { + int mr = int.Parse(match.Groups["mr"].Value); + references.Add(ModemResponse.IsResultSuccess(new SmsReference(mr))); + } + } + else + { + if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) + references.Add(ModemResponse.HasResultError(error)); + } + } + return references; + } + + public override async Task>> SendSmsInPduFormatAsync(PhoneNumber phoneNumber, string message, CodingScheme codingScheme) + { + if (phoneNumber is null) + throw new ArgumentNullException(nameof(phoneNumber)); + if (message is null) + throw new ArgumentNullException(nameof(message)); + + IEnumerable pdus = SmsSubmitEncoder.Encode(new SmsSubmitRequest(phoneNumber, message, codingScheme) { IncludeEmptySmscLength = true }); + List> references = new List>(); + foreach (string pdu in pdus) + { + string cmd1 = $"AT+CMGS={(pdu.Length - 2) / 2}"; // Subtract 2 (one octet) for SMSC. + string cmd2 = pdu; + AtResponse response = await channel.SendSmsAsync(cmd1, cmd2, "+CMGS:", TimeSpan.FromSeconds(30)); + + if (response.Success) + { + string line = response.Intermediates.First(); + var match = Regex.Match(line, @"\+CMGS:\s(?\d+)"); + if (match.Success) + { + int mr = int.Parse(match.Groups["mr"].Value); + references.Add(ModemResponse.IsResultSuccess(new SmsReference(mr))); + } + } + else + { + if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) + references.Add(ModemResponse.HasResultError(error)); + } + } + return references; + } + + //public override async Task> ReadSmsAsync(int index, SmsTextFormat smsTextFormat) + //{ + // switch (smsTextFormat) + // { + // case SmsTextFormat.PDU: + // AtResponse pduResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); + + // if (pduResponse.Success) + // { + // string line1 = pduResponse.Intermediates[0]; + // var line1Match = Regex.Match(line1, @"\+CMGR:\s(?\d),(""(?\w*)"")*,(?\d+)"); + // string line2 = pduResponse.Intermediates[1]; + // var line2Match = Regex.Match(line2, @"(?[0-9A-Z]*)"); + // if (line1Match.Success && line2Match.Success) + // { + // int statusCode = int.Parse(line1Match.Groups["status"].Value); + // SmsStatus status = (SmsStatus)statusCode; + + // string pdu = line2Match.Groups["status"].Value; + // SmsDeliver pduMessage = SmsDeliverDecoder.Decode(pdu.ToByteArray()); + + // return ModemResponse.IsResultSuccess(new Sms(status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); + // } + // if (AtErrorParsers.TryGetError(pduResponse.FinalResponse, out Error pduError)) + // return ModemResponse.HasResultError(pduError); + // } + // break; + // case SmsTextFormat.Text: + // AtResponse textResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); + + // if (textResponse.Success && textResponse.Intermediates.Count > 0) + // { + // string line = textResponse.Intermediates.First(); + // var match = Regex.Match(line, @"\+CMGR:\s""(?[A-Z\s]+)"",""(?\+\d+)"",,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"",(?\d+),(?\d),(?\d),(?\d),(?""\+\d+""),(?\d+),(?\d+)"); + // if (match.Success) + // { + // SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); + // PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); + // int year = int.Parse(match.Groups["year"].Value); + // int month = int.Parse(match.Groups["month"].Value); + // int day = int.Parse(match.Groups["day"].Value); + // int hour = int.Parse(match.Groups["hour"].Value); + // int minute = int.Parse(match.Groups["minute"].Value); + // int second = int.Parse(match.Groups["second"].Value); + // int zone = int.Parse(match.Groups["zone"].Value); + + // int addressType = int.Parse(match.Groups["addressType"].Value); + // int tpduFirstOctet = int.Parse(match.Groups["tpduFirstOctet"].Value); + // int protocolIdentifier = int.Parse(match.Groups["pid"].Value); + // int dataCodingScheme = int.Parse(match.Groups["dcs"].Value); + // string serviceCenterAddress = match.Groups["serviceCenterAddress"].Value; + // int serviceCenterAddressType = int.Parse(match.Groups["serviceCenterAddressType"].Value); + // int length = int.Parse(match.Groups["length"].Value); + + // DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + // string message = textResponse.Intermediates.Last(); + + // CodingScheme dcs = (CodingScheme)dataCodingScheme; + // if (dcs == CodingScheme.UCS2) + // message = UCS2.Decode(message); + + // return ModemResponse.IsResultSuccess(new Sms(status, sender, received, message)); + // } + // } + // if (AtErrorParsers.TryGetError(textResponse.FinalResponse, out Error textError)) + // return ModemResponse.HasResultError(textError); + // break; + // default: + // throw new NotSupportedException("The format is not supported"); + // } + // return ModemResponse.HasResultError(); + //} + + public override async Task> GetBatteryStatusAsync() + { + AtResponse response = await channel.SendSingleLineCommandAsync("AT^SBC?", "^SBC:"); + + if (response.Success) + { + string line = response.Intermediates.First(); + var match = Regex.Match(line, @"\^SBC:\s(?\d+),(?\d+),(?\d+)"); + if (match.Success) + { + int bcs = int.Parse(match.Groups["bcs"].Value); + int bcl = int.Parse(match.Groups["bcl"].Value); + int mpc = int.Parse(match.Groups["mpc"].Value); + return ModemResponse.IsResultSuccess(new BatteryStatus((BatteryChargeStatus)bcs, Ratio.FromPercent(bcl))); + } + } + return ModemResponse.HasResultError(); + } + + public async Task> MC55i_GetBatteryStatusAsync() + { + AtResponse response = await channel.SendSingleLineCommandAsync("AT^SBC?", "^SBC:"); + + if (response.Success) + { + string line = response.Intermediates.First(); + var match = Regex.Match(line, @"\^SBC:\s(?\d+),(?\d+),(?\d+)"); + if (match.Success) + { + int bcs = int.Parse(match.Groups["bcs"].Value); + int bcl = int.Parse(match.Groups["bcl"].Value); + int mpc = int.Parse(match.Groups["mpc"].Value); + return ModemResponse.IsResultSuccess(new MC55iBatteryStatus(new ElectricCurrent(mpc, ElectricCurrentUnit.Milliampere))); + } + } + return ModemResponse.HasResultError(); + } + } +} diff --git a/src/HeboTech.ATLib/Modems/Cinterion/MC55iBatteryStatus.cs b/src/HeboTech.ATLib/Modems/Cinterion/MC55iBatteryStatus.cs new file mode 100644 index 0000000..5a130ed --- /dev/null +++ b/src/HeboTech.ATLib/Modems/Cinterion/MC55iBatteryStatus.cs @@ -0,0 +1,19 @@ +using UnitsNet; + +namespace HeboTech.ATLib.Modems.Cinterion +{ + public class MC55iBatteryStatus + { + public MC55iBatteryStatus(ElectricCurrent powerConsumption) + { + PowerConsumption = powerConsumption; + } + + public ElectricCurrent PowerConsumption { get; } + + public override string ToString() + { + return $"Average power consumption: {PowerConsumption}"; + } + } +} diff --git a/src/HeboTech.ATLib/Modems/D_LINK/DWM222.cs b/src/HeboTech.ATLib/Modems/D_LINK/DWM222.cs index 9d88e2b..9d47d61 100644 --- a/src/HeboTech.ATLib/Modems/D_LINK/DWM222.cs +++ b/src/HeboTech.ATLib/Modems/D_LINK/DWM222.cs @@ -3,7 +3,7 @@ namespace HeboTech.ATLib.Modems.D_LINK { - public class DWM222 : MDM9225, IModem + public class DWM222 : MDM9225, IModem, IDWM222 { /// /// Based on Qualcomm MDM9225 chipset @@ -11,7 +11,7 @@ public class DWM222 : MDM9225, IModem /// Serial port settings: /// 9600 8N1 Handshake.RequestToSend /// - public DWM222(AtChannel channel) + public DWM222(IAtChannel channel) : base(channel) { } diff --git a/src/HeboTech.ATLib/Modems/D_LINK/IDWM222.cs b/src/HeboTech.ATLib/Modems/D_LINK/IDWM222.cs new file mode 100644 index 0000000..97a09e5 --- /dev/null +++ b/src/HeboTech.ATLib/Modems/D_LINK/IDWM222.cs @@ -0,0 +1,6 @@ +namespace HeboTech.ATLib.Modems.D_LINK +{ + public interface IDWM222 : IModem + { + } +} diff --git a/src/HeboTech.ATLib/Modems/Generic/MessageStorage.cs b/src/HeboTech.ATLib/Modems/Generic/MessageStorage.cs new file mode 100644 index 0000000..79616bb --- /dev/null +++ b/src/HeboTech.ATLib/Modems/Generic/MessageStorage.cs @@ -0,0 +1,73 @@ +using System; + +namespace HeboTech.ATLib.Modems.Generic +{ + /// + /// Message Storage Areas + /// + public class MessageStorage + { + /// + /// SIM card storage area + /// + public static readonly MessageStorage SM = new MessageStorage("SM"); + /// + /// Modem storage area + /// + public static readonly MessageStorage ME = new MessageStorage("ME"); + /// + /// All storage combined + /// + public static readonly MessageStorage MT = new MessageStorage("MT"); + /// + /// Broadcast message storage area + /// + public static readonly MessageStorage BM = new MessageStorage("BM"); + /// + /// Status report storage area + /// + public static readonly MessageStorage SR = new MessageStorage("SR"); + /// + /// Terminal adaptor storage area + /// + public static readonly MessageStorage TA = new MessageStorage("TA"); + + protected MessageStorage(string value) + { + Value = value; + } + + public string Value { get; } + + protected static MessageStorage ParseString(string value) + { + if (value == SM.ToString()) + return SM; + + if (value == ME.ToString()) + return ME; + + if (value == MT.ToString()) + return MT; + + if (value == BM.ToString()) + return BM; + + if (value == SR.ToString()) + return SR; + + if (value == TA.ToString()) + return TA; + + throw new ArgumentException($"\"{value}\" is not supported"); + } + + public static implicit operator string(MessageStorage value) => value.Value; + public static explicit operator MessageStorage(string value) => ParseString(value); + + public override string ToString() + { + return Value; + } + } +} diff --git a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs index a98371e..53ab704 100644 --- a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs +++ b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs @@ -17,10 +17,18 @@ namespace HeboTech.ATLib.Modems.Generic { public abstract class ModemBase : IDisposable { - protected readonly AtChannel channel; + protected enum CurrentSmsTextFormat + { + PDU = 0, + Text = 1, + Unknown + } + + protected readonly IAtChannel channel; private bool disposed; + protected CurrentSmsTextFormat currentSmsTextFormat = CurrentSmsTextFormat.Unknown; - public ModemBase(AtChannel channel) + public ModemBase(IAtChannel channel) { this.channel = channel; channel.UnsolicitedEvent += Channel_UnsolicitedEvent; @@ -55,6 +63,19 @@ private void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) public event EventHandler CallStarted; public event EventHandler CallEnded; + public virtual async Task SetRequiredSettingsBeforePinAsync() + { + ModemResponse echo = await DisableEchoAsync(); + ModemResponse errorFormat = await SetErrorFormat(1); + return echo.Success && errorFormat.Success; + } + + public virtual async Task SetRequiredSettingsAfterPinAsync() + { + ModemResponse detailedTextModeResultCodes = await ShowSmsTextModeParameters(true); + return detailedTextModeResultCodes.Success; + } + public virtual async Task> GetImsiAsync() { AtResponse response = await channel.SendSingleLineCommandAsync("AT+CIMI", string.Empty); @@ -66,29 +87,29 @@ public virtual async Task> GetImsiAsync() if (match.Success) { string imsi = match.Groups["imsi"].Value; - return ModemResponse.ResultSuccess(new Imsi(imsi)); + return ModemResponse.IsResultSuccess(new Imsi(imsi)); } } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task AnswerIncomingCallAsync() { AtResponse response = await channel.SendCommand("ATA"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } public virtual async Task DialAsync(PhoneNumber phoneNumber, bool hideCallerNumber = false, bool closedUserGroup = false) { string command = $"ATD{phoneNumber}{(hideCallerNumber ? 'I' : 'i')}{(closedUserGroup ? 'G' : 'g')};"; AtResponse response = await channel.SendCommand(command); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } public virtual async Task DisableEchoAsync() { AtResponse response = await channel.SendCommand("ATE0"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } public virtual async Task> GetProductIdentificationInformationAsync() @@ -103,15 +124,15 @@ public virtual async Task> GetPr builder.AppendLine(line); } - return ModemResponse.ResultSuccess(new ProductIdentificationInformation(builder.ToString())); + return ModemResponse.IsResultSuccess(new ProductIdentificationInformation(builder.ToString())); } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task HangupAsync() { AtResponse response = await channel.SendCommand($"AT+CHUP"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } public virtual async Task>> GetAvailableCharacterSetsAsync() @@ -124,10 +145,10 @@ public virtual async Task>> GetAvailableCharac var match = Regex.Match(line, @"\+CSCS:\s\((?:""(?\w+)"",*)+\)"); if (match.Success) { - return ModemResponse.ResultSuccess(match.Groups["characterSet"].Captures.Select(x => x.Value)); + return ModemResponse.IsResultSuccess(match.Groups["characterSet"].Captures.Select(x => x.Value)); } } - return ModemResponse.ResultError>(); + return ModemResponse.HasResultError>(); } public virtual async Task> GetCurrentCharacterSetAsync() @@ -141,16 +162,16 @@ public virtual async Task> GetCurrentCharacterSetAsync() if (match.Success) { string characterSet = match.Groups["characterSet"].Value; - return ModemResponse.ResultSuccess(characterSet); + return ModemResponse.IsResultSuccess(characterSet); } } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task SetCharacterSetAsync(string characterSet) { AtResponse response = await channel.SendCommand($"AT+CSCS=\"{characterSet}\""); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } #endregion @@ -160,7 +181,11 @@ public virtual async Task SetCharacterSetAsync(string characterSe public virtual async Task SetSmsMessageFormatAsync(SmsTextFormat format) { AtResponse response = await channel.SendCommand($"AT+CMGF={(int)format}"); - return ModemResponse.Success(response.Success); + + if (response.Success) + currentSmsTextFormat = (CurrentSmsTextFormat)format; + + return ModemResponse.IsSuccess(response.Success); } public virtual async Task SetNewSmsIndication(int mode, int mt, int bm, int ds, int bfr) @@ -177,7 +202,7 @@ public virtual async Task SetNewSmsIndication(int mode, int mt, i throw new ArgumentOutOfRangeException(nameof(bfr)); AtResponse response = await channel.SendCommand($"AT+CNMI={mode},{mt},{bm},{ds},{bfr}"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } public virtual async Task> SendSmsInTextFormatAsync(PhoneNumber phoneNumber, string message) @@ -198,10 +223,10 @@ public virtual async Task> SendSmsInTextFormatAsync( if (match.Success) { int mr = int.Parse(match.Groups["mr"].Value); - return ModemResponse.ResultSuccess(new SmsReference(mr)); + return ModemResponse.IsResultSuccess(new SmsReference(mr)); } } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public abstract Task>> SendSmsInPduFormatAsync(PhoneNumber phoneNumber, string message); @@ -228,13 +253,13 @@ protected virtual async Task>> SendSmsIn if (match.Success) { int mr = int.Parse(match.Groups["mr"].Value); - references.Add(ModemResponse.ResultSuccess(new SmsReference(mr))); + references.Add(ModemResponse.IsResultSuccess(new SmsReference(mr))); } } else { if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) - references.Add(ModemResponse.ResultError(error.ToString())); + references.Add(ModemResponse.HasResultError(error)); } } return references; @@ -264,13 +289,13 @@ protected virtual async Task>> SendSmsIn if (match.Success) { int mr = int.Parse(match.Groups["mr"].Value); - references.Add(ModemResponse.ResultSuccess(new SmsReference(mr))); + references.Add(ModemResponse.IsResultSuccess(new SmsReference(mr))); } } else { if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) - references.Add(ModemResponse.ResultError(error.ToString())); + references.Add(ModemResponse.HasResultError(error)); } } return references; @@ -290,10 +315,12 @@ public virtual async Task> GetS IEnumerable s2Storages = match.Groups["s2Storages"].Value.Split(',').Select(x => x.Trim('"')); IEnumerable s3Storages = match.Groups["s3Storages"].Value.Split(',').Select(x => x.Trim('"')); - return ModemResponse.ResultSuccess(new SupportedPreferredMessageStorages(s1Storages, s2Storages, s3Storages)); + return ModemResponse.IsResultSuccess(new SupportedPreferredMessageStorages(s1Storages, s2Storages, s3Storages)); } } - return ModemResponse.ResultError(); + if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) + return ModemResponse.HasResultError(error); + return ModemResponse.HasResultError(); } public virtual async Task> GetPreferredMessageStoragesAsync() @@ -310,16 +337,18 @@ public virtual async Task> GetPreferredM string[] s2Split = match.Groups["storage2"].Value.Split(','); string[] s3Split = match.Groups["storage3"].Value.Split(','); - return ModemResponse.ResultSuccess(new PreferredMessageStorages( - new PreferredMessageStorage(s1Split[0].Trim('"'), int.Parse(s1Split[1]), int.Parse(s1Split[2])), - new PreferredMessageStorage(s2Split[0].Trim('"'), int.Parse(s2Split[1]), int.Parse(s2Split[2])), - new PreferredMessageStorage(s3Split[0].Trim('"'), int.Parse(s3Split[1]), int.Parse(s3Split[2])))); + return ModemResponse.IsResultSuccess(new PreferredMessageStorages( + new PreferredMessageStorage((MessageStorage)s1Split[0].Trim('"'), int.Parse(s1Split[1]), int.Parse(s1Split[2])), + new PreferredMessageStorage((MessageStorage)s2Split[0].Trim('"'), int.Parse(s2Split[1]), int.Parse(s2Split[2])), + new PreferredMessageStorage((MessageStorage)s3Split[0].Trim('"'), int.Parse(s3Split[1]), int.Parse(s3Split[2])))); } } - return ModemResponse.ResultError(); + if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) + return ModemResponse.HasResultError(error); + return ModemResponse.HasResultError(); } - public virtual async Task> SetPreferredMessageStorageAsync(string storage1Name, string storage2Name, string storage3Name) + public virtual async Task> SetPreferredMessageStorageAsync(MessageStorage storage1Name, MessageStorage storage2Name, MessageStorage storage3Name) { AtResponse response = await channel.SendSingleLineCommandAsync($"AT+CPMS=\"{storage1Name}\",\"{storage2Name}\",\"{storage3Name}\"", "+CPMS:"); @@ -336,13 +365,16 @@ public virtual async Task> SetPreferredM int s3Used = int.Parse(match.Groups["s3Used"].Value); int s3Total = int.Parse(match.Groups["s3Total"].Value); - return ModemResponse.ResultSuccess(new PreferredMessageStorages( + return ModemResponse.IsResultSuccess(new PreferredMessageStorages( new PreferredMessageStorage(storage1Name, s1Used, s1Total), new PreferredMessageStorage(storage2Name, s2Used, s2Total), new PreferredMessageStorage(storage3Name, s3Used, s3Total))); } } - return ModemResponse.ResultError(); + + if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) + return ModemResponse.HasResultError(error); + return ModemResponse.HasResultError(); } public virtual async Task> ReadSmsAsync(int index, SmsTextFormat smsTextFormat) @@ -350,7 +382,7 @@ public virtual async Task> ReadSmsAsync(int index, SmsTextFor switch (smsTextFormat) { case SmsTextFormat.PDU: - AtResponse pduResponse = await channel.SendMultilineCommand($"AT+CMGR={index},0", null); + AtResponse pduResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); if (pduResponse.Success) { @@ -361,22 +393,24 @@ public virtual async Task> ReadSmsAsync(int index, SmsTextFor if (line1Match.Success && line2Match.Success) { int statusCode = int.Parse(line1Match.Groups["status"].Value); - SmsStatus status = SmsStatusHelpers.ToSmsStatus(statusCode); + SmsStatus status = (SmsStatus)statusCode; string pdu = line2Match.Groups["status"].Value; SmsDeliver pduMessage = SmsDeliverDecoder.Decode(pdu.ToByteArray()); - return ModemResponse.ResultSuccess(new Sms(status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); + return ModemResponse.IsResultSuccess(new Sms(status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); } + if (AtErrorParsers.TryGetError(pduResponse.FinalResponse, out Error pduError)) + return ModemResponse.HasResultError(pduError); } break; case SmsTextFormat.Text: - AtResponse textResponse = await channel.SendMultilineCommand($"AT+CMGR={index},0", null); + AtResponse textResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); if (textResponse.Success && textResponse.Intermediates.Count > 0) { string line = textResponse.Intermediates.First(); - var match = Regex.Match(line, @"\+CMGR:\s""(?[A-Z\s]+)"",""(?\+\d+)"",,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"""); + var match = Regex.Match(line, @"\+CMGR:\s""(?[A-Z\s]+)"",""(?\+\d+)"",("""")?,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"",(?\d+),(?\d),(?\d),(?\d),(?""\+\d+""),(?\d+),(?\d+)"); if (match.Success) { SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); @@ -388,72 +422,121 @@ public virtual async Task> ReadSmsAsync(int index, SmsTextFor int minute = int.Parse(match.Groups["minute"].Value); int second = int.Parse(match.Groups["second"].Value); int zone = int.Parse(match.Groups["zone"].Value); + + int addressType = int.Parse(match.Groups["addressType"].Value); + int tpduFirstOctet = int.Parse(match.Groups["tpduFirstOctet"].Value); + int protocolIdentifier = int.Parse(match.Groups["pid"].Value); + int dataCodingScheme = int.Parse(match.Groups["dcs"].Value); + string serviceCenterAddress = match.Groups["serviceCenterAddress"].Value; + int serviceCenterAddressType = int.Parse(match.Groups["serviceCenterAddressType"].Value); + int length = int.Parse(match.Groups["length"].Value); + DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); string message = textResponse.Intermediates.Last(); - return ModemResponse.ResultSuccess(new Sms(status, sender, received, message)); + + CodingScheme dcs = (CodingScheme)dataCodingScheme; + if (dcs == CodingScheme.UCS2) + message = UCS2.Decode(message); + + return ModemResponse.IsResultSuccess(new Sms(status, sender, received, message)); } } + if (AtErrorParsers.TryGetError(textResponse.FinalResponse, out Error textError)) + return ModemResponse.HasResultError(textError); break; default: throw new NotSupportedException("The format is not supported"); } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task>> ListSmssAsync(SmsStatus smsStatus) { - AtResponse response = await channel.SendMultilineCommand($"AT+CMGL=\"{SmsStatusHelpers.ToString(smsStatus)}\",0", null); + string command = currentSmsTextFormat switch + { + CurrentSmsTextFormat.Text => $"AT+CMGL=\"{SmsStatusHelpers.ToString(smsStatus)}\"", + CurrentSmsTextFormat.PDU => $"AT+CMGL={(int)smsStatus}", + _ => throw new Exception("Unknown SMS Text Format") + }; + + AtResponse response = await channel.SendMultilineCommand(command, null); List smss = new List(); if (response.Success) { - string metaRegEx = @"\+CMGL:\s(?\d+),""(?[A-Z\s]+)"",""(?\+*\d+)"",,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"""; - - using (var enumerator = response.Intermediates.GetEnumerator()) + switch (currentSmsTextFormat) { - string line = null; - AdvanceIterator(); - while (line != null) - { - var match = Regex.Match(line, metaRegEx); - if (match.Success) - { - int index = int.Parse(match.Groups["index"].Value); - SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); - PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); - int year = int.Parse(match.Groups["year"].Value); - int month = int.Parse(match.Groups["month"].Value); - int day = int.Parse(match.Groups["day"].Value); - int hour = int.Parse(match.Groups["hour"].Value); - int minute = int.Parse(match.Groups["minute"].Value); - int second = int.Parse(match.Groups["second"].Value); - int zone = int.Parse(match.Groups["zone"].Value); - DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + case CurrentSmsTextFormat.PDU: + if ((response.Intermediates.Count % 2) != 0) + return ModemResponse.HasResultError>(); - StringBuilder messageBuilder = new StringBuilder(); - AdvanceIterator(); - while (line != null && !Regex.Match(line, metaRegEx).Success) + for (int i = 0; i < response.Intermediates.Count; i += 2) + { + string metaDataLine = response.Intermediates[i]; + string messageLine = response.Intermediates[i + 1]; + var match = Regex.Match(metaDataLine, @"\+CMGL:\s(?\d+),(?\d+),,(?\d+)"); + if (match.Success) { - messageBuilder.AppendLine(line); - AdvanceIterator(); + int index = int.Parse(match.Groups["index"].Value); + SmsStatus status = (SmsStatus)int.Parse(match.Groups["status"].Value); + + // Sent when AT+CSDH=1 is set + int length = int.Parse(match.Groups["length"].Value); + + SmsDeliver sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); + smss.Add(new SmsWithIndex(index, status, sms.SenderNumber, sms.Timestamp, sms.Message)); } - smss.Add(new SmsWithIndex(index, status, sender, received, messageBuilder.ToString())); } - } + break; + case CurrentSmsTextFormat.Text: + if ((response.Intermediates.Count % 2) != 0) + return ModemResponse.HasResultError>(); - void AdvanceIterator() - { - line = enumerator.MoveNext() ? enumerator.Current : null; - } + for (int i = 0; i < response.Intermediates.Count; i += 2) + { + string metaDataLine = response.Intermediates[i]; + string messageLine = response.Intermediates[i + 1]; + var match = Regex.Match(metaDataLine, @"\+CMGL:\s(?\d+),""(?[A-Z\s]+)"",""(?\+*\d+)"",,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"",(?\d+),(?\d+)"); + if (match.Success) + { + int index = int.Parse(match.Groups["index"].Value); + SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); + PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); + int year = int.Parse(match.Groups["year"].Value); + int month = int.Parse(match.Groups["month"].Value); + int day = int.Parse(match.Groups["day"].Value); + int hour = int.Parse(match.Groups["hour"].Value); + int minute = int.Parse(match.Groups["minute"].Value); + int second = int.Parse(match.Groups["second"].Value); + int zone = int.Parse(match.Groups["zone"].Value); + + // Sent when AT+CSDH=1 is set + int addressType = int.Parse(match.Groups["addressType"].Value); + int dataLength = int.Parse(match.Groups["length"].Value); + + DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + + string message = messageLine; + if (messageLine.Length != dataLength) + message = UCS2.Decode(messageLine); + + smss.Add(new SmsWithIndex(index, status, sender, received, message)); + } + } + break; + case CurrentSmsTextFormat.Unknown: + break; + default: + break; } } - return ModemResponse.ResultSuccess(smss); + return ModemResponse.IsResultSuccess(smss); } public virtual async Task DeleteSmsAsync(int index) { AtResponse response = await channel.SendCommand($"AT+CMGD={index}"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } #endregion @@ -467,7 +550,7 @@ public virtual async Task> GetSimStatusAsync() if (!response.Success) { if (AtErrorParsers.TryGetError(response.FinalResponse, out Error cmeError)) - return ModemResponse.ResultError(cmeError.ToString()); + return ModemResponse.HasResultError(cmeError); } // CPIN? has succeeded, now look at the result @@ -478,27 +561,21 @@ public virtual async Task> GetSimStatusAsync() string cpinResult = match.Groups["pinresult"].Value; return cpinResult switch { - "SIM PIN" => ModemResponse.ResultSuccess(SimStatus.SIM_PIN), - "SIM PUK" => ModemResponse.ResultSuccess(SimStatus.SIM_PUK), - "PH-NET PIN" => ModemResponse.ResultSuccess(SimStatus.SIM_NETWORK_PERSONALIZATION), - "READY" => ModemResponse.ResultSuccess(SimStatus.SIM_READY), - _ => ModemResponse.ResultSuccess(SimStatus.SIM_ABSENT),// Treat unsupported lock types as "sim absent" + "SIM PIN" => ModemResponse.IsResultSuccess(SimStatus.SIM_PIN), + "SIM PUK" => ModemResponse.IsResultSuccess(SimStatus.SIM_PUK), + "PH-NET PIN" => ModemResponse.IsResultSuccess(SimStatus.SIM_NETWORK_PERSONALIZATION), + "READY" => ModemResponse.IsResultSuccess(SimStatus.SIM_READY), + _ => ModemResponse.IsResultSuccess(SimStatus.SIM_ABSENT),// Treat unsupported lock types as "sim absent" }; } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task EnterSimPinAsync(PersonalIdentificationNumber pin) { AtResponse response = await channel.SendCommand($"AT+CPIN={pin}"); - return ModemResponse.Success(response.Success); - } - - public virtual async Task ReInitializeSimAsync() - { - AtResponse response = await channel.SendCommand($"AT+CRFSIM"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } public virtual async Task> GetSignalStrengthAsync() @@ -513,10 +590,10 @@ public virtual async Task> GetSignalStrengthAsync( { int rssi = int.Parse(match.Groups["rssi"].Value); int ber = int.Parse(match.Groups["ber"].Value); - return ModemResponse.ResultSuccess(new SignalStrength(PowerRatio.FromDecibelMilliwatts(rssi), Ratio.FromPercent(ber))); + return ModemResponse.IsResultSuccess(new SignalStrength(PowerRatio.FromDecibelMilliwatts(rssi), Ratio.FromPercent(ber))); } } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task> GetBatteryStatusAsync() @@ -531,10 +608,10 @@ public virtual async Task> GetBatteryStatusAsync() { int bcs = int.Parse(match.Groups["bcs"].Value); int bcl = int.Parse(match.Groups["bcl"].Value); - return ModemResponse.ResultSuccess(new BatteryStatus((BatteryChargeStatus)bcs, Ratio.FromPercent(bcl))); + return ModemResponse.IsResultSuccess(new BatteryStatus((BatteryChargeStatus)bcs, Ratio.FromPercent(bcl))); } } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task SetDateTimeAsync(DateTimeOffset value) @@ -545,7 +622,7 @@ public virtual async Task SetDateTimeAsync(DateTimeOffset value) sb.Append(offsetQuarters.ToString("+00;-#", CultureInfo.InvariantCulture)); sb.Append("\""); AtResponse response = await channel.SendCommand(sb.ToString()); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } public virtual async Task> GetDateTimeAsync() @@ -555,34 +632,48 @@ public virtual async Task> GetDateTimeAsync() if (response.Success) { string line = response.Intermediates.First(); - var match = Regex.Match(line, @"\+CCLK:\s""(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d)"""); + var match = Regex.Match(line, @"\+CCLK:\s""(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d)?"""); if (match.Success) { + DateTimeOffset time; + int year = int.Parse(match.Groups["year"].Value); int month = int.Parse(match.Groups["month"].Value); int day = int.Parse(match.Groups["day"].Value); int hour = int.Parse(match.Groups["hour"].Value); int minute = int.Parse(match.Groups["minute"].Value); int second = int.Parse(match.Groups["second"].Value); - int zone = int.Parse(match.Groups["zone"].Value); - DateTimeOffset time = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); - return ModemResponse.ResultSuccess(time); + if (match.Groups["zone"].Success) + { + int zone = int.Parse(match.Groups["zone"].Value); + time = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + } + else + time = new DateTimeOffset(new DateTime(2000 + year, month, day, hour, minute, second)); + return ModemResponse.IsResultSuccess(time); } } - return ModemResponse.ResultError(); + return ModemResponse.HasResultError(); } public virtual async Task SendUssdAsync(string code, int codingScheme = 15) { AtResponse response = await channel.SendCommand($"AT+CUSD=1,\"{code}\",{codingScheme}"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); } #endregion public virtual async Task SetErrorFormat(int errorFormat) { AtResponse response = await channel.SendCommand($"AT+CMEE={errorFormat}"); - return ModemResponse.Success(response.Success); + return ModemResponse.IsSuccess(response.Success); + } + + public virtual async Task ShowSmsTextModeParameters(bool activate) + { + + AtResponse response = await channel.SendCommand($"AT+CSDH={(activate ? "1" : "0")}"); + return ModemResponse.IsSuccess(response.Success); } public void Close() diff --git a/src/HeboTech.ATLib/Modems/IModem.cs b/src/HeboTech.ATLib/Modems/IModem.cs index a0c5c0b..6ccbb9e 100644 --- a/src/HeboTech.ATLib/Modems/IModem.cs +++ b/src/HeboTech.ATLib/Modems/IModem.cs @@ -1,6 +1,7 @@ using HeboTech.ATLib.CodingSchemes; using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Events; +using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System; using System.Collections.Generic; @@ -160,7 +161,7 @@ public interface IModem : IDisposable /// /// /// Command status with set preferred message storages - Task> SetPreferredMessageStorageAsync(string storage1Name, string storage2Name, string storage3Name); + Task> SetPreferredMessageStorageAsync(MessageStorage storage1Name, MessageStorage storage2Name, MessageStorage storage3Name); /// /// Get supported preferred message storages @@ -181,12 +182,6 @@ public interface IModem : IDisposable /// Command status with SMS Task> ReadSmsAsync(int index, SmsTextFormat smsTextFormat); - /// - /// Reload and initialize the SIM card - /// - /// Command status - Task ReInitializeSimAsync(); - /// /// Sends an SMS in text format /// @@ -252,11 +247,30 @@ public interface IModem : IDisposable /// Command status Task SetNewSmsIndication(int mode, int mt, int bm, int ds, int bfr); + /// + /// Sets settings required for correct operation after PIN is entered. + /// + /// + Task SetRequiredSettingsAfterPinAsync(); + + /// + /// Sets settings required for correct operation before PIN is entered + /// + /// Command status + Task SetRequiredSettingsBeforePinAsync(); + /// /// Sets the input and output format of SMSs. Currently, only Text is supported and must be set before sending SMSs /// /// The format /// Command status Task SetSmsMessageFormatAsync(SmsTextFormat format); + + /// + /// Sets whether or not detailed header information is shown in text mode result codes + /// + /// True to activate, false to deactivate + /// Command status + Task ShowSmsTextModeParameters(bool activate); } } \ No newline at end of file diff --git a/src/HeboTech.ATLib/Modems/ModemExtensions.cs b/src/HeboTech.ATLib/Modems/ModemExtensions.cs deleted file mode 100644 index d8c6901..0000000 --- a/src/HeboTech.ATLib/Modems/ModemExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using HeboTech.ATLib.Parsers; -using System.Threading.Tasks; - -namespace HeboTech.ATLib.Modems -{ - public static class ModemExtensions - { - public static async Task SetRequiredSettingsAsync(this IModem modem) - { - ModemResponse echo = await modem.DisableEchoAsync(); - ModemResponse errorFormat = await modem.SetErrorFormat(1); - return echo.IsSuccess && errorFormat.IsSuccess; - } - } -} diff --git a/src/HeboTech.ATLib/Modems/Qualcomm/IMDM9225.cs b/src/HeboTech.ATLib/Modems/Qualcomm/IMDM9225.cs new file mode 100644 index 0000000..046626a --- /dev/null +++ b/src/HeboTech.ATLib/Modems/Qualcomm/IMDM9225.cs @@ -0,0 +1,6 @@ +namespace HeboTech.ATLib.Modems.Qualcomm +{ + public interface IMDM9225 : IModem + { + } +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs b/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs index eeb2c17..2de42d2 100644 --- a/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs +++ b/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs @@ -7,9 +7,9 @@ namespace HeboTech.ATLib.Modems.Qualcomm { - public class MDM9225 : ModemBase, IModem + public class MDM9225 : ModemBase, IModem, IMDM9225 { - public MDM9225(AtChannel channel) + public MDM9225(IAtChannel channel) : base(channel) { } diff --git a/src/HeboTech.ATLib/Modems/SIMCOM/ISIM5320.cs b/src/HeboTech.ATLib/Modems/SIMCOM/ISIM5320.cs new file mode 100644 index 0000000..4cc752f --- /dev/null +++ b/src/HeboTech.ATLib/Modems/SIMCOM/ISIM5320.cs @@ -0,0 +1,21 @@ +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Parsers; +using System.Threading.Tasks; + +namespace HeboTech.ATLib.Modems.SIMCOM +{ + public interface ISIM5320 : IModem + { + /// + /// Get remaining PIN and PUK attempts + /// + /// Remaining PIN and PUK attempts + Task GetRemainingPinPukAttemptsAsync(); + + /// + /// Reload and initialize the SIM card + /// + /// Command status + Task ReInitializeSimAsync(); + } +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs index 86ff7be..c041477 100644 --- a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs +++ b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs @@ -12,9 +12,9 @@ namespace HeboTech.ATLib.Modems.SIMCOM { - public class SIM5320 : ModemBase, IModem + public class SIM5320 : ModemBase, IModem, ISIM5320 { - public SIM5320(AtChannel channel) + public SIM5320(IAtChannel channel) : base(channel) { } @@ -53,90 +53,167 @@ public override Task>> SendSmsInPduForma return base.SendSmsInPduFormatAsync(phoneNumber, message, codingScheme, false); } - public override async Task> ReadSmsAsync(int index, SmsTextFormat smsTextFormat) + //public virtual async Task> ReadSmsAsync(int index, SmsTextFormat smsTextFormat) + //{ + // switch (smsTextFormat) + // { + // case SmsTextFormat.PDU: + // AtResponse pduResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); + + // if (pduResponse.Success) + // { + // string line1 = pduResponse.Intermediates[0]; + // var line1Match = Regex.Match(line1, @"\+CMGR:\s(?\d),(""(?\w*)"")*,(?\d+)"); + // string line2 = pduResponse.Intermediates[1]; + // var line2Match = Regex.Match(line2, @"(?[0-9A-Z]*)"); + // if (line1Match.Success && line2Match.Success) + // { + // int statusCode = int.Parse(line1Match.Groups["status"].Value); + // SmsStatus status = (SmsStatus)statusCode; + + // string pdu = line2Match.Groups["status"].Value; + // SmsDeliver pduMessage = SmsDeliverDecoder.Decode(pdu.ToByteArray()); + + // return ModemResponse.IsResultSuccess(new Sms(status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); + // } + // if (AtErrorParsers.TryGetError(pduResponse.FinalResponse, out Error pduError)) + // return ModemResponse.HasResultError(pduError); + // } + // break; + // case SmsTextFormat.Text: + // AtResponse textResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); + + // if (textResponse.Success && textResponse.Intermediates.Count > 0) + // { + // string line = textResponse.Intermediates.First(); + // var match = Regex.Match(line, @"\+CMGR:\s""(?[A-Z\s]+)"",""(?\+\d+)"",("""")?,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"",(?\d+),(?\d),(?\d),(?\d),(?""\+\d+""),(?\d+),(?\d+)"); + // if (match.Success) + // { + // SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); + // PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); + // int year = int.Parse(match.Groups["year"].Value); + // int month = int.Parse(match.Groups["month"].Value); + // int day = int.Parse(match.Groups["day"].Value); + // int hour = int.Parse(match.Groups["hour"].Value); + // int minute = int.Parse(match.Groups["minute"].Value); + // int second = int.Parse(match.Groups["second"].Value); + // int zone = int.Parse(match.Groups["zone"].Value); + + // int addressType = int.Parse(match.Groups["addressType"].Value); + // int tpduFirstOctet = int.Parse(match.Groups["tpduFirstOctet"].Value); + // int protocolIdentifier = int.Parse(match.Groups["pid"].Value); + // int dataCodingScheme = int.Parse(match.Groups["dcs"].Value); + // string serviceCenterAddress = match.Groups["serviceCenterAddress"].Value; + // int serviceCenterAddressType = int.Parse(match.Groups["serviceCenterAddressType"].Value); + // int length = int.Parse(match.Groups["length"].Value); + + // DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + // string message = textResponse.Intermediates.Last(); + + // CodingScheme dcs = (CodingScheme)dataCodingScheme; + // if (dcs == CodingScheme.UCS2) + // message = UCS2.Decode(message); + + // return ModemResponse.IsResultSuccess(new Sms(status, sender, received, message)); + // } + // } + // if (AtErrorParsers.TryGetError(textResponse.FinalResponse, out Error textError)) + // return ModemResponse.HasResultError(textError); + // break; + // default: + // throw new NotSupportedException("The format is not supported"); + // } + // return ModemResponse.HasResultError(); + //} + + public virtual async Task>> ListSmssAsync(SmsStatus smsStatus) { - switch (smsTextFormat) + string command = currentSmsTextFormat switch { - case SmsTextFormat.PDU: - AtResponse pduResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); - - if (pduResponse.Success && pduResponse.Intermediates.Count > 0) - { - string line1 = pduResponse.Intermediates[0]; - string line2 = pduResponse.Intermediates[1]; - var line1Match = Regex.Match(line1, @"\+CMGR:\s(?\d{1}),""(?.*)"",(?\d+)"); - var line2Match = Regex.Match(line2, @"(?[0-9A-F]*)"); - if (line1Match.Success && line2Match.Success) - { - int status = int.Parse(line1Match.Groups["status"].Value); - string alphabet = line1Match.Groups["alphabet"].Value; - int length = int.Parse(line1Match.Groups["length"].Value); - string pdu = line2Match.Groups["pdu"].Value; - SmsDeliver pduMessage = SmsDeliverDecoder.Decode(pdu.ToByteArray()); - return ModemResponse.ResultSuccess(new Sms((SmsStatus)status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); - } - } - return ModemResponse.ResultError(); - case SmsTextFormat.Text: - AtResponse textResponse = await channel.SendMultilineCommand($"AT+CMGR={index}", null); - - if (textResponse.Success && textResponse.Intermediates.Count > 0) - { - string line = textResponse.Intermediates.First(); - var match = Regex.Match(line, @"\+CMGR:\s""(?[A-Z\s]+)"",""(?\+?\d+)"",("""")?,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"""); - if (match.Success) - { - SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); - PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); - int year = int.Parse(match.Groups["year"].Value); - int month = int.Parse(match.Groups["month"].Value); - int day = int.Parse(match.Groups["day"].Value); - int hour = int.Parse(match.Groups["hour"].Value); - int minute = int.Parse(match.Groups["minute"].Value); - int second = int.Parse(match.Groups["second"].Value); - int zone = int.Parse(match.Groups["zone"].Value); - DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); - string message = textResponse.Intermediates.Last(); - return ModemResponse.ResultSuccess(new Sms(status, sender, received, message)); - } - } - return ModemResponse.ResultError(); - default: - throw new NotSupportedException("The format is not supported"); - } - } + CurrentSmsTextFormat.Text => $"AT+CMGL=\"{SmsStatusHelpers.ToString(smsStatus)}\"", + CurrentSmsTextFormat.PDU => $"AT+CMGL={(int)smsStatus}", + _ => throw new Exception("Unknown SMS Text Format") + }; - public override async Task>> ListSmssAsync(SmsStatus smsStatus) - { - AtResponse response = await channel.SendMultilineCommand($"AT+CMGL=\"{SmsStatusHelpers.ToString(smsStatus)}\"", null); + AtResponse response = await channel.SendMultilineCommand(command, null); List smss = new List(); if (response.Success) { - for (int i = 0; i < response.Intermediates.Count; i += 2) + switch (currentSmsTextFormat) { - string metaData = response.Intermediates[i]; - var match = Regex.Match(metaData, @"\+CMGL:\s(?\d+),""(?[A-Z\s]+)"",""(?\+?\d+)"",("""")?,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"""); - if (match.Success) - { - int index = int.Parse(match.Groups["index"].Value); - SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); - PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); - int year = int.Parse(match.Groups["year"].Value); - int month = int.Parse(match.Groups["month"].Value); - int day = int.Parse(match.Groups["day"].Value); - int hour = int.Parse(match.Groups["hour"].Value); - int minute = int.Parse(match.Groups["minute"].Value); - int second = int.Parse(match.Groups["second"].Value); - int zone = int.Parse(match.Groups["zone"].Value); - DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); - string message = response.Intermediates[i + 1]; - smss.Add(new SmsWithIndex(index, status, sender, received, message)); - } + case CurrentSmsTextFormat.PDU: + if ((response.Intermediates.Count % 2) != 0) + return ModemResponse.HasResultError>(); + + for (int i = 0; i < response.Intermediates.Count; i += 2) + { + string metaDataLine = response.Intermediates[i]; + string messageLine = response.Intermediates[i + 1]; + var match = Regex.Match(metaDataLine, @"\+CMGL:\s(?\d+),(?\d+),,(?\d+)"); + if (match.Success) + { + int index = int.Parse(match.Groups["index"].Value); + SmsStatus status = (SmsStatus)int.Parse(match.Groups["status"].Value); + + // Sent when AT+CSDH=1 is set + int length = int.Parse(match.Groups["length"].Value); + + SmsDeliver sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); + smss.Add(new SmsWithIndex(index, status, sms.SenderNumber, sms.Timestamp, sms.Message)); + } + } + break; + case CurrentSmsTextFormat.Text: + if ((response.Intermediates.Count % 2) != 0) + return ModemResponse.HasResultError>(); + + for (int i = 0; i < response.Intermediates.Count; i += 2) + { + string metaDataLine = response.Intermediates[i]; + string messageLine = response.Intermediates[i + 1]; + var match = Regex.Match(metaDataLine, @"\+CMGL:\s(?\d+),""(?[A-Z\s]+)"",""(?\+*\d+)"",("""")?,""(?(?\d\d)/(?\d\d)/(?\d\d),(?\d\d):(?\d\d):(?\d\d)(?[-+]\d\d))"",(?\d+),(?\d),(?\d),(?\d),(?""\+\d+""),(?\d+),(?\d+)"); + if (match.Success) + { + int index = int.Parse(match.Groups["index"].Value); + SmsStatus status = SmsStatusHelpers.ToSmsStatus(match.Groups["status"].Value); + PhoneNumberDTO sender = new PhoneNumberDTO(match.Groups["sender"].Value); + int year = int.Parse(match.Groups["year"].Value); + int month = int.Parse(match.Groups["month"].Value); + int day = int.Parse(match.Groups["day"].Value); + int hour = int.Parse(match.Groups["hour"].Value); + int minute = int.Parse(match.Groups["minute"].Value); + int second = int.Parse(match.Groups["second"].Value); + int zone = int.Parse(match.Groups["zone"].Value); + + // Sent when AT+CSDH=1 is set + int addressType = int.Parse(match.Groups["addressType"].Value); + int dataLength = int.Parse(match.Groups["length"].Value); + + DateTimeOffset received = new DateTimeOffset(2000 + year, month, day, hour, minute, second, TimeSpan.FromMinutes(15 * zone)); + + string message = messageLine; + if (messageLine.Length != dataLength) + message = UCS2.Decode(messageLine); + + smss.Add(new SmsWithIndex(index, status, sender, received, message)); + } + } + break; + case CurrentSmsTextFormat.Unknown: + break; + default: + break; } } - return ModemResponse.ResultSuccess(smss); + return ModemResponse.IsResultSuccess(smss); + } + #endregion + + public virtual async Task ReInitializeSimAsync() + { + AtResponse response = await channel.SendCommand($"AT+CRFSIM"); + return ModemResponse.IsSuccess(response.Success); } -#endregion } } diff --git a/src/HeboTech.ATLib/Modems/TP-LINK/IMA260.cs b/src/HeboTech.ATLib/Modems/TP-LINK/IMA260.cs new file mode 100644 index 0000000..e6083ad --- /dev/null +++ b/src/HeboTech.ATLib/Modems/TP-LINK/IMA260.cs @@ -0,0 +1,6 @@ +namespace HeboTech.ATLib.Modems.TP_LINK +{ + public interface IMA260 : IModem + { + } +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs b/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs index 70f8c2b..300c5e8 100644 --- a/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs +++ b/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs @@ -8,7 +8,7 @@ namespace HeboTech.ATLib.Modems.TP_LINK { - public class MA260 : ModemBase, IModem + public class MA260 : ModemBase, IModem, IMA260 { /// /// Based on some Qualcomm chipset @@ -16,7 +16,7 @@ public class MA260 : ModemBase, IModem /// Serial port settings: /// 9600 8N1 Handshake.RequestToSend /// - public MA260(AtChannel channel) + public MA260(IAtChannel channel) : base(channel) { } diff --git a/src/HeboTech.ATLib/Parsers/AtErrorParsers.cs b/src/HeboTech.ATLib/Parsers/AtErrorParsers.cs index 5a03a1f..ee139b7 100644 --- a/src/HeboTech.ATLib/Parsers/AtErrorParsers.cs +++ b/src/HeboTech.ATLib/Parsers/AtErrorParsers.cs @@ -206,7 +206,7 @@ public Error(int errorCode, string errorMessage) public override string ToString() { - return $"{ErrorMessage} (Error code {ErrorCode})"; + return $"Error code {ErrorCode} - {ErrorMessage}"; } } } diff --git a/src/HeboTech.ATLib/Parsers/ModemResponse.cs b/src/HeboTech.ATLib/Parsers/ModemResponse.cs index b7acab8..cfa6267 100644 --- a/src/HeboTech.ATLib/Parsers/ModemResponse.cs +++ b/src/HeboTech.ATLib/Parsers/ModemResponse.cs @@ -2,49 +2,55 @@ { public class ModemResponse { - public ModemResponse(bool isSuccess, string errorMessage) + public ModemResponse(bool isSuccess, Error error) { - IsSuccess = isSuccess; - ErrorMessage = errorMessage; + Success = isSuccess; + Error = error; } /// /// Indicates whether the command was successful or not. /// - public bool IsSuccess { get; } + public bool Success { get; } /// /// This property is only valid if 'Success' is false. /// - public string ErrorMessage { get; } + public Error Error { get; } public override string ToString() { - if (IsSuccess) + if (Success) return "Success"; else - return $"Error: {ErrorMessage}"; + return $"Error: {Error}"; } - public static ModemResponse Success() => + public static ModemResponse IsSuccess() => new ModemResponse(true, default); - public static ModemResponse Success(bool isSuccess) => + public static ModemResponse IsSuccess(bool isSuccess) => new ModemResponse(isSuccess, default); - public static ModemResponse Error(string error = "") => + public static ModemResponse HasError() => + new ModemResponse(false, null); + + public static ModemResponse HasError(Error error) => new ModemResponse(false, error); - public static ModemResponse ResultSuccess(T result) => + public static ModemResponse IsResultSuccess(T result) => new ModemResponse(true, default, result); - public static ModemResponse ResultError(string error = "") => + public static ModemResponse HasResultError() => + new ModemResponse(false, null, default); + + public static ModemResponse HasResultError(Error error) => new ModemResponse(false, error, default); } public class ModemResponse : ModemResponse { - public ModemResponse(bool success, string error, T result) + public ModemResponse(bool success, Error error, T result) : base(success, error) { Result = result; @@ -57,10 +63,10 @@ public ModemResponse(bool success, string error, T result) public override string ToString() { - if (IsSuccess) + if (Success) return $"{Result}"; else - return $"Error: {ErrorMessage}"; + return $"Error: {Error}"; } } }