1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Collections.Generic;
9 using System.Linq;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Logging;
12 using Antmicro.Renode.Utilities;
13 
14 namespace Antmicro.Renode.Peripherals.Network
15 {
16     public class Quectel_BC660K : QuectelModem
17     {
Quectel_BC660K(IMachine machine, string imeiNumber = DefaultImeiNumber, string softwareVersionNumber = DefaultSoftwareVersionNumber, string serialNumber = DefaultSerialNumber)18         public Quectel_BC660K(IMachine machine, string imeiNumber = DefaultImeiNumber,
19             string softwareVersionNumber = DefaultSoftwareVersionNumber,
20             string serialNumber = DefaultSerialNumber) : base(machine, imeiNumber, softwareVersionNumber, serialNumber)
21         {
22             dataOutputSurrounding = "\"";
23 
24             nameModemConfigDecoder = new Dictionary<string, ModemConfigBC660K>(StringComparer.OrdinalIgnoreCase)
25             {
26                 {"EPCO", ModemConfigBC660K.ExtendedProtocolConfigurationOptions},
27                 {"DataInactTimer", ModemConfigBC660K.DataInactivityTimer},
28                 {"OOSScheme", ModemConfigBC660K.NetworkSearchingMechanismInOOS},
29                 {"logbaudrate", ModemConfigBC660K.LogBaudRate},
30                 {"slplocktimes", ModemConfigBC660K.InitialSleepLockDuration},
31                 {"dsevent", ModemConfigBC660K.DeepSleepEvent},
32                 {"statisr", ModemConfigBC660K.ReportIntervalOfStatisticsURC},
33                 {"MacRAI", ModemConfigBC660K.MacRAI},
34                 {"relversion", ModemConfigBC660K.ProtocolVersionSupported},
35                 {"NBcategory", ModemConfigBC660K.NBCategory},
36                 {"wakeupRXD", ModemConfigBC660K.WakeUpByRXD},
37                 {"faultaction", ModemConfigBC660K.ActionOnError},
38                 {"GPIO", ModemConfigBC660K.GPIOStatusConfiguration},
39                 {"NcellMeas", ModemConfigBC660K.NeighborCellMeasurement},
40                 {"SimBip", ModemConfigBC660K.SIMBIP},
41                 {"activetimer", ModemConfigBC660K.ActiveTimer}
42             };
43 
44             modemBasicConfig = new Dictionary<ModemConfigBC660K, int>()
45             {
46                 {ModemConfigBC660K.ExtendedProtocolConfigurationOptions, 1},
47                 {ModemConfigBC660K.DataInactivityTimer, 60},
48                 {ModemConfigBC660K.NetworkSearchingMechanismInOOS, 1},
49                 {ModemConfigBC660K.LogBaudRate, 6000000},
50                 {ModemConfigBC660K.InitialSleepLockDuration, 10},
51                 {ModemConfigBC660K.DeepSleepEvent, 1},
52                 {ModemConfigBC660K.ReportIntervalOfStatisticsURC, 0},
53                 {ModemConfigBC660K.MacRAI, 0},
54                 {ModemConfigBC660K.ProtocolVersionSupported, 13},
55                 {ModemConfigBC660K.NBCategory, 1},
56                 {ModemConfigBC660K.WakeUpByRXD, 1},
57                 {ModemConfigBC660K.ActionOnError, 4},
58                 // ModemConfigBC660K.GPIOStatusConfiguration // GPIO configuration is stored separately
59                 {ModemConfigBC660K.NeighborCellMeasurement, 1},
60                 {ModemConfigBC660K.SIMBIP, 1},
61                 {ModemConfigBC660K.ActiveTimer, 0}
62             };
63 
64             gpioConfig = new Dictionary<int, GPIOStatusConfiguration>()
65             {
66                 {1, new GPIOStatusConfiguration()},
67                 {2, new GPIOStatusConfiguration()},
68                 {3, new GPIOStatusConfiguration()},
69                 {4, new GPIOStatusConfiguration()}
70             };
71 
72             deepSleepEventEnabled = true;
73         }
74 
75         // AT+QR14FEATURE - Query Status of R14 Features
76         [AtCommand("AT+QR14FEATURE", CommandType.Execution)]
Qr14feature()77         protected virtual Response Qr14feature()
78         {
79             var parameters = new string[]
80             {
81                 "+QR14FEATURE: 14,1",      // UE supports R14 protocol, MAC RAI is enabled.
82                 "+QR14FEATURE: 0",         // None of the features listed below is enabled by the network.
83                 // The options below have the following meaning:
84                 // MAC RAI status is disabled.
85                 // 2-HARQ is disabled.
86                 // Random access on non-anchor carrier is not supported.
87                 // Paging on non-anchor carrier is not supported.
88                 // Re-establishing with CP-CIOT is not supported.
89                 "+QR14FEATURE: 0,0,0,0,0"
90             };
91             return Ok.WithParameters(parameters); // stub
92         }
93 
94         // AT+QLEDMODE - Configure Network-status-indication Light
95         [AtCommand("AT+QLEDMODE", CommandType.Write)]
QledmodeWrite(int ledMode)96         protected virtual Response QledmodeWrite(int ledMode)
97         {
98             return SetNetLightMode(ledMode);
99         }
100 
101         [AtCommand("AT+QLEDMODE", CommandType.Read)]
QledmodeRead()102         protected virtual Response QledmodeRead() => Ok.WithParameters($"+QLEDMODE: {netLightMode}");
103 
104         // CCLK - Set and Get Current Date and Time
105         [AtCommand("AT+CCLK", CommandType.Read)]
CclkRead()106         protected virtual Response CclkRead()
107         {
108             return Ok.WithParameters("+CCLK: " + machine.RealTimeClockDateTime.ToString("yy/MM/dd,HH:mm:sszz"));
109         }
110 
111         // CGPADDR - Show PDP Addresses
112         [AtCommand("AT+CGPADDR", CommandType.Read)]
Cgpaddr()113         protected override Response Cgpaddr() => Ok.WithParameters($"+CGPADDR: 1,\"{NetworkIp}\""); // stub
114 
115         // CREG - Network Registration
116         [AtCommand("AT+CREG", CommandType.Write)]
CregWrite(NetworkRegistrationUrcType type)117         protected override Response CregWrite(NetworkRegistrationUrcType type)
118         {
119             if(type > NetworkRegistrationUrcType.StatLocationEmmCause)
120             {
121                 this.Log(LogLevel.Warning, "AT+CREG: Argument <n> set to {0}, not supported by this modem", (int)type);
122                 return Error;
123             }
124             return base.CregWrite(type);
125         }
126 
127         // QCFG - System Configuration
128         [AtCommand("AT+QCFG", CommandType.Write)]
Qcfg(string function, params int[] args)129         protected override Response Qcfg(string function, params int[] args)
130         {
131             if(!nameModemConfigDecoder.TryGetValue(function, out var modemFunction))
132             {
133                 return base.Qcfg(function, args); // unrecognized function
134             }
135 
136             if(modemBasicConfig.TryGetValue(modemFunction, out int value))
137             {
138                 if(args.Length == 0)
139                 {
140                     // If the optional parameter is omitted, query the current configuration.
141                     var parameters = string.Format("+QCFG: \"{0}\",{1}", function, value);
142                     return Ok.WithParameters(parameters);
143                 }
144                 else if(args.Length == 1)
145                 {
146                     modemBasicConfig[modemFunction] = args[0];
147                     // Handle functions with side effects
148                     switch(modemFunction)
149                     {
150                         case ModemConfigBC660K.DeepSleepEvent:
151                             deepSleepEventEnabled = args[0] != 0;
152                             break;
153                     }
154                 }
155                 else
156                 {
157                     return base.Qcfg(function, args);
158                 }
159 
160                 return Ok;
161             }
162 
163             // Handle functions not covered by basic config
164             switch(modemFunction)
165             {
166                 case ModemConfigBC660K.GPIOStatusConfiguration:
167                     var isOk = ConfigureGPIOStatus(out var parameters, args);
168                     if(isOk)
169                     {
170                         return Ok.WithParameters(parameters);
171                     }
172                     break;
173             }
174 
175             return base.Qcfg(function, args);
176         }
177 
178         [AtCommand("AT+QCFG", CommandType.Read)]
QcfgRead()179         protected virtual Response QcfgRead()
180         {
181             var parameters = new List<string>();
182             foreach(string function in nameModemConfigDecoder.Keys)
183             {
184                 var modemFunction = nameModemConfigDecoder[function];
185                 if(modemBasicConfig.TryGetValue(modemFunction, out int value))
186                 {
187                     parameters.Add(string.Format("+QCFG: \"{0}\",{1}", function, value));
188                 }
189                 else
190                 {
191                     switch(modemFunction)
192                     {
193                         case ModemConfigBC660K.GPIOStatusConfiguration:
194                             parameters.Add(GetQcfgGPIOStatus());
195                             break;
196                     }
197                 }
198             }
199 
200             return Ok.WithParameters(parameters.ToArray());
201         }
202 
203         // QCGDEFCONT - Set Default PSD Connection Settings
204         [AtCommand("AT+QCGDEFCONT", CommandType.Write)]
Qcgdefcont(PdpType pdpType, string apn = R, string username = R, string password = R, AuthenticationType authenticationType = AuthenticationType.None)205         protected virtual Response Qcgdefcont(PdpType pdpType, string apn = "", string username = "",
206             string password = "", AuthenticationType authenticationType = AuthenticationType.None)
207         {
208             pdpContextApn = apn;
209             return Ok; // stub
210         }
211 
212         // QICFG - Configure Optional TCP/IP Parameters
213         [AtCommand("AT+QICFG", CommandType.Write)]
Qicfg(string parameter, params int[] args)214         protected override Response Qicfg(string parameter, params int[] args)
215         {
216             if(args.Length < 1)
217             {
218                 return Error;
219             }
220 
221             switch(parameter)
222             {
223                 case "dataformat":
224                     if(args.Length < 2)
225                     {
226                         return Error;
227                     }
228                     sendDataFormat = args[0] != 0 ? DataFormat.Hex : DataFormat.Text;
229                     receiveDataFormat = args[1] != 0 ? DataFormat.Hex : DataFormat.Text;
230                     break;
231                 case "showlength":
232                     showLength = args[0] != 0;
233                     break;
234                 case "viewmode":
235                     dataOutputSeparator = args[0] != 0 ? "," : CrLf;
236                     break;
237                 case "showRA": // display the address of the remote end while displaying received data
238                     this.Log(LogLevel.Warning, "TCP/IP config value '{0}' set to {1}, not implemented", parameter, args.Stringify());
239                     break;
240                 default:
241                     return base.Qicfg(parameter, args);
242             }
243             return Ok;
244         }
245 
246         // QNBIOTRAI - NB-IoT Release Assistance Indication
247         [AtCommand("AT+QNBIOTRAI", CommandType.Write)]
Qnbiotrai(int raiMode = 0)248         protected override Response Qnbiotrai(int raiMode = 0)
249         {
250             if(raiMode > 1)
251             {
252                 return Error;
253             }
254 
255             return base.Qnbiotrai(raiMode);
256         }
257 
258         // QOOSAIND - Enable or Disable OOSA URC
259         [AtCommand("AT+QOOSAIND", CommandType.Write)]
QoosaindWrite(int oosaUrcEnabled)260         protected virtual Response QoosaindWrite(int oosaUrcEnabled)
261         {
262             if(oosaUrcEnabled < 0 || oosaUrcEnabled > 1)
263             {
264                 this.Log(LogLevel.Warning, "AT+QOOSAIND: Parameter QOOSAIND set to {0}, not supported by this modem", oosaUrcEnabled);
265                 return Error;
266             }
267 
268             outOfServiceAreaUrcEnabled = oosaUrcEnabled == 1;
269             return Ok;
270         }
271 
272         [AtCommand("AT+QOOSAIND", CommandType.Read)]
QoosaindRead()273         protected virtual Response QoosaindRead() => Ok.WithParameters($"+QOOSAIND: {(outOfServiceAreaUrcEnabled ? 1 : 0)}");
274 
275         // QPLMNS - Search PLMN
276         [AtCommand("AT+QPLMNS", CommandType.Execution)]
QplmnsExec()277         protected virtual Response QplmnsExec()
278         {
279             if(ModemPLMNState != PublicLandMobileNetworkSearchingState.OutOfService)
280             {
281                 const int errorCode = 111; // errorCode: PLMN not allowed
282                 return new Response($"+CME ERROR: {errorCode}");
283             }
284             ModemPLMNState = PublicLandMobileNetworkSearchingState.Searching;
285             return Ok;
286         }
287 
288         [AtCommand("AT+QPLMNS", CommandType.Read)]
QplmnsRead()289         protected virtual Response QplmnsRead()
290         {
291             var fragments = new List<string>
292             {
293                 ((int)ModemPLMNState).ToString()
294             };
295 
296             if(ModemPLMNState == PublicLandMobileNetworkSearchingState.OutOfService)
297             {
298                 fragments.Add(PLMNSearchTime.ToString());
299             }
300 
301             return Ok.WithParameters("+QPLMNS: " + string.Join(",", fragments));
302         }
303 
304         // QPSMS - Power Saving Mode Setting
305         [AtCommand("AT+QPSMS", CommandType.Read)]
QpsmsRead()306         protected virtual Response QpsmsRead() => Ok.WithParameters($"+QPSMS: {ActiveTimeSeconds},{PeriodicTauSeconds}");
307 
308         [AtCommand("AT+QPSMS", CommandType.Write)]
QpsmsWrite(int t1, int t2)309         protected virtual Response QpsmsWrite(int t1, int t2)
310         {
311             SetPowerSavingMode(true);
312             ActiveTime = ConvertSecondsToEncodedString(t1, ModemTimerType.ActiveTimeT3324);
313             PeriodicTau = ConvertSecondsToEncodedString(t2, ModemTimerType.PeriodicTauT3412);
314 
315             return Ok;
316         }
317 
318         // QPSC - Power Saving Control
319         [AtCommand("AT+QPSC", CommandType.Read)]
QpscRead()320         protected virtual Response QpscRead() => Ok.WithParameters($"+QPSC: \"minT3324\",{minimumT3324},\"minT3412\",{minimumT3412},\"minTeDRX\",{minimumTeDRX}");
321 
322         [AtCommand("AT+QPSC", CommandType.Write)]
QpscWrite(int minT3324, int minT3412, int minTeDRX)323         protected virtual Response QpscWrite(int minT3324, int minT3412, int minTeDRX)
324         {
325             minimumT3324 = minT3324;
326             minimumT3412 = minT3412;
327             minimumTeDRX = minTeDRX;
328 
329             return Ok;
330         }
331 
332         [AtCommand("AT+QPSC", CommandType.Execution)]
QpscExec()333         protected virtual Response QpscExec()
334         {
335             minimumT3324 = 0;
336             minimumT3412 = 0;
337             minimumTeDRX = 0;
338 
339             return Ok;
340         }
341 
IsValidContextId(int id)342         protected override bool IsValidContextId(int id)
343         {
344             return id == 0;
345         }
346 
ConfigureGPIOStatus(out string parameters, params int[] args)347         private bool ConfigureGPIOStatus(out string parameters, params int[] args)
348         {
349             parameters = string.Empty;
350 
351             if(args.Length == 0)
352             {
353                 // If the optional parameter is omitted, query the current configuration
354                 parameters = GetQcfgGPIOStatus();
355                 return true;
356             }
357 
358             switch((GPIOStatusOperation)args[0])
359             {
360                 case GPIOStatusOperation.Initialize:
361                 {
362                     // no parameter shall be omitted
363                     if(args.Length != 5)
364                     {
365                         break;
366                     }
367 
368                     var pin = args[1];
369                     if(gpioConfig.TryGetValue(pin, out var gpioStatus))
370                     {
371                         gpioStatus.Direction = args[2];
372                         gpioStatus.PullTypeSelection = args[3];
373                         gpioStatus.LogicLevel = args[4];
374                         return true;
375                     }
376 
377                     break;
378                 }
379                 case GPIOStatusOperation.Query:
380                 {
381                     // query the current configuration
382                     parameters = GetQcfgGPIOStatus();
383                     return true;
384                 }
385                 case GPIOStatusOperation.Configure:
386                 {
387                     if(args.Length != 3)
388                     {
389                         break;
390                     }
391 
392                     var pin = args[1];
393                     if(gpioConfig.TryGetValue(pin, out var gpioStatus))
394                     {
395                         gpioStatus.LogicLevel = args[2];
396                         return true;
397                     }
398 
399                     break;
400                 }
401             }
402 
403             return false;
404         }
405 
GetQcfgGPIOStatus()406         private string GetQcfgGPIOStatus()
407         {
408             const string function = "GPIO";
409             var gpioStatus = string.Join(",", gpioConfig.Values.Select(gpio => gpio.LogicLevel));
410             return string.Format("+QCFG: \"{0}\",{1}", function, gpioStatus);
411         }
412 
413         protected override string Vendor => "Quectel_Ltd";
414         protected override string ModelName => "Quectel_BC660K-GL";
415         protected override string Revision => "Revision: QCX212";
416         protected override string ManufacturerRevision => "BC660KGLAAR01A03";
417         protected override string SoftwareRevision => "01.002.01.002";
418 
419         private int minimumT3324;
420         private int minimumT3412;
421         private int minimumTeDRX;
422         private readonly Dictionary<string, ModemConfigBC660K> nameModemConfigDecoder;
423         private readonly Dictionary<ModemConfigBC660K, int> modemBasicConfig;
424         private readonly Dictionary<int, GPIOStatusConfiguration> gpioConfig;
425 
426         private const string DefaultImeiNumber = "866818039921444";
427         private const string DefaultSoftwareVersionNumber = "31";
428         private const string DefaultSerialNumber = "<serial number>";
429 
430         private enum ModemConfigBC660K
431         {
432             ExtendedProtocolConfigurationOptions,
433             DataInactivityTimer,
434             NetworkSearchingMechanismInOOS,
435             LogBaudRate,
436             InitialSleepLockDuration,
437             DeepSleepEvent,
438             ReportIntervalOfStatisticsURC,
439             MacRAI,
440             ProtocolVersionSupported,
441             NBCategory,
442             WakeUpByRXD,
443             ActionOnError,
444             GPIOStatusConfiguration,
445             NeighborCellMeasurement,
446             SIMBIP,
447             ActiveTimer
448         }
449 
450         private enum GPIOStatusOperation
451         {
452             Initialize = 1,
453             Query = 2,
454             Configure = 3
455         }
456 
457         private sealed class GPIOStatusConfiguration
458         {
459             public int Direction { get; set; }
460             public int PullTypeSelection { get; set; }
461             public int LogicLevel { get; set; }
462         }
463     }
464 }
465