diff --git a/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs b/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs index 8959fee..145fa96 100644 --- a/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs +++ b/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs @@ -1,6 +1,7 @@ using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Events; using HeboTech.ATLib.Modems.Cinterion; +using HeboTech.ATLib.Modems.D_LINK; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System; @@ -17,17 +18,29 @@ 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 IMC55i modem = new MC55i(atChannel); + //using IDWM222 modem = new DWM222(atChannel); atChannel.Open(); await atChannel.ClearAsync(); + modem.ErrorReceived += Modem_ErrorReceived; + + modem.GenericEvent += Modem_GenericEvent; + modem.IncomingCall += Modem_IncomingCall; modem.MissedCall += Modem_MissedCall; modem.CallStarted += Modem_CallStarted; modem.CallEnded += Modem_CallEnded; + + modem.SmsStorageReferenceReceived += Modem_SmsStorageReferenceReceived; modem.SmsReceived += Modem_SmsReceived; + + modem.SmsStatusReportReceived += Modem_SmsStatusReportReceived; + modem.SmsStatusReportStorageReferenceReceived += Modem_SmsStatusReportStorageReferenceReceived; + + modem.BroadcastMessageReceived += Modem_BroadcastMessageReceived; + modem.BroadcastMessageStorageReferenceReceived += Modem_BroadcastMessageStorageReferenceReceived; + modem.UssdResponseReceived += Modem_UssdResponseReceived; - modem.ErrorReceived += Modem_ErrorReceived; - modem.GenericEvent += Modem_GenericEvent; // Configure modem with required settings before PIN var requiredSettingsBeforePin = await modem.SetRequiredSettingsBeforePinAsync(); @@ -81,14 +94,6 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) var batteryStatus = await modem.GetBatteryStatusAsync(); Console.WriteLine($"Battery Status: {batteryStatus}"); - { - if (modem is IMC55i mc55i) - { - var mc55iBatteryStatus = await mc55i.MC55i_GetBatteryStatusAsync(); - Console.WriteLine($"MC55i Battery Status: {mc55iBatteryStatus}"); - } - } - var productInfo = await modem.GetProductIdentificationInformationAsync(); Console.WriteLine($"Product Information:{Environment.NewLine}{productInfo}"); @@ -98,18 +103,20 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) var dateTime = await modem.GetDateTimeAsync(); Console.WriteLine($"Date and time: {dateTime}"); + var selectMessageService = await modem.SetSelectMessageService(0); + Console.WriteLine($"Setting select message service: {selectMessageService}"); - var newSmsIndicationResult = await modem.SetNewSmsIndicationAsync(2, 1, 0, 0, 1); + var newSmsIndicationResult = await modem.SetNewSmsIndicationAsync(2, 1, 0, 2, 0); // 2, 1, 0, 2, 0 (CSMS=0) 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(MessageStorage.SM, MessageStorage.SM, MessageStorage.SM); + var setPreferredStorages = await modem.SetPreferredMessageStorageAsync(MessageStorage.MT, MessageStorage.MT, MessageStorage.MT); Console.WriteLine($"Storages set:{Environment.NewLine}{setPreferredStorages}"); - 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, 'x' to send raw command, 'z' to send raw command with response, '+' 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, 'p' to delete an SMS, 'u' to send USSD code, 'x' to send raw command, 'z' to send raw command with response, '+' to enable debug, '-' to disable debug and 'q' to exit..."); ConsoleKey key; while ((key = Console.ReadKey().Key) != ConsoleKey.Q) { @@ -164,21 +171,38 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) string smsMessage = Console.ReadLine(); Console.WriteLine("Sending SMS..."); - IEnumerable> smsReferences = await modem.SendSmsAsync(phoneNumber, smsMessage); + IEnumerable> smsReferences = await modem.SendSmsAsync(new SmsSubmitRequest(phoneNumber, smsMessage) { EnableStatusReportRequest = true, ValidityPeriod = ValidityPeriod.Relative(RelativeValidityPeriods.Minutes_5) }); foreach (var smsReference in smsReferences) Console.WriteLine($"SMS Reference: {smsReference}"); break; } case ConsoleKey.R: - Console.WriteLine("Enter SMS index:"); - if (int.TryParse(Console.ReadLine(), out int smsIndex)) { - var sms = await modem.ReadSmsAsync(smsIndex); - Console.WriteLine(sms); + Console.WriteLine("Enter SMS index:"); + if (int.TryParse(Console.ReadLine(), out int smsIndex)) + { + var sms = await modem.ReadSmsAsync(smsIndex); + if (sms.Success) + { + Console.WriteLine(sms.Result); + } + } + else + Console.WriteLine("Invalid SMS index"); + break; + } + case ConsoleKey.P: + { + Console.WriteLine("Enter SMS index:"); + if (int.TryParse(Console.ReadLine(), out int smsIndex)) + { + var deleteResponse = await modem.DeleteSmsAsync(smsIndex); + Console.WriteLine(deleteResponse); + } + else + Console.WriteLine("Invalid SMS index"); + break; } - else - Console.WriteLine("Invalid SMS index"); - break; case ConsoleKey.U: Console.WriteLine("Enter USSD Code:"); var ussd = Console.ReadLine(); @@ -188,10 +212,14 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) case ConsoleKey.L: Console.WriteLine("List all SMSs:"); var smss = await modem.ListSmssAsync(SmsStatus.ALL); + Console.WriteLine($"{smss.Result.Count} SMSs:"); if (smss.Success && smss.Result.Any()) { foreach (var sms in smss.Result) - Console.WriteLine($"------------------------------------------------{Environment.NewLine}{sms}"); + { + Console.WriteLine($"------------------------------------------------"); + Console.WriteLine($"Index: {sms.Index}, {sms.Sms}"); + } Console.WriteLine($"------------------------------------------------"); } @@ -208,6 +236,36 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) } } + private static void Modem_BroadcastMessageStorageReferenceReceived(object sender, BreadcastMessageStorageReferenceReceivedEventArgs e) + { + Console.WriteLine($"Broadcast Message. Index {e.Index} at storage location {e.Storage}"); + } + + private static void Modem_SmsStorageReferenceReceived(object sender, SmsStorageReferenceReceivedEventArgs e) + { + Console.WriteLine($"SMS Deliver. Index {e.Index} at storage location {e.Storage}"); + } + + private static void Modem_SmsStatusReportStorageReferenceReceived(object sender, SmsStatusReportStorageReferenceEventArgs e) + { + Console.WriteLine($"SMS Status Report. Index {e.Index} at storage location {e.Storage}"); + } + + private static void Modem_BroadcastMessageReceived(object sender, BreadcastMessageReceivedEventArgs e) + { + Console.WriteLine($"Broadcast Message: {e.BroadcastMessage}"); + } + + private static void Modem_SmsReceived(object sender, SmsReceivedEventArgs e) + { + Console.WriteLine($"SMS Deliver: {e.SmsDeliver}"); + } + + private static void Modem_SmsStatusReportReceived(object sender, SmsStatusReportEventArgs e) + { + Console.WriteLine($"SMS Status Report: {e.SmsStatusReport}"); + } + private static void Modem_GenericEvent(object sender, GenericEventArgs e) { Console.WriteLine($"Generic event: {e.Message}"); @@ -234,11 +292,6 @@ private static void Modem_CallStarted(object sender, CallStartedEventArgs e) Console.WriteLine("Call started"); } - private static void Modem_SmsReceived(object sender, SmsReceivedEventArgs e) - { - Console.WriteLine($"SMS received. Index {e.Index} at storage location {e.Storage}"); - } - private static void Modem_MissedCall(object sender, MissedCallEventArgs e) { Console.WriteLine($"Missed call at {e.Time} from {e.PhoneNumber}"); diff --git a/src/HeboTech.ATLib.TestConsole/Program.cs b/src/HeboTech.ATLib.TestConsole/Program.cs index 38e515e..305bc99 100644 --- a/src/HeboTech.ATLib.TestConsole/Program.cs +++ b/src/HeboTech.ATLib.TestConsole/Program.cs @@ -46,7 +46,7 @@ static async Task Main(string[] args) /* ######## 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); + socket.Connect("192.168.1.5", 7000); Console.WriteLine("Network socket opened"); Stream stream; stream = new NetworkStream(socket); diff --git a/src/HeboTech.ATLib.TestConsole/StressTest.cs b/src/HeboTech.ATLib.TestConsole/StressTest.cs index a877474..e32d3be 100644 --- a/src/HeboTech.ATLib.TestConsole/StressTest.cs +++ b/src/HeboTech.ATLib.TestConsole/StressTest.cs @@ -20,7 +20,7 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) modem.IncomingCall += Modem_IncomingCall; modem.MissedCall += Modem_MissedCall; - modem.SmsReceived += Modem_SmsReceived; + modem.SmsStorageReferenceReceived += Modem_SmsReceived; await modem.DisableEchoAsync(); @@ -73,7 +73,7 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) Console.ReadKey(); } - private static void Modem_SmsReceived(object sender, Events.SmsReceivedEventArgs e) + private static void Modem_SmsReceived(object sender, Events.SmsStorageReferenceReceivedEventArgs e) { Console.WriteLine($"SMS received. Index {e.Index} at storage location {e.Storage}"); } diff --git a/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs b/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs index 533fd1b..ccec72c 100644 --- a/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs +++ b/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs @@ -40,9 +40,8 @@ public void Encode_SmsSubmit_test(string countryCode, string subscriberNumber, s encodedMessage, dataCodingScheme) { - IncludeEmptySmscLength = includeEmptySmscLength, MessageReferenceNumber = 12 - }); + }, includeEmptySmscLength); Assert.Equal(answer, encoded.ToArray()); } @@ -57,10 +56,9 @@ public void Encode_SmsSubmit_message_too_long_test(string countryCode, string su new string('a', characterCount), dataCodingScheme) { - IncludeEmptySmscLength = includeEmptySmscLength, MessageReferenceNumber = 12 }; - Assert.Throws(() => SmsSubmitEncoder.Encode(request).ToList()); + Assert.Throws(() => SmsSubmitEncoder.Encode(request, includeEmptySmscLength).ToList()); } } } diff --git a/src/HeboTech.ATLib/DTOs/BroadcastMessage.cs b/src/HeboTech.ATLib/DTOs/BroadcastMessage.cs new file mode 100644 index 0000000..02baf27 --- /dev/null +++ b/src/HeboTech.ATLib/DTOs/BroadcastMessage.cs @@ -0,0 +1,9 @@ +namespace HeboTech.ATLib.DTOs +{ + public class BroadcastMessage + { + public BroadcastMessage() + { + } + } +} diff --git a/src/HeboTech.ATLib/DTOs/Sms.cs b/src/HeboTech.ATLib/DTOs/Sms.cs index 4fafdc6..368cae7 100644 --- a/src/HeboTech.ATLib/DTOs/Sms.cs +++ b/src/HeboTech.ATLib/DTOs/Sms.cs @@ -1,36 +1,26 @@ -using System; +using HeboTech.ATLib.PDU; namespace HeboTech.ATLib.DTOs { public class Sms { - public Sms(SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message) - : this(status, sender, receiveTime, message, 0, 1, 1) + protected Sms(MessageTypeIndicatorInbound messageTypeIndicator) { + MessageTypeIndicator = messageTypeIndicator; } - public Sms(SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message, int messageReferenceNumber, int totalNumberOfParts, int partNumber) + protected Sms(MessageTypeIndicatorInbound messageTypeIndicator, int messageReference) + : this(messageTypeIndicator) { - Status = status; - Sender = sender; - ReceiveTime = receiveTime; - Message = message; - MessageReferenceNumber = messageReferenceNumber; - TotalNumberOfParts = totalNumberOfParts; - PartNumber = partNumber; + MessageReference = messageReference; } - public SmsStatus Status { get; } - public PhoneNumberDTO Sender { get; } - public DateTimeOffset ReceiveTime { get;} - public string Message { get; } - public int MessageReferenceNumber { get; } - public int TotalNumberOfParts { get; } - public int PartNumber { get; } + public int MessageReference { get; } + public MessageTypeIndicatorInbound MessageTypeIndicator { get; } public override string ToString() { - return $"Sender:\t\t{Sender}{Environment.NewLine}ReceiveTime:\t{ReceiveTime}{Environment.NewLine}Ref. no.:\t{MessageReferenceNumber}{Environment.NewLine}Part:\t\t{PartNumber}/{TotalNumberOfParts}{Environment.NewLine}Message:\t{Message}"; + return $"MTI: {MessageTypeIndicator}, Msg. ref.: {MessageReference}"; } } -} +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/DTOs/SmsDeliver.cs b/src/HeboTech.ATLib/DTOs/SmsDeliver.cs index 05e14dd..ef7e3d0 100644 --- a/src/HeboTech.ATLib/DTOs/SmsDeliver.cs +++ b/src/HeboTech.ATLib/DTOs/SmsDeliver.cs @@ -1,13 +1,15 @@ -using System; +using HeboTech.ATLib.PDU; +using System; namespace HeboTech.ATLib.DTOs { /// /// Data object for a received SMS /// - public class SmsDeliver + public class SmsDeliver : Sms { public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumber, string message, DateTimeOffset timestamp) + : base(MessageTypeIndicatorInbound.SMS_DELIVER) { ServiceCenterNumber = serviceCenterNumber; SenderNumber = senderNumber; @@ -15,13 +17,13 @@ public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumbe Timestamp = timestamp; } - public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumber, string message, DateTimeOffset timestamp, int messageReferenceNumber, int totalNumberOfParts, int partNumber) + public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumber, string message, DateTimeOffset timestamp, int messageReference, int totalNumberOfParts, int partNumber) + : base(MessageTypeIndicatorInbound.SMS_DELIVER, messageReference) { ServiceCenterNumber = serviceCenterNumber; SenderNumber = senderNumber; Message = message; Timestamp = timestamp; - MessageReferenceNumber = messageReferenceNumber; TotalNumberOfParts = totalNumberOfParts; PartNumber = partNumber; } @@ -30,8 +32,16 @@ public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumbe public PhoneNumberDTO SenderNumber { get; } public string Message { get; } public DateTimeOffset Timestamp { get; } - public int MessageReferenceNumber { get; } public int TotalNumberOfParts { get; } public int PartNumber { get; } + + public void DeliverMethod() + { + } + + public override string ToString() + { + return base.ToString() + $" From: {SenderNumber}. Message: {Message}. Timestamp: {Timestamp}"; + } } } diff --git a/src/HeboTech.ATLib/DTOs/SmsDeliveryStatus.cs b/src/HeboTech.ATLib/DTOs/SmsDeliveryStatus.cs new file mode 100644 index 0000000..245610a --- /dev/null +++ b/src/HeboTech.ATLib/DTOs/SmsDeliveryStatus.cs @@ -0,0 +1,55 @@ +namespace HeboTech.ATLib.DTOs +{ + /// + /// Bits 0..6. Bit 7 is reserved. + /// + public enum SmsDeliveryStatus : byte + { + // Suffix _1, _2 and _3 are used to separate identical names, one suffix for each 'group' of messages + + // Transaction completed + Message_received_by_SME = 0b0000_0000, + Forwarded_to_SME_but_unconfirmed_delivery = 0b0000_0001, + Message_replaced_by_the_SC = 0b0000_0010, + + // 000_0011..000_1111 Reserved + // 001_0000..0011111 Values specific to each SC + + // Temporary error, SC still trying to transfer to SM + Congestion_1 = 0b0010_0000, + SME_busy_1 = 0b0010_0001, + Service_rejected_1 = 0b0010_0011, + Quality_of_service_not_available_1 = 0b0010_0100, + Error_in_SME_1 = 0b0010_0101, + + // 0100110..0101111 Reserved + // 0110000..0111111 Values specific to each SC + + // Permanent error, SC is not making any more transfer attempts + Remote_procedure_error = 0b0100_0000, + Incompatible_destination = 0b0100_0001, + Connection_rejected_by_SME = 0b0100_0010, + Not_obtainable = 0b0100_0011, + Quality_of_service_not_available_2 = 0b0100_0100, + No_interworking_available = 0b0100_0101, + SM_validity_period_expired = 0b0100_0110, + SM_deleted_by_originating_SME = 0b0100_0111, + SM_deleted_by_SC_administration = 0b0100_1000, + SM_does_not_exist = 0b0100_1001, + + // 1001010..1001111 Reserved + // 1010000..1011111 Values specific to each SC + + // Temporary error, SC is not making any more transfer attempts + Congestion_3 = 0b0110_0000, + SME_busy_3 = 0b0110_0001, + No_response_from_SME = 0b0110_0010, + Service_rejected_3 = 0b0110_0011, + Quality_of_service_not_available_3 = 0b0110_0100, + Error_in_SME_3 = 0b0110_0101, + + //1100110..1101001 Reserved + //1101010..1101111 Reserved + //1110000..1111111 Values specific to each SC + } +} diff --git a/src/HeboTech.ATLib/DTOs/SmsStatusReport.cs b/src/HeboTech.ATLib/DTOs/SmsStatusReport.cs new file mode 100644 index 0000000..ce64d0f --- /dev/null +++ b/src/HeboTech.ATLib/DTOs/SmsStatusReport.cs @@ -0,0 +1,27 @@ +using HeboTech.ATLib.PDU; +using System; + +namespace HeboTech.ATLib.DTOs +{ + public class SmsStatusReport : Sms + { + public SmsStatusReport(int messageReference, PhoneNumberDTO recipientAddress, DateTimeOffset serviceCenterTimestamp, DateTimeOffset dischargeTime, SmsDeliveryStatus status) + : base(MessageTypeIndicatorInbound.SMS_STATUS_REPORT, messageReference) + { + RecipientAddress = recipientAddress; + ServiceCenterTimestamp = serviceCenterTimestamp; + DischargeTime = dischargeTime; + Status = status; + } + + public PhoneNumberDTO RecipientAddress { get; } + public DateTimeOffset ServiceCenterTimestamp { get; } + public DateTimeOffset DischargeTime { get; } + public SmsDeliveryStatus Status { get; } + + public override string ToString() + { + return base.ToString() + $" Delivered with status {Status}. RA: {RecipientAddress}. SCTS: {ServiceCenterTimestamp}. DT: {DischargeTime}."; + } + } +} diff --git a/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs b/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs index a9e0590..ede6e5e 100644 --- a/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs +++ b/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs @@ -34,38 +34,17 @@ public SmsSubmitRequest( PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - : this( - phoneNumber, - message, - codingScheme, - ValidityPeriod.NotPresent()) - { - } - - /// - /// Creates a data object for submitting an SMS in PDU format. - /// - /// The receiver phone number - /// The message to send - /// The coding scheme to use - /// The validity period to use - public SmsSubmitRequest( - PhoneNumber phoneNumber, - string message, - CharacterSet codingScheme, - ValidityPeriod validityPeriod) { PhoneNumber = phoneNumber; Message = message; CodingScheme = codingScheme; - ValidityPeriod = validityPeriod; } public PhoneNumber PhoneNumber { get; } public string Message { get; } public CharacterSet CodingScheme { get; } - public bool IncludeEmptySmscLength { get; set; } public byte MessageReferenceNumber { get; set; } public ValidityPeriod ValidityPeriod { get; set; } + public bool EnableStatusReportRequest { get; set; } } } diff --git a/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs b/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs index fee9dbe..ee387fa 100644 --- a/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs +++ b/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs @@ -1,26 +1,14 @@ -using System; - -namespace HeboTech.ATLib.DTOs +namespace HeboTech.ATLib.DTOs { - public class SmsWithIndex : Sms + public class SmsWithIndex { - public SmsWithIndex(int index, SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message) - : base(status, sender, receiveTime, message) - { - Index = index; - } - - public SmsWithIndex(int index, SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message, int messageReferenceNumber, int totalNumberOfParts, int partNumber) - : base(status, sender, receiveTime, message, messageReferenceNumber, totalNumberOfParts, partNumber) + public SmsWithIndex(Sms sms, int index) { + Sms = sms; Index = index; } + public Sms Sms { get; } public int Index { get; } - - public override string ToString() - { - return $"Index:\t\t{Index}{Environment.NewLine}" + base.ToString(); - } } -} +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs b/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs index 3c64718..d20b9fc 100644 --- a/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs +++ b/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs @@ -40,6 +40,8 @@ private ValidityPeriod(ValidityPeriodFormat format, byte[] value) /// public static ValidityPeriod Relative(byte value) => new ValidityPeriod(ValidityPeriodFormat.Relative, new byte[] { value }); + public static ValidityPeriod Relative(RelativeValidityPeriods value) => Relative((byte)value); + /// /// An absolute validity period /// @@ -51,4 +53,264 @@ public static ValidityPeriod Absolute(DateTimeOffset value) return new ValidityPeriod(ValidityPeriodFormat.Absolute, encoded); } } + + public enum RelativeValidityPeriods + { + Minutes_5 = 0, + Minutes_10 = 1, + Minutes_15 = 2, + Minutes_20 = 3, + Minutes_25 = 4, + Minutes_30 = 5, + Minutes_35 = 6, + Minutes_40 = 7, + Minutes_45 = 8, + Minutes_50 = 9, + Minutes_55 = 10, + Hours_1 = 11, + Hours_1_Minutes_5 = 12, + Hours_1_Minutes_10 = 13, + Hours_1_Minutes_15 = 14, + Hours_1_Minutes_20 = 15, + Hours_1_Minutes_25 = 16, + Hours_1_Minutes_30 = 17, + Hours_1_Minutes_35 = 18, + Hours_1_Minutes_40 = 19, + Hours_1_Minutes_45 = 20, + Hours_1_Minutes_50 = 21, + Hours_1_Minutes_55 = 22, + Hours_2 = 23, + Hours_2_Minutes_5 = 24, + Hours_2_Minutes_10 = 25, + Hours_2_Minutes_15 = 26, + Hours_2_Minutes_20 = 27, + Hours_2_Minutes_25 = 28, + Hours_2_Minutes_30 = 29, + Hours_2_Minutes_35 = 30, + Hours_2_Minutes_40 = 31, + Hours_2_Minutes_45 = 32, + Hours_2_Minutes_50 = 33, + Hours_2_Minutes_55 = 34, + Hours_3 = 35, + Hours_3_Minutes_5 = 36, + Hours_3_Minutes_10 = 37, + Hours_3_Minutes_15 = 38, + Hours_3_Minutes_20 = 39, + Hours_3_Minutes_25 = 40, + Hours_3_Minutes_30 = 41, + Hours_3_Minutes_35 = 42, + Hours_3_Minutes_40 = 43, + Hours_3_Minutes_45 = 44, + Hours_3_Minutes_50 = 45, + Hours_3_Minutes_55 = 46, + Hours_4 = 47, + Hours_4_Minutes_5 = 48, + Hours_4_Minutes_10 = 49, + Hours_4_Minutes_15 = 50, + Hours_4_Minutes_20 = 51, + Hours_4_Minutes_25 = 52, + Hours_4_Minutes_30 = 53, + Hours_4_Minutes_35 = 54, + Hours_4_Minutes_40 = 55, + Hours_4_Minutes_45 = 56, + Hours_4_Minutes_50 = 57, + Hours_4_Minutes_55 = 58, + Hours_5 = 59, + Hours_5_Minutes_5 = 60, + Hours_5_Minutes_10 = 61, + Hours_5_Minutes_15 = 62, + Hours_5_Minutes_20 = 63, + Hours_5_Minutes_25 = 64, + Hours_5_Minutes_30 = 65, + Hours_5_Minutes_35 = 66, + Hours_5_Minutes_40 = 67, + Hours_5_Minutes_45 = 68, + Hours_5_Minutes_50 = 69, + Hours_5_Minutes_55 = 70, + Hours_6 = 71, + Hours_6_Minutes_5 = 72, + Hours_6_Minutes_10 = 73, + Hours_6_Minutes_15 = 74, + Hours_6_Minutes_20 = 75, + Hours_6_Minutes_25 = 76, + Hours_6_Minutes_30 = 77, + Hours_6_Minutes_35 = 78, + Hours_6_Minutes_40 = 79, + Hours_6_Minutes_45 = 80, + Hours_6_Minutes_50 = 81, + Hours_6_Minutes_55 = 82, + Hours_7 = 83, + Hours_7_Minutes_5 = 84, + Hours_7_Minutes_10 = 85, + Hours_7_Minutes_15 = 86, + Hours_7_Minutes_20 = 87, + Hours_7_Minutes_25 = 88, + Hours_7_Minutes_30 = 89, + Hours_7_Minutes_35 = 90, + Hours_7_Minutes_40 = 91, + Hours_7_Minutes_45 = 92, + Hours_7_Minutes_50 = 93, + Hours_7_Minutes_55 = 94, + Hours_8 = 95, + Hours_8_Minutes_5 = 96, + Hours_8_Minutes_10 = 97, + Hours_8_Minutes_15 = 98, + Hours_8_Minutes_20 = 99, + Hours_8_Minutes_25 = 100, + Hours_8_Minutes_30 = 101, + Hours_8_Minutes_35 = 102, + Hours_8_Minutes_40 = 103, + Hours_8_Minutes_45 = 104, + Hours_8_Minutes_50 = 105, + Hours_8_Minutes_55 = 106, + Hours_9 = 107, + Hours_9_Minutes_5 = 108, + Hours_9_Minutes_10 = 109, + Hours_9_Minutes_15 = 110, + Hours_9_Minutes_20 = 111, + Hours_9_Minutes_25 = 112, + Hours_9_Minutes_30 = 113, + Hours_9_Minutes_35 = 114, + Hours_9_Minutes_40 = 115, + Hours_9_Minutes_45 = 116, + Hours_9_Minutes_50 = 117, + Hours_9_Minutes_55 = 118, + Hours_10 = 119, + Hours_10_Minutes_5 = 120, + Hours_10_Minutes_10 = 121, + Hours_10_Minutes_15 = 122, + Hours_10_Minutes_20 = 123, + Hours_10_Minutes_25 = 124, + Hours_10_Minutes_30 = 125, + Hours_10_Minutes_35 = 126, + Hours_10_Minutes_40 = 127, + Hours_10_Minutes_45 = 128, + Hours_10_Minutes_50 = 129, + Hours_10_Minutes_55 = 130, + Hours_11 = 131, + Hours_11_Minutes_5 = 132, + Hours_11_Minutes_10 = 133, + Hours_11_Minutes_15 = 134, + Hours_11_Minutes_20 = 135, + Hours_11_Minutes_25 = 136, + Hours_11_Minutes_30 = 137, + Hours_11_Minutes_35 = 138, + Hours_11_Minutes_40 = 139, + Hours_11_Minutes_45 = 140, + Hours_11_Minutes_50 = 141, + Hours_11_Minutes_55 = 142, + Hours_12 = 143, + Hours_12_Minutes_30 = 144, + Hours_13 = 145, + Hours_13_Minutes_30 = 146, + Hours_14 = 147, + Hours_14_Minutes_30 = 148, + Hours_15 = 149, + Hours_15_Minutes_30 = 150, + Hours_16 = 151, + Hours_16_Minutes_30 = 152, + Hours_17 = 153, + Hours_17_Minutes_30 = 154, + Hours_18 = 155, + Hours_18_Minutes_30 = 156, + Hours_19 = 157, + Hours_19_Minutes_30 = 158, + Hours_20 = 159, + Hours_20_Minutes_30 = 160, + Hours_21 = 161, + Hours_21_Minutes_30 = 162, + Hours_22 = 163, + Hours_22_Minutes_30 = 164, + Hours_23 = 165, + Hours_23_Minutes_30 = 166, + Hours_24 = 167, + Days_2 = 168, + Days_3 = 169, + Days_4 = 170, + Days_5 = 171, + Days_6 = 172, + Days_7 = 173, + Days_8 = 174, + Days_9 = 175, + Days_10 = 176, + Days_11 = 177, + Days_12 = 178, + Days_13 = 179, + Days_14 = 180, + Days_15 = 181, + Days_16 = 182, + Days_17 = 183, + Days_18 = 184, + Days_19 = 185, + Days_20 = 186, + Days_21 = 187, + Days_22 = 188, + Days_23 = 189, + Days_24 = 190, + Days_25 = 191, + Days_26 = 192, + Days_27 = 193, + Days_28 = 194, + Days_29 = 195, + Days_30 = 196, + Weeks_5 = 197, + Weeks_6 = 198, + Weeks_7 = 199, + Weeks_8 = 200, + Weeks_9 = 201, + Weeks_10 = 202, + Weeks_11 = 203, + Weeks_12 = 204, + Weeks_13 = 205, + Weeks_14 = 206, + Weeks_15 = 207, + Weeks_16 = 208, + Weeks_17 = 209, + Weeks_18 = 210, + Weeks_19 = 211, + Weeks_20 = 212, + Weeks_21 = 213, + Weeks_22 = 214, + Weeks_23 = 215, + Weeks_24 = 216, + Weeks_25 = 217, + Weeks_26 = 218, + Weeks_27 = 219, + Weeks_28 = 220, + Weeks_29 = 221, + Weeks_30 = 222, + Weeks_31 = 223, + Weeks_32 = 224, + Weeks_33 = 225, + Weeks_34 = 226, + Weeks_35 = 227, + Weeks_36 = 228, + Weeks_37 = 229, + Weeks_38 = 230, + Weeks_39 = 231, + Weeks_40 = 232, + Weeks_41 = 233, + Weeks_42 = 234, + Weeks_43 = 235, + Weeks_44 = 236, + Weeks_45 = 237, + Weeks_46 = 238, + Weeks_47 = 239, + Weeks_48 = 240, + Weeks_49 = 241, + Weeks_50 = 242, + Weeks_51 = 243, + Weeks_52 = 244, + Weeks_53 = 245, + Weeks_54 = 246, + Weeks_55 = 247, + Weeks_56 = 248, + Weeks_57 = 249, + Weeks_58 = 250, + Weeks_59 = 251, + Weeks_60 = 252, + Weeks_61 = 253, + Weeks_62 = 254, + Weeks_63 = 255 + } } diff --git a/src/HeboTech.ATLib/Events/BreadcastMessageReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/BreadcastMessageReceivedEventArgs.cs new file mode 100644 index 0000000..cd7d50a --- /dev/null +++ b/src/HeboTech.ATLib/Events/BreadcastMessageReceivedEventArgs.cs @@ -0,0 +1,27 @@ +using HeboTech.ATLib.DTOs; +using System; +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class BreadcastMessageReceivedEventArgs + { + public BreadcastMessageReceivedEventArgs(BroadcastMessage broadcastMessage) + { + BroadcastMessage = broadcastMessage; + } + + public BroadcastMessage BroadcastMessage { get; } + + public static BreadcastMessageReceivedEventArgs CreateFromResponse(string line1, string line2) + { + var line1Match = Regex.Match(line1, @"\+CBM:\s(?\d+)"); + if (line1Match.Success) + { + throw new NotImplementedException(); + } + + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/BreadcastMessageStorageReferenceReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/BreadcastMessageStorageReferenceReceivedEventArgs.cs new file mode 100644 index 0000000..08bcc8b --- /dev/null +++ b/src/HeboTech.ATLib/Events/BreadcastMessageStorageReferenceReceivedEventArgs.cs @@ -0,0 +1,28 @@ +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class BreadcastMessageStorageReferenceReceivedEventArgs + { + public BreadcastMessageStorageReferenceReceivedEventArgs(string storage, int index) + { + Storage = storage; + Index = index; + } + + public string Storage { get; } + public int Index { get; } + + public static BreadcastMessageStorageReferenceReceivedEventArgs CreateFromResponse(string response) + { + var match = Regex.Match(response, @"\+CBMI:\s""(?[A-Z]+)"",(?\d+)"); + if (match.Success) + { + string storage = match.Groups["storage"].Value; + int index = int.Parse(match.Groups["index"].Value); + return new BreadcastMessageStorageReferenceReceivedEventArgs(storage, index); + } + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs index cbd4c19..613cac9 100644 --- a/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs +++ b/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs @@ -1,27 +1,29 @@ -using System.Text.RegularExpressions; +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using HeboTech.ATLib.PDU; +using System.Text.RegularExpressions; namespace HeboTech.ATLib.Events { public class SmsReceivedEventArgs { - public SmsReceivedEventArgs(string storage, int index) + public SmsReceivedEventArgs(SmsDeliver smsDeliver) { - Storage = storage; - Index = index; + SmsDeliver = smsDeliver; } - public string Storage { get; } - public int Index { get; } + public SmsDeliver SmsDeliver { get; } - public static SmsReceivedEventArgs CreateFromResponse(string response) + public static SmsReceivedEventArgs CreateFromResponse(string line1, string line2) { - var match = Regex.Match(response, @"\+CMTI:\s""(?[A-Z]+)"",(?\d+)"); - if (match.Success) + var line1Match = Regex.Match(line1, @"\+CMT:\s(""(?[\+0-9]*)"")?,(?\d+)"); + if (line1Match.Success) { - string storage = match.Groups["storage"].Value; - int index = int.Parse(match.Groups["index"].Value); - return new SmsReceivedEventArgs(storage, index); + byte length = byte.Parse(line1Match.Groups["length"].Value); + var smsDeliver = SmsDeliverDecoder.Decode(line2.ToByteArray()); + return new SmsReceivedEventArgs(smsDeliver); } + return default; } } diff --git a/src/HeboTech.ATLib/Events/SmsStatusReportEventArgs.cs b/src/HeboTech.ATLib/Events/SmsStatusReportEventArgs.cs new file mode 100644 index 0000000..ccdbd58 --- /dev/null +++ b/src/HeboTech.ATLib/Events/SmsStatusReportEventArgs.cs @@ -0,0 +1,30 @@ +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using HeboTech.ATLib.PDU; +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class SmsStatusReportEventArgs + { + public SmsStatusReportEventArgs(SmsStatusReport smsStatusReport) + { + SmsStatusReport = smsStatusReport; + } + + public SmsStatusReport SmsStatusReport { get; } + + public static SmsStatusReportEventArgs CreateFromResponse(string line1, string line2) + { + var line1Match = Regex.Match(line1, @"\+CDS:\s(?\d+)"); + if (line1Match.Success) + { + byte length = byte.Parse(line1Match.Groups["length"].Value); + var report = SmsStatusReportDecoder.Decode(line2.ToByteArray(), length); + return new SmsStatusReportEventArgs(report); + } + + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/SmsStatusReportStorageReferenceEventArgs.cs b/src/HeboTech.ATLib/Events/SmsStatusReportStorageReferenceEventArgs.cs new file mode 100644 index 0000000..f3b0ec3 --- /dev/null +++ b/src/HeboTech.ATLib/Events/SmsStatusReportStorageReferenceEventArgs.cs @@ -0,0 +1,30 @@ +using HeboTech.ATLib.Modems.Generic; +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class SmsStatusReportStorageReferenceEventArgs + { + public SmsStatusReportStorageReferenceEventArgs(MessageStorage storage, int index) + { + Storage = storage; + Index = index; + } + + public MessageStorage Storage { get; } + public int Index { get; } + + public static SmsStatusReportStorageReferenceEventArgs CreateFromResponse(string line1) + { + var match = Regex.Match(line1, @"\+CDSI:\s""(?[a-zA-Z]+)"",(?\d+)"); + if (match.Success) + { + string storage = match.Groups["storage"].Value; + int index = int.Parse(match.Groups["index"].Value); + return new SmsStatusReportStorageReferenceEventArgs((MessageStorage)storage, index); + } + + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/SmsStorageReferenceReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/SmsStorageReferenceReceivedEventArgs.cs new file mode 100644 index 0000000..5b2a591 --- /dev/null +++ b/src/HeboTech.ATLib/Events/SmsStorageReferenceReceivedEventArgs.cs @@ -0,0 +1,28 @@ +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class SmsStorageReferenceReceivedEventArgs + { + public SmsStorageReferenceReceivedEventArgs(string storage, int index) + { + Storage = storage; + Index = index; + } + + public string Storage { get; } + public int Index { get; } + + public static SmsStorageReferenceReceivedEventArgs CreateFromResponse(string response) + { + var match = Regex.Match(response, @"\+CMTI:\s""(?[A-Z]+)"",(?\d+)"); + if (match.Success) + { + string storage = match.Groups["storage"].Value; + int index = int.Parse(match.Groups["index"].Value); + return new SmsStorageReferenceReceivedEventArgs(storage, index); + } + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Extensions/SmsDeliverExtensions.cs b/src/HeboTech.ATLib/Extensions/SmsDeliverExtensions.cs deleted file mode 100644 index 524cf1c..0000000 --- a/src/HeboTech.ATLib/Extensions/SmsDeliverExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using HeboTech.ATLib.DTOs; - -namespace HeboTech.ATLib.Extensions -{ - internal static class SmsDeliverExtensions - { - public static Sms ToSms(this SmsDeliver sms, SmsStatus status) - { - return new Sms(status, sms.SenderNumber, sms.Timestamp, sms.Message, sms.MessageReferenceNumber, sms.TotalNumberOfParts, sms.PartNumber); - } - - public static SmsWithIndex ToSmsWithIndex(this SmsDeliver sms, int index, SmsStatus status) - { - return new SmsWithIndex(index, status, sms.SenderNumber, sms.Timestamp, sms.Message, sms.MessageReferenceNumber, sms.TotalNumberOfParts, sms.PartNumber); - } - } -} diff --git a/src/HeboTech.ATLib/Extensions/SmsExtensions.cs b/src/HeboTech.ATLib/Extensions/SmsExtensions.cs new file mode 100644 index 0000000..f06d930 --- /dev/null +++ b/src/HeboTech.ATLib/Extensions/SmsExtensions.cs @@ -0,0 +1,12 @@ +using HeboTech.ATLib.DTOs; + +namespace HeboTech.ATLib.Extensions +{ + internal static class SmsExtensions + { + public static SmsWithIndex ToSmsWithIndex(this Sms sms, int index) + { + return new SmsWithIndex(sms, index); + } + } +} diff --git a/src/HeboTech.ATLib/HeboTech.ATLib.csproj b/src/HeboTech.ATLib/HeboTech.ATLib.csproj index 4eb7f4e..6059117 100644 --- a/src/HeboTech.ATLib/HeboTech.ATLib.csproj +++ b/src/HeboTech.ATLib/HeboTech.ATLib.csproj @@ -4,10 +4,10 @@ netstandard2.1 HeboTech HeboTech ATLib - 7.1.0-beta2 - 7.1.0-beta2 - 7.1.0.0 - 7.1.0.0 + 8.0.0-alpha1 + 8.0.0-alpha1 + 8.0.0.0 + 8.0.0.0 HeboTech.ATLib AT command library that makes it easy to communicate with modems. AT command library that makes it easy to communicate with modems. diff --git a/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs b/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs index b3afccd..1d88ea0 100644 --- a/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs +++ b/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using HeboTech.ATLib.PDU; @@ -26,48 +25,12 @@ public MC55i(IAtChannel channel) { } - public async Task>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override async Task>> SendSmsAsync(SmsSubmitRequest request) { - if (phoneNumber is null) - throw new ArgumentNullException(nameof(phoneNumber)); - if (message is null) - throw new ArgumentNullException(nameof(message)); + if (request is null) + throw new ArgumentNullException(nameof(request)); - 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 async Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet 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 }); + IEnumerable pdus = SmsSubmitEncoder.Encode(request, true); List> references = new List>(); foreach (string pdu in pdus) { @@ -134,5 +97,25 @@ public async Task> MC55i_GetBatteryStatusAsync AtErrorParsers.TryGetError(response.FinalResponse, out Error error); return ModemResponse.HasResultError(error); } + + /// + /// Sets how receiving a new SMS is indicated + /// + /// mode + /// mt + /// bm + /// ds + /// Not in use + /// Command status + public override async Task SetNewSmsIndicationAsync(int mode, int mt, int bm, int ds, int bfr) + { + AtResponse response = await channel.SendCommand($"AT+CNMI={mode},{mt},{bm},{ds}"); + + if (response.Success) + return ModemResponse.IsSuccess(); + + AtErrorParsers.TryGetError(response.FinalResponse, out Error error); + return ModemResponse.HasError(error); + } } } diff --git a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs index aa4b286..209f053 100644 --- a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs +++ b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs @@ -26,7 +26,7 @@ public ModemBase(IAtChannel channel) channel.UnsolicitedEvent += Channel_UnsolicitedEvent; } - private void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) + protected virtual void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) { if (e.Line1 == "RING") IncomingCall?.Invoke(this, new IncomingCallEventArgs()); @@ -36,12 +36,28 @@ private void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) CallEnded?.Invoke(this, CallEndedEventArgs.CreateFromResponse(e.Line1)); else if (e.Line1.StartsWith("MISSED_CALL: ")) MissedCall?.Invoke(this, MissedCallEventArgs.CreateFromResponse(e.Line1)); + + else if (e.Line1.StartsWith("+CMT: ")) + SmsReceived?.Invoke(this, SmsReceivedEventArgs.CreateFromResponse(e.Line1, e.Line2)); else if (e.Line1.StartsWith("+CMTI: ")) - SmsReceived?.Invoke(this, SmsReceivedEventArgs.CreateFromResponse(e.Line1)); + SmsStorageReferenceReceived?.Invoke(this, SmsStorageReferenceReceivedEventArgs.CreateFromResponse(e.Line1)); + + else if (e.Line1.StartsWith("+CBM: ")) + BroadcastMessageReceived?.Invoke(this, BreadcastMessageReceivedEventArgs.CreateFromResponse(e.Line1, e.Line2)); + else if (e.Line1.StartsWith("+CBMI: ")) + BroadcastMessageStorageReferenceReceived?.Invoke(this, BreadcastMessageStorageReferenceReceivedEventArgs.CreateFromResponse(e.Line1)); + + else if (e.Line1.StartsWith("+CDS: ")) + SmsStatusReportReceived?.Invoke(this, SmsStatusReportEventArgs.CreateFromResponse(e.Line1, e.Line2)); + else if (e.Line1.StartsWith("+CDSI: ")) + SmsStatusReportStorageReferenceReceived?.Invoke(this, SmsStatusReportStorageReferenceEventArgs.CreateFromResponse(e.Line1)); + else if (e.Line1.StartsWith("+CUSD: ")) UssdResponseReceived?.Invoke(this, UssdResponseEventArgs.CreateFromResponse(e.Line1)); + else if (AtErrorParsers.TryGetError(e.Line1, out Error error)) ErrorReceived?.Invoke(this, new ErrorEventArgs(error.ToString())); + else GenericEvent?.Invoke(this, new GenericEventArgs(e.Line1)); } @@ -49,6 +65,15 @@ private void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) public event EventHandler ErrorReceived; public event EventHandler GenericEvent; + public event EventHandler SmsReceived; + public event EventHandler SmsStorageReferenceReceived; + + public event EventHandler BroadcastMessageReceived; + public event EventHandler BroadcastMessageStorageReferenceReceived; + + public event EventHandler SmsStatusReportReceived; + public event EventHandler SmsStatusReportStorageReferenceReceived; + #region _V_25TER public event EventHandler IncomingCall; public event EventHandler MissedCall; @@ -186,8 +211,6 @@ public virtual async Task SetCharacterSetAsync(CharacterSet chara #endregion #region _3GPP_TS_27_005 - public event EventHandler SmsReceived; - public virtual async Task> GetSmsMessageFormatAsync() { AtResponse response = await channel.SendSingleLineCommandAsync($"AT+CMGF?", "+CMGF:"); @@ -218,19 +241,19 @@ public virtual async Task SetSmsMessageFormatAsync(SmsTextFormat return ModemResponse.HasError(error); } - public virtual async Task SetNewSmsIndicationAsync(int mode, int mt, int bm, int ds, int bfr) + public virtual async Task SetSelectMessageService(int service) { - if (mode < 0 || mode > 2) - throw new ArgumentOutOfRangeException(nameof(mode)); - if (mt < 0 || mt > 3) - throw new ArgumentOutOfRangeException(nameof(mt)); - if (!(bm == 0 || bm == 2)) - throw new ArgumentOutOfRangeException(nameof(bm)); - if (ds < 0 || ds > 2) - throw new ArgumentOutOfRangeException(nameof(ds)); - if (bfr < 0 || bfr > 1) - throw new ArgumentOutOfRangeException(nameof(bfr)); + AtResponse response = await channel.SendCommand($"AT+CSMS={service}"); + + if (response.Success) + return ModemResponse.IsSuccess(); + AtErrorParsers.TryGetError(response.FinalResponse, out Error error); + return ModemResponse.HasError(error); + } + + public virtual async Task SetNewSmsIndicationAsync(int mode, int mt, int bm, int ds, int bfr) + { AtResponse response = await channel.SendCommand($"AT+CNMI={mode},{mt},{bm},{ds},{bfr}"); if (response.Success) @@ -240,20 +263,17 @@ public virtual async Task SetNewSmsIndicationAsync(int mode, int return ModemResponse.HasError(error); } - protected virtual Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, bool includeEmptySmscLength) + public virtual Task>> SendSmsAsync(SmsSubmitRequest request) { - CharacterSet characterSet = Gsm7.IsGsm7Compatible(message.ToCharArray()) ? CharacterSet.Gsm7 : CharacterSet.UCS2; - return SendSmsAsync(phoneNumber, message, characterSet, includeEmptySmscLength); + return SendSmsAsync(request, true); } - protected virtual async Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme, bool includeEmptySmscLength) + protected async Task>> SendSmsAsync(SmsSubmitRequest request, bool includeEmptySmscLength) { - if (phoneNumber is null) - throw new ArgumentNullException(nameof(phoneNumber)); - if (message is null) - throw new ArgumentNullException(nameof(message)); + if (request is null) + throw new ArgumentNullException(nameof(request)); - IEnumerable pdus = SmsSubmitEncoder.Encode(new SmsSubmitRequest(phoneNumber, message, codingScheme) { IncludeEmptySmscLength = includeEmptySmscLength }); + IEnumerable pdus = SmsSubmitEncoder.Encode(request, includeEmptySmscLength); List> references = new List>(); foreach (string pdu in pdus) { @@ -375,13 +395,12 @@ public virtual async Task> ReadSmsAsync(int index) if (length > 0) { string line2 = pduResponse.Intermediates[1]; - var line2Match = Regex.Match(line2, @"(?[0-9A-Z]*)"); + var line2Match = Regex.Match(line2, @"(?[0-9A-Z]*)"); if (line2Match.Success) { - string pdu = line2Match.Groups["status"].Value; - SmsDeliver pduMessage = SmsDeliverDecoder.Decode(pdu.ToByteArray()); - - return ModemResponse.IsResultSuccess(pduMessage.ToSms(status)); + string pduString = line2Match.Groups["pdu"].Value; + Sms sms = SmsDecoder.Decode(pduString.ToByteArray(), status); + return ModemResponse.IsResultSuccess(sms); } } } @@ -416,8 +435,8 @@ public virtual async Task>> ListSmssAsync(SmsSt // Sent when AT+CSDH=1 is set int length = int.Parse(match.Groups["length"].Value); - SmsDeliver sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); - smss.Add(sms.ToSmsWithIndex(index, status)); + Sms sms = SmsDecoder.Decode(messageLine.ToByteArray(), status); + smss.Add(sms.ToSmsWithIndex(index)); } } } diff --git a/src/HeboTech.ATLib/Modems/IModem.cs b/src/HeboTech.ATLib/Modems/IModem.cs index 49cb28a..20695d1 100644 --- a/src/HeboTech.ATLib/Modems/IModem.cs +++ b/src/HeboTech.ATLib/Modems/IModem.cs @@ -39,13 +39,23 @@ public interface IModem : IDisposable /// /// Indicates that an SMS is received /// - event EventHandler SmsReceived; + event EventHandler SmsStorageReferenceReceived; /// /// Indicates that a USSD response is received /// event EventHandler UssdResponseReceived; + + event EventHandler SmsReceived; + + event EventHandler BroadcastMessageReceived; + event EventHandler BroadcastMessageStorageReferenceReceived; + + event EventHandler SmsStatusReportReceived; + event EventHandler SmsStatusReportStorageReferenceReceived; + + /// /// Indicates that an event with no specific event handler is received /// @@ -188,22 +198,12 @@ public interface IModem : IDisposable /// Command status Task ResetToFactoryDefaultsAsync(); - /// - /// Sends an SMS in PDU format. This will automatically select the Data Coding Scheme that will result in the fewest messages being sent in case of a concatenated SMS based on the content of the message. - /// - /// The number to send to - /// The message body - /// Command status with SMS reference - Task>> SendSmsAsync(PhoneNumber phoneNumber, string message); - /// /// Sends an SMS in PDU format /// - /// The number to send to - /// The message body - /// Encoding to use + /// The SMS request /// Command status with SMS reference - Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme = CharacterSet.UCS2); + Task>> SendSmsAsync(SmsSubmitRequest request); /// /// Sends an USSD code. Results in an UssdResponseReceived event @@ -234,6 +234,13 @@ public interface IModem : IDisposable /// Command status Task SetErrorFormatAsync(int errorFormat); + /// + /// Select Message Service + /// + /// Typical: 0, 1 + /// Command status + Task SetSelectMessageService(int service); + /// /// Sets how receiving a new SMS is indicated /// diff --git a/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs b/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs index af2d81e..6a85e7f 100644 --- a/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs +++ b/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System.Collections.Generic; @@ -14,14 +13,9 @@ public MDM9225(IAtChannel channel) { } - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } } } diff --git a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs index 62ba9cd..04c4328 100644 --- a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs +++ b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Extensions; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; @@ -42,14 +41,9 @@ public virtual async Task GetRemainingPinPukAttemptsAsy #region _3GPP_TS_27_005 - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } public override async Task>> ListSmssAsync(SmsStatus smsStatus) @@ -77,8 +71,8 @@ public override async Task>> ListSmssAsync(SmsS // 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)); + Sms sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); + smss.Add(sms.ToSmsWithIndex(index)); } } } diff --git a/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs b/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs index dd79989..803109a 100644 --- a/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs +++ b/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System.Collections.Generic; @@ -20,14 +19,9 @@ public MA260(IAtChannel channel) { } - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } } } diff --git a/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs b/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs index f6b47f6..54d2708 100644 --- a/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs +++ b/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs @@ -31,14 +31,9 @@ public override async Task SetRequiredSettingsAfterPinAsync() return currentCharacterSet.Success && smsMessageFormat.Success; } - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } } } diff --git a/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs b/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs index be1f389..782ff34 100644 --- a/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs +++ b/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs @@ -1,13 +1,38 @@ namespace HeboTech.ATLib.PDU { - internal enum MessageTypeIndicator : byte + /// + /// Inbound (SMSC/SC -> MS) + /// SMSC: Short Message Service Center + /// SC: SMS Center + /// MS: Mobile Station + /// + public enum MessageTypeIndicatorInbound : byte { - SMS_DELIVER_REPORT = 0x00, + // SC -> MS SMS_DELIVER = 0x00, - SMS_SUBMIT = 0x01, + // SC -> MS SMS_SUBMIT_REPORT = 0x01, - SMS_COMMAND = 0x10, - SMS_STATUS_REPORT = 0x10, - Reserved = 0x11 + // SC -> MS + SMS_STATUS_REPORT = 0x02, + + Reserved = 0x03 + } + + /// + /// Outbound (MS -> SMSC/SC) + /// SMSC: Short Message Service Center + /// SC: SMS Center + /// MS: Mobile Station + /// + internal enum MessageTypeIndicatorOutbound : byte + { + // MS -> SC + SMS_DELIVER_REPORT = 0x00, + // MS -> SC + SMS_SUBMIT = 0x01, + // MS -> SC + SMS_COMMAND = 0x02, + + Reserved = 0x03 } } diff --git a/src/HeboTech.ATLib/PDU/PhoneNumberDecoder.cs b/src/HeboTech.ATLib/PDU/PhoneNumberDecoder.cs new file mode 100644 index 0000000..36e3004 --- /dev/null +++ b/src/HeboTech.ATLib/PDU/PhoneNumberDecoder.cs @@ -0,0 +1,48 @@ +using HeboTech.ATLib.CodingSchemes; +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using System; +using System.Linq; + +namespace HeboTech.ATLib.PDU +{ + internal class PhoneNumberDecoder + { + public static PhoneNumberDTO DecodePhoneNumber(ReadOnlySpan data) + { + byte ext_ton_npi = data[0]; + TypeOfNumber ton = (TypeOfNumber)((ext_ton_npi & 0b0111_0000) >> 4); + + string number = string.Empty; + switch (ton) + { + case TypeOfNumber.Unknown: + break; + case TypeOfNumber.International: + number = "+"; + break; + case TypeOfNumber.National: + break; + case TypeOfNumber.NetworkSpecific: + break; + case TypeOfNumber.Subscriber: + break; + case TypeOfNumber.AlphaNumeric: + var unpacked = Gsm7.Unpack(data[1..].ToArray()); + var decoded = Gsm7.DecodeFromBytes(unpacked); + return new PhoneNumberDTO(decoded); + case TypeOfNumber.Abbreviated: + break; + case TypeOfNumber.ReservedForExtension: + break; + default: + throw new NotImplementedException($"TON {ton} is not supported"); + } + + number += string.Join("", data[1..].ToArray().Select(x => x.SwapNibbles().ToString("X2"))); + if (number[^1] == 'F') + number = number[..^1]; + return new PhoneNumberDTO(number); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/ReceivedMessageTypeParser.cs b/src/HeboTech.ATLib/PDU/ReceivedMessageTypeParser.cs new file mode 100644 index 0000000..0b06090 --- /dev/null +++ b/src/HeboTech.ATLib/PDU/ReceivedMessageTypeParser.cs @@ -0,0 +1,18 @@ +using System; + +namespace HeboTech.ATLib.PDU +{ + internal static class ReceivedMessageTypeParser + { + public static MessageTypeIndicatorInbound Parse(ReadOnlySpan bytes) + { + byte smsc_length = bytes[0]; // Get length of SMSC + byte headerByte = bytes[1 + smsc_length]; // Skip over SMSC length byte and SMSC to get to header + + byte mti = (byte)(headerByte & 0b0000_0011); + if (Enum.IsDefined(typeof(MessageTypeIndicatorInbound), mti)) + return (MessageTypeIndicatorInbound)mti; + throw new ArgumentOutOfRangeException(nameof(mti)); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/SmsDecoder.cs b/src/HeboTech.ATLib/PDU/SmsDecoder.cs new file mode 100644 index 0000000..bbc0cff --- /dev/null +++ b/src/HeboTech.ATLib/PDU/SmsDecoder.cs @@ -0,0 +1,41 @@ +using HeboTech.ATLib.DTOs; +using System; + +namespace HeboTech.ATLib.PDU +{ + internal class SmsDecoder + { + public static Sms Decode(ReadOnlySpan bytes, SmsStatus status, int timestampYearOffset = 2000) + { + int offset = 0; + + // SMSC information + byte smsc_length = bytes[offset++]; + PhoneNumberDTO serviceCenterNumber = null; + if (smsc_length > 0) + { + serviceCenterNumber = PhoneNumberDecoder.DecodePhoneNumber(bytes[offset..(offset += smsc_length)]); + } + + // Header + byte headerByte = bytes[offset++]; + + MessageTypeIndicatorInbound mti = (MessageTypeIndicatorInbound)(headerByte & 0b0000_0011); + switch (mti) + { + case MessageTypeIndicatorInbound.SMS_DELIVER: + return SmsDeliverDecoder.Decode(bytes, timestampYearOffset); + case MessageTypeIndicatorInbound.SMS_SUBMIT_REPORT: + break; + case MessageTypeIndicatorInbound.SMS_STATUS_REPORT: + return SmsStatusReportDecoder.Decode(bytes, timestampYearOffset); + case MessageTypeIndicatorInbound.Reserved: + break; + default: + break; + } + + throw new NotImplementedException(); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs b/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs index 56c102f..05deee7 100644 --- a/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs +++ b/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs @@ -1,9 +1,7 @@ using HeboTech.ATLib.CodingSchemes; using HeboTech.ATLib.DTOs; -using HeboTech.ATLib.Extensions; using System; using System.Linq; -using System.Threading; namespace HeboTech.ATLib.PDU { @@ -15,7 +13,7 @@ private SmsDeliverHeader() { } - public SmsDeliverHeader(MessageTypeIndicator mti, bool mms, bool lp, bool sri, bool udhi, bool rp) + public SmsDeliverHeader(MessageTypeIndicatorInbound mti, bool mms, bool lp, bool sri, bool udhi, bool rp) { MTI = mti; MMS = mms; @@ -25,7 +23,7 @@ public SmsDeliverHeader(MessageTypeIndicator mti, bool mms, bool lp, bool sri, b RP = rp; } - public MessageTypeIndicator MTI { get; private set; } + public MessageTypeIndicatorInbound MTI { get; private set; } public bool MMS { get; private set; } public bool LP { get; private set; } public bool SRI { get; private set; } @@ -36,14 +34,14 @@ public static SmsDeliverHeader Parse(byte header) { SmsDeliverHeader parsedHeader = new SmsDeliverHeader(); - parsedHeader.MTI = (MessageTypeIndicator)(header & 0b0000_0011); - if (parsedHeader.MTI != (byte)MessageTypeIndicator.SMS_DELIVER) + parsedHeader.MTI = (MessageTypeIndicatorInbound)(header & 0b0000_0011); + if (parsedHeader.MTI != (byte)MessageTypeIndicatorInbound.SMS_DELIVER) throw new ArgumentException("Invalid SMS-DELIVER data"); - parsedHeader.MMS = (header & 0b0000_0100) != 0; - parsedHeader.SRI = (header & 0b0000_1000) != 0; - parsedHeader.UDHI = (header & 0b0100_0000) != 0; - parsedHeader.RP = (header & 0b1000_0000) != 0; + parsedHeader.MMS = (header & (1 << 2)) != 0; + parsedHeader.SRI = (header & (1 << 3)) != 0; + parsedHeader.UDHI = (header & (1 << 6)) != 0; + parsedHeader.RP = (header & (1 << 7)) != 0; return parsedHeader; } @@ -65,7 +63,7 @@ public static SmsDeliver Decode(ReadOnlySpan bytes, int timestampYearOffse PhoneNumberDTO serviceCenterNumber = null; if (smsc_length > 0) { - serviceCenterNumber = DecodePhoneNumber(bytes[offset..(offset += smsc_length)]); + serviceCenterNumber = PhoneNumberDecoder.DecodePhoneNumber(bytes[offset..(offset += smsc_length)]); } // SMS-DELIVER start @@ -78,7 +76,7 @@ public static SmsDeliver Decode(ReadOnlySpan bytes, int timestampYearOffse PhoneNumberDTO oa = null; if (tp_oa_bytes_length > 0) { - oa = DecodePhoneNumber(bytes[offset..(offset += tp_oa_bytes_length)]); + oa = PhoneNumberDecoder.DecodePhoneNumber(bytes[offset..(offset += tp_oa_bytes_length)]); } byte tp_pid = bytes[offset++]; @@ -143,42 +141,5 @@ public static SmsDeliver Decode(ReadOnlySpan bytes, int timestampYearOffse else return new SmsDeliver(serviceCenterNumber, oa, message, scts); } - - private static PhoneNumberDTO DecodePhoneNumber(ReadOnlySpan data) - { - byte ext_ton_npi = data[0]; - TypeOfNumber ton = (TypeOfNumber)((ext_ton_npi & 0b0111_0000) >> 4); - - string number = string.Empty; - switch (ton) - { - case TypeOfNumber.Unknown: - break; - case TypeOfNumber.International: - number = "+"; - break; - case TypeOfNumber.National: - break; - case TypeOfNumber.NetworkSpecific: - break; - case TypeOfNumber.Subscriber: - break; - case TypeOfNumber.AlphaNumeric: - var unpacked = Gsm7.Unpack(data[1..].ToArray()); - var decoded = Gsm7.DecodeFromBytes(unpacked); - return new PhoneNumberDTO(decoded); - case TypeOfNumber.Abbreviated: - break; - case TypeOfNumber.ReservedForExtension: - break; - default: - throw new NotImplementedException($"TON {ton} is not supported"); - } - - number += string.Join("", data[1..].ToArray().Select(x => x.SwapNibbles().ToString("X2"))); - if (number[^1] == 'F') - number = number[..^1]; - return new PhoneNumberDTO(number); - } } } diff --git a/src/HeboTech.ATLib/PDU/SmsStatusReportDecoder.cs b/src/HeboTech.ATLib/PDU/SmsStatusReportDecoder.cs new file mode 100644 index 0000000..496628e --- /dev/null +++ b/src/HeboTech.ATLib/PDU/SmsStatusReportDecoder.cs @@ -0,0 +1,76 @@ +using HeboTech.ATLib.DTOs; +using System; + +namespace HeboTech.ATLib.PDU +{ + public static class SmsStatusReportDecoder + { + private class SmsStatusReportHeader + { + private SmsStatusReportHeader() + { + } + + public SmsStatusReportHeader(MessageTypeIndicatorInbound mti, bool mms, bool lp, bool sri, bool udhi) + { + MTI = mti; + MMS = mms; + LP = lp; + SRQ = sri; + UDHI = udhi; + } + + public MessageTypeIndicatorInbound MTI { get; private set; } + public bool MMS { get; private set; } + public bool LP { get; private set; } + /// + /// If set - this is a result of an SMS-COMMAND; otherwise it is a result of an SMS-SUBMIT + /// 9 2 2 3 + /// + public bool SRQ { get; private set; } + public bool UDHI { get; private set; } + + public static SmsStatusReportHeader Parse(byte header) + { + SmsStatusReportHeader parsedHeader = new SmsStatusReportHeader(); + + parsedHeader.MTI = (MessageTypeIndicatorInbound)(header & (3 << 0)); + if (parsedHeader.MTI != MessageTypeIndicatorInbound.SMS_STATUS_REPORT) + throw new ArgumentException("Invalid SMS_STATUS_REPORT data"); + + parsedHeader.MMS = (header & (1 << 2)) != 0; + parsedHeader.LP = (header & (1 << 3)) != 0; + parsedHeader.SRQ = (header & (1 << 5)) != 0; + parsedHeader.UDHI = (header & (1 << 6)) != 0; + + return parsedHeader; + } + } + + public static SmsStatusReport Decode(ReadOnlySpan bytes, int timestampYearOffset = 2000) + { + int offset = 0; + + // SMSC information + byte smsc_length = bytes[offset++]; + PhoneNumberDTO serviceCenterNumber = null; + if (smsc_length > 0) + { + serviceCenterNumber = PhoneNumberDecoder.DecodePhoneNumber(bytes[offset..(offset += smsc_length)]); + } + + // SMS-STATUS-REPORT start + byte headerByte = bytes[offset++]; + SmsStatusReportHeader header = SmsStatusReportHeader.Parse(headerByte); + + byte tp_mr = bytes[offset++]; + byte tp_ra_length = (byte)((bytes[offset++] / 2) + 1); + ReadOnlySpan tp_ra = bytes[offset..(offset += tp_ra_length)]; + ReadOnlySpan tp_scts = bytes[offset..(offset += 7)]; + ReadOnlySpan tp_dt = bytes[offset..(offset += 7)]; + byte tp_st = bytes[offset++]; + + return new SmsStatusReport(tp_mr, PhoneNumberDecoder.DecodePhoneNumber(tp_ra), TpduTime.DecodeTimestamp(tp_scts, timestampYearOffset), TpduTime.DecodeTimestamp(tp_dt, timestampYearOffset), (SmsDeliveryStatus) tp_st); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs b/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs index 1110d9d..ffda24c 100644 --- a/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs +++ b/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs @@ -32,14 +32,14 @@ internal class SmsSubmitEncoder protected byte pi; // TP-DCS Data Coding Scheme. '00'-7bit default alphabet. '04'-8bit protected CharacterSet dcs; - // TP-Validity-Period. 'AA'-4 days - protected List vp = new List(); + // TP-Validity-Period + protected ValidityPeriod validityPeriod = null; // Message protected Message partitionedMessage; protected SmsSubmitEncoder() { - header = (byte)MessageTypeIndicator.SMS_SUBMIT; + header = (byte)MessageTypeIndicatorOutbound.SMS_SUBMIT; } protected static SmsSubmitEncoder Initialize() @@ -52,13 +52,14 @@ protected static SmsSubmitEncoder Initialize() /// /// Data object /// PDUs - public static IEnumerable Encode(SmsSubmitRequest smsSubmit) + public static IEnumerable Encode(SmsSubmitRequest smsSubmit, bool includeEmptySmscLength) { // Build TPDU var messageParts = SmsSubmitEncoder .Initialize() .DestinationAddress(smsSubmit.PhoneNumber) .ValidityPeriod(smsSubmit.ValidityPeriod) + .EnableStatusReportRequest(smsSubmit.EnableStatusReportRequest) .Message(smsSubmit.Message, smsSubmit.CodingScheme, smsSubmit.MessageReferenceNumber) .Build(); @@ -67,7 +68,7 @@ public static IEnumerable Encode(SmsSubmitRequest smsSubmit) StringBuilder sb = new StringBuilder(); // Length of SMSC information - if (smsSubmit.IncludeEmptySmscLength) + if (includeEmptySmscLength) sb.Append("00"); sb.Append(messagePart); @@ -80,7 +81,7 @@ public static IEnumerable Encode(SmsSubmitRequest smsSubmit) protected SmsSubmitEncoder EnableUserDataHeaderIndicator() { - header |= 0b0100_0000; + header |= (1 << 6); return this; } @@ -90,13 +91,13 @@ protected SmsSubmitEncoder EnableUserDataHeaderIndicator() /// protected SmsSubmitEncoder EnableReplyPath() { - header |= 0b1000_0000; + header |= (1 << 7); return this; } protected static byte GetAddressType(PhoneNumber phoneNumber) { - return (byte)(0b1000_0000 + ((byte)phoneNumber.GetTypeOfNumber() << 4) + (byte)phoneNumber.GetNumberPlanIdentification()); + return (byte)((1 << 7) + ((byte)phoneNumber.GetTypeOfNumber() << 4) + (byte)phoneNumber.GetNumberPlanIdentification()); } protected static string SwapPhoneNumberDigits(string data) @@ -120,7 +121,7 @@ protected static string SwapPhoneNumberDigits(string data) /// protected SmsSubmitEncoder RejectDuplicates() { - header |= 0b0000_0100; + header |= (1 << 2); return this; } @@ -132,19 +133,18 @@ protected SmsSubmitEncoder RejectDuplicates() protected SmsSubmitEncoder ValidityPeriod(ValidityPeriod validityPeriod) { // Set format - byte mask = 0b0001_1000; - header = (byte)((header & ~mask) | ((byte)validityPeriod.Format & mask)); + header |= (byte)((byte)validityPeriod.Format << 3); // Set value - vp.Clear(); - vp.AddRange(validityPeriod.Value); + this.validityPeriod = validityPeriod; return this; } - protected SmsSubmitEncoder EnableStatusReportRequest() + protected SmsSubmitEncoder EnableStatusReportRequest(bool enable) { - header |= 0b0010_0000; + if (enable) + header |= (1 << 5); return this; } @@ -214,8 +214,8 @@ protected IEnumerable Build() sb.Append(daNumber); sb.Append(pi.ToString("X2")); sb.Append(((byte)dcs).ToString("X2")); - if (vp.Count > 0) - sb.Append(String.Join("", vp.Select(x => x.ToString("X2")))); + if (validityPeriod != null) + sb.Append(String.Join("", validityPeriod.Value.Select(x => x.ToString("X2")))); switch (dcs) {