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