diff --git a/examples/A7682E/deep_sleep/deep_sleep.ino b/examples/A7682E/deep_sleep/deep_sleep.ino new file mode 100644 index 0000000..bf04035 --- /dev/null +++ b/examples/A7682E/deep_sleep/deep_sleep.ino @@ -0,0 +1,156 @@ +#include "Arduino.h" +#include "utilities.h" + +bool reply = false; + +/** + * After first time power on, some register of this pin will be written then + * it will lose shutdown function, so it is recommended to use PWRKEY to power + * on the module and RESET key only used as reset function. + */ +void A7682E_reset(void) +{ + pinMode(BOARD_A7682E_RST, OUTPUT); + digitalWrite(BOARD_A7682E_RST, HIGH); + delay(100); + digitalWrite(BOARD_A7682E_RST, LOW); + delay(2500); // Pull down for at least 2 seconds to reset + digitalWrite(BOARD_A7682E_RST, HIGH); +} +/** + * + */ +void A7682E_power_on(void) +{ + digitalWrite(BOARD_A7682E_PWRKEY, LOW); + delay(10); + digitalWrite(BOARD_A7682E_PWRKEY, HIGH); + delay(50); + digitalWrite(BOARD_A7682E_PWRKEY, LOW); + delay(10); +} +/** + * + */ +void A7682E_power_off(void) +{ + digitalWrite(BOARD_A7682E_PWRKEY, LOW); + delay(10); + digitalWrite(BOARD_A7682E_PWRKEY, HIGH); + delay(3000); + digitalWrite(BOARD_A7682E_PWRKEY, LOW); + delay(10); +} + +void setup(void) +{ + // enable A7682 module power + pinMode(BOARD_A7682E_POWER_EN, OUTPUT); + digitalWrite(BOARD_A7682E_POWER_EN, HIGH); + + SerialMon.begin(115200); + SerialAT.begin(115200, SERIAL_8N1, BOARD_A7682E_TXD, BOARD_A7682E_RXD); + + // Power on the modem PWRKEY + pinMode(BOARD_A7682E_PWRKEY, OUTPUT); + digitalWrite(BOARD_A7682E_PWRKEY, HIGH); + + delay(2000); + +#ifdef BOARD_A7682E_RST + // Release reset GPIO hold + gpio_hold_dis((gpio_num_t)BOARD_A7682E_RST); + + // Set modem reset pin ,reset modem + // The module will also be started during reset. + Serial.println("Set Reset Pin."); + A7682E_reset(); +#endif + + // A7682 Power on + Serial.println("Power on the modem PWRKEY."); + A7682E_power_on(); + + // UART is ready to exit from the sleep mode if DTR pin is pulled down + pinMode(BOARD_A7682E_DTR, OUTPUT); + digitalWrite(BOARD_A7682E_DTR, LOW); + + int i = 10; + Serial.println("\nTesting Modem Response...\n"); + Serial.println("****"); + while (i) + { + SerialAT.println("AT"); + delay(500); + if (SerialAT.available()) + { + String r = SerialAT.readString(); + SerialAT.println(r); + if (r.indexOf("OK") >= 0) + { + reply = true; + break; + } + } + delay(500); + i--; + } + Serial.println("****\n"); + + if (reply) + { + Serial.println(F("***********************************************************")); + Serial.println(F(" You can now send AT commands")); + Serial.println(F(" Enter \"AT\" (without quotes), and you should see \"OK\"")); + Serial.println(F(" If it doesn't work, select \"Both NL & CR\" in Serial Monitor")); + Serial.println(F(" DISCLAIMER: Entering AT commands without knowing what they do")); + Serial.println(F(" can have undesired consiquinces...")); + Serial.println(F("***********************************************************\n")); + } + else + { + Serial.println(F("***********************************************************")); + Serial.println(F(" Failed to connect to the modem! Check the baud and try again.")); + Serial.println(F("***********************************************************\n")); + } +} + +void loop(void) +{ + // while (SerialAT.available()) + // { + // SerialMon.write(SerialAT.read()); + // } + // while (SerialMon.available()) + // { + // SerialAT.write(SerialMon.read()); + // } + + // 检查是否有可读取的串口数据 + if (Serial.available() > 0) + { + char inputChar = Serial.read(); // 读取一个字符 + Serial.print("recvice commend:"); + Serial.println(inputChar); + + // 根据输入的字符输出不同的消息 + switch (inputChar) + { + case 'o': + Serial.println("A7682 Power on"); + A7682E_power_on(); + break; + case 'f': + Serial.println("A7682 Power off"); + A7682E_power_off(); + break; + case 'r': + Serial.println("A7682 reset"); + A7682E_reset(); + break; + default: + Serial.println("input 'o', 'f' or 'r'"); + break; + } + } +} \ No newline at end of file diff --git a/examples/A7682E/deep_sleep/utilities.h b/examples/A7682E/deep_sleep/utilities.h new file mode 100644 index 0000000..90ed48e --- /dev/null +++ b/examples/A7682E/deep_sleep/utilities.h @@ -0,0 +1,14 @@ +#pragma once + + +#define SerialMon Serial +#define SerialAT Serial1 + +// A7682E Modem +#define BOARD_A7682E_POWER_EN 41 // enable 7682 module +#define BOARD_A7682E_RI 7 +#define BOARD_A7682E_DTR 8 +#define BOARD_A7682E_RST 9 +#define BOARD_A7682E_RXD 10 +#define BOARD_A7682E_TXD 11 +#define BOARD_A7682E_PWRKEY 40 \ No newline at end of file diff --git a/examples/A7682E/test_AT/README.md b/examples/A7682E/test_AT/README.md new file mode 100644 index 0000000..03c05f4 --- /dev/null +++ b/examples/A7682E/test_AT/README.md @@ -0,0 +1,38 @@ +## Some AT command test + +| command | explain | Response | +| ------------ | ------------------------------------ | ------------ | +| AT | AT test instruction | OK | +| ATI | version information | | +| AT+CPIN? | Example Query the SIM card status | +CPIN: READY | +| AT+CGREG? | Network Registration Status | +CGREG: 0,1 | +| AT+CSQ | Network signal quality query | | +| AT+COPS? | Querying current Carrier Information | | + +Refer to the T-A7670X instruction manual for more [AT instructions](../../../hardware/A76XX_series/A76XX_Series_AT_Command_Manual_V1.09.pdf) + + +## File System + + +## Audio AT test + +1. `AT+CTTSPARAM=1,3,0,1,1` : TTS parameters setting +2. `AT+CTTS=2,"1234567890"`:synth and play ASCII text + +AT+CTTSPARAM=1,3,0,1,1,1 +AT+CTTS=2,"1234567890" + + +AT+CCMXPLAY="c:/iPhone_Ring.mp3",0,255 +AT+CCMXSTOP + +More audio application references [A76XX Series_Audio_Application Note](../../../hardware/A76XX_series/A76XX%20Series_Audio_Application%20Note_V1.03.pdf) + + + + + + + + diff --git a/examples/A7682E/test_AT/utilities.h b/examples/A7682E/test_AT/utilities.h new file mode 100644 index 0000000..90ed48e --- /dev/null +++ b/examples/A7682E/test_AT/utilities.h @@ -0,0 +1,14 @@ +#pragma once + + +#define SerialMon Serial +#define SerialAT Serial1 + +// A7682E Modem +#define BOARD_A7682E_POWER_EN 41 // enable 7682 module +#define BOARD_A7682E_RI 7 +#define BOARD_A7682E_DTR 8 +#define BOARD_A7682E_RST 9 +#define BOARD_A7682E_RXD 10 +#define BOARD_A7682E_TXD 11 +#define BOARD_A7682E_PWRKEY 40 \ No newline at end of file diff --git a/examples/aduio_pcm5102a/readme.md b/examples/aduio_pcm5102a/readme.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/factory/peri_gyroscope.cpp b/examples/factory/peri_gyroscope.cpp new file mode 100644 index 0000000..a86561d --- /dev/null +++ b/examples/factory/peri_gyroscope.cpp @@ -0,0 +1,123 @@ + +#include +#include +#include +#include "SensorBHI260AP.hpp" +#include "utilities.h" +#include "peripheral.h" + +SensorBHI260AP bhy; +struct bhy2_data_xyz accel_data; +struct bhy2_data_xyz gyro_data; +struct bhy2_data_xyz magn_data; + +float accel_factor; +float gyro_factor; +float magn_factor; + +void accel_process_callback(uint8_t sensor_id, uint8_t *data_ptr, uint32_t len, uint64_t *timestamp) +{ + accel_factor = get_sensor_default_scaling(sensor_id); + bhy2_parse_xyz(data_ptr, &accel_data); + // Serial.print(bhy.getSensorName(sensor_id)); + // Serial.print(":"); + // Serial.printf("x: %f, y: %f, z: %f;\r\n", + // accel_data.x * accel_factor, + // accel_data.y * accel_factor, + // accel_data.z * accel_factor + // ); +} + +void gyro_process_callback(uint8_t sensor_id, uint8_t *data_ptr, uint32_t len, uint64_t *timestamp) +{ + gyro_factor = get_sensor_default_scaling(sensor_id); + bhy2_parse_xyz(data_ptr, &gyro_data); + // Serial.print(bhy.getSensorName(sensor_id)); + // Serial.print(":"); + // Serial.printf("x: %f, y: %f, z: %f;\r\n", + // gyro_data.x * gyro_factor, + // gyro_data.y * gyro_factor, + // gyro_data.z * gyro_factor + // ); +} + +void magn_process_callback(uint8_t sensor_id, uint8_t *data_ptr, uint32_t len, uint64_t *timestamp) +{ + magn_factor = get_sensor_default_scaling(sensor_id); + bhy2_parse_xyz(data_ptr, &magn_data); + Serial.print(bhy.getSensorName(sensor_id)); + // Serial.print(":"); + // Serial.printf("x: %f, y: %f, z: %f;\r\n", + // magn_data.x * magn_factor, + // magn_data.y * magn_factor, + // magn_data.z * magn_factor + // ); +} + +/** + * int val_type + * 1 --- acceleration + * 2 --- gyroscope + * 3 --- Magnetometer +*/ +void BHI260AP_get_val(int val_type, float *x, float *y, float *z) +{ + bhy.update(); + + switch (val_type) { + case 1: + *x = accel_data.x * accel_factor; + *y = accel_data.y * accel_factor; + *z = accel_data.z * accel_factor; + break; + case 2 : + *x = gyro_data.x * gyro_factor; + *y = gyro_data.y * gyro_factor; + *z = gyro_data.z * gyro_factor; + break; + case 3: + *x = magn_data.x * magn_factor; + *y = magn_data.y * magn_factor; + *z = magn_data.z * magn_factor; + break; + default: + *x = 0; + *y = 0; + *z = 0; + break; + } +} + +bool BHI260AP_init(void) +{ + // Set the reset pin and interrupt pin, if any + bhy.setPins(BOARD_GYROSCOPDE_RST, BOARD_GYROSCOPDE_INT); + + if (!bhy.init(Wire, BOARD_I2C_SDA, BOARD_I2C_SCL, BHI260AP_SLAVE_ADDRESS_L)) { + Serial.print("Failed to init BHI260AP - "); + Serial.println(bhy.getError()); + return false; + } + + // Output all available sensors to Serial + // bhy.printSensors(Serial); + + float sample_rate = 100.0; /* Read out hintr_ctrl measured at 100Hz */ + uint32_t report_latency_ms = 0; /* Report immediately */ + + // Enable acceleration + bhy.configure(SENSOR_ID_ACC_PASS, sample_rate, report_latency_ms); + // Enable gyroscope + bhy.configure(SENSOR_ID_GYRO_PASS, sample_rate, report_latency_ms); + + // Set the acceleration sensor result callback function + bhy.onResultEvent(SENSOR_ID_ACC_PASS, accel_process_callback); + + // Set the gyroscope sensor result callback function + bhy.onResultEvent(SENSOR_ID_GYRO_PASS, gyro_process_callback); + + // Set the Magnetometer sensor result callback function + bhy.onResultEvent(SENSOR_ID_MAG_PASS, magn_process_callback); + + return true; +} \ No newline at end of file diff --git a/examples/factory/peri_ltr553.cpp b/examples/factory/peri_ltr553.cpp new file mode 100644 index 0000000..4dd88c6 --- /dev/null +++ b/examples/factory/peri_ltr553.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include "SensorLTR553.hpp" +#include "utilities.h" +#include "peripheral.h" + +SensorLTR553 als; + +bool LTR553_init(void) +{ + if (!als.begin(Wire, LTR553_SLAVE_ADDRESS, BOARD_I2C_SDA, BOARD_I2C_SCL)) { + Serial.println("Failed to find LTR553 - check your wiring!"); + return false; + } + + Serial.println("Init LTR553 Sensor success!"); + + // Set the ambient light high and low thresholds. + // If the value exceeds or falls below the set value, an interrupt will be triggered. + als.setLightSensorThreshold(10, 200); + + // Set the high and low thresholds of the proximity sensor. + // If the value exceeds or falls below the set value, an interrupt will be triggered. + als.setProximityThreshold(10, 30); + + // Controls the Light Sensor N number of times the measurement data is outside the range + // defined by the upper and lower threshold limits before asserting the interrupt. + als.setLightSensorPersists(5); + + // Controls the Proximity N number of times the measurement data is outside the range + // defined by the upper and lower threshold limits before asserting the interrupt. + als.setProximityPersists(5); + + /* + * ALS_IRQ_ACTIVE_LOW, // INT pin is considered active when it is a logic 0 (default) + * ALS_IRQ_ACTIVE_HIGH // INT pin is considered active when it is a logic 1 + * * */ + als.setIRQLevel(SensorLTR553::ALS_IRQ_ACTIVE_LOW); + + /* + * ALS_IRQ_ONLY_PS, // Only PS measurement can trigger interrupt + * ALS_IRQ_ONLY_ALS, // Only ALS measurement can trigger interrupt + * ALS_IRQ_BOTH, // Both ALS and PS measurement can trigger interrupt + * * * */ + als.enableIRQ(SensorLTR553::ALS_IRQ_BOTH); + + /* + * ALS_GAIN_1X , -> 1 lux to 64k lux (default) + * ALS_GAIN_2X , -> 0.5 lux to 32k lux + * ALS_GAIN_4X , -> 0.25 lux to 16k lux + * ALS_GAIN_8X , -> 0.125 lux to 8k lux + * ALS_GAIN_48X , -> 0.02 lux to 1.3k lux + * ALS_GAIN_96X , -> 0.01 lux to 600 lux + * */ + als.setLightSensorGain(SensorLTR553::ALS_GAIN_8X); + + /* + * PS_LED_PLUSE_30KHZ, + * PS_LED_PLUSE_40KHZ, + * PS_LED_PLUSE_50KHZ, + * PS_LED_PLUSE_60KHZ, + * PS_LED_PLUSE_70KHZ, + * PS_LED_PLUSE_80KHZ, + * PS_LED_PLUSE_90KHZ, + * PS_LED_PLUSE_100KHZ, + * * * * * * * * * * */ + als.setPsLedPulsePeriod(SensorLTR553::PS_LED_PLUSE_100KHZ); + + /* + * PS_LED_DUTY_25, + * PS_LED_DUTY_50, + * PS_LED_DUTY_75, + * PS_LED_DUTY_100, + * * * */ + als.setPsLedDutyCycle(SensorLTR553::PS_LED_DUTY_100); + + /* + * PS_LED_CUR_5MA, + * PS_LED_CUR_10MA, + * PS_LED_CUR_20MA, + * PS_LED_CUR_50MA, + * PS_LED_CUR_100MA, + * * * * * * * */ + als.setPsLedCurrent(SensorLTR553::PS_LED_CUR_50MA); + + /* + * PS_MEAS_RATE_50MS, + * PS_MEAS_RATE_70MS, + * PS_MEAS_RATE_100MS, + * PS_MEAS_RATE_200MS, + * PS_MEAS_RATE_500MS, + * PS_MEAS_RATE_1000MS, + * PS_MEAS_RATE_2000MS, + * PS_MEAS_RATE_10MS + * * * * * * * */ + als.setProximityRate(SensorLTR553::PS_MEAS_RATE_200MS); + + // Number of pulses + als.setPsLedPulses(1); + + // Enable proximity sensor saturation indication + als.enablePsIndicator(); + + // Enable ambient light sensor + als.enableLightSensor(); + + // Enable proximity sensor + als.enableProximity(); + + return true; +} + +uint16_t LTR_553ALS_get_channel(int ch) // ch 0~1 +{ + return als.getLightSensor(ch); +} + +uint16_t LTR_553ALS_get_ps(void) +{ + bool saturated = false; + return als.getProximity(&saturated); +} \ No newline at end of file diff --git a/lib/TinyGSM/.clang-format b/lib/TinyGSM/.clang-format new file mode 100644 index 0000000..1329b59 --- /dev/null +++ b/lib/TinyGSM/.clang-format @@ -0,0 +1,112 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 + - Regex: '.*.tpp' + Priority: 4 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +# ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 25 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 600 +PenaltyReturnTypeOnItsOwnLine: 50 +PointerAlignment: Left +PointerBindsToType: true +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 2 +UseTab: Never +--- diff --git a/lib/TinyGSM/.gitattributes b/lib/TinyGSM/.gitattributes new file mode 100644 index 0000000..7a76c7a --- /dev/null +++ b/lib/TinyGSM/.gitattributes @@ -0,0 +1,6 @@ +/extras export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore + +*.ino filter=updateUsersAndPasses \ No newline at end of file diff --git a/lib/TinyGSM/.github/ISSUE_TEMPLATE.md b/lib/TinyGSM/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..6f620d6 --- /dev/null +++ b/lib/TinyGSM/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,42 @@ + + +[ ] I have read the Troubleshooting section of the ReadMe + +## What type of issues is this? + +[ ] Request to support a new module + +[ ] Bug or problem compiling the library +[ ] Bug or issue with library functionality (ie, sending data over TCP/IP) +[ ] Question or request for help + + +### What are you working with? + +Modem: +Main processor board: +TinyGSM version: +Code: + +### Scenario, steps to reproduce + + +### Expected result + + +### Actual result + + +### Debug and AT command log + + diff --git a/lib/TinyGSM/.github/dependabot.yml b/lib/TinyGSM/.github/dependabot.yml new file mode 100644 index 0000000..0f88bec --- /dev/null +++ b/lib/TinyGSM/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'daily' + labels: + - 'CI/CD' + commit-message: + prefix: ci diff --git a/lib/TinyGSM/.github/workflows/build_examples_platformio.yaml b/lib/TinyGSM/.github/workflows/build_examples_platformio.yaml new file mode 100644 index 0000000..4107953 --- /dev/null +++ b/lib/TinyGSM/.github/workflows/build_examples_platformio.yaml @@ -0,0 +1,82 @@ +name: Build Examples with PlatformIO + +# Triggers the workflow on push or pull request events +on: [push, pull_request, workflow_dispatch] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + modem: [ + # LILYGO Boards + TINY_GSM_MODEM_A7608, + TINY_GSM_MODEM_A7670, + TINY_GSM_MODEM_SIM7000, + TINY_GSM_MODEM_SIM7000SSL, + TINY_GSM_MODEM_SIM7020, + TINY_GSM_MODEM_SIM7070, + TINY_GSM_MODEM_SIM7600, + TINY_GSM_MODEM_SIM7672, + # Other boards from original forks + TINY_GSM_MODEM_A6, + TINY_GSM_MODEM_BG96, + TINY_GSM_MODEM_M95, + TINY_GSM_MODEM_M590, + TINY_GSM_MODEM_MC60, + TINY_GSM_MODEM_SIM800, + TINY_GSM_MODEM_SIM808, + TINY_GSM_MODEM_SIM5360, + TINY_GSM_MODEM_UBLOX, + TINY_GSM_MODEM_SARAR4, + TINY_GSM_MODEM_XBEE, + TINY_GSM_MODEM_SEQUANS_MONARCH, + # Disabled boards - not supported by Lilygo + # Tests are not passive with these + # TINY_GSM_MODEM_ESP8266, + ] + example: + [ + examples/BlynkClient/BlynkClient.ino, + examples/FileDownload/FileDownload.ino, + examples/MqttClient/MqttClient.ino, + examples/WebClient/WebClient.ino, + tools/test_build/test_build.ino, + tools/Diagnostics/Diagnostics.ino, + ] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + # This should be pulled from cache, if there's not a new version + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + + - name: Restore or Cache Platforms and Libraries + uses: actions/cache@v4 + id: cache_pio + with: + path: ~/.platformio + # if nothing in the example_dependencies.json file has changed, then it will + # be a "cache hit" and we can restore libraries from cache and not + # download them. If it has changed we have to re-download. + key: ${{ hashFiles('./examples/example_dependencies.json') }} + + # Install cores and library dependencies for the Arduino CLI, iff no cache + - name: Install the Arduino libraries + run: pio lib --global install 89 415 1202 1286 + + - name: Run PlatformIO + run: | + sed -i 's/#define TINY_GSM_MODEM_SIM800/\/\/ #define TINY_GSM_MODEM_SIM800/g' "${{ matrix.example }}" + platformio ci "${{ matrix.example }}" -l '.' --project-option='build_flags=-D ${{ matrix.modem }}' --project-option='lib_deps=89, 415, 1202, 1286' --project-option='framework=arduino' --board=esp32dev --board=esp32s3box diff --git a/lib/TinyGSM/.gitignore b/lib/TinyGSM/.gitignore new file mode 100644 index 0000000..de5d0b2 --- /dev/null +++ b/lib/TinyGSM/.gitignore @@ -0,0 +1,48 @@ +# Compiled files, executables +*.o +*.a +*.exe +*.out +*.app + +# Junk +*.orig +*.log +*.bak + +# IDE project files +.cproject +.project +.settings +.pioenvs +.piolibdeps +.pio/* +.clang_complete +.gcc-flags.json +extra_envs.ini +lib/readme.txt +include/readme.txt +.atomrc.cson +.history + +# VSCode +.vscode/* +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json + +# Markers +.development + +# Extras +extras/docs/* +extras/Module Datasheets/* +extras/Older Command Manuals/* +extras/Older Command Manuals/* +extras/At Command Manuals - Unsupported/* + +# Filters +.gitconfig +filters/* +platformio_extra_envs.ini +*.tar.gz diff --git a/lib/TinyGSM/.piopm b/lib/TinyGSM/.piopm deleted file mode 100644 index b102d32..0000000 --- a/lib/TinyGSM/.piopm +++ /dev/null @@ -1 +0,0 @@ -{"type": "library", "name": "TinyGSM", "version": "0.12.0", "spec": {"owner": "vshymanskyy", "id": 1287, "name": "TinyGSM", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/TinyGSM/.travis_archive.yml_archive b/lib/TinyGSM/.travis_archive.yml_archive new file mode 100644 index 0000000..1e3ec6b --- /dev/null +++ b/lib/TinyGSM/.travis_archive.yml_archive @@ -0,0 +1,62 @@ +language: python +python: + - "2.7" + +# Cache PlatformIO packages using Travis CI container-based infrastructure +#sudo: false +cache: + directories: + - "~/.platformio" + +env: + - PLATFORMIO_CI_SRC=examples/BlynkClient + - PLATFORMIO_CI_SRC=examples/FileDownload + - PLATFORMIO_CI_SRC=examples/MqttClient + - PLATFORMIO_CI_SRC=examples/WebClient + - PLATFORMIO_CI_SRC=tools/AT_Debug + - PLATFORMIO_CI_SRC=tools/Diagnostics + - PLATFORMIO_CI_SRC=tools/FactoryReset + + # Arduino test + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_A6' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_BG96' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_ESP8266' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M95' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M590' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_MC60' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM800' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM808' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM5360' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM7600' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM7000' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_UBLOX' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SARAR4' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_XBEE' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SEQUANS_MONARCH' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev" + + # Disabled due to a bug in Energia readBytes implementation + #- PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_XBEE' --project-option='framework=energia' --board=lplm4f120h5qr" + +install: + # ChipKIT issue: install 32-bit support for GCC PIC32 + - sudo apt-get install libc6-i386 + + - pip install -U https://github.com/platformio/platformio/archive/develop.zip + - platformio upgrade + - platformio update + + # + # Libraries from PlatformIO Library Registry: + # + # http://platformio.org/lib/show/89/PubSubClient + # http://platformio.org/lib/show/415/Blynk + # http://platformio.org/lib/show/1202/CRC32 + # http://platformio.org/lib/show/1286/StreamDebugger + + - platformio lib -g install 89 415 1202 1286 + - platformio lib -g update + - platformio lib update + - platformio lib list + - platformio lib -g list + +script: make travis-build diff --git a/lib/TinyGSM/Makefile b/lib/TinyGSM/Makefile new file mode 100644 index 0000000..40cbd82 --- /dev/null +++ b/lib/TinyGSM/Makefile @@ -0,0 +1,9 @@ +.PHONY: travis-build + +travis-build: +ifdef PLATFORMIO_CI_ARGS + platformio ci --lib="." $(PLATFORMIO_CI_ARGS) +else + platformio ci --lib="." --board=leonardo +endif + diff --git a/lib/TinyGSM/README.md b/lib/TinyGSM/README.md index 4b45b8e..890a6d9 100644 --- a/lib/TinyGSM/README.md +++ b/lib/TinyGSM/README.md @@ -17,7 +17,7 @@ If you like **TinyGSM** - give it a star, or fork it and contribute! [![GitHub forks](https://img.shields.io/github/forks/vshymanskyy/TinyGSM.svg?style=social&label=Fork)](https://github.com/vshymanskyy/TinyGSM/network) You can also join our chat: -[![Gitter](https://img.shields.io/gitter/room/vshymanskyy/TinyGSM.svg)](https://app.gitter.im/#/room/#tinygsm_Lobby:gitter.im) +[![Gitter](https://img.shields.io/gitter/room/vshymanskyy/TinyGSM.svg)](https://gitter.im/tinygsm) - [Supported modems](#supported-modems) - [Supported boards/modules](#supported-boardsmodules) @@ -29,7 +29,7 @@ You can also join our chat: - [How does it work?](#how-does-it-work) - [API Reference](#api-reference) - [Troubleshooting](#troubleshooting) - - [Ensure stable data \& power connection](#ensure-stable-data--power-connection) + - [Ensure stable data & power connection](#ensure-stable-data--power-connection) - [Baud rates](#baud-rates) - [Broken initial configuration](#broken-initial-configuration) - [Failed connection or no data received](#failed-connection-or-no-data-received) @@ -70,32 +70,34 @@ TinyGSM also pulls data gently from the modem (whenever possible), so it can ope - SIMCom LTE Modules (SIM7100E, SIM7500E, SIM7500A, SIM7600C, SIM7600E) - SIMCom SIM7000E/A/G CAT-M1/NB-IoT Module - SIMCom SIM7070/SIM7080/SIM7090 CAT-M1/NB-IoT Module -- SIMCom A7672X CAT-M1 Module - AI-Thinker A6, A6C, A7, A20 - ESP8266/ESP32 (AT commands interface, similar to GSM modems) - Digi XBee WiFi and Cellular (using XBee command mode) - Neoway M590 - u-blox 2G, 3G, 4G, and LTE Cat1 Cellular Modems (many modules including LEON-G100, LISA-U2xx, SARA-G3xx, SARA-U2xx, TOBY-L2xx, LARA-R2xx, MPCI-L2xx) -- u-blox LTE-M/NB-IoT Modems (SARA-R4xx, SARA-N4xx, SARA-R5xx, _but NOT SARA-N2xx_) +- u-blox LTE-M/NB-IoT Modems (SARA-R4xx, SARA-N4xx, _but NOT SARA-N2xx_) - Sequans Monarch LTE Cat M1/NB1 (VZM20Q) - Quectel BG96 -- Quectel BG95 - Quectel M95 - Quectel MC60 ***(alpha)*** ### Supported boards/modules -- EnviroDIY LTE Bee, WiFi Bee - Arduino MKR GSM 1400 -- Sodaq GPRSbee, uBee +- GPRSbee - Microduino GSM -- Adafruit FONA Mini Cellular GSM Breakout, 800/808 Shield, FONA 3G +- Adafruit FONA (Mini Cellular GSM Breakout) +- Adafruit FONA 800/808 Shield - Industruino GSM -- Dragino NB-IoT Bee -- Digi XBee S6B, XBee LTE Cat 1, XBee3 LTE Cat 1, XBee3 CatM -- Nimbelink Skywire/Airgain NL-SW-LTE-QBG96, NL-SW-LTE-QBG95 - RAK WisLTE ***(alpha)*** - ... other modules, based on supported modems. Some boards require [**special configuration**](https://github.com/vshymanskyy/TinyGSM/wiki/Board-configuration). +More modems may be supported later: +- [ ] Quectel M10, UG95 +- [ ] SIMCom SIM7020 +- [ ] Telit GL865 +- [ ] ZTE MG2639 +- [ ] Hi-Link HLK-RM04 + Watch this repo for new updates! And of course, contributions are welcome ;) ## Features @@ -108,7 +110,6 @@ Watch this repo for new updates! And of course, contributions are welcome ;) - ESP8266 - 5 - Neoway M590 - 2 - Quectel BG96 - 12 - - Quectel BG95 - 12 - Quectel M95 - 6 - Quectel MC60/MC60E - 6 - Sequans Monarch - 6 @@ -117,7 +118,6 @@ Watch this repo for new updates! And of course, contributions are welcome ;) - SIM7000 - 8 possible without SSL, only 2 with - SIM 7070/7080/7090 - 12 - SIM 7500/7600/7800 - 10 - - SIM A7672X - 10 - u-blox 2G/3G - 7 - u-blox SARA R4/N4 - 7 - Digi XBee - _only 1 connection supported!_ @@ -125,10 +125,10 @@ Watch this repo for new updates! And of course, contributions are welcome ;) - Not yet supported on any module, though it may be some day - SSL/TLS (HTTPS) - Supported on: - - SIM800, SIM7000, A7672X, u-Blox, XBee _cellular_, ESP8266, Sequans Monarch and Quectel BG95 and BG96 + - SIM800, SIM7000, u-Blox, XBee _cellular_, ESP8266, and Sequans Monarch - Note: **only some device models or firmware revisions have this feature** (SIM8xx R14.18, A7, etc.) - Not yet supported on: - - SIM 5360/5320/7100, SIM 7500/7600/7800 + - Quectel modems, SIM 5360/5320/7100, SIM 7500/7600/7800 - Not possible on: - SIM900, A6/A7, Neoway M590, XBee _WiFi_ - Like TCP, most modules support simultaneous connections @@ -151,14 +151,14 @@ Watch this repo for new updates! And of course, contributions are welcome ;) - Not yet supported on: - SIM7000, SIM5360/5320/7100, SIM7500/7800, VZM20Q (Monarch) - Not possible on: - - XBee (any type), u-blox SARA R4/R5/N4, Neoway M590, ESP8266 (obviously) + - XBee (any type), u-blox SARA R4/N4, Neoway M590, ESP8266 (obviously) - Functions: - Dial, hangup - DTMF sending **Location** - GPS/GNSS - - SIM808, SIM7000, SIM7500/7600/7800, BG96, BG95, u-blox + - SIM808, SIM7000, SIM7500/7600/7800, BG96, u-blox - NOTE: u-blox chips do _NOT_ have embedded GPS - this functionality only works if a secondary GPS is connected to primary cellular chip over I2C - GSM location service - SIM800, SIM7000, Quectel, u-blox @@ -176,10 +176,6 @@ Watch this repo for new updates! And of course, contributions are welcome ;) - [V1pr](https://github.com/V1pr) - Quectel M95 - [replicadeltd](https://github.com/replicadeltd) -- UBLOX SARA-R5 - - [Sebastian Bergner](https://github.com/sebastianbergner) -- SIMCOM A7672X - - [Giovanni de Rosso Unruh](https://github.com/giovannirosso) - Other Contributors: - https://github.com/vshymanskyy/TinyGSM/graphs/contributors diff --git a/lib/TinyGSM/cpplint.cfg b/lib/TinyGSM/cpplint.cfg new file mode 100644 index 0000000..6485487 --- /dev/null +++ b/lib/TinyGSM/cpplint.cfg @@ -0,0 +1,3 @@ +# Allow references to be used to change values +filter=-runtime/references +filter=-build/namespaces diff --git a/lib/TinyGSM/examples/AllFunctions/AllFunctions.ino b/lib/TinyGSM/examples/AllFunctions/AllFunctions.ino index df183fc..b3e6ff6 100644 --- a/lib/TinyGSM/examples/AllFunctions/AllFunctions.ino +++ b/lib/TinyGSM/examples/AllFunctions/AllFunctions.ino @@ -19,12 +19,9 @@ // #define TINY_GSM_MODEM_SIM7080 // #define TINY_GSM_MODEM_SIM5360 // #define TINY_GSM_MODEM_SIM7600 -// #define TINY_GSM_MODEM_A7672X // #define TINY_GSM_MODEM_UBLOX // #define TINY_GSM_MODEM_SARAR4 -// #define TINY_GSM_MODEM_SARAR5 // #define TINY_GSM_MODEM_M95 -// #define TINY_GSM_MODEM_BG95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 @@ -32,7 +29,6 @@ // #define TINY_GSM_MODEM_MC60 // #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 -// #define TINY_GSM_MODEM_ESP32 // #define TINY_GSM_MODEM_XBEE // #define TINY_GSM_MODEM_SEQUANS_MONARCH @@ -73,17 +69,17 @@ SoftwareSerial SerialAT(2, 3); // RX, TX #define TINY_GSM_TEST_WIFI false #define TINY_GSM_TEST_TCP true #define TINY_GSM_TEST_SSL true -#define TINY_GSM_TEST_CALL true -#define TINY_GSM_TEST_SMS true -#define TINY_GSM_TEST_USSD true +#define TINY_GSM_TEST_CALL false +#define TINY_GSM_TEST_SMS false +#define TINY_GSM_TEST_USSD false #define TINY_GSM_TEST_BATTERY true #define TINY_GSM_TEST_TEMPERATURE true -#define TINY_GSM_TEST_GSM_LOCATION true -#define TINY_GSM_TEST_GPS true -#define TINY_GSM_TEST_NTP true -#define TINY_GSM_TEST_TIME true +#define TINY_GSM_TEST_GSM_LOCATION false +#define TINY_GSM_TEST_NTP false +#define TINY_GSM_TEST_TIME false +#define TINY_GSM_TEST_GPS false // disconnect and power down modem after tests -#define TINY_GSM_POWERDOWN true +#define TINY_GSM_POWERDOWN false // set GSM PIN, if any #define GSM_PIN "" @@ -139,7 +135,7 @@ void setup() { // !!!!!!!!!!! DBG("Wait..."); - delay(6000L); + delay(6000); // Set GSM module baud rate TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); @@ -158,32 +154,18 @@ void loop() { return; } - String modemInfo = modem.getModemInfo(); - DBG("Modem Info:", modemInfo); - String name = modem.getModemName(); DBG("Modem Name:", name); - String manufacturer = modem.getModemManufacturer(); - DBG("Modem Manufacturer:", manufacturer); - - String hw_ver = modem.getModemModel(); - DBG("Modem Hardware Version:", hw_ver); - - String fv_ver = modem.getModemRevision(); - DBG("Modem Firware Version:", fv_ver); - -#if not defined(TINY_GSM_MODEM_ESP8266) && not defined(TINY_GSM_MODEM_ESP32) - String mod_sn = modem.getModemSerialNumber(); - DBG("Modem Serial Number (may be SIM CCID):", mod_sn); -#endif + String modemInfo = modem.getModemInfo(); + DBG("Modem Info:", modemInfo); #if TINY_GSM_TEST_GPRS // Unlock your SIM card with a PIN if needed if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } #endif -#if TINY_GSM_TEST_WIFI && defined(TINY_GSM_MODEM_HAS_WIFI) +#if TINY_GSM_TEST_WIFI DBG("Setting SSID/password..."); if (!modem.networkConnect(wifiSSID, wifiPass)) { DBG(" fail"); @@ -193,7 +175,7 @@ void loop() { SerialMon.println(" success"); #endif -#if TINY_GSM_TEST_GPRS && defined(TINY_GSM_MODEM_XBEE) +#if TINY_GSM_TEST_GPRS && defined TINY_GSM_MODEM_XBEE // The XBee must run the gprsConnect function BEFORE waiting for network! modem.gprsConnect(apn, gprsUser, gprsPass); #endif @@ -228,9 +210,6 @@ void loop() { String cop = modem.getOperator(); DBG("Operator:", cop); - // String prov = modem.getProvider(); - // DBG("Provider:", prov); - IPAddress local = modem.localIP(); DBG("Local IP:", local); @@ -286,7 +265,6 @@ void loop() { #endif #if TINY_GSM_TEST_SSL && defined TINY_GSM_MODEM_HAS_SSL - // TODO: Add test of adding certificcate TinyGsmClientSecure secureClient(modem, 1); const int securePort = 443; DBG("Connecting securely to", server); @@ -325,8 +303,8 @@ void loop() { } #endif -#if TINY_GSM_TEST_CALL && defined(TINY_GSM_MODEM_HAS_CALLING) && \ - defined(CALL_TARGET) +#if TINY_GSM_TEST_CALL && defined TINY_GSM_MODEM_HAS_CALLING && \ + defined CALL_TARGET DBG("Calling:", CALL_TARGET); // This is NOT supported on M590 @@ -349,7 +327,6 @@ void loop() { } #endif -// Test the SMS functions #if TINY_GSM_TEST_SMS && defined TINY_GSM_MODEM_HAS_SMS && defined SMS_TARGET res = modem.sendSMS(SMS_TARGET, String("Hello from ") + imei); DBG("SMS:", res ? "OK" : "fail"); @@ -366,27 +343,24 @@ void loop() { #endif -// Test the GSM location functions #if TINY_GSM_TEST_GSM_LOCATION && defined TINY_GSM_MODEM_HAS_GSM_LOCATION - float gsm_latitude = 0; - float gsm_longitude = 0; - float gsm_accuracy = 0; - int gsm_year = 0; - int gsm_month = 0; - int gsm_day = 0; - int gsm_hour = 0; - int gsm_minute = 0; - int gsm_second = 0; + float lat = 0; + float lon = 0; + float accuracy = 0; + int year = 0; + int month = 0; + int day = 0; + int hour = 0; + int min = 0; + int sec = 0; for (int8_t i = 15; i; i--) { DBG("Requesting current GSM location"); - if (modem.getGsmLocation(&gsm_latitude, &gsm_longitude, &gsm_accuracy, - &gsm_year, &gsm_month, &gsm_day, &gsm_hour, - &gsm_minute, &gsm_second)) { - DBG("Latitude:", String(gsm_latitude, 8), - "\tLongitude:", String(gsm_longitude, 8)); - DBG("Accuracy:", gsm_accuracy); - DBG("Year:", gsm_year, "\tMonth:", gsm_month, "\tDay:", gsm_day); - DBG("Hour:", gsm_hour, "\tMinute:", gsm_minute, "\tSecond:", gsm_second); + if (modem.getGsmLocation(&lat, &lon, &accuracy, &year, &month, &day, &hour, + &min, &sec)) { + DBG("Latitude:", String(lat, 8), "\tLongitude:", String(lon, 8)); + DBG("Accuracy:", accuracy); + DBG("Year:", year, "\tMonth:", month, "\tDay:", day); + DBG("Hour:", hour, "\tMinute:", min, "\tSecond:", sec); break; } else { DBG("Couldn't get GSM location, retrying in 15s."); @@ -398,38 +372,33 @@ void loop() { DBG("GSM Based Location String:", location); #endif -// Test the GPS functions #if TINY_GSM_TEST_GPS && defined TINY_GSM_MODEM_HAS_GPS DBG("Enabling GPS/GNSS/GLONASS and waiting 15s for warm-up"); -#if !defined(TINY_GSM_MODEM_SARAR5) // not needed for this module modem.enableGPS(); -#endif delay(15000L); - float gps_latitude = 0; - float gps_longitude = 0; - float gps_speed = 0; - float gps_altitude = 0; - int gps_vsat = 0; - int gps_usat = 0; - float gps_accuracy = 0; - int gps_year = 0; - int gps_month = 0; - int gps_day = 0; - int gps_hour = 0; - int gps_minute = 0; - int gps_second = 0; + float lat2 = 0; + float lon2 = 0; + float speed2 = 0; + float alt2 = 0; + int vsat2 = 0; + int usat2 = 0; + float accuracy2 = 0; + int year2 = 0; + int month2 = 0; + int day2 = 0; + int hour2 = 0; + int min2 = 0; + int sec2 = 0; for (int8_t i = 15; i; i--) { DBG("Requesting current GPS/GNSS/GLONASS location"); - if (modem.getGPS(&gps_latitude, &gps_longitude, &gps_speed, &gps_altitude, - &gps_vsat, &gps_usat, &gps_accuracy, &gps_year, &gps_month, - &gps_day, &gps_hour, &gps_minute, &gps_second)) { - DBG("Latitude:", String(gps_latitude, 8), - "\tLongitude:", String(gps_longitude, 8)); - DBG("Speed:", gps_speed, "\tAltitude:", gps_altitude); - DBG("Visible Satellites:", gps_vsat, "\tUsed Satellites:", gps_usat); - DBG("Accuracy:", gps_accuracy); - DBG("Year:", gps_year, "\tMonth:", gps_month, "\tDay:", gps_day); - DBG("Hour:", gps_hour, "\tMinute:", gps_minute, "\tSecond:", gps_second); + if (modem.getGPS(&lat2, &lon2, &speed2, &alt2, &vsat2, &usat2, &accuracy2, + &year2, &month2, &day2, &hour2, &min2, &sec2)) { + DBG("Latitude:", String(lat2, 8), "\tLongitude:", String(lon2, 8)); + DBG("Speed:", speed2, "\tAltitude:", alt2); + DBG("Visible Satellites:", vsat2, "\tUsed Satellites:", usat2); + DBG("Accuracy:", accuracy2); + DBG("Year:", year2, "\tMonth:", month2, "\tDay:", day2); + DBG("Hour:", hour2, "\tMinute:", min2, "\tSecond:", sec2); break; } else { DBG("Couldn't get GPS/GNSS/GLONASS location, retrying in 15s."); @@ -438,34 +407,31 @@ void loop() { } DBG("Retrieving GPS/GNSS/GLONASS location again as a string"); String gps_raw = modem.getGPSraw(); -#if !defined(TINY_GSM_MODEM_SARAR5) // not available for this module DBG("GPS/GNSS Based Location String:", gps_raw); DBG("Disabling GPS"); modem.disableGPS(); #endif -#endif -// Test the Network time functions #if TINY_GSM_TEST_NTP && defined TINY_GSM_MODEM_HAS_NTP DBG("Asking modem to sync with NTP"); - modem.NTPServerSync("pool.ntp.org", 20); + modem.NTPServerSync("132.163.96.5", 20); #endif #if TINY_GSM_TEST_TIME && defined TINY_GSM_MODEM_HAS_TIME - int ntp_year = 0; - int ntp_month = 0; - int ntp_day = 0; - int ntp_hour = 0; - int ntp_min = 0; - int ntp_sec = 0; - float ntp_timezone = 0; + int year3 = 0; + int month3 = 0; + int day3 = 0; + int hour3 = 0; + int min3 = 0; + int sec3 = 0; + float timezone = 0; for (int8_t i = 5; i; i--) { DBG("Requesting current network time"); - if (modem.getNetworkTime(&ntp_year, &ntp_month, &ntp_day, &ntp_hour, - &ntp_min, &ntp_sec, &ntp_timezone)) { - DBG("Year:", ntp_year, "\tMonth:", ntp_month, "\tDay:", ntp_day); - DBG("Hour:", ntp_hour, "\tMinute:", ntp_min, "\tSecond:", ntp_sec); - DBG("Timezone:", ntp_timezone); + if (modem.getNetworkTime(&year3, &month3, &day3, &hour3, &min3, &sec3, + &timezone)) { + DBG("Year:", year3, "\tMonth:", month3, "\tDay:", day3); + DBG("Hour:", hour3, "\tMinute:", min3, "\tSecond:", sec3); + DBG("Timezone:", timezone); break; } else { DBG("Couldn't get network time, retrying in 15s."); @@ -477,18 +443,16 @@ void loop() { DBG("Current Network Time:", time); #endif -// Test Battery functions #if TINY_GSM_TEST_BATTERY && defined TINY_GSM_MODEM_HAS_BATTERY - int8_t chargeState = -99; - int8_t chargePercent = -99; - int16_t milliVolts = -9999; - modem.getBattStats(chargeState, chargePercent, milliVolts); + uint8_t chargeState = -99; + int8_t percent = -99; + uint16_t milliVolts = -9999; + modem.getBattStats(chargeState, percent, milliVolts); DBG("Battery charge state:", chargeState); - DBG("Battery charge 'percent':", chargePercent); + DBG("Battery charge 'percent':", percent); DBG("Battery voltage:", milliVolts / 1000.0F); #endif -// Test temperature functions #if TINY_GSM_TEST_TEMPERATURE && defined TINY_GSM_MODEM_HAS_TEMPERATURE float temp = modem.getTemperature(); DBG("Chip temperature:", temp); diff --git a/lib/TinyGSM/examples/BlynkClient/BlynkClient.ino b/lib/TinyGSM/examples/BlynkClient/BlynkClient.ino index 6be1eb3..2ab7f3d 100644 --- a/lib/TinyGSM/examples/BlynkClient/BlynkClient.ino +++ b/lib/TinyGSM/examples/BlynkClient/BlynkClient.ino @@ -22,11 +22,12 @@ * Change GPRS apm, user, pass, and Blynk auth token to run :) **************************************************************/ -#define BLYNK_TEMPLATE_ID "TMPxxxxxx" -#define BLYNK_TEMPLATE_NAME "Device" -#define BLYNK_AUTH_TOKEN "YourAuthToken" +/* Fill in information from Blynk Device Info here */ +#define BLYNK_TEMPLATE_ID "TMPxxxxxx" +#define BLYNK_TEMPLATE_NAME "Device" +#define BLYNK_AUTH_TOKEN "YourAuthToken" -#define BLYNK_PRINT Serial // Comment this out to disable prints and save space +#define BLYNK_PRINT Serial // Comment this out to disable prints and save space // Default heartbeat interval for GSM is 60 // If you want override this value, uncomment and set this option: @@ -42,12 +43,9 @@ // #define TINY_GSM_MODEM_SIM7080 // #define TINY_GSM_MODEM_SIM5360 // #define TINY_GSM_MODEM_SIM7600 -// #define TINY_GSM_MODEM_A7672X // #define TINY_GSM_MODEM_UBLOX // #define TINY_GSM_MODEM_SARAR4 -// #define TINY_GSM_MODEM_SARAR5 // #define TINY_GSM_MODEM_M95 -// #define TINY_GSM_MODEM_BG95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 @@ -55,7 +53,6 @@ // #define TINY_GSM_MODEM_MC60 // #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 -// #define TINY_GSM_MODEM_ESP32 // #define TINY_GSM_MODEM_XBEE // #define TINY_GSM_MODEM_SEQUANS_MONARCH @@ -87,7 +84,8 @@ const char auth[] = "YourAuthToken"; TinyGsm modem(SerialAT); -void setup() { +void setup() +{ // Set console baud rate SerialMon.begin(115200); delay(10); @@ -106,11 +104,12 @@ void setup() { SerialMon.println(modemInfo); // Unlock your SIM card with a PIN - // modem.simUnlock("1234"); + //modem.simUnlock("1234"); Blynk.begin(auth, modem, apn, user, pass); } -void loop() { +void loop() +{ Blynk.run(); } diff --git a/lib/TinyGSM/examples/FileDownload/FileDownload.ino b/lib/TinyGSM/examples/FileDownload/FileDownload.ino index 8d102e5..d66e4b7 100644 --- a/lib/TinyGSM/examples/FileDownload/FileDownload.ino +++ b/lib/TinyGSM/examples/FileDownload/FileDownload.ino @@ -23,12 +23,9 @@ // #define TINY_GSM_MODEM_SIM7080 // #define TINY_GSM_MODEM_SIM5360 // #define TINY_GSM_MODEM_SIM7600 -// #define TINY_GSM_MODEM_A7672X // #define TINY_GSM_MODEM_UBLOX // #define TINY_GSM_MODEM_SARAR4 -// #define TINY_GSM_MODEM_SARAR5 // #define TINY_GSM_MODEM_M95 -// #define TINY_GSM_MODEM_BG95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 @@ -36,7 +33,6 @@ // #define TINY_GSM_MODEM_MC60 // #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 -// #define TINY_GSM_MODEM_ESP32 // #define TINY_GSM_MODEM_XBEE // #define TINY_GSM_MODEM_SEQUANS_MONARCH diff --git a/lib/TinyGSM/examples/HttpClient/HttpClient.ino b/lib/TinyGSM/examples/HttpClient/HttpClient.ino index f3d2e01..3e4659b 100644 --- a/lib/TinyGSM/examples/HttpClient/HttpClient.ino +++ b/lib/TinyGSM/examples/HttpClient/HttpClient.ino @@ -28,12 +28,9 @@ // #define TINY_GSM_MODEM_SIM7080 // #define TINY_GSM_MODEM_SIM5360 // #define TINY_GSM_MODEM_SIM7600 -// #define TINY_GSM_MODEM_A7672X // #define TINY_GSM_MODEM_UBLOX // #define TINY_GSM_MODEM_SARAR4 -// #define TINY_GSM_MODEM_SARAR5 // #define TINY_GSM_MODEM_M95 -// #define TINY_GSM_MODEM_BG95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 @@ -41,7 +38,6 @@ // #define TINY_GSM_MODEM_MC60 // #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 -// #define TINY_GSM_MODEM_ESP32 // #define TINY_GSM_MODEM_XBEE // #define TINY_GSM_MODEM_SEQUANS_MONARCH diff --git a/lib/TinyGSM/examples/HttpsClient/HttpsClient.ino b/lib/TinyGSM/examples/HttpsClient/HttpsClient.ino index 95a107b..33092f2 100644 --- a/lib/TinyGSM/examples/HttpsClient/HttpsClient.ino +++ b/lib/TinyGSM/examples/HttpsClient/HttpsClient.ino @@ -30,7 +30,6 @@ // #define TINY_GSM_MODEM_UBLOX // #define TINY_GSM_MODEM_SARAR4 // #define TINY_GSM_MODEM_ESP8266 -// #define TINY_GSM_MODEM_ESP32 // #define TINY_GSM_MODEM_XBEE // #define TINY_GSM_MODEM_SEQUANS_MONARCH diff --git a/lib/TinyGSM/examples/MqttClient/MqttClient.ino b/lib/TinyGSM/examples/MqttClient/MqttClient.ino index 797a52c..77a8500 100644 --- a/lib/TinyGSM/examples/MqttClient/MqttClient.ino +++ b/lib/TinyGSM/examples/MqttClient/MqttClient.ino @@ -32,12 +32,9 @@ // #define TINY_GSM_MODEM_SIM7080 // #define TINY_GSM_MODEM_SIM5360 // #define TINY_GSM_MODEM_SIM7600 -// #define TINY_GSM_MODEM_A7672X // #define TINY_GSM_MODEM_UBLOX // #define TINY_GSM_MODEM_SARAR4 -// #define TINY_GSM_MODEM_SARAR5 // #define TINY_GSM_MODEM_M95 -// #define TINY_GSM_MODEM_BG95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 @@ -45,7 +42,6 @@ // #define TINY_GSM_MODEM_MC60 // #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 -// #define TINY_GSM_MODEM_ESP32 // #define TINY_GSM_MODEM_XBEE // #define TINY_GSM_MODEM_SEQUANS_MONARCH diff --git a/lib/TinyGSM/examples/WebClient/WebClient.ino b/lib/TinyGSM/examples/WebClient/WebClient.ino index 3cb2703..74ffa0a 100644 --- a/lib/TinyGSM/examples/WebClient/WebClient.ino +++ b/lib/TinyGSM/examples/WebClient/WebClient.ino @@ -18,12 +18,9 @@ // #define TINY_GSM_MODEM_SIM7080 // #define TINY_GSM_MODEM_SIM5360 // #define TINY_GSM_MODEM_SIM7600 -// #define TINY_GSM_MODEM_A7672X // #define TINY_GSM_MODEM_UBLOX // #define TINY_GSM_MODEM_SARAR4 -// #define TINY_GSM_MODEM_SARAR5 // #define TINY_GSM_MODEM_M95 -// #define TINY_GSM_MODEM_BG95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 @@ -31,7 +28,6 @@ // #define TINY_GSM_MODEM_MC60 // #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 -// #define TINY_GSM_MODEM_ESP32 // #define TINY_GSM_MODEM_XBEE // #define TINY_GSM_MODEM_SEQUANS_MONARCH @@ -121,10 +117,6 @@ TinyGsm modem(debugger); TinyGsm modem(SerialAT); #endif -#if defined(TINY_GSM_MODEM_HAS_SSL) -#define USE_SSL -#endif - #ifdef USE_SSL TinyGsmClientSecure client(modem); const int port = 443; diff --git a/lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino b/lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino index cefb37c..eef4207 100644 --- a/lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino +++ b/lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino @@ -32,11 +32,11 @@ #ifdef DUMP_AT_COMMANDS -#include -StreamDebugger debugger(SerialAT, SerialMon); -TinyGsm modem(debugger); + #include + StreamDebugger debugger(SerialAT, SerialMon); + TinyGsm modem(debugger); #else -TinyGsm modem(SerialAT); + TinyGsm modem(SerialAT); #endif void setup() { @@ -57,21 +57,23 @@ void setup() { const int cert_size = sizeof(cert); modem.sendAT(GF("+FSWRITE=" CERT_FILE ",0,"), cert_size, GF(",10")); - if (modem.waitResponse(GF(">")) != 1) { return; } + if (modem.waitResponse(GF(">")) != 1) { + return; + } for (int i = 0; i < cert_size; i++) { char c = pgm_read_byte(&cert[i]); modem.stream.write(c); } - modem.stream.write(AT_NL); + modem.stream.write(GSM_NL); modem.stream.flush(); if (modem.waitResponse(2000) != 1) return; modem.sendAT(GF("+SSLSETCERT=\"" CERT_FILE "\"")); if (modem.waitResponse() != 1) return; - if (modem.waitResponse(5000L, GF(AT_NL "+SSLSETCERT:")) != 1) return; + if (modem.waitResponse(5000L, GF(GSM_NL "+SSLSETCERT:")) != 1) return; const int retCode = modem.stream.readStringUntil('\n').toInt(); @@ -84,7 +86,11 @@ void setup() { } void loop() { - if (SerialAT.available()) { SerialMon.write(SerialAT.read()); } - if (SerialMon.available()) { SerialAT.write(SerialMon.read()); } + if (SerialAT.available()) { + SerialMon.write(SerialAT.read()); + } + if (SerialMon.available()) { + SerialAT.write(SerialMon.read()); + } delay(0); } diff --git a/lib/TinyGSM/extras/examples.png b/lib/TinyGSM/extras/examples.png new file mode 100644 index 0000000..e8f7592 Binary files /dev/null and b/lib/TinyGSM/extras/examples.png differ diff --git a/lib/TinyGSM/extras/logo.svg b/lib/TinyGSM/extras/logo.svg new file mode 100644 index 0000000..90a6d35 --- /dev/null +++ b/lib/TinyGSM/extras/logo.svg @@ -0,0 +1,12 @@ + + + + _____ _____ _____ _____ + | | |\ | \_/ | ___ |_____ | | | + | | | \| | |_____| _____|| | | + + diff --git a/lib/TinyGSM/extras/logo.txt b/lib/TinyGSM/extras/logo.txt new file mode 100644 index 0000000..0d8bbf3 --- /dev/null +++ b/lib/TinyGSM/extras/logo.txt @@ -0,0 +1,5 @@ + + _____ _____ _____ _____ + | | |\ | \_/ | ___ |_____ | | | + | | | \| | |_____| _____|| | | + diff --git a/lib/TinyGSM/extras/test_100k.bin b/lib/TinyGSM/extras/test_100k.bin new file mode 100644 index 0000000..58d1af0 Binary files /dev/null and b/lib/TinyGSM/extras/test_100k.bin differ diff --git a/lib/TinyGSM/extras/test_100k.hex b/lib/TinyGSM/extras/test_100k.hex new file mode 100644 index 0000000..f83af17 --- /dev/null +++ b/lib/TinyGSM/extras/test_100k.hex @@ -0,0 +1,2090 @@ +ef c0 75 60 cb 9b fb 4b 61 2d 0e f9 a5 b2 9c b7 +63 a3 c4 f2 b1 aa 0d e6 0e 5b ad 73 2e 23 a6 8e +60 b3 dc a0 c4 d5 f4 6e 74 c9 82 d7 d4 15 18 75 +b6 ed 38 09 39 89 1c 37 24 15 f0 e7 53 f1 da ca +f6 90 68 ae a5 de 1a 36 fb 57 b2 99 2e 47 5f 49 +80 d0 ff 0d 51 8a 4e 69 be d0 23 24 26 44 4a 6d +e1 cf 1f e8 8b 75 6e c3 a6 e3 16 a0 df 35 48 1c +81 19 50 d2 af 9f 32 cb 13 b0 41 53 e4 37 aa a6 +ba 08 85 e4 89 62 40 b2 66 1b ba ed f2 1e ed 7a +0b ad 27 ab 81 71 07 07 12 50 8a d9 b9 49 21 cf +ad da aa 83 b0 53 ad c8 a2 e2 33 75 86 b9 b7 8e +d3 33 fb 99 88 33 18 ce 1f 08 a1 cf 38 d0 bc f2 +ea 32 67 55 06 3a 75 2a 6e 67 98 66 ac 11 65 0b +47 4d 3c ed 80 5a 7e 64 c9 9d f1 57 a0 e5 d0 50 +e3 03 26 fd 1c d2 c1 98 e1 c2 38 f1 5e 82 66 c3 +8a 79 ab 33 7b 48 db 37 3d ec e7 c2 0d 71 48 73 +60 f5 4f 75 34 8e ee 49 18 bf 03 98 57 8c c9 b2 +b2 2f 1b b0 86 38 8c 59 4e 2f 3f b1 dc 16 88 70 +5c be 90 0c 9b 88 5e d5 21 e5 a0 b0 c4 20 7a 16 +71 73 73 bc 8c 67 64 2b 4f 18 31 35 36 f6 a1 7a +ca f1 f3 5a 0e b8 71 4b c9 13 15 c4 7a f4 24 57 +89 ad 4f 26 ee 80 68 fd 58 ac aa cf 29 f2 0e 62 +76 3e b6 93 55 f4 66 18 97 d0 13 27 06 59 cb 33 +65 fd a9 0d 2d 60 b1 e8 5d 56 7b d0 f7 71 80 2f +d5 6e 43 3d f6 5e 32 f5 2a db 40 fe fe d0 ee ca +c8 c0 ad d3 c5 65 e2 52 ec 7d ee b9 1b 3c d0 08 +0d 80 5a 74 c3 ca 7e 12 33 3f a0 79 68 5f 33 de +d6 51 d5 a8 72 1c b7 f8 cc da 27 3e 73 2f fd 3b +21 26 93 c9 65 1b 45 ab 67 d3 12 c6 b0 af ca bf +60 12 5c 5f 3f b9 de 6b eb d6 19 38 80 a7 86 05 +5a 7a 77 53 07 53 f5 f5 24 9b 72 f5 aa cb ff cf +b0 7b 01 39 6c 9e e6 90 8a c1 e6 d0 64 c6 f9 b6 +55 bb 93 dd a0 f0 c8 91 f1 1b 3b f4 60 5f 59 aa +86 42 27 ca b9 2c c5 de 2b f9 68 cc d0 2e 21 2d +b6 69 58 8f ea e2 51 e1 e8 d7 c4 88 5e 46 b1 45 +09 8c 2a 67 c4 b4 70 52 9c b8 d4 17 ce 7c 19 e0 +fa 1d 93 fb 34 f2 5c 9d a7 1e d3 2c 2d 9a 0b 01 +8e 85 74 d1 b8 d9 49 22 79 9d 96 89 64 d2 65 0d +bd c2 94 ea 7d 3e 90 ca 9b ba e8 46 03 dd f6 7f +f2 86 74 85 3e eb 68 f7 11 52 2d 99 5a a1 cf 11 +87 b1 eb 16 a8 47 bd c9 b6 9e 19 67 bd 2a 41 ae +bd 8a 4f 3e 89 84 b3 5b 6c 04 af 76 4c 1b 0a 49 +87 28 09 31 6f 57 27 00 23 5c 78 68 de 50 11 34 +ca 25 c0 dc c8 1d 59 72 d3 2a 77 32 07 2d 36 93 +d8 9a ba 25 34 0f cf 19 7f 0a cd 84 4c b8 4c 57 +a8 50 1d 20 1f 56 9c d9 be e7 47 f6 7e 51 68 b0 +49 47 e4 fc 3a 38 29 55 20 d6 94 15 2d 35 05 40 +01 6e 53 72 f3 47 58 7d dd 30 96 60 b4 4e 51 b1 +48 1e 40 0a c7 c3 5e 46 96 3a be c1 b4 5b 34 15 +07 8f 2e 71 70 93 64 fb 3a 7e d0 5a 23 7a b2 af +77 8f 31 89 a1 6c 42 c3 a4 4d fa 60 7e 13 ad 54 +92 23 a8 39 02 4f f4 84 86 b7 1f 18 1f d4 f7 55 +ed 9a b9 03 62 be 06 d9 29 0a ad 80 fe 23 84 65 +a0 bb e0 83 94 45 19 ef 77 3c 65 f0 90 9f 36 4f +5e b1 6e a4 dd 17 c6 f5 fc 74 58 fa f5 a0 dd 54 +80 e8 ed b6 c2 e1 8b cc 30 16 6c 50 d3 57 1c 77 +52 24 72 0f a4 3c 80 31 86 10 54 34 e3 3d 8b bc +8a f0 87 14 4f 0d 5c dc e1 12 89 d8 69 3a 81 a6 +df 60 20 56 5c e5 6e 04 2a 45 0d 4e f2 b4 f4 10 +09 15 c3 c4 ed 3b 86 f2 89 17 99 f6 f3 bb c4 2d +63 42 4b 3a fd 34 6a 87 fc de 94 50 36 86 27 3f +02 98 f3 46 2b 69 88 b2 17 7f 65 1e 74 c5 57 68 +c4 af 7f c3 49 f9 71 e6 fb 28 dc 4a 15 21 cc ec +87 b2 8e 00 87 07 a2 d5 ec 83 1c 78 e2 c4 2c 43 +a9 08 d2 dc fa 10 7c f4 51 04 86 57 f4 7e 2d 78 +83 f1 1b 35 b4 de b9 08 3b a3 72 34 56 21 bc f1 +6b 86 7d f4 d2 d2 f0 ef 4e fa bd 06 f1 0d 3f b3 +5b d8 a2 62 36 e1 f0 2c 10 ff 6f 9f 8d 66 c0 9b +37 c2 f3 50 e8 b6 43 95 9b 3d b5 7b c4 76 b5 24 +89 86 f8 52 05 6a 37 4d d5 57 f2 0a c1 97 89 f9 +eb 5f 06 b3 68 3f 6d 2f 26 1f 05 7a b5 47 61 ae +f6 e9 0a 00 bd 55 b4 26 58 22 60 17 2d df f7 08 +a7 8c 0d 85 2e 15 27 bc 18 0c b4 6a 11 f1 22 67 +13 1c 2f 95 77 10 b3 a3 77 48 67 fb 81 dd d7 b5 +e3 40 b9 d5 b4 eb be 02 e2 9f 60 ef a8 c4 65 af +fb 9e 9c 40 a5 5c 77 41 0a b2 b1 88 51 b6 c5 5b +ce ab 75 59 b7 49 75 89 01 10 dd fb 29 36 37 27 +b2 4f 6b ec e1 c8 53 10 4c 93 69 34 67 01 af 79 +7e dd 8d ca e5 c9 3e d7 92 5c b5 40 b3 f4 da c9 +25 e7 45 27 82 d5 34 f0 cc 87 80 bc 5c ef 6e 45 +0f 35 ca a9 9c 5a 13 b7 50 df e0 96 4f a6 df 33 +f3 f7 2b 77 f4 c5 46 ae f8 0c 92 0f 53 16 65 97 +18 4b a4 2b 92 cc ce 91 a4 10 21 cb 99 a7 09 3b +cc e9 a2 c9 40 3a d2 2b 2a 41 48 84 57 a5 95 70 +38 be c5 d0 fd b5 51 78 38 bd 27 97 a7 34 81 b3 +72 62 ed 7f 8e 59 19 50 2a 42 b5 f7 33 81 dd 5e +e7 d9 c0 5c 0d 9e 2f e1 03 83 5c b7 8b 4e 7a b9 +49 a0 f2 bd 05 7e ea e7 e6 fe 65 17 65 ec 58 e0 +93 8c b7 39 e8 86 09 1a b6 73 63 df d9 ca 21 c2 +45 07 d4 ac 21 84 e7 c6 0b 83 15 96 fd 57 12 c2 +61 b8 d9 df 89 08 c4 ea 65 28 1e ef c4 f6 de ea +76 88 c5 b6 b0 ec 62 ba 60 ef af e2 16 59 53 91 +c6 af 39 08 9a 0e 31 69 cb 23 e4 c4 73 eb 98 fc +4a a1 d6 ea e6 7c 1e d5 01 0b c0 7c 37 19 c5 5f +4a 2e f4 7d f6 a0 e3 91 0d 19 96 42 7a 50 54 ff +d8 fe 9c cc ca 0a 11 02 6f 51 78 eb 09 a9 1e 80 +7b af 6e 40 22 8f a6 51 b5 59 5a 79 34 76 73 1b +72 69 b6 5d 97 90 84 88 b9 c9 11 5e 9d a5 3c 6b +66 16 23 b0 fa df 89 f3 6f d5 0d d6 6b 55 b6 b8 +5b 43 c4 46 15 d7 7a c0 d7 7f 7c 49 36 21 c6 93 +47 a7 38 83 19 98 fe 4f 03 89 29 00 e9 00 d9 42 +de 1e ec 2b 3f 7c 12 f3 42 c0 72 fd 10 7e 51 8b +17 77 a0 83 7f 97 2e a3 fe 36 7c 23 c5 82 a8 dd +a8 23 44 c7 46 57 c7 af df cc fc dc 5b 28 ad 7c +a2 2b 70 60 e7 b7 12 ae 4c 22 a3 74 46 37 33 33 +55 fd f9 b5 a1 bc 24 41 f6 15 7d 15 12 7c b8 16 +59 ba 0e 46 4f 13 60 08 53 99 86 e7 73 b7 66 31 +bc ef 30 df 50 9a 34 81 b9 23 c4 d7 e1 a0 1d 3e +88 a7 a9 ab 3e fb 60 ab 36 4e 21 67 11 51 15 86 +b3 2d 09 c4 43 88 d5 6a 4f c5 dd 4e a7 5a ab 4c +8b 77 31 ae 3d 53 5d 05 21 64 95 b6 ab 7e fb 36 +23 26 52 51 a5 a4 ad 36 bc 74 35 ca 29 51 91 4c +44 2c 25 95 5e 48 f6 06 9a 2a ed e8 5e 2c af 02 +ff 77 b6 5c ae 43 65 3a 83 eb 46 ee d0 cd 43 46 +19 ed 13 d0 1a 9a 97 0d c1 ea 60 3b 35 de 98 6b +cf c6 ec b7 4c 8a d6 65 4e 6d de 5f 8d c6 48 85 +73 d5 73 73 0e bb 4a 08 87 e1 65 17 72 c2 e0 8e +d0 1d 08 1f b7 91 6f 3c 3c 4d f5 1a 50 78 be ac +20 c4 af 0f fa f4 f1 2e 1a 16 ba d6 c5 4a 07 42 +db d8 2f 9e fd e0 41 29 cb ca 2d 34 af 59 1c 8c +bd d3 64 60 3c e4 75 4c bc 0c e2 93 b6 c4 90 e7 +5f d7 6b 14 13 b1 e6 d2 29 11 e2 8a f3 d0 86 d7 +07 13 e1 e9 2e b0 d1 e4 c9 02 f5 fd 8f b8 63 8b +4a 8e e1 9c 0e 15 c5 df 54 b3 da 27 ad f1 5f 24 +a8 26 c7 b2 78 fa 25 a0 cc 4d 16 c6 2e 4c 91 04 +61 09 60 3b 32 3d cb 2b 2f 60 83 24 ba a6 9a 66 +a6 f0 bc 11 fc 3c a7 c9 e4 3d e5 8f 62 42 5e fb +8b dc a4 82 ed 0d 1b 88 14 85 9f 8a 70 a2 20 a1 +81 61 11 5a 77 d9 c4 0f ce 95 f3 f8 35 13 34 94 +36 92 9b fe db a1 14 d3 5d 2c 3b 56 e7 9b b2 56 +ea 3b 2e d6 20 b9 20 93 e5 c0 01 08 be c4 3e 8a +76 8c da 27 eb ba 67 9e 62 ae a7 6f 39 83 8c 66 +2d 5f df 61 74 96 a6 0b 01 72 59 24 e5 16 50 46 +b1 7e 48 b6 a3 ef 52 82 ad 17 c5 31 62 53 66 0e +da 8e fd cc 92 96 4d de 4b 6b 60 38 49 47 aa 12 +ee b8 2a ae bf f8 62 5b 71 2d 21 d4 61 8b 3d aa +0d 36 01 91 2f 97 8f 61 52 42 f0 33 b5 94 2b e2 +67 3e 75 51 f8 cf 0e ff 06 6c 3d 38 7d b8 22 fe +a6 76 5c f4 a0 dc 96 19 b3 71 b3 6f b2 55 66 71 +70 74 13 15 01 87 e5 48 4f 21 31 d5 13 a6 87 b9 +f3 fe fc de 4b cb 48 7c d8 34 97 be e0 61 b9 cb +df 26 42 f2 3c 1b 82 2e 20 d4 08 0b 2a d5 e1 0f +87 06 ee 6e 8c 15 f5 f1 e7 ac cc 32 a1 f8 61 55 +a0 dd 62 4a f3 b2 99 16 d0 43 0b c9 b2 46 c9 5d +f1 ab d2 a9 6d 60 10 5d 0a 64 28 f7 b8 a8 be c4 +a6 8d ff 73 18 59 b9 94 ca a1 3b 35 56 1e 37 98 +6f 8f 02 36 ab ee f8 1c 16 38 2c 64 45 d7 8f ab +10 ac a9 fb 06 be ba 8a d6 bc 24 f3 16 34 eb 1c +2f e9 9f 7d 42 44 8a d5 f8 08 5d 3f 06 79 51 57 +20 36 7d a9 c0 50 92 54 5e 35 8e a7 4f 83 06 41 +8d 2d d1 01 8a d6 5c 7e 88 86 39 71 d5 c9 b5 20 +3f 03 2a d8 fb a5 81 8a c1 e3 80 a9 bd 07 f2 5a +9e 67 96 de a3 e1 74 96 72 4f 35 98 57 69 cc 3c +8f 64 0e 23 b7 18 1c 43 22 d8 f4 fe 1e ac 51 19 +13 b6 fa ab af 02 d8 b3 7a d5 20 01 fd 75 14 c6 +62 7d 3e a9 66 8e c5 16 53 4a 86 80 df ce 8f eb +e3 d2 1b 50 e3 27 9a 23 d5 df 63 ed eb 79 5e a6 +7c 67 33 9c ab 14 a0 4d 4b db 95 e2 06 31 bb 91 +3d 0f 2d 2c e8 1e 44 e0 c3 64 14 89 a6 17 c6 a4 +89 af ea f0 b5 c7 01 45 2d c7 1e ff bf 95 84 5b +c3 b2 a5 d7 73 93 6f eb 8b 61 8d 3b 49 31 f1 71 +1a 1c fb 74 07 18 a7 50 d4 64 22 ff ec d6 81 c0 +d7 36 0d a0 96 26 dd 74 d4 b0 0d 59 fb 5b ee 57 +5e be 75 98 c8 bd ed 6c e9 86 40 65 92 a0 37 17 +a3 de a4 be 08 f9 bb 07 d5 c4 97 d7 53 cf ee 93 +47 6f 3f 39 d5 84 7c 5c a8 ff 73 04 08 bc 80 e1 +d8 d8 55 ec 42 b1 f3 51 be 80 4b 2d e6 c9 61 e0 +9c 0e e1 6f 09 fe 84 9d 6f d1 fd f3 9a b1 ae 95 +65 b1 36 9f c8 2e f7 61 95 4a b3 ae 96 c8 c9 6c +27 72 06 89 47 12 b5 89 c1 ec fb 03 eb 5c 49 c4 +d6 1a b0 43 30 a3 0e 2b 06 9d ff df 9a 04 0d 7e +b7 9e a3 f1 20 ad b1 cb 77 61 b6 8f 79 bb 36 ef +b8 45 1a a3 d8 d5 e1 8e 83 de df 95 4b 25 91 80 +d2 19 fd 60 97 75 19 60 23 5a 62 f7 2b 86 7f a5 +cb f2 6f ff bf 21 32 03 38 ab cd 32 ea 57 ea 22 +8d 3c a7 f3 18 3c d8 02 00 6d 38 86 1f de 51 9a +12 77 a0 ea 30 c1 bf df c2 b5 4d 02 3e 98 9e bf +c2 36 2c 44 96 02 e5 f2 a3 e1 d1 ee b2 d1 24 50 +30 7b d7 21 15 7f 44 36 fd 6b 84 b8 ec 8f 30 c1 +8a f4 d3 05 8a 57 f6 2e 34 2b 2f 3b df af 85 76 +24 44 57 ef 6c 91 b3 a2 7b d3 cf 3d 57 05 5f 06 +65 3f fc 78 40 5b 05 9f 0c 76 02 7f 50 7e 20 10 +37 5e b1 c0 fd 84 57 63 09 ee 55 55 4d 26 99 56 +d1 25 e4 43 98 41 c7 30 9a 29 c3 72 0d ae 33 1f +49 9d 24 d8 27 4a 23 94 68 f4 47 19 29 bc 63 28 +b2 49 cc 8e 72 3a 99 ec 65 e3 da 89 ff b9 cd f7 +f1 47 ab 48 01 83 ab 77 eb 2d 64 bc ca d6 e4 91 +07 5d ca 08 e6 6c 8d a6 d7 d5 1e 4e e3 88 30 0f +e9 53 1a 22 91 61 58 4b 4b 36 20 94 0f 2a e1 3f +6a 47 50 e4 08 36 ab 74 1e 4a 17 74 6e 6e 2c dc +2b c4 58 e0 62 1f 7a ae fa d0 62 fb da 39 60 17 +f3 5f 77 76 5e 27 73 c6 02 a6 80 96 e9 ab 94 bc +7b dd 77 6a ee 03 1b 57 2c 12 91 34 9b 75 44 f2 +a1 68 60 0b 5d 24 ff 81 6b 12 92 f5 b1 78 58 ac +41 88 9a d6 25 1c 91 c9 1e 08 12 e6 be 45 4a 96 +91 e1 de c8 0b 2d d3 10 f9 cd 0f 0c fb 19 9b 55 +2b a8 fd e1 91 9f 0a 39 72 2a 76 72 1c f7 0a a2 +e9 d3 86 d0 98 1f e3 04 f7 65 6c 9a 8a f7 87 5f +25 5c 9b e8 da 30 7d 7b 30 9a ab 18 21 45 da fd +9c d2 86 1f bb 96 3c e1 da b1 89 4e fe 96 ff 99 +36 e9 0d f1 cb 32 29 51 4c e4 a5 d2 29 a5 19 1c +22 54 84 73 c0 de da 67 82 5b 6a da 69 f6 ec 72 +84 b4 c0 42 40 11 0c bd fa 1c da 87 5a d0 54 33 +37 59 3f 26 58 56 12 c4 ed 49 7d b8 a3 87 1b 41 +31 11 0c 3f 54 52 f0 43 1e 3c 5f 59 1e 96 1a 04 +01 21 c0 c7 f5 b7 97 a5 62 18 9b ed 61 08 90 f8 +d5 e9 ed ee 57 6c 94 a6 d7 3c 1b 70 40 11 6f f7 +f5 b5 74 21 06 68 1a cd 9d 17 39 a4 17 7f f1 71 +ec db ac 33 01 db 4f de 3c 81 80 4b e5 2b 2b 71 +4c 01 d6 ac 7f d2 64 20 ea be 44 57 58 a5 e9 fd +fc 3f 04 3b ab 85 d1 40 f5 ee 16 50 30 a4 e9 10 +f0 41 d7 96 68 0e 71 8d c9 df 0a 59 f7 43 2c 1f +46 d4 b9 80 d3 e7 f2 99 e9 78 33 ee e0 34 45 ba +b3 ed 2c 95 0d 49 75 4e 77 a2 7f b5 97 b4 80 9f +b5 4e 7f dc 59 ed 9a 91 a1 6d 5b f8 33 4b 51 a0 +18 c8 9e e1 4e 78 d2 5e d7 a7 51 8f 91 3c a6 cc +08 18 7f cd 8e 3b 6d ff eb 39 57 88 10 2b 18 86 +bd 3a 4d dc 56 93 38 dc 4e 0c 37 34 62 93 a8 db +82 ec 67 a4 39 97 c5 04 d4 74 00 5e a1 ba 79 6d +68 27 4e 37 41 d6 fe ed ff 3f 33 29 9c e9 3e a6 +33 32 4a 34 f2 9b 4a 3a dd f0 c3 04 86 e4 cb 8d +02 3d 84 59 69 3a ee 2c 5a 05 8d be 9e e4 25 8f +a4 81 79 7b af 9a cc 8e db c4 80 eb a3 c9 88 b4 +66 d5 3a 70 05 99 f1 5a 8e 45 3f 9c bc a1 b1 8e +bf 62 ec 5c 20 f7 fd d8 1c 93 76 00 03 97 cd bc +d9 87 47 43 22 bd 55 99 35 08 94 fc be b0 e1 94 +d2 d0 8e 3b 78 ff a8 ee 6d bb 37 59 9a ad ca 37 +00 a9 2c 0e b9 3a d0 62 47 d8 45 5b 4f 5e b6 4a +7c 0a f8 08 49 38 a0 58 c2 c3 2e e5 1a a6 b7 51 +fb d0 90 a2 1c f5 c6 77 2b 1d 0c 11 b9 b0 b9 df +be 1a 57 dc c1 74 a7 98 ef 2f 2a 2b 82 4f a2 18 +5a 30 75 e6 0a a3 0d e2 ca 1f 80 20 cb 4c 2c 1c +04 50 00 21 78 ae a1 b5 8b 57 2a 01 07 c1 4a e3 +ce a3 ca 29 d9 ae 0e 37 ac c2 b1 d0 de 13 0d 56 +aa 25 40 1f 5f 05 4a 73 1a a4 30 e1 43 d4 ae a6 +e5 2f 74 ce 11 6d ef 72 a3 de a2 01 7f 92 42 1b +78 b2 92 39 40 0a 28 64 d6 5c 7a 57 ab 32 36 dc +4d 56 c1 96 91 bf bb 64 31 b2 9a 06 0d f0 15 49 +d4 fe 2a e8 8e 40 38 bf 70 5e 87 3e 9d 42 da 16 +6c 5d f6 47 4b 8d 77 32 76 6a d5 ec 61 1c d2 eb +fb 7e a3 fc 90 63 88 f4 39 76 0c c1 22 f3 da 98 +16 cb a8 fa 19 03 f5 92 88 2d 66 17 72 2e ca 73 +31 9c 6f bc 5e 3c 57 4a f7 fb 1d b2 83 f0 8f 7c +6d 62 2c d2 b4 cd ae ec b2 4d 59 2f 96 5a 07 ad +c6 5c 1c c5 b4 42 70 90 dd 4e 7e db 05 b9 e2 53 +05 a0 0d 86 2f 3f 4a 3b e2 e2 94 14 3a ab 1c f5 +0f a1 53 28 37 df 4d 62 3c c7 1d 51 eb 3f 7d dc +02 23 6f 83 5a 8d 09 d6 94 b0 ad 21 2c fb 65 7a +0f 92 aa a1 a1 a1 aa d0 d9 c4 13 ea 54 1e fc 57 +f5 3a ea a5 52 d5 ed 7b dd 75 e1 06 5d f4 f9 3e +d0 55 92 5a 4a a2 b0 78 5c 34 44 67 5e c9 44 95 +a2 c0 3d b6 87 09 ca d8 26 c6 82 3a 20 5a e1 87 +5e dd c7 a5 89 ba 6a cb 92 61 69 6a 47 63 ae 51 +85 54 82 ae 6c f7 65 7a 86 c5 dc 07 00 50 ba f3 +8e 7d 92 0b b4 95 92 3e 9e 8c 6e 3e 3b 6c 42 c6 +9f a7 8c aa 20 62 6e 2f 26 65 39 7d 06 0e 1e 96 +19 6b ec 64 a5 ab c5 00 37 d0 5b 17 25 3b aa 3b +6b 45 fa b0 cd f1 a3 7e 03 22 60 9d 10 cb ce a8 +5f ef 80 73 62 c0 e2 e6 1d fa 60 e3 32 d4 27 5d +a6 a5 58 72 cd 96 76 c2 57 a9 60 32 e0 d3 23 72 +db 6c 5d 02 a4 fb 37 01 6d 92 38 d1 e1 49 6c e4 +61 97 79 b2 a1 52 6b cc 32 3c 9f 09 4c 80 08 6d +a7 c2 58 4f f3 31 33 72 15 45 28 7f 0d 85 0c 7c +9c a6 72 f1 5d 49 6b 5b dc 1f 81 a2 5c 3e 6c 4f +4f 88 d6 ca 19 f0 2b 0f 59 7c cc d8 e9 77 f5 05 +85 b7 24 4b 03 b5 2e 6d fb f4 89 90 d1 29 b8 f5 +4c f7 f4 e5 18 ae 82 b7 a7 8c 2c db b3 d7 21 53 +c1 70 3b b8 96 7a c1 af 93 f1 fa 41 28 c4 8e 04 +54 47 96 b4 65 83 ff d0 a6 eb 42 dd 1d 10 84 b7 +fa 89 6a 87 03 f4 f5 e2 dd 7b 6c 32 c2 cb 5d 9a +b5 4e 77 b4 d6 cc 74 dd d4 13 cc 39 4f 56 c7 61 +d6 25 6c dc 85 04 06 c2 8d 18 17 39 28 29 28 fe +62 03 8a 65 16 29 7d fb 34 9c 6e 5b 1a df f7 7c +a9 fd 34 8e a5 21 b7 6b 61 c5 c5 52 d1 e1 7e c7 +46 03 8e e7 c5 50 cf 30 0c 4e 49 aa 70 ac 49 74 +d6 34 8f 2d b7 33 97 75 e8 50 46 62 e2 eb e5 18 +9e ae ab 65 00 ec 37 c2 e6 b4 c5 bb 6d b3 b1 6d +5e 4d d3 de 74 0f 66 19 0e 69 61 79 62 f3 37 55 +5c 69 28 05 c9 90 05 61 c4 f1 11 b5 b1 1d d7 95 +12 0d 90 d4 62 7f 32 cb 44 ce 2d c8 fd aa 36 21 +b7 08 1c b5 bc 71 17 6b 01 da d6 03 86 93 07 6b +da 89 d8 a6 7b 2e c5 50 5b 49 d8 e5 eb d6 36 2a +58 5a 12 f9 e1 12 ab ce 2a 6b 98 41 b2 60 fa 4d +53 75 dd 56 74 40 23 ae e3 3c 9d 19 d3 5c 4c a1 +55 45 b9 71 f3 62 fc c2 b1 36 2b 07 28 fe ca 56 +d5 89 8f 23 79 66 e3 c8 9c ce 33 cd e8 25 6d b4 +6d 3d db 14 e4 1e 46 81 79 a3 ac 43 fe 0d c5 92 +1f 1c ea 5f 6b f7 6b 5b 25 39 03 e9 cf 61 ed f6 +60 19 e7 72 06 fb 11 7f 37 95 53 bc 72 2a 7a da +38 80 5d db b6 37 5b da ad 6a 4f 87 0a 9b e0 56 +fe b6 d5 0c a1 06 69 5b 8a c0 09 6c 6e e2 8d 16 +ab b1 c5 53 cc 7a 0d 98 2c d5 1b 8a 7a 91 cb 33 +fa f3 89 01 e3 43 9f 7f c4 4c b7 27 d5 2f d5 68 +d5 51 09 17 2d 0b 69 33 a6 df a3 ec 50 2f be 22 +13 a8 96 ad 7f 60 1d 07 2d 2b 1b b4 62 aa 81 c8 +31 23 b2 0a f3 ec 59 eb 0d c9 2c aa 06 03 a6 53 +77 3e aa ec bc 14 7e 57 ff f5 c5 f8 ac 36 ec ff +7b d8 7c de 3a 5a 0a c1 55 7e a3 02 98 ea fa fe +a3 45 0d c5 2f ef 19 48 07 e9 fc 2b 33 f6 b6 5d +e4 47 04 a4 a5 fe d4 2e 64 44 f0 81 a4 3c 9f 59 +77 c7 05 6c e7 2a 3a 4d d3 e7 45 b6 5d 49 8b 08 +25 2b de 46 f0 7c b0 b5 97 57 2b ba f6 58 72 e9 +09 46 c5 ae 47 01 1c 9c 08 39 b3 d1 24 2f 13 c4 +ee 62 48 7a 98 ea 7e 79 41 bb 60 c6 96 e9 d7 54 +ad ee c3 12 f7 dd 60 48 bc 53 17 90 6d 40 c1 fd +0d 71 65 31 22 ba fa 7b aa 51 3d 02 a3 35 28 cf +65 ef 49 ba b1 d8 3f 33 9b 00 a0 03 a9 24 c7 bf +6c 42 34 48 43 5e b8 ea 3f 9a e2 68 10 3b 4c 1d +b1 79 b5 0a b8 44 2b 8b a8 6b d4 cd f7 14 44 2f +32 99 43 bd fe 5f 59 42 2c c1 12 c6 f4 54 a1 84 +0e b8 7e 6d 46 05 6f 6f 42 80 c9 ce f5 99 9d c9 +13 39 ef f5 01 dc 06 9e 3d 52 8e fa 7b 43 64 70 +cf f9 eb 12 ff 05 84 28 83 2c a6 1f f3 c5 bf 03 +26 68 19 e2 ae 43 2b 33 c0 a2 06 c1 2d a9 a3 f4 +41 e6 dc 73 87 4c c6 9e 71 27 3c df 6a ce c9 7f +0b 6a 58 eb cf 86 31 6a 31 56 2a be 55 0e b2 ff +49 53 bf c2 8b d9 65 0d f2 61 50 67 e6 4c 27 3a +ea 7f 90 1c 82 85 60 1e 77 48 77 01 3a 67 12 68 +47 e4 43 83 56 17 e3 cb f4 48 11 23 9e af cd 54 +4b c9 8b 74 f6 ad 40 fa db 69 69 52 a8 99 93 d6 +a0 bc fa b2 d2 c1 a7 95 27 31 cf e5 cf 70 5e fd +17 59 72 ee ed df 58 7e 36 cd ab 54 78 44 d4 85 +bb 38 11 8b b6 03 fe 4e a1 1b 2f 39 c3 f3 a4 00 +77 55 36 d4 cc 27 94 f9 81 77 15 40 30 69 23 98 +7a 8e c0 c1 f1 d2 fd 6a 0c d6 7c 62 19 56 df 65 +85 ab da 7b 03 e4 2f e7 3a f7 88 47 29 ce 3f f1 +d2 bc 87 04 bc 3c 3f c2 ec c1 4f fa d8 41 eb bf +50 32 41 e8 b0 7b f8 c7 0e 16 4f 1b ab 83 48 40 +7e d6 66 9f 1d 37 c1 fc f6 0d c5 02 b9 8d f7 60 +7e a7 36 de c4 19 4a d2 88 77 4e 51 ae 9f 56 98 +37 90 ee be 28 45 b1 1f 7a 23 cf 62 1f 07 ce e1 +20 08 f7 3b 39 0a f2 76 57 cb 35 03 6f b4 b9 65 +b6 36 aa 1c 0d 9d 94 c2 f8 79 10 0a 69 50 7d 49 +b9 53 42 e7 a0 74 72 9b 6c 59 3a 09 d6 31 71 da +7a c8 b8 62 17 65 43 35 f8 a6 5a 98 db 0c 09 aa +73 c5 91 18 50 82 42 2c ed 21 b7 51 95 65 01 e7 +f8 a8 9e 05 43 ea b9 27 be bf bb 88 ee d6 06 ee +02 ae 73 af 17 62 8d c7 21 82 f8 0d d6 ca 8c 34 +5e 31 e2 68 b3 67 f9 3d 79 1a f4 d9 e9 80 b8 cf +35 a6 b0 80 82 b3 33 4c df 4a d6 ce f9 36 29 b6 +1e 43 74 ba 6b 7e f3 1d d4 89 9e b6 fb 50 74 81 +06 a2 3b 11 b4 18 e3 3c bc 03 70 e9 ef 1a fa e4 +2c 24 93 c9 b3 35 a1 ce 79 cd 15 71 47 65 67 8a +54 f3 52 b0 4a 9b d3 cf 72 68 ed 8c 3c a9 31 68 +31 1b 69 03 19 a3 5c 4e da d9 96 f3 95 8f f5 76 +c1 d6 8c f9 b6 15 59 bf 63 84 c0 07 6d 83 f2 10 +5f 39 b3 9c 07 38 39 90 5f 2c cc 09 d8 60 0d 6c +ee 36 bf 51 f5 a2 d4 72 b1 fe 68 75 6d c0 26 55 +cb 66 7c 19 90 77 56 ae 50 e7 b4 4e 93 89 b0 b2 +fb 22 ec 67 1b 81 54 fd 80 a3 e5 f8 eb 98 2b 43 +d3 d4 2e 38 44 f5 3d ee 0b 2a 8f dd ad 22 4f 4f +9b 37 10 84 5d af 02 54 75 b3 48 4c f3 92 7b 73 +14 bd 16 10 3c b2 99 f9 04 a9 1e 1e 7a 55 78 73 +80 2a 01 4e c3 9f 21 54 ec d7 27 ff 6e 8b d4 bb +59 ca fc e8 8c 62 71 4c 97 f0 1f a6 f0 a2 4d a4 +a5 5b af bc 29 7a d7 56 90 97 f7 1c d0 18 7c e8 +96 8e 57 fc 71 85 8d 90 94 9e c0 33 45 a1 a9 6f +53 6e 74 92 44 cc d3 36 26 22 41 45 40 f6 0e 21 +fd d4 61 d2 cd fe 60 6c 38 e4 2e 3c ba 69 b7 32 +45 81 a5 45 d6 9e a2 da 21 fc 31 80 e4 51 63 4f +cc 5d 3e ef 4d cd 0c 70 d7 31 02 69 f6 64 21 4b +62 4d 8d c9 dc 6e 7a c4 bb a0 be 90 1e 8f 5f 3a +92 36 c6 68 47 bb d9 b7 82 c7 80 71 d4 e1 56 d6 +74 d0 a0 8c 3c de 5e 13 64 c4 21 a1 1d e1 12 3b +d3 44 52 03 54 da e6 60 40 19 50 95 6c 74 a6 43 +74 a0 43 76 44 f2 d4 c7 92 eb 4b 36 5f 5c c7 7c +c0 07 31 77 71 6e 7b 79 b3 97 f5 dc c5 2f a2 b4 +4e 18 61 4d ca 05 3c 2b 6a 21 d9 66 ff 09 a0 78 +94 80 ba df 31 36 4f 85 1e c6 e2 57 8b 03 0a 66 +c6 b4 b3 cf f6 76 f2 97 77 d6 8c 09 c3 51 a7 74 +a0 4c c9 3c 33 ab 0c 86 46 14 2c c0 a4 89 7e cc +0a 7b 54 ef 0a e7 23 47 9d f5 a2 e5 88 c9 c4 09 +b5 a5 b8 bd 73 0b 43 d7 d1 8b 64 27 ad 78 b5 b0 +ef e3 87 b4 c0 8c 62 34 45 15 ef a9 9f 2f 51 3b +ba 0b 33 79 3d 93 54 ab 8c 68 88 56 af 68 fb 09 +99 a0 43 a6 0f 6c bb 36 b4 88 13 1b 7e b8 ff 88 +03 14 40 63 d6 a2 ca 37 bc 2c 94 92 83 ca 46 f2 +04 14 b8 f2 44 e4 ca 02 3d 97 78 ee c7 82 f2 64 +df 18 29 00 4a 42 a8 0c 0f 36 f0 f2 a0 e1 9c aa +a2 a3 17 10 33 25 92 23 99 f0 45 9e 7b 72 6b 77 +5e 73 c4 b1 b6 59 f0 3c 59 b6 e7 80 75 20 93 8d +f7 c7 ca 45 22 6a 20 24 4f 92 e4 ad a2 3d 21 aa +89 12 bc 10 cc 9e 84 b3 b9 14 d4 57 a8 80 c3 ff +bb 2c 04 95 39 89 b2 1f 8f 8d 0c c5 5a be 3d fb +48 e2 64 e4 9b 18 a9 c8 24 36 c2 a4 a1 fc 92 5a +30 42 f5 17 c3 da 4a 94 65 cb 3a 3d 3f 43 51 f5 +1f 92 bb d6 60 6b f9 60 02 e8 5c 53 43 29 3a f1 +19 e3 e0 ef 6b 9d 9f ec 11 82 46 be 97 8b 71 2f +ff da 64 15 2d 88 78 35 37 d8 c4 ce 7f 5f 10 4f +94 b7 9c 58 04 2d 9d 8c a5 f3 a4 42 bf d8 f6 40 +19 21 5a 4b 2c 3e 16 31 5a 26 a6 c5 f7 95 2a 62 +a6 1a 3e 59 4e af ef 79 f3 81 91 be 7c d1 aa af +fa a3 7c c7 df 1f ab e0 db 04 0c 61 ed af 85 45 +ff 29 bf 20 b6 be ef 73 5e d8 67 5d 12 b1 a2 21 +80 0d 09 93 e9 84 b6 01 f2 54 b7 b9 78 27 a4 10 +a6 7a e1 d9 cc de 15 63 0b 73 bd e4 48 42 c8 2c +7e cb 98 b7 42 50 51 79 d3 09 d7 0b a6 4f 72 89 +a6 42 e2 10 96 01 42 60 6d 92 3b c6 8b c8 c0 b7 +b8 c0 6f 8b c9 10 6c 25 84 1b 51 81 e3 68 fa 4e +8b 3d f0 d2 c4 b1 47 a6 54 38 66 f7 02 c8 4f 71 +19 7a ca dd f3 09 4c 71 02 73 74 2e 67 4b 7a 28 +27 33 0a 64 2c 06 63 ae e9 c2 1d 4c fc 7c 01 ec +6f 97 cc f4 a2 62 11 7d bf f2 f7 2d d7 3c 64 07 +e6 96 2e d9 79 59 77 48 6c 05 a2 2e 69 6b d0 ae +e8 6b 95 ca e3 d5 dd 17 68 f6 8f cb 74 63 6b 0b +ce 04 41 5c 52 d7 9c 81 8c c8 13 ec 67 0c 93 2d +01 20 83 00 13 29 60 15 77 60 38 1b ac 2f d8 3e +c1 d6 59 fc c3 fb 61 56 6b 05 20 62 b3 0c 93 6c +4b 7f b0 c4 33 c5 d0 cb 33 ba b9 d1 7d 24 de 65 +01 12 42 07 a7 06 f7 1f e5 ce a4 d8 6d d7 73 0b +29 12 28 af b0 8c e3 42 22 da f4 75 9d d4 88 b2 +f0 f3 56 79 23 a8 41 3c b5 fd 46 4e 5a ae d4 1e +5a a7 dd 72 a4 49 11 d1 18 c1 ab af 05 d0 60 13 +50 3c d2 fe f8 ba 17 cf b8 ea c1 1d 9f 33 5b 9f +16 db a4 6a a4 25 05 96 fc 39 f0 f2 47 6d eb 67 +95 74 7b b0 2a 92 1d ea 4c 0b 60 f1 38 10 da d5 +1c bb c4 f0 c1 17 0e 3f 55 a6 54 aa 0e fd 82 29 +c2 07 8f b0 db 9f 25 17 5d 00 b5 79 cb cb 6f 2b +56 f1 d9 ab 69 d2 0a 2f 6f 1e 2f 30 45 90 b0 f5 +d1 bf 7b 8f bb 99 2b 2a 22 ca a6 12 37 8b c6 e8 +cc 4a f9 6d 40 18 40 cd 9a 3b 61 24 f1 03 42 e6 +3a e3 42 e2 44 34 35 67 66 93 ef 17 80 8f 93 a8 +5a a5 cb f4 da ef a2 63 da 89 55 d6 25 09 eb b0 +7a 2d 4d 82 14 f5 21 76 a3 d1 85 20 a5 18 4c 23 +d0 58 8e 44 8d 97 8e de ee 02 01 31 e6 c3 3b d9 +f0 f2 e0 a7 62 11 7c da b5 04 f8 23 31 73 52 85 +8c 48 2c 21 fd 6f ab f6 5f fd ca fa 78 e9 ef 80 +e0 17 36 b2 cd 52 f0 61 fc 39 26 03 20 89 50 b9 +84 7f 39 44 e9 6e e0 81 e1 eb 75 fc 9a c1 f7 38 +62 b8 f8 64 15 0c 9a d5 67 af 3c 94 f8 80 d1 b0 +35 22 28 0a b1 b4 8f 89 15 e4 29 42 68 23 f8 a7 +dd 07 45 f6 ec ab bd 79 15 de cf 95 44 55 ac be +73 ad a7 d0 43 c4 03 05 bf a7 d5 68 7b 14 29 47 +17 e0 a2 41 f8 91 ee 08 d6 b5 c7 18 68 2f b6 75 +16 9c be 70 9b be 7e dd 58 60 92 65 bc 79 ff 25 +60 72 a7 2f 6d 20 6c 41 41 f1 44 f0 17 e5 d6 5a +00 42 1b fc 14 53 2b 69 30 e7 dc b3 16 4a 7f b2 +31 55 0e 37 53 32 b8 96 5a 8f 46 20 10 f6 ed 3d +44 29 7a 70 73 4a cb d7 ab 39 43 84 73 91 92 bc +76 55 b8 9b 72 de bd 46 2d c4 57 02 97 a2 84 ca +2a e0 14 7d d6 d8 7a 6a c1 80 39 bc 18 2a 16 6c +d8 c0 1a ce 5e 3f d4 73 52 ee 0f 26 d3 27 3f d8 +b2 af bd 25 bb fb ca fa 8f cc 4a 34 f8 db b4 2e +f9 75 d6 ce 5d 59 44 e4 a0 a5 aa bb 94 16 19 3a +e5 80 b9 c9 32 8d 15 f5 8b 3a 70 10 32 5a da 19 +9c 12 9a d1 c8 1e f0 8e 8b 4a 4a 29 7e 31 32 09 +4c 7b fe 04 f2 a1 32 d0 43 0b 63 19 00 7b f1 53 +84 e9 bd 4a e7 5b ae b4 1f 12 30 63 9f 00 74 b9 +63 04 2b b2 a3 46 97 87 8a 68 4b f0 90 03 a9 15 +d0 82 cc 72 05 3d ad 43 7d 76 91 94 80 a9 c2 fb +91 30 25 4b e5 05 7a f9 c4 c6 b8 31 01 66 81 cf +e6 e4 ea b4 0a 15 19 39 f3 02 a3 0b ca 13 d6 46 +53 4b ce d3 f9 d7 ad 58 25 1a fa db 30 f2 d3 6e +54 a7 66 be 30 4e 2f 30 53 0b e4 24 aa b8 4c 1e +9c 13 e0 0a 95 f2 5d 2b 49 03 8f 82 71 8f f1 ec +37 73 a5 8e 3f eb f8 2c 7b 17 b9 9a 8b c2 15 19 +71 35 59 ce 6e 6c 4b c8 3c d9 b3 1e b7 47 e5 11 +a7 fd 4f 25 22 4e fe a2 cb 09 0c e1 d7 39 36 0d +85 f3 c0 37 28 b7 6d 0d e9 66 95 bb 17 82 4e 99 +8d c3 e0 a1 bc ee d7 38 89 92 36 db 73 79 f8 34 +70 ea 7a b1 38 39 f1 00 7b ef c7 2e f8 93 37 0b +36 43 6a bb 5f 04 8d c5 56 28 9c d8 58 21 af ab +fa f1 9f ae d2 de 7d 98 65 2b 31 a6 67 81 98 50 +48 c6 3d 24 f6 d3 20 b4 53 38 1b b0 3c 55 b6 c8 +4d 65 8a 5c 45 ae 28 58 34 fd 95 4c 90 d2 6c 30 +d7 81 fa ed c3 cc 85 be 28 0c aa 6c c2 94 91 5d +d2 30 c7 42 dd 0f d4 bf 8e f6 b9 ee ba 3b de ee +5b dc 75 b4 81 ef 44 90 07 30 5a bd c5 20 d8 39 +82 e2 4e 0b 17 67 9c a7 c3 7f 09 8b 26 25 44 e9 +7c 02 85 11 64 cd 5a 6c 58 26 32 c1 a8 70 07 e8 +d2 ee 03 6b 5f 0b 59 5c 46 c4 42 c3 9a ba 4b 76 +16 bf 80 e5 d1 fa 48 43 2a 87 9b 68 2f a3 9e 1d +48 ac 85 8a 88 b4 c8 4a 7e ce 91 45 34 60 c2 bf +6f 3d d9 8a 34 06 41 7c 0c c6 de 59 fc 45 26 3e +2a 53 8f 70 ad 65 15 62 b7 ea 01 fa 5a 20 67 5b +df 45 49 c8 fa 8b 00 a5 42 b9 bc fb 24 59 bf 63 +ac d6 44 9e 6e 5a 54 13 0e e0 23 2f cd c9 56 6d +81 e7 61 f9 41 1b 9a f8 81 4f 08 92 6d e5 97 af +dc a8 44 85 6b e2 06 44 e9 62 02 0e ed 37 ac 50 +69 e2 01 fe 69 1d 52 2a 70 ba 24 03 61 ae 68 1b +f7 78 93 a9 71 68 53 3f 3f 50 fb d9 fe e5 8b 6a +2d a4 46 0f d3 92 f6 a7 66 a0 3a be e9 34 9b cb +57 6e 2e c1 bb 5f 20 c5 c2 7a 24 84 bb 07 3e 91 +70 46 06 12 8a c7 a6 6d b7 df 00 22 f8 43 b4 fd +d3 19 1d 02 80 53 56 91 d2 e1 05 00 d6 d7 ee 1b +f4 dc 5d d8 e6 ca cb e4 8a 46 c9 eb 80 18 6b 48 +0a e9 98 29 9d dc 12 03 96 fb 80 58 5d f1 30 dc +0b 58 72 06 ff c3 c9 3f 1a 3e 49 c4 c8 c0 33 cc +a5 61 75 65 7a 9d d5 ea 18 10 96 78 72 bc 8c e9 +7d 63 13 87 a0 f4 3d e5 1e 57 87 ac 25 ac 4b 56 +4d a3 d3 3b 8c ad b1 a5 89 b4 c9 2a 4b 79 eb 6e +04 ce 5f 6b da 91 90 7e d4 7f 4c ae e0 99 fb e1 +db 15 0f da e0 77 29 c9 e7 88 d7 fb 7f eb 71 7b +98 19 14 ea e0 7a 7b 55 ea 7f e5 a6 dc 98 91 4c +2d 48 20 ee 5e a3 12 91 09 61 fd 68 0a 31 cb c5 +5c e9 f8 ce fd 79 c9 ed 2e 02 b9 7d f4 3b 3b 80 +8e b8 e5 4f 2b e5 6a fe 02 c5 f4 92 83 01 0f 1e +55 cb b4 e5 35 b3 7e 23 ce 84 3e bc 29 9b 72 c5 +1a 3b 7c 9a a8 a8 d7 ee e7 84 ab 03 02 bc 81 d8 +6a 5a bf dc 6f f3 00 11 83 8c b4 4f 66 7c 38 fd +b2 86 c1 47 50 c7 a3 c7 24 b2 14 88 13 59 0c b2 +9b ae 19 61 11 b8 9f e1 3c f8 cf 00 fa d7 1c a1 +70 29 2f 9e 8f 62 d5 d9 b1 a8 1d 29 58 90 62 d0 +f5 0c 6c 8b 04 e3 aa a3 9e 8b 76 24 69 da 08 72 +e8 c0 50 18 fd 41 32 3b af ce d8 0d 47 6d 77 d4 +3f b8 72 86 3c dc 94 f1 64 a3 66 ec c5 77 63 90 +3c 59 d0 ca 16 0d af 0b ff 9c e5 f4 b6 1f 82 c0 +46 32 cc 1e 7f 1a 7d 7d 36 8f d1 10 98 f3 c0 6a +ba 7e 18 a8 8a 55 43 80 7c 4b 4f 2c 3a 22 da 7e +7f 55 73 78 9a 78 e7 86 66 9e 25 3b 38 93 83 71 +d8 19 2f b4 46 af a7 ae c2 b8 a4 5e e3 a9 00 f6 +be c7 a5 55 47 e7 65 3f 01 02 27 46 9d f4 a2 3d +76 a7 aa bd 8c 9e 00 10 6a e5 8b d8 23 7d b2 72 +00 a1 42 df e5 40 ac d3 c4 bb c9 cc 79 56 ea 16 +43 83 4a 98 98 f0 2f a1 6b b6 24 f9 6b 36 76 d9 +2b 41 c8 0f 3a 2c e7 50 dc 57 73 03 2a 33 a8 9b +c1 05 8b 9e f2 95 cf 3f 1d c8 4c ca 78 9e 28 63 +0d 2d c7 df 2a 51 9c 2c 45 3d 0c 4a e1 99 97 b4 +57 f8 6c 50 3f 09 5b e5 c4 3e 24 32 79 7e 01 1a +bd 36 38 5e 89 9a 7a 00 a6 bb 38 17 41 d8 1f 0f +3a f5 e2 9b 00 ef ad e6 33 1b 89 eb 08 e4 8f e9 +00 2e 61 b8 95 1c 4c a3 e4 ba c1 e8 91 f2 c9 a9 +09 6d ab 4d 15 3f 08 fe 01 0c d9 6a b0 d7 3c 21 +4c ec ec ad f7 5e 65 2a ba 13 8c 34 08 39 cb fb +e0 95 fd 55 d8 1c ef 24 9b 47 a6 8f 1d fa ef 3a +9b 91 70 4f 48 c0 5b 65 2f 3f f6 9a 45 0a e0 19 +92 ce a2 ea 5e 38 3d 42 78 53 6e cc a8 30 cc db +7e ab 2d 57 f2 05 cb df 04 b7 b6 7f d4 4b 81 f5 +c7 64 07 08 d3 de a0 65 cf 71 dd f3 d5 03 11 49 +99 a9 3d 23 48 52 a8 c9 bd 7b 0f 04 a4 d2 f3 36 +0e 1a 44 17 d8 78 19 c2 e5 ce fa cc c5 0d ab c4 +c9 b0 41 fd c8 f8 b2 96 ca f0 48 e2 a8 2d a8 2b +17 40 fc bf 7e 4a 7a 46 ba bc f0 7f b6 08 95 ee +4f 9e fa 2a 80 04 9a 00 94 1b bd a7 79 66 06 12 +ce b2 58 35 1f 5e c2 96 06 74 45 8a 74 8f 48 f9 +00 bd 0b ba d1 3b 96 22 18 74 77 64 a1 5d 6d 0b +96 f1 6c c3 73 b1 f2 c3 bd 35 b6 0c 56 ff 7f 9c +ac b4 b6 ab 11 d7 3b bf e3 33 95 97 5d 33 cd 51 +ca 55 f7 98 ba 28 03 f5 14 72 15 d7 34 6e ad 30 +ec 3a 20 c8 d2 2f b5 47 35 59 75 46 04 85 1f 18 +b9 4a 02 67 29 dc 07 99 ea 76 2a 07 fe 08 2f 80 +14 88 5d 6e 97 23 41 3a 5a fa fe dc af 38 a1 00 +4a 52 60 42 d3 de d4 e0 82 63 7f 09 f8 e5 68 75 +f4 18 3c 17 97 29 2b 4e 40 cb 8d 54 ec 88 b9 54 +bb a8 9d 97 1f 8d 5e 73 ea 58 46 e8 34 7e 14 55 +23 dd ef cd 01 91 4f 27 03 34 33 21 0d 75 e7 bc +39 9b 03 49 fc ec 9c 2a 08 8d 61 16 ee ef 13 65 +73 1b 24 97 e5 c0 ec 10 f3 95 23 5c 8b dc 67 b0 +b6 9e ef fb 75 83 3c 0a d8 38 c4 ed 28 0f c3 9b +99 01 9b 59 15 31 d9 2a 8c ce d1 92 21 68 10 61 +82 26 41 1d f8 7a 08 4f f7 78 8f 08 4f a1 5b 87 +27 a5 01 3c d0 8b 80 2a fb 89 f6 b6 a9 47 12 a6 +e1 f3 b9 d9 21 e9 0c 0f 64 a8 b6 69 f9 d9 f0 b3 +44 c8 41 40 a6 3e 10 78 0e ae 36 37 0a 6a 7c 03 +a5 ed 1c 9d 18 18 20 34 44 e0 97 17 fb 08 11 53 +11 3a 9f 29 9d 4f 6e a6 8e e2 4b 79 73 e1 ca 6c +54 22 19 fa 17 7a 97 a8 bc 53 fc f6 99 24 88 8a +40 13 d3 d7 26 59 78 c0 c5 bb a3 e2 ca 90 b4 47 +64 29 ac f5 95 19 a9 de 29 05 43 aa 58 65 b7 bc +02 d0 aa ec dd c0 ed b8 de d8 b3 2c 08 e1 f5 c8 +a8 46 b4 66 86 65 e6 2c 26 8f 81 e4 f3 e2 45 49 +77 7a 41 40 a4 92 8b 95 1b 09 90 13 2d 6f dd e9 +85 e6 30 d9 ce 03 99 6d f9 c3 d5 3f be d8 0d ce +45 5f d3 2c 4d 2d 32 37 28 7a 3a 81 ad 32 d2 de +fa 6b aa 4b f1 a9 5e 9c 94 a8 8b 42 23 74 7e e9 +27 48 7c 85 ee d4 7d 2f 8d dc a6 6e 14 b2 85 4f +22 03 50 cc cc be 0e 5d 08 1d f1 5e 89 12 e9 2c +e9 76 dc 4e 59 57 8b f3 1c ff 8f 1e 10 f2 c1 bb +df e9 3d f5 4b d9 67 bd 5b e5 75 f2 4e ac 23 50 +63 c2 12 38 81 ce 44 7a 5b 41 60 3f 64 46 32 90 +90 71 9b 51 be ac 23 77 85 a1 25 c2 7f 66 3c 16 +15 7b 27 83 3f 2e 33 6e 70 e2 59 97 cc c0 0d e4 +ba 3f 3a 3b bf 10 da 83 ab 64 4b 63 c9 e7 21 02 +c9 7c 41 bc a3 1a 91 5b 9a 7f c4 67 32 1e 63 cf +50 0e c0 25 d4 d4 7f f7 9f 7c be 7e 05 44 5e 18 +1e 9a f9 4d f7 0a ea 25 64 3c d0 4c bc 53 73 af +c6 22 27 17 fc 2b 24 79 b3 38 5b c8 a3 6f c8 be +b5 17 73 f6 b0 df 99 49 ef ce 87 d4 28 cc d1 2c +b7 d2 06 d3 aa 1e fc 39 50 3f fa 7f e3 59 b0 86 +f5 4d a6 0d 3c 91 ed 95 66 3d 47 4f f4 53 e5 5a +7e 9b e0 4f 5c 7b 82 97 e2 83 bd 44 7c 00 03 ae +04 3e 80 22 08 2b 2c 8e e6 5c d7 0b 31 47 fc 44 +10 98 6c cf 11 5d 1a 44 de a5 43 5c 68 2e ba c4 +d0 a3 28 b2 b8 17 df d6 a0 e9 b0 bf fd af cf 0e +b5 10 41 36 5a d3 72 98 32 83 7b c6 37 43 7a d0 +8c 43 24 e3 e3 93 8f b0 51 6f f1 06 57 ac 54 0f +61 6b 88 3f 0f 6d a3 aa a0 46 fe 77 ef 19 02 d0 +7a a0 b6 a0 2f a6 c3 6c cc d7 42 24 d2 30 aa d3 +3e 97 7d ae 72 69 55 63 96 24 ec a0 91 81 0d 54 +dc 5f ff c6 3d 90 b7 39 ee e6 85 65 bb 5e 81 b8 +6c 39 e6 4a 35 6e 43 19 0f f3 bb fb fd 28 27 b5 +fd 15 62 1a f5 09 a0 ef 9a d5 72 f1 a2 9f 05 f0 +e3 75 ed da ec ee 81 d1 c2 98 ad a4 d7 b2 a5 0c +9a 1c c5 b1 0f 27 ef 94 23 0a b5 be 70 91 b6 93 +18 45 f9 ec 8f 58 ca 13 3c ef 54 3d d0 29 ec ba +fa 80 20 56 bc f5 4f e4 03 c9 ac 17 0b 56 55 37 +31 11 88 24 71 9f 18 c1 ab 6c 71 ea db da 74 54 +29 e0 c8 ee eb 7f c4 5a 80 9a 79 ed 51 e8 44 ef +34 e7 df e0 b7 4a db 58 94 91 d4 80 22 d3 54 36 +ce 94 02 d4 3c 75 0b 4d c8 e9 13 fa d5 64 0a df +18 3d b2 df 04 ea fa 69 05 8d 8f 42 a3 7b 05 a7 +58 50 cd 96 93 9e c5 67 9e 0f e0 9a dc 25 f4 9e +7e 5b ec c6 27 c0 1e 6b 69 e6 77 f0 e2 9e c0 cb +89 7b 65 45 7d e6 59 94 4a fc b3 c1 0a b7 6e 09 +58 fa 4f 0c 84 5e 36 ee e5 ff d7 7e 02 64 be 77 +7e 35 22 41 6f 30 ce 42 43 a8 41 90 8c 98 7a 36 +36 7b 2f c2 bb cc 5e 03 62 64 fb 70 28 f0 e3 a3 +c9 80 61 a0 06 90 31 ea b6 2b 03 26 2a 45 34 5d +ff 26 21 26 c7 d4 50 5a 6a a3 d9 03 77 81 96 14 +2d f8 30 15 b2 51 3f 94 31 9d ce 24 95 d0 ab 5d +59 3f 5d 0e ed 02 f4 48 f3 37 ba 8a 7f 9b fa 72 +82 5d 50 bc d7 45 eb ff 54 09 d5 d9 20 be 96 8b +8d e2 7f 72 fd 01 8b c1 ac 07 fd af 80 f6 62 73 +91 eb 9f 32 20 43 53 bf a7 19 48 a4 6c d9 65 ee +8d 92 c7 bf f7 a6 b1 95 6e 13 9b 11 8b f3 8e 66 +c7 0c 3d a2 45 be 68 f1 56 cf 5c 8d cc b7 5a b3 +87 ab af e6 c7 c3 ba 59 08 d8 95 15 a0 2f 58 8a +c7 70 41 8a 4b 40 7e f2 81 48 8d 95 38 8a 26 9f +2e 7b 30 8d e8 22 ec a2 4a 2a 10 13 86 28 b4 51 +63 8b eb 41 2d 07 a3 c7 ab d9 c3 9b 2d f4 a1 04 +95 4b 33 af f2 78 82 b3 dd 34 e3 a9 b0 7e e7 b5 +38 11 41 27 ec 84 c3 8b ce 03 3a c1 92 70 05 d6 +87 50 f3 0b f3 f0 65 67 f7 eb 1b e3 7d ec 8b 49 +f3 bf a7 e8 96 65 ae 0c 97 6f 02 3e 83 4a f9 a2 +f6 57 d3 7b 98 60 cb 68 4b 8e 1a 66 08 77 61 ed +c6 87 8b 8b ba 5e 9f 06 3d 7e 5d 27 88 9d eb 2d +4d 43 12 98 5f 5f a7 26 8a b8 68 9c ac 6b 15 0f +dc b9 8b d1 03 81 a2 19 8e 95 7f 39 f4 7f 8d 97 +ec a4 76 2e 59 09 d3 a8 bf 58 f7 ad 58 66 fc ad +6a 84 0a e2 03 44 53 94 c9 ce 04 f0 41 c8 ac a9 +ea 78 92 ee c5 c0 b7 1c 02 84 76 55 6f a3 79 42 +02 0b 3e e2 96 12 62 78 18 f6 54 fb 67 de 7b c3 +ae 63 b9 da 8d 87 a1 0b 9c 0a bb 93 8e 98 04 1e +d0 23 1a 92 52 d7 f2 d0 95 db 35 46 8f 8f 46 5e +5f e0 de fb 62 4f 6b 07 51 7b 9a 81 dc 24 80 12 +5c e6 18 a2 5d fc 8d d6 ac 3a e2 e7 c3 0b 6b ee +99 ae 87 ee 02 76 18 fb 44 0a 73 9b 79 f7 fc 94 +ba 10 16 da 98 99 f2 62 fa 67 5a 3c 93 b7 9b 5f +24 2c 94 5f 74 67 90 c5 c0 fd 0a d2 18 be 4c 25 +cf 5f e4 90 8c d6 34 24 43 11 ee 8a 3b f5 14 7b +6f 49 cc a6 c3 2b 77 fd 9e 92 05 6b 73 bf 3a 8b +84 cc 9c 4f fe 80 7f bd 49 b4 36 6e c3 10 66 40 +5e 34 21 e1 7a 37 95 78 6b fa e6 aa 3e 80 1e 83 +13 fe 35 26 5f b3 8f 41 07 14 88 08 44 58 c3 08 +8d 9d 80 a7 92 77 cc c4 9a 8f 72 a6 ae e5 0c c1 +d1 f6 31 64 a2 b0 69 69 a5 44 98 66 18 f8 bf 1e +5d 56 ad 03 07 bf 1c a5 48 05 43 dc e2 05 69 15 +6a e0 fa ad ca bd b7 46 5b 4d dc 95 b7 00 a6 ce +ca ce 6e bf e8 0d 91 00 cd 20 25 a5 19 fb bf d6 +8f 48 9c 12 df 32 40 b1 14 b1 aa 1d 3a eb 13 b7 +e3 39 a1 4e 67 3a ac 31 8a 94 72 06 bd 26 20 01 +b3 9c 8e 78 b1 05 a7 c5 c0 ac 6c 31 fc 40 c7 2c +cd dc 96 73 42 5f 7f 40 76 4e 42 cc 79 96 da 30 +b6 cb 5d 75 c8 7d ad 72 a6 c9 f2 37 e0 f8 3c 82 +bb cc 31 d0 93 d9 38 50 61 ab df 07 65 a5 47 57 +84 70 40 37 4e a4 0e 96 e4 bf fd 4d 30 26 89 99 +8d 75 58 ed 96 53 99 b9 b7 77 08 77 9f 24 af 37 +5f cc 58 32 b4 c3 ef ad c4 81 50 e1 59 71 d8 ba +d4 92 79 e6 19 01 28 e9 d0 74 cc 5c 5c 3f 6b 18 +95 44 7b e8 fe a9 04 93 8d ae 7b 00 eb e6 d6 e2 +3e b0 fe ae 5b 70 25 e5 e4 99 d6 07 9c e1 97 ba +d6 a1 71 1b 01 a1 a1 9e cb 07 60 d2 8e c8 b2 39 +ed a6 84 ad f6 b3 98 b0 c2 27 53 0e e8 49 17 bb +1a 7d 8f ee 42 ab 52 e1 8d fa 04 13 54 9b fb e9 +4e 5b d7 30 41 0b 7f c7 d8 c8 a4 01 11 a6 a1 87 +a9 a3 b7 60 a9 4d bd 34 25 ed d3 00 f4 84 af 77 +30 88 d1 7e d5 6b d6 7d ea f4 96 65 35 77 68 64 +38 3a ec ca d8 55 b6 aa 20 55 8c 8e 72 a8 41 9a +97 33 7e c5 26 b3 6e 6d ff 5d 16 7d f2 5b a1 42 +36 fa e5 d2 ab 2e 2c 66 37 70 ad db 98 42 01 ce +1d b7 17 77 9c 67 75 bb 95 68 44 a6 b1 4e 8a 98 +da 0c db 66 d1 c2 dd 84 a4 52 7a ac bd 65 c2 b3 +b9 35 32 ed 79 24 08 e2 66 3a 9a da 08 70 33 d5 +db 31 be d7 5b c7 27 95 fe 4d 05 06 90 41 d9 c5 +f0 42 61 03 8e 9e 32 72 55 9e 9d 0b 5f 23 9e d5 +c3 20 1c 77 5e e3 37 00 66 fc 9c c9 2c 53 10 55 +05 5d 06 a7 81 af 81 e8 85 5e 2f b8 0a 90 24 d7 +44 5e a6 44 36 29 77 47 6b e5 56 01 95 ab 42 5d +8f d7 57 23 c4 8c 7a 63 93 41 06 34 04 98 49 c7 +28 2e 67 eb 7a 9a 16 3d c1 a2 75 cc 50 66 d7 37 +6c 2c 28 e8 9b 26 93 72 c0 22 0e cc 3f 72 24 e4 +a7 c1 02 eb ce b9 eb 5e 4c fb 48 c5 da 0f 23 f5 +fc 81 74 b0 2d 9f fd ab 3b 7c 9a f8 3e da a8 3a +4d 1f 9d 04 3c 92 a0 a4 63 2c 33 5f 3e 2e 15 38 +98 ec a0 bc 5c ca 60 e4 51 9a 58 62 e2 cf d7 d9 +61 0a fc 74 d4 ce df 36 e9 c8 ce 48 2c 58 79 08 +ec 2b 92 39 5e d2 9a 41 bb 32 da 3c a2 af 0e b0 +db 40 3a 06 33 76 b9 d2 3b 2e 5b 73 16 cf 76 da +f7 3e 85 bf fe af 90 97 37 c9 06 ba bb 07 de 6d +cc e4 7e c6 2b 15 6e 12 d0 20 41 82 e1 0b d1 bd +d9 e7 f6 bd dc 6f 64 90 f6 35 55 c5 89 6f ee 37 +ff fd 6e 6b 42 87 81 70 73 39 d8 b7 0e dc 96 7a +75 4b 45 f7 08 80 78 1c 65 c5 42 b7 82 ac 64 74 +d9 5f dc 06 57 ef c5 94 0d 49 13 9e b0 b2 f8 97 +ea d3 96 63 33 27 c2 55 fe 08 31 e4 ce 8b 44 b7 +84 fa 4b 49 ab 77 2b 6f d3 30 7b f3 da c8 e0 e1 +e3 1f d2 96 6b a1 fc 72 d1 f7 43 c5 28 15 eb 06 +90 5d 92 89 0a 2d c5 78 e9 c2 7a 4f 84 d8 bf 78 +66 5f bb 27 af af 50 f1 bd 92 04 68 4c 8d f4 29 +d8 76 ca 85 14 69 01 20 36 99 13 f3 f9 6a 3f 86 +b7 ce ce 91 86 72 02 1b 79 af 7b 1a a7 50 52 fe +fe c6 1c 51 67 97 9a 29 97 6a 05 53 be fd d9 d8 +3e d2 90 95 87 32 c0 73 0a f2 c5 df 33 08 a0 7a +1b a7 27 a4 4e b8 65 f3 2e 74 06 4f 90 ec af 9e +ec 13 47 22 15 71 03 35 8f 17 47 04 de 5d 20 64 +17 7a 0f 55 fd 55 3d 77 aa 37 9b c8 0e 55 a3 9c +95 d0 e6 b5 6f dd 41 6f 49 20 e2 87 67 13 59 a1 +ad 10 fd ea a3 0d f9 c0 99 12 28 3c 37 4a 74 f2 +b1 31 f2 1a e4 30 d4 5e 0d a3 78 3a 31 95 f5 97 +aa 69 5c 2c d5 66 8c e5 a7 23 96 68 3d b1 d9 65 +20 24 21 0b 5f 35 ef 55 b8 dc 3b c2 40 a3 c9 d1 +dd 49 cc 92 ea ea 15 b2 5a 42 43 a6 99 15 9e 5f +ca 3a 05 e5 cd 3c db 9a f3 1a a8 c6 b9 0f 80 aa +87 f9 6c 60 bc b9 fe 76 95 35 19 86 d9 ab f9 14 +b9 c5 d9 5e 00 66 ac fb 11 d9 e6 c0 be 67 fe 72 +82 24 98 b9 06 59 ec f3 0e 64 8f 6d 8b c2 89 5e +7f 10 a8 16 96 dc 3f 3e f5 4b 8e 23 53 95 cd 92 +d1 65 76 55 33 9d 47 70 43 cd 45 fe aa 39 dd 05 +d3 29 f0 6e ea d6 e9 17 18 a3 b3 8d 21 65 e0 9d +c4 b7 5f ab ac 7e 7c 03 d2 fd 5d 32 2a 92 4e 4c +29 08 a9 e1 96 70 2f ae 91 51 74 ac 2f 22 9c 07 +cc b5 19 f8 58 93 6d 97 06 1d 3c 5d 16 58 27 c1 +91 ad a8 1c 03 ae 7e 54 f5 59 54 ae 03 2d e8 f0 +42 0a 4f 92 e1 50 96 49 80 3d a8 f6 f1 61 c9 1c +73 2a 99 f7 35 f1 8a 61 43 a5 1c 9b 28 53 6e a5 +89 fa 10 ee 36 c3 fa 07 47 88 03 c5 a8 12 a4 7e +cc f9 52 40 54 d2 48 0d 86 85 19 38 59 b4 d9 5c +71 bb 47 59 f3 63 b8 c9 cb f9 1a 99 0a 18 33 fd +28 a7 85 88 78 72 5e 17 c4 ef 09 19 7c f2 23 a1 +eb cf fb 3c b3 82 a5 d8 78 f4 f0 5b f7 9b 88 32 +4d cf d3 cb cd 0c 0e 47 e1 38 95 d1 2c 7f 80 61 +ad f7 7a b6 3e 47 75 99 fe 12 58 b8 30 57 ec 78 +33 54 1b 5c 4a 23 29 b2 83 a4 6e 40 27 f3 45 b4 +79 bd 14 82 26 94 47 c5 ca b8 c4 a7 2a fd 20 31 +a6 56 f0 fe a3 99 e7 9e ea ff c9 05 6f 3a 95 fe +7f f3 68 59 91 17 37 35 8c d6 9d b7 f5 f1 0b 78 +c6 70 36 6c 7f 7f 14 98 ef 4d 97 7f 16 db 3c 0d +af ab 23 f8 27 68 6a 2a 1d a5 8d 41 22 a7 14 d3 +55 64 fb 3f 05 25 97 5d 12 95 12 08 36 67 ed 0d +f5 46 ab ff 9e 06 52 c0 3f 72 d6 72 38 83 fa 60 +15 8f bc 34 90 3c 78 1d 7c fb 9c 27 f1 30 6d 65 +2d 5c 6c 82 63 c0 17 e1 d7 f0 77 9c 1c f2 17 5d +86 20 32 ab 6f c7 11 1a 31 2b 6b ea 74 3c 2b b2 +9c 07 e3 d2 79 db 7f 6f 95 b8 dc 40 9a 87 fb 50 +13 b4 65 36 e2 3c c7 db 0e 14 fe d8 52 14 72 a8 +56 a7 d7 1a 36 25 26 d0 e9 28 ed 81 e0 0c 1d 6b +f2 fb d4 9e 21 fa 35 fa 64 52 3d 16 12 fa fe b5 +b7 44 19 b7 1b 60 a9 c9 6f 1e 05 39 a9 8c 71 dc +fe 0a 24 c5 d6 ea cb 7e 79 2c b9 7a 1c 52 ef 97 +06 80 29 0a 4d a5 2a ee 05 e9 c4 e2 d8 c0 13 04 +3e c2 d9 15 4d 98 67 9e dc 3a ef 9b f6 88 62 43 +cd 17 5f 79 40 92 e7 4b 60 42 01 0c 77 76 8f 76 +ea f6 fb a6 45 58 2c d5 89 a2 8d 61 02 7a 7e 26 +8e 28 56 45 72 7e e3 ed f9 f9 3b 1e 5b ee 97 88 +db 89 53 5c 79 d9 79 ae 9f 39 7f 84 b7 0c 8d e0 +1d f8 0c 8b 21 3f 2f f0 9c 82 79 a5 10 02 e7 b5 +26 f3 b7 11 2b b7 98 2b 4b 9f 32 9b 43 28 02 8f +f6 3c 06 da 57 94 79 ca 5a ac 9c 1c 9d 86 54 24 +d7 0b 67 34 82 c4 c6 a1 05 4f 13 41 f9 15 62 88 +c6 88 68 49 5e b0 18 91 eb 30 d4 68 dd ee 9a 13 +cb 0c 27 be 5b a2 0b 66 c9 78 d6 bb 82 4f bc 50 +6a 31 b1 18 81 a5 e0 30 d1 0c c0 e7 25 f7 05 87 +78 64 4d c3 09 4e 52 36 22 8f cc cb c1 6e ed aa +08 27 93 b8 19 95 11 7d c8 ac 5c 34 88 90 e6 da +fb c7 ee e9 91 f2 1b 05 5c 03 ce 4e d3 32 fc 22 +94 ad f8 5e 32 30 db 94 05 7c b2 5c 0b ce 6c 0e +d9 af 96 4c f6 15 a9 5c 8e e1 42 0b da 56 90 6d +25 f0 1f 88 7f 95 46 05 48 5f 91 b2 e7 65 4d 89 +50 cf 0f 72 1c 74 66 6c 04 ec d7 84 e7 92 e8 79 +28 b8 22 9c 78 2b 00 18 c8 fb 4c bc cc eb 35 53 +af bb c9 99 27 56 f2 4e 33 6a 56 86 d2 5f a3 b4 +4a 95 a0 3a 20 c1 4e 3f 9e cd 79 33 0e 09 95 b8 +e0 60 81 60 fa b5 f6 86 61 48 14 0a ea 83 73 b1 +79 38 2d 60 55 2a fd 05 d0 9d 24 8f 7b 24 a4 ba +24 e8 f3 2f 08 83 0e af 9d 58 13 bc 11 1c 35 80 +e3 72 d2 5c 04 16 70 85 86 d5 c6 ee f5 51 c5 29 +d2 8d c3 3c a9 6c de 50 c2 e7 59 36 c5 39 65 1b +52 9a 4c d8 a6 6e c5 fb 9e d1 c9 35 2e 2b 33 84 +08 2a ee 35 08 8a 05 38 aa bd 93 53 f5 a7 b0 4a +13 74 62 2d b5 a2 74 64 08 1b 9c dd 6b f2 6e 9b +d1 fa c2 61 3b 21 57 37 f9 01 4d 35 c0 3d a9 93 +41 a1 ad d5 3e b2 c4 ea 2d 01 a6 23 df 6f 07 6d +5e 7b 60 2a 8c 02 dd 8f 56 6b c7 04 da 59 77 70 +3a ab 65 9b c2 93 8d 31 b9 93 d6 49 75 67 d7 7d +e6 a8 9a f0 62 14 2f 88 67 84 93 c5 96 37 25 8a +9a f1 75 66 5d fa 5a b1 43 2e 14 e3 c5 38 c5 bb +85 e9 fc 3a 6e b1 31 7f 04 92 91 b3 68 ae a1 7b +e5 b8 4b ab 9e 1d 57 9f 02 73 5d 9c 74 e3 8d 68 +fc fc b3 3b 05 91 6f ce 93 54 80 d1 13 bd 89 e1 +2b 39 a1 97 6b cd 0a 8f f7 2d d6 bf 21 eb 91 bb +5d b2 e9 b2 b8 5f 71 57 e5 54 b8 82 78 1e d3 8c +2e 64 49 0d 7e 43 bf ee aa 6a c8 56 03 10 ea 7f +43 79 28 3a 56 4f 46 41 04 46 72 71 8c c5 f7 d7 +71 c0 95 3d 3c 2c 8f eb 0b 7d 0f 47 da 88 fb 04 +26 f3 f2 f6 1d d0 6e 72 bd 05 29 76 a6 20 94 fe +9c 48 36 d1 88 52 22 62 9f 40 b1 4c 5d c4 cc 11 +65 31 31 1e bd 7e 7a 72 e1 70 fc 56 f8 79 7c 9c +11 e7 87 b6 6a 6c a5 3b 66 a1 ca 4f a8 76 ce d1 +21 2d 2e 4b 91 9a 54 0b a7 b8 4c 5d 3d 01 0d 77 +6e 4a 15 58 8f f7 eb d3 aa 5b 88 11 dd 0f 95 0a +4b 26 f0 8c 3e 40 72 1c 37 91 9c 1d b5 1a a8 46 +8d 64 92 3e 8f d8 60 d5 13 f1 60 fb 1d be 74 17 +2f 96 6e de ed 90 ca fc 9d 84 69 b5 38 71 3d 05 +3e 86 f0 ab f8 10 c6 7e 30 fc ea c5 12 18 f8 71 +60 ca 7b 7a 60 bc a0 88 c1 76 09 81 f4 b1 d4 be +aa 31 e5 a8 9f 68 70 0d 0a 62 3b bf cb 5b 5c 57 +09 a4 3d 0b 96 58 9b 9a 29 3e 9e 0b 4c 34 21 56 +94 e2 d1 c3 ae 9a 3a 7f e4 01 28 6b 38 14 ee dc +53 53 e2 9d c2 01 d6 b0 39 37 38 32 e7 4e 40 e1 +4d 4e 3b 4b cc ff 2e d0 48 18 6a fd b9 bb d5 c7 +ef 66 a9 9d 4e b1 ce 9e a7 4d ec 07 24 45 79 84 +b6 50 96 d5 64 ea 6a 9d b0 6d a6 8e dc 6c 60 51 +0e 55 05 c2 de c9 06 00 44 ee a0 64 38 1a 58 4b +60 83 f5 7b a5 0e fd 9e c0 bf 98 c3 32 71 94 af +39 a6 9e 6b cf 8a 59 18 17 1e 61 2b ec 59 66 44 +c6 01 a8 08 b7 42 34 13 b1 59 a9 a9 2d 55 8b 5b +e9 8a e1 16 f0 e6 a7 92 1a db a2 2d c9 37 d7 05 +58 34 82 0a 70 18 21 8d 20 b5 64 f2 58 41 f4 92 +ad 88 6f cd 4b 5c ce f7 bf e4 9f 75 4b 8d 42 34 +c0 ff 86 e0 eb 1a ec fc 39 70 84 e2 93 49 67 9d +31 b3 dd ac 8b 4a c1 27 f6 de 8d 70 ac 2f 1d 3b +8e 2c 33 46 c2 b2 ef 6e af 88 4e 80 9b 09 33 0f +64 60 f6 4b 9d 05 c9 70 b9 e6 be a2 f8 34 3d 61 +f8 be f4 84 be dd ff 8e 6a c7 f8 08 4f bb 2b 0a +c3 8f 32 97 3e 60 e1 40 78 7a b8 18 0b 1b 36 b2 +12 ef 74 7e 7b db b2 3d 0e 12 40 ec 4a ff 62 3c +19 2f 76 e3 42 e4 b0 33 45 52 c6 7e 9c f1 50 cd +8e 1f 35 5a 0e 83 91 60 7f 32 24 b5 16 86 40 1b +5e 4a c0 92 71 90 ee 5a 6d 6d 47 f5 db 1d ba 73 +a1 ed 35 2d 4a da 5f c2 d3 a3 e5 74 8f 86 08 d1 +0d f4 d4 a8 7c 53 b2 d9 2b 9f 0b ab 9c bb 9b 27 +6a 1f a8 eb 23 3b 7c d4 ef 69 bd cb 3c d9 3b 05 +0d 4a dc 9f cc 8e 45 f4 50 ad c2 f3 d2 df 00 12 +3b 9e 2b 6f 0a cf 7d c1 b0 7e dd d0 62 ff 49 db +61 c4 30 d8 ad 59 7f ac 65 0e 23 6a d7 b8 ee 99 +95 7a c0 ce da 07 a6 5f 69 23 7d 62 51 44 0d 12 +b8 01 69 80 02 e2 fb 59 29 aa 4c a6 f7 f3 54 2a +a2 23 e4 36 8b 74 fb 99 7e 78 c2 f4 e4 f6 51 cd +90 92 51 9f 12 be b7 11 b7 a4 33 7a b8 a1 04 94 +c6 08 bb 05 3f d8 f2 42 7e 24 f5 db ca aa 8b d4 +0e 51 6d 83 9c e0 12 d9 7f 51 a4 00 c7 c5 ec 16 +c3 51 e7 b9 05 79 de e5 2b ec e8 80 76 17 47 03 +f1 47 1b f0 9a 24 df 1e 65 b1 5a 6c f8 e8 8c 48 +36 8a b7 9e 2e 71 60 ff c8 67 3f 5e 05 d5 6a eb +62 e3 18 c6 59 8a 79 f7 7b df 7a e1 b2 b9 36 7d +23 91 91 57 69 26 c1 03 9d 1f 79 30 b9 8e 67 d9 +08 fd e3 67 c6 46 89 87 cd 89 2a 40 31 71 ee 8c +77 19 31 55 44 89 bf a1 4e 3b 72 94 d8 b2 dc 16 +02 d0 eb 7e 9d e0 c6 2d 89 58 56 2f 64 f4 ed 8a +e2 b3 70 04 6a 63 9c 68 80 e0 5c 04 af 9b f1 95 +8c 89 f9 43 3d 74 06 34 62 b0 a5 5b 8a d0 4c 1f +68 c9 b5 e9 18 e3 b5 bc bd c0 10 4f dc 11 77 88 +29 19 96 ba c0 7e c9 c8 be d6 e7 37 71 18 6d cf +6b e1 47 69 68 da 26 49 e0 77 25 57 ad ec 03 66 +cf 60 49 0e a7 51 83 89 2a 90 f4 05 1e 0b db bf +8d 66 f5 c5 a0 47 a4 88 25 ec cc 11 fc 85 e3 d8 +06 77 61 3f dc ea 24 46 32 75 83 50 05 e3 03 34 +3c 95 00 bd b3 da bc d3 1f 99 7b 77 86 36 ab b0 +48 2b 2b b2 e0 ea 78 f9 6e 92 e5 06 eb eb 89 40 +c8 d1 1c 79 26 2e 4d ca fb 58 d0 39 f6 82 22 e3 +8a 93 29 10 58 63 08 d9 7e 85 e3 c0 c0 d4 be b9 +db 81 f1 4d ce 1d 98 c4 f6 87 48 0e 73 0a 96 d7 +1b 02 ea a0 74 bf cc 6a 83 52 ec 7c 46 58 66 d8 +a7 e8 02 9e 29 29 4e dc 17 62 af 28 4e 00 78 00 +1d 4f 46 0f 34 73 20 70 cd e3 09 29 9e 38 c0 99 +23 f0 31 f1 00 b6 fa ce 65 73 4e f1 c6 ea 32 20 +f5 1f 2c 1c 89 58 c2 be b0 dd 51 eb 49 eb 98 3d +8a fa b7 88 39 7c ea 7d 63 40 27 6d 09 07 c3 73 +95 4d 21 75 d8 fa ee d4 58 a5 36 f8 c2 9c b4 d5 +70 27 d0 5a b4 e4 91 5e 37 2e 5f 3f f0 13 b4 9b +85 a9 60 d0 e0 fa e0 e5 9d a3 d1 3d e2 23 31 44 +31 ff 4c b0 9b 58 98 83 c5 20 c4 68 40 14 1f 54 +4c 7c c4 6e a7 1a fa 66 ca dd b5 d8 0d e6 fc 4f +00 44 04 e4 3b a2 df 8d c5 33 af 02 91 ff ef 8a +f8 58 52 53 a9 fc 8c bc 45 10 33 1a f1 ca d5 bc +e4 b4 ab 64 18 cd d0 2e 8d 64 2c b4 05 a7 1e b0 +7b e4 9f 80 75 21 4c 4d f4 b2 88 12 6d a1 58 d6 +84 89 37 84 43 67 6b 1f 59 1f ff 37 b3 0e 59 c1 +7c 18 92 3e 96 78 0c 9e 1f 31 4a 2a 00 cd 36 fb +1e dc e1 fc 10 e8 f3 53 57 5d 51 cc 99 ce 18 16 +3d 45 77 63 64 de e9 70 af e3 75 fb 4c 80 a8 44 +ff 9b cb 03 92 e1 d6 36 9d 29 2f 03 77 09 8e e7 +59 4d 45 45 e8 2c 61 69 42 a9 ac 7c c9 e1 d8 1f +01 8e 74 1b 0a 50 ec 30 8c fa 3b a2 ae be f4 fa +c1 bb e3 00 1f a3 c6 c8 a6 8b d2 9f 91 ed be 3b +3d 23 1a 0e 4b dc 9b e0 21 17 d9 0e 5c 9a a4 fa +05 bd 2e 9c 03 a2 47 4a 38 89 6a e8 da ca 2f 9d +23 7d 4e 7c 10 08 26 64 53 f1 eb de 09 0f 62 ba +9a df 98 bd 31 51 1a d0 8c 1d 93 88 64 cc d0 70 +c5 22 4f dc 3d 36 8d ca d3 37 43 1e 7a 50 e1 d4 +ee d2 1b 88 94 a6 c9 2e 61 4d c8 fc 4b 9f fa ad +50 eb a7 dc d6 44 89 47 17 e2 64 16 d5 6b c0 80 +1f ff f5 ee 9c 5a 05 1b fd 0b bd c0 b1 3d 5b dd +41 f7 cd 34 7f 06 94 98 7b 0e dd ca 6d a5 85 4d +1b 94 5b 62 56 5e 39 c4 6b c7 9a 02 24 fa 45 50 +5b 61 a7 4e 29 f0 7b 82 f2 97 b2 ba b0 d5 51 67 +22 fd c8 9a 88 1b 83 25 7b 3a 8b 41 61 fa a2 e5 +8f 5b 2f 9a 12 23 a8 90 f7 12 7a 54 de 57 e9 a2 +44 03 fc 90 74 85 b9 6f f1 f3 20 dd 1a 4c 65 5d +fb e0 67 61 52 14 18 98 dc 5e 84 35 1b 31 ea 62 +7f 69 2f 1d 43 09 22 3e 8f bb ff 99 15 5f 7e 65 +c9 bd 0e c6 b2 f2 4c a9 6b cf 7d 59 45 e9 af e8 +34 c5 98 0e d2 fb 85 11 46 11 49 c2 d5 7a cb f5 +db f3 69 11 b6 a7 a8 c3 46 b4 93 f6 b9 95 b9 57 +b1 21 cb f7 2e 58 78 85 63 0a 89 14 ec 15 dd 36 +c2 46 c9 ce a6 bd 5c 94 f4 dd 24 9a 79 1d 59 3b +c2 b5 01 14 54 c4 39 fd 86 fd 90 8d 19 7e 83 d8 +c2 8e d3 3c 59 36 70 8c 5c 34 29 81 a4 14 c4 50 +cc b7 1c 4f 8e 35 96 83 87 cf 74 4e 82 f6 19 84 +74 d8 a6 94 d9 5a 06 25 b9 5a f8 04 05 a6 cc f6 +90 59 24 6b c2 f5 fc 9c 8d 74 0f 93 c2 bf 77 32 +24 50 83 56 d5 a0 5c e9 26 2d 3a c7 97 ba 0b 88 +74 f9 5c c1 ce 3d a2 75 10 17 38 f8 81 c0 e4 0b +9f c2 d9 2a 43 68 17 46 e0 cd 4e 48 45 b5 ed 8b +d1 27 26 4f 5d ca 6f fe 3c 1b 17 ae 58 63 93 68 +7c 34 9e c4 e3 91 4c 7d 74 43 44 d8 9c 81 7f 23 +ab 4a 65 48 22 c6 82 a2 c1 e1 8b 8e 89 a0 2a 60 +57 76 21 59 e2 61 d7 15 02 70 11 5f c0 d8 ef 04 +65 73 90 3a ff 51 cd 86 f6 ba 7c 6b dc 0b 7f a7 +df b0 28 61 cc 08 9c a7 64 9c 94 4e 13 8c 28 56 +08 07 bd cc df 2f 69 31 b1 e1 a6 b4 0d 56 12 34 +a5 69 64 23 1c 20 16 a3 a2 2a cf 80 79 1d a0 65 +77 d4 8a 30 05 b5 ef cf 66 5f 0e 6c fb bc dd 64 +2e f8 eb 9b 91 e8 9f 58 dc 4a 33 62 bc ea d5 13 +9c 13 da 29 ad d2 09 81 6d 17 cc 14 da 39 03 47 +ee ea 3f 62 3a 2f 88 52 3f dd a3 d4 75 49 8d 69 +9e 0d 54 dd be 40 ca 4f b8 c5 38 92 90 5d 46 9e +ea 94 4c 42 49 54 88 75 73 22 81 1d 30 21 48 83 +da 36 10 d9 e4 a3 47 a2 f7 ae d8 4a 34 30 55 bf +70 b5 d7 91 86 ec cc 70 bb 7a ae 5b 58 c8 37 4b +e0 43 8f db d5 16 c0 e0 46 66 34 9c 3c de 42 53 +34 87 34 39 c6 c2 b3 a0 95 b6 9a f3 ad 7e 30 e4 +f1 a8 1e d5 a2 48 4b b8 82 c5 84 bb 4c 52 37 52 +62 14 e5 8f de a1 0c 9c 90 08 52 b3 bb c1 12 01 +0c 16 0a e8 4c a5 c3 2b 36 96 fd 78 c8 42 b1 c6 +93 db 3a 7b 04 61 02 b4 c6 58 3c ee ca 2c cf c1 +b3 c8 2d a4 be d4 cb 8a 08 2b 99 f2 99 75 e8 5f +11 fb 60 30 cf 70 e6 b4 df dc c2 c7 2d 2c d6 44 +6d 4a 13 9c 08 42 9b 22 60 35 99 98 28 7e 2f a6 +b8 09 d3 5a 33 f1 f3 58 28 3f d9 99 25 d5 64 78 +05 a5 d9 a3 4d 6d 78 cc 2f 59 ce 19 b4 7c c7 7d +7d be 1d bd 3b 6d 28 b3 73 ea 6e 87 23 4e aa ec +08 05 0b 7b e9 5e 5d b1 55 7f 66 2d dc 9b 6c 2b +5f 6b 30 7f a3 f6 29 a1 a2 9c f1 4b 22 7e 1f 1a +4c 26 ca 88 e6 a8 86 c4 9d f4 bb 32 3f 5e 15 d9 +6e ba ca e7 b4 ad dd 48 e4 47 5c e7 d4 21 a1 b2 +b5 0e 14 91 aa 36 7e 0c 29 3a ec ce 98 ff e7 4f +e2 2c 71 20 12 41 ad a6 06 9f a2 7f a7 2d 07 82 +f1 af 6d a2 5b 55 8f f3 39 b2 bc f6 d8 e8 89 fd +a5 73 ba 89 3c d9 00 88 3d ec ac 86 c6 79 96 a9 +9b 96 5e e9 b8 ae 0e ef ae 34 51 e9 91 f6 3d b2 +83 b7 34 20 1f 01 9a 7d b9 cc 74 17 69 b1 2a d8 +59 bb 86 16 17 45 a5 1d cd 65 b7 0c b0 8e 94 57 +3d 85 36 63 81 3f 5c 11 2e df 3e 8c a1 ab bb 86 +a9 5c b7 26 f7 85 2f 0c e5 a5 81 eb f5 a7 22 e0 +91 97 26 ef ce 8d 0c b8 d3 75 fd 23 14 5a 32 9e +5a 3f 12 e7 2b 8b 2c 21 42 50 d1 15 e9 eb 10 f4 +5e 8d ee 5b 84 6c 6b 84 6d 97 84 a7 26 19 35 55 +27 a2 3a 92 83 df 9f b5 83 78 e4 9e 80 60 fc b3 +24 f8 91 cd dd 12 86 93 b5 38 ac 8b cd 8d 62 22 +03 64 63 c4 e8 75 9e 31 a7 3a 4f 6e 7e 98 b4 16 +03 99 13 60 65 df e3 d0 af 63 0a d7 64 40 c7 73 +d6 d0 e8 53 00 38 dc 69 f8 55 3b 34 5f 5f 43 0e +c8 05 0f ff 72 56 b7 2e 29 d1 ff 3d f2 e8 73 03 +b0 70 0f 35 23 11 98 9c 87 0d 50 25 90 f2 bf ec +fd bd 3b 21 c5 01 8e 1c eb 83 ac ca 32 a3 57 3a +df d9 b6 b9 ac 3d e1 dc b2 1a e3 80 7c 4c 9a f0 +b3 a0 84 d6 a7 46 c2 2e 81 0e 45 9d 5b 8d bd 35 +34 ad 7e 39 de 2e ce a5 f5 df 09 12 79 e2 2e 7e +81 99 08 75 04 ea ff 6c aa ef a1 54 25 d9 6f 4f +88 58 a2 80 14 3b 71 f0 68 83 c9 61 93 d4 61 fd +ef c1 15 39 8e 41 fd e3 16 b9 c8 17 8b 0e 2a 19 +cd 2c 86 d0 3c e5 ce 4f e4 44 f8 1c d8 f9 3e 3d +71 04 8e 8d ee e0 bb 37 54 42 ac 7a b0 3c c8 0c +b7 60 15 93 59 bc d3 14 3b e2 72 61 75 39 ea 52 +e9 47 94 29 7c 3c 36 91 86 b2 36 05 2a 61 af 04 +14 ac 77 b3 6b 85 dc f0 59 cd 60 4c c5 b1 52 8e +1d 4e d9 99 7d 46 23 b0 9f c4 ed de 99 7f f9 14 +4a cf b7 32 02 07 f8 5c d9 1d b1 72 67 9a 2b 07 +98 e5 b5 3a 31 ab 06 12 b4 dc 63 0b 38 e4 4f aa +78 20 8e 9b c8 97 9b d0 0b 54 98 8e 3d d0 63 88 +42 16 ff 9f 05 46 c1 18 85 ee ef 11 54 f9 f1 97 +27 e7 4e 40 14 66 0f 30 3c 0b 09 47 14 69 40 72 +11 62 6d 08 70 96 6c 5b da 77 7f 36 b0 03 d5 06 +fc d4 7c db eb 71 db ee 23 4e a6 c1 76 83 74 4d +c8 3d cd fc 22 dc 2e 99 c4 23 36 05 16 95 1a 2c +29 dd 16 12 6c a3 90 7a ad 90 73 b2 e1 69 5a 6e +3e 99 fa 1f b2 f5 33 89 f3 71 e6 c6 32 73 32 a8 +81 f7 da ac 0c 2b 2e e2 c3 1a 1e 01 f9 a9 87 95 +0f ea 7b a5 a4 76 6d 14 13 79 89 12 98 7b 1c 28 +89 23 12 86 84 25 a9 be fa 3c 4c 88 ce 04 d2 b2 +92 88 68 9a 49 5b 84 e6 d8 86 32 3d 4a e1 2e 74 +bb 61 0f cf ad 8b ce 74 2f 99 40 c7 97 b9 3f 04 +1e 56 a2 d9 f9 f5 d4 bd 0c 0d 9c 34 66 ba ff 17 +7c 93 cf 98 d0 d5 a8 02 e0 71 89 20 45 e3 b4 5a +f3 de f5 88 92 31 fd 93 9a 5c 7e 11 6c bb e7 de +a4 b7 bb 8f 20 b0 78 7c 3b 24 32 e6 1f 5f 3c aa +e9 5a a4 3f 0c c8 20 57 17 76 78 ea 05 69 6b 81 +94 da a4 0d 2e 99 3a 3d 14 db af 01 7d 50 99 dd +60 e7 88 58 8d 32 a8 05 c4 dc 6d 2f d0 c2 b5 ba +f5 8b 9f 93 a6 e4 7f 8b 08 f2 98 f5 ef 0b ac f9 +e6 d1 f9 47 fa 9a 35 5c 45 6b b0 dd 16 bc cd b8 +22 68 ca 91 f1 3a 48 ae e5 88 42 77 75 ec d1 36 +25 f5 7a 93 2a 05 44 32 03 b1 d9 40 76 1e 55 a0 +3e 14 ed 67 c2 bf 43 92 f7 4f 44 ed 08 a2 f9 58 +ac af c8 ec 5e 84 f9 06 58 e6 3d 5e 81 34 11 ea +b8 b0 d5 4c 89 50 2f fd e7 49 bc 3c 9a 2a 1a a1 +86 c7 ad a3 4b 70 80 d3 8b 9a 21 b5 54 4b 02 02 +c0 7d df a9 be 06 65 76 9f 6f 65 4c db 6f 37 17 +30 ec 2e 5f 1e 5e 1c 9a f3 95 82 4c 7d d3 54 3e +c7 f9 d4 fd 1c e5 98 78 a4 be 63 ae 31 cc 4d 6d +07 cd 68 6a ea f3 fe ea 1e d4 21 26 d8 21 b5 2d +2a 6e b2 a6 88 e4 26 da 31 87 73 1f 31 22 f7 c6 +18 9c eb 9d 55 8c c9 9f a2 4b 05 cd 6a 77 9e fc +54 57 16 b1 cc dd 0a 73 46 77 38 67 af d4 77 12 +67 17 63 0c 24 76 9b e1 8b c2 83 db d0 12 64 f6 +b4 9d 69 9e e0 13 8a 5b 6f 0e a4 cd 8f f3 f1 b9 +63 e7 ee 64 10 72 52 a5 ff 61 af af 33 6b 97 99 +ea 4c f8 33 f8 7f 91 4a 5f d3 b6 a3 42 b8 1d df +63 36 97 b1 e9 98 31 26 fa ec fb 70 2b e1 1f 35 +57 d0 ee 71 83 87 d5 da ed 30 d0 5b c2 db 4c c9 +f0 52 e8 49 d0 1f cb 21 0c cd 22 45 4a 64 dd 11 +86 fb 53 aa 03 80 a7 50 82 3e e6 71 1a ea f7 ae +c6 6b 52 32 b5 80 92 5c 57 a1 59 3d 36 91 40 34 +63 6f c1 70 5a bc 0a d6 96 e4 2c cd 24 5f 66 92 +0b 0b 01 6e 38 1b fb d0 de 3f 98 d6 1f 59 7d cb +e9 ac c1 d5 ed 01 79 31 2b 0b 4d b2 24 23 3b da +13 b5 a9 5d dd 3a 65 86 09 7c b9 36 13 bb 5e 1c +a5 07 82 f1 66 b2 ef ed 6a a1 40 6a 2a 2c a9 4e +ce 9d 93 01 62 41 3b 29 a1 e6 6b 2c 4b a4 ac b2 +d4 78 13 ee 30 f4 79 6d a4 59 7b 93 61 7a 39 e2 +d6 59 99 6e 17 c0 13 18 97 eb 4f 4d 17 1c 2d 20 +66 b1 91 ef 98 d1 d2 ce eb a9 59 9a 95 23 f9 63 +bb e7 df ed 77 b5 69 3f 04 de 2d 67 c9 89 aa e8 +25 6d 80 51 6a 9d e4 5e 21 98 c0 fb e7 bc 19 f7 +ec c6 50 cc 43 9e dd 8a 50 85 37 85 97 98 87 e7 +d7 86 d9 ac af 19 76 86 7d 55 a3 42 79 73 8a b9 +52 67 09 63 02 87 1d e2 47 af ca 3d f9 dc cf 18 +e3 98 56 95 59 0f 8c c5 4b b2 8d 9a ba 62 5c 1d +68 a3 2a 96 84 67 a0 12 cf 5d 52 a2 72 91 2e ca +c0 55 89 39 d6 c2 45 b0 4f a1 d9 e8 c6 4f c5 d3 +df 3c e0 86 98 76 cb 83 72 89 22 50 4f 8b 12 ab +e7 24 25 43 78 f8 ca 1d 59 ce c9 77 ab dd 8f 73 +b8 ce b4 60 9c 57 3a b9 73 25 ff b8 8e 49 49 17 +61 f1 32 bc 8a 7f 5e 43 c3 b3 72 5b b8 aa 6a 81 +a0 80 bf aa f4 c0 22 ef 26 25 3a fe 94 15 bb c0 +fe e5 85 19 85 ca 02 37 3a 53 2f 11 e8 bd 9a a0 +34 81 58 21 56 3e c1 a8 b4 62 ac 97 e5 42 0c 37 +c4 70 bb 84 53 1c e6 cc de 5a 19 2d a4 82 32 49 +ac 5c 99 f4 5f 9d 0b d0 52 03 9e 7e 80 41 48 fd +b8 8e 63 97 d5 8b 6d 17 60 c2 cb 61 cd 5f aa 49 +3f 00 b8 8f 31 c0 90 c4 ed 68 5d 51 5a 5f 9d 6f +4c c6 78 06 bd 5c 4a 89 74 99 2e 6f 9c 99 d5 2d +01 a5 a1 f4 9a 7e 9e 7b a1 02 6c 32 bb 3e 01 44 +6f b5 6d ad f4 e5 86 f9 77 cd ad a2 56 78 33 80 +bb 36 47 8c c0 92 2f 9e 59 9d 3b 65 50 c4 3b 4c +d9 62 f4 95 18 30 76 87 a6 17 a7 19 f1 76 6b 95 +0d 80 13 38 41 5f 42 ca b7 1f 2c 95 b5 20 1b 74 +2d db 7e 79 32 74 3e 0d c1 f5 42 41 ed db f5 c7 +55 61 5e 3f ff 01 a2 df 44 67 ef e6 75 e1 45 72 +75 b8 34 85 7a 94 c9 9b 6a a3 de aa 6d 61 36 21 +4d 50 23 32 db fc 35 c0 bc 93 0b 69 10 a4 61 30 +3b 39 39 c4 7b a7 11 02 41 c8 e7 b1 f7 54 ce 2d +77 ff 48 e4 14 04 db ca 31 8b 45 ca bd f7 be 62 +1c a4 f3 2b 42 4e 1f 99 42 18 d3 c2 b5 9e b7 2d +8d 4a 0e 24 84 cc 31 ea 63 ad bf 53 b8 00 f1 73 +97 ed 0f 58 6e 26 5d 56 c9 f5 a2 d0 ca ec 05 8b +d6 9b 79 76 45 0d 91 bb 5a fc 99 7e 69 21 8f 01 +c1 e8 d7 a4 71 25 26 53 7b d5 ca f0 18 bc 16 0b +44 c2 8e db 29 30 98 62 36 f4 dd 71 13 9c 44 6a +75 39 72 a1 d0 b7 83 3e 89 21 08 a4 81 3f 6d 1d +1b e6 8e f5 7d 5f 4a 59 be 46 77 db d0 9e c5 d9 +7a 00 76 62 62 4a 11 44 87 6c 3d 22 07 a5 27 f4 +8c b7 4e 03 f5 90 78 23 64 68 92 69 65 98 5c 6f +d0 83 62 e3 d1 3b 7a ee a0 5a 55 19 8a 7d 8a 6e +44 a1 47 71 e9 69 bf 21 f3 d7 48 27 ae 9a ed 95 +5b ec 51 c8 b9 e3 fc 98 9a d6 64 b0 0d 43 4a 1e +73 33 aa 8f ed 19 f7 de 63 55 1e c6 c3 c0 1c 8b +75 38 8a ea ea ec 9c b0 88 3d 85 66 43 15 8c 67 +a5 1f dd 56 33 02 7d df d2 fb 93 db 94 e9 c2 82 +95 25 3d 3f e5 cd 4e 43 b1 ac a4 bf 78 95 a8 5d +c1 31 7e 61 fa 91 84 22 8a 41 9c 50 6c a6 13 d6 +a4 36 7c 40 37 25 fa 91 dd 5a 2d ee 7e de 4f c4 +34 56 4e b1 9b 0e 9f ee 18 79 c7 0f de 8b 32 0d +2a e7 38 a0 44 2c a8 83 23 80 1c 73 26 ce 13 9d +52 92 19 24 0e 42 4b 70 e5 7f bc 7b 8b 12 10 b6 +36 31 ee 3c 20 61 49 f7 af 72 95 d3 73 b0 40 6f +19 a7 c9 43 08 a9 7e 93 e3 f2 82 5a 9d f3 35 57 +bf 87 10 97 ff 00 fb 15 b4 88 30 fd f6 13 96 86 +06 ca 96 44 3e 6b 7b 4d 00 ee 08 e2 dd 61 34 96 +9d 60 aa c2 53 c5 b7 f5 bb 10 67 27 47 68 be a3 +4c 54 4f 22 22 69 74 60 ad 80 dd e7 91 7f 8a d6 +50 84 08 55 a1 3f 77 f4 16 d9 d0 54 8f 77 52 1c +3a 1f 0e d5 2b a0 78 0f 68 08 d0 c7 8e ae ed 7a +05 5b a0 84 5b 52 f0 8d be 2a 51 b0 af 71 3d 90 +c7 c6 16 7f ce 7b 6f fc ae d4 51 6e 14 3f f8 f2 +28 9c 99 61 3d 04 6b 5b ec 7d f3 4e 65 c1 08 df +62 7a 42 ff 33 7e fe 09 26 c6 ab 37 4f 7d 03 fe +9c e3 32 89 f1 39 88 6f 68 c5 62 6d 23 84 a3 04 +60 13 be 76 e2 ed 21 15 ec f7 6e 7e 82 0e 70 ac +bd e1 7e 28 ac de 73 d3 a0 1a 0e 85 87 55 75 99 +5b ce 26 52 e8 a6 dc 72 a5 4e 54 af 3c 0f 53 c1 +8a 17 38 03 89 61 05 3b 6c ba d1 fa 3c 69 70 de +ee 97 6e 10 42 7c 1a 8e 90 ea f7 45 fc b6 70 38 +8a d3 82 a2 21 83 eb 0b a7 8f 03 4c 68 2e ea 76 +ad 9b 1d 0c 62 06 b2 a7 a7 22 ac 2a c9 2d f3 45 +ac d7 71 2a e9 23 25 2a 1f 46 9c cc 5d b5 e1 e5 +a3 e8 56 fd d3 eb 4f ff de 3f d2 68 67 95 18 95 +ad ce 47 e3 d0 d4 68 16 be e9 6f e9 ac 1e e8 72 +0f 0a a7 c4 b4 24 ba 9e 0d 0b 8a 80 c1 40 d7 5d +d1 1c e6 38 9a b1 c6 61 b5 0b 88 3a ae de 31 c2 +96 8f d8 0d a9 3d b3 a7 f4 8c 0d 86 5a 08 03 96 +73 8b 8d ed 1a 74 6c 3d 41 c7 6f 8e 17 37 99 00 +8f d1 d5 e3 e8 e0 57 5d 11 e5 3c 5f 33 12 8e 1d +cc 2d 65 89 eb da ec f3 e1 1b 20 d5 7b d6 44 d7 +0a 8c 1a 3b 15 da bc da f9 8c 39 29 fd c6 cf 60 +73 18 93 cf fb e3 09 26 03 64 34 22 64 51 4e 8a +74 9d 71 6f 67 07 c4 a8 4d f8 dd 40 e7 13 d0 94 +5e f4 80 3e 40 c2 89 20 29 49 da d8 15 ad 3e 86 +f7 25 e2 5a 96 a4 5e b8 82 85 23 2f 20 39 fa 99 +8f 57 67 a1 95 7d ca 84 c1 0b 16 9e a7 dd 05 31 +3c 4a 22 b0 ef 76 b8 34 09 48 55 3b 36 04 b0 38 +53 06 25 65 6c 07 e0 5c 66 8d 1c 6e a7 d8 f1 a5 +13 ba 49 16 3f dc 4c 63 dd de 8f a7 a3 1e 32 0b +bc 35 3d b7 34 a4 96 9c a9 0e c3 00 13 5a ec ed +74 8c 59 5a c6 b1 80 00 d0 e7 87 55 f2 64 9f bc +2e 65 da 76 34 96 00 38 90 5b 92 cb 37 04 62 90 +5e 7e 06 78 f8 dc f2 9d 70 7e 54 46 e6 7b 50 12 +66 43 fa 2d c0 7c d3 28 72 19 76 96 04 1b c4 d4 +d3 8a d2 07 ef 91 ea f1 7c 3d c8 83 c1 0b 8b e1 +63 f9 f3 4a 23 cb e0 6b 55 5e d5 44 f3 4c 46 03 +85 eb 3e b4 03 2a 18 bd af 03 0b 1e ad 60 8a 45 +ba 32 e0 46 87 78 91 7d b6 47 40 ef 38 38 9b e1 +50 39 08 f3 b6 89 b8 88 42 72 a4 95 ee 70 b7 91 +ca 5e c0 20 31 02 72 01 e9 f5 fb 1b 8a 54 26 bc +2d 5d fa 9d 9d ae f8 69 6b 52 a4 be b6 a6 11 98 +c5 9b 3c 43 1d c3 d2 5a b1 4f 36 2d 45 78 b2 3a +0a f7 83 6d 77 17 b8 b8 0d 92 d4 02 c8 93 41 d1 +a5 8e cc a0 f6 b3 f3 83 ab fc 0d b0 e7 fd 6f 7b +bc 56 ee f6 91 db 24 26 c0 c2 b5 d1 3b aa d8 b3 +b8 f6 47 e2 9a 25 d6 ae 83 0f c1 74 91 47 75 59 +34 a4 03 2a a7 92 47 b9 a4 97 68 fa 38 06 3a 06 +33 ce be 41 75 a8 1d 83 39 5c b9 bc d2 de 8a d6 +36 ba 5d c3 d2 23 8b ed 9c 73 71 95 46 9e 7d c0 +60 91 85 6f dd e7 da 9d af 4a 95 23 d8 09 44 73 +6d e7 77 0f 68 9c 7b f3 a8 d8 5c e2 06 4b 7f 30 +d5 33 da ca b7 92 ac 24 3b 09 de 70 43 61 f9 c3 +9b de 4b 7f 0c e4 5e 7d 2a d3 92 4f 95 ca f7 7f +32 18 6d db 9f 5d f3 61 53 64 23 4a 8b c9 6f a6 +3b 67 c8 d5 85 9e 38 87 0f 4a 9f 61 8d 59 7f 98 +95 cd 41 a8 d8 2e a1 02 e7 c5 5f 85 56 a6 c3 58 +29 b9 13 cb e8 8a 08 8b 49 2e 1d 2c 8d c8 73 00 +8d 3d c2 40 77 b8 71 48 08 93 4e 7e 5f 7c 16 02 +3f 49 a4 9a 6d cd 1d a0 87 40 35 e8 e6 96 cf e1 +dc d4 43 f7 80 bc 87 f3 e9 60 a1 43 21 3a 35 23 +0f a6 d1 8f df 91 e6 18 53 ed da 0b da 4a b3 76 +89 91 a4 bf cd d4 6a 91 02 1f 57 79 57 3e 7a 82 +17 71 61 89 a5 d3 d9 bf 3c ce fd bd 0a e0 c1 f1 +f2 df 59 bb 45 66 bb 7d 57 2e 49 d7 77 74 37 f3 +63 72 5f 48 96 36 21 2c 5d c7 68 56 90 48 3b 30 +03 af fc 99 35 e0 69 1a b0 ad 07 f9 6e 74 ff 6e +59 61 2d 08 1e 4c 19 72 b3 fb 97 aa 04 fd 42 61 +2f f7 93 3f 8b 66 81 dd 20 c0 cc 97 d3 37 95 a2 +fd cd 5d 12 69 4f 28 f5 b4 b9 ef 1e 22 25 cf 4a +4e db 05 63 ca d4 da be 95 67 16 a7 fa c3 4a bd +c7 ad eb 83 c4 3d 04 f3 88 9e 4a 0f 2a ef 0b cf +39 20 71 15 f4 cb bb 9e 1e c0 56 19 a9 0d 08 1d +93 32 ef b6 57 54 ca 02 a7 83 77 37 44 37 34 51 +26 19 d3 b0 25 ad 19 44 99 f0 6c 8b c5 b5 44 5b +25 99 e0 f0 c8 8c 49 a5 b0 aa f3 40 f5 41 5a f5 +ab 61 2b 0c 76 ef a8 e2 5c ef 4d 09 b2 6b 5b b9 +32 30 8f 50 fb 31 c7 dc 91 5a 12 10 21 ff 31 b7 +c6 04 fb 9a e1 a3 36 be 51 d0 bb 81 cc d7 79 b7 +92 8b 61 20 8c 76 3b a8 ea f9 d3 3d ce 83 7c 58 +a0 a9 a4 f5 eb d0 b1 28 d3 0b 7a bd d2 9f 65 7c +17 4f 35 69 79 cd 3f 7a d0 1c 48 83 73 18 9c 35 +af 1a 85 ac 74 8d f8 b5 ac f6 d9 11 77 c4 8b d3 +da 8c 31 22 78 32 c8 5a 52 ea 4d 3f 59 ae 66 49 +ba 8f 1d 94 76 8e bc af c9 ed e0 c9 20 26 27 77 +c5 d3 72 7f e0 12 9d 05 3d b0 4a ce 7f e0 c4 5b +81 ff 49 12 8a 8f 36 ad 25 b7 5c 84 f0 8f 2f 80 +ff f1 cd b5 78 a1 90 cc 39 63 1e 50 a1 ca 82 1d +43 a5 94 96 47 b6 ae 13 63 6a ab 17 ab 3a a8 9e +8b f9 60 19 70 52 9d 45 46 2b df e2 c0 8d fa 30 +97 27 d9 c5 3a b5 d4 b5 38 ce 43 64 4c ba 19 17 +c6 2e 10 f0 93 dd 3b 11 5b de 80 fa cf 94 cc f2 +52 9b ae e1 95 05 9f 88 c0 8d 9c e3 75 7f 7f 94 +1b 52 c9 b9 32 ed e2 e9 cd ae fc d9 d6 06 58 91 +a7 3a 8d 22 00 f4 2d 9c 7e b5 ce 92 ff a2 ad 91 +3e e0 4e 03 27 5a 33 bb d3 89 43 ad 54 f6 5b 60 +fb 0c 37 1d 21 09 4e 17 07 d0 d3 9d f5 ec 5b 64 +09 f4 4d 6c 7d a7 35 78 fc 41 0f a2 23 3b e2 ef +4f 9b db 69 d5 15 a4 0e 82 ae dd 3f 93 6f b3 a9 +26 4d 81 35 d9 0f b4 03 f1 7f 33 b6 d5 96 32 a6 +d2 2e 1d 02 b6 e3 ee 34 88 74 30 1b bf 3e d2 c4 +22 68 da e9 5d 5a 3e 8d d4 17 94 bb f1 8e bc 37 +79 49 82 98 bc e5 de 30 31 cf 9a 7f 9c ec ab 3d +8c 1c 7f 65 b5 9a 11 91 83 24 7e e8 6d 99 ab be +9f 49 64 4c 3b b8 fd 62 be 6c db 5a cf 9a 37 d6 +56 95 b2 53 ac 9f 50 38 65 f3 8a c0 22 d7 23 d4 +15 34 7e 61 61 34 f6 da 04 81 65 3b c2 16 1d d8 +da 5c bb b2 01 70 aa d0 58 45 4a 9f bc 16 88 02 +df 7d c3 a5 9a a0 5e 6d 6e b0 66 bb 88 80 0f 1b +22 cf c0 ff bc 0a 59 56 c0 c1 68 04 01 d9 9e 32 +6c 7c 84 af 48 34 9c a3 10 66 11 50 01 e2 ff 5d +bf 3e 25 b7 5c de e3 60 cc a8 ed 21 4c 94 b1 94 +70 69 7a f1 bd 96 22 3b 82 0f 99 e8 a4 c3 b6 f5 +e1 fe 10 2c c9 f5 88 ff 89 09 b4 6f e4 92 0d 04 +46 06 1a a4 f0 aa 2a 39 34 79 0d 54 be a0 e7 8c +09 25 02 5f 2c 02 9e 72 d0 95 01 44 08 10 bb 9e +f7 a0 ca de a8 f2 f8 f8 f9 e9 27 0d 34 c2 43 e2 +c7 34 8c 17 0e 48 6b 71 96 3f 74 3d 0e 05 a6 4d +bd e5 fc 15 52 86 24 84 f3 f3 55 44 7d 27 89 21 +f9 81 5f 12 0e f0 8c da 1d 22 0a 7c 6b 04 ea 96 +dd 57 7a c4 7c b0 c7 d4 8e dc b9 93 a1 9f 2b d1 +b9 7f 14 1e 43 37 9d c7 aa 1a 62 d7 b7 c0 b9 77 +a1 6f 3c 25 60 4c a9 07 b7 90 17 9a 2c 1f a0 94 +6b 69 6d 8e ef 9e 19 5c d1 e8 4a 8c d5 e0 80 30 +72 31 a9 32 91 c8 f9 57 55 f8 ff 5e 4e d0 bb c0 +f1 4f fb 55 42 76 76 da 9c 0a a3 21 a4 d2 28 76 +42 ab a5 fe ca 9d 03 97 bd b5 07 e6 b0 91 8e 0c +87 b2 3e 92 e7 12 ee 18 15 ab 58 11 f7 40 07 f6 +d3 e7 2f dd e2 86 86 21 36 0e 95 15 b7 d1 85 f2 +28 21 71 a4 e3 51 c1 87 45 b9 fa 7e be 0b ee e8 +20 21 11 3a ac ad 34 a5 c5 d5 27 5b 2b 92 ff 32 +a7 f2 b5 62 87 9f 8f 07 5e 8f ac 37 84 ee 2c 9a +ab 64 c8 b5 51 3c ce 1b c5 22 f9 70 e7 e3 ef 13 +e2 01 42 86 db 19 2e 75 10 90 fa e6 67 1a d1 c4 +ee a1 99 6b 61 2b bb f2 83 44 c9 03 ea a6 50 40 +52 8a c4 20 9b ed 0b d9 b5 50 06 a2 0a e9 b4 d0 +bf 47 0f ee ba 9a b7 6e ab 1b 47 1b dd 8b 8a 30 +46 ac 22 c6 b5 7c 16 b1 68 a4 68 d1 c4 74 52 16 +b6 3f a7 cb ad 36 dd 05 59 71 c9 05 75 28 f8 6a +91 cb 83 67 ca 48 d9 c6 d5 b8 e5 57 02 af ef e3 +78 bb 77 cf aa f0 51 5a 74 0e 17 4f 3a 37 a8 49 +7e cf 8d 9c 15 4f 2f 7d fd 29 c3 b4 3f e8 77 f0 +15 aa 98 2f ca 15 f4 bb 89 fd 8b cb fc 85 f7 cc +8e ad 32 5b 3e b3 94 50 6b 64 55 ff 80 c0 48 0b +32 f4 8b 98 29 29 ac e2 7d 4e 5c c3 38 34 47 e7 +cb f8 4b 85 84 68 d1 d7 aa d1 de 23 b6 79 f0 ca +40 1c 49 d0 11 56 8e f7 fe ef 14 c0 82 fd d2 25 +c6 8e 16 54 19 02 91 8c b3 08 d5 d4 e7 f6 55 c8 +82 7e 4f 7a 43 02 d4 ff ce 99 05 e3 79 6c 7f 18 +3b b3 74 c4 ae 08 12 a6 a5 a8 72 7e d2 52 00 95 +64 e6 f9 9e a6 ae 17 7e ea 2a 82 81 ea 4d 3b d1 +63 0e f8 7c c3 a2 1d 62 f0 af 4a d1 78 9b 79 73 +0d 43 ab 5c f8 dd e7 e7 31 3e 39 03 83 f3 77 7b +70 11 d7 b2 b2 e4 d2 9f fc 9d 63 d2 4e 4d 22 2f +91 a6 a2 34 2a b1 3e de 04 80 b2 f7 0e 8e cb 7a +1f 4f 97 a7 f4 2d 10 36 31 84 6f 31 b9 65 37 ec +74 82 8d e0 ad e9 1a 8a b6 d2 3c 73 a9 2d 8f 20 +95 aa 5c 4f 04 5e 2e 20 c0 e2 e9 fd c7 cf 2b 4e +57 2f ba 8a 80 98 c4 fc 2f 67 49 f6 fd c6 53 d0 +a7 51 51 44 56 91 57 62 6e b8 ab 0b 37 ea ab b3 +11 21 af f8 70 c7 e2 44 24 82 2d 95 f4 f9 90 71 +d2 12 6c 65 0b cf 1a 10 38 c8 87 f4 f8 80 92 87 +c0 40 7b 0a df 16 0e ca 5a b3 a5 51 2c a7 d8 6e +75 db c9 63 b3 88 54 26 6b c4 ce ce 5a 76 72 1b +5d 5a 5e 86 b2 47 42 6e d3 df 08 5d 5c 20 54 b5 +9f cd 31 e0 c6 71 86 9f 59 8d 41 e1 1c f7 51 f8 +57 ae db ba 1e 00 47 cc 5d 13 d0 41 73 d1 42 08 +e8 87 60 92 aa ac a8 56 46 4c e9 ee 03 d6 a4 83 +bd 3b f8 5a f9 dd 20 fe 27 b0 6a 53 f3 c0 a0 53 +27 bd 03 7e 0e 90 90 37 61 a0 00 79 04 18 e8 5c +5b 09 50 e7 1b ed d8 aa 91 6f 93 5d 14 3c 49 c3 +90 92 13 03 43 8c 61 94 04 66 96 54 8e a0 84 dd +67 d8 1f e8 2a a7 bf da 64 18 17 3a a7 e7 80 d4 +ca ee c6 12 cc b5 e8 8e e2 38 12 41 da 41 e3 34 +01 65 40 fe 4e f4 69 3f ce 30 12 6f be 85 29 34 +11 69 03 47 fd 4f 3f db ee 1a 85 ee 9c 61 1c 05 +23 ce 8c 20 b7 da 9e 41 6a 87 7c 6b 47 25 3c 82 +be 13 ee f0 e0 14 ae d6 fa f6 81 a0 cc 52 7e 60 +27 51 f6 3c bb b1 18 50 e0 3a a0 27 1b 8f 76 5b +76 b7 07 3b ff 4a bb c4 0c ce a2 0a de 10 54 0d +82 1d 3a b3 70 a2 d8 07 0d 77 39 6a d7 b6 56 c5 +ae 07 a9 63 53 fe 85 cb 44 4b c9 02 95 e1 de 0c +9c 3e 05 99 fb 73 7d be fa ae 00 67 c7 c9 10 aa +09 5c 47 99 60 26 3f a7 c0 f6 d5 3d ca a0 dd 73 +07 97 a2 85 f3 eb df fd 5f 9e c4 07 8f 9f 50 2e +83 fb 19 ce 5e 9c 2f 72 12 98 bf e1 ed 7e 1b 74 +c3 47 d0 2a d2 bc 50 f6 f2 30 44 24 7b f6 6c d8 +be 83 b9 9a 72 8c 30 93 0b 25 64 45 f2 e8 f2 7f +7d 26 37 ef cf 60 99 87 39 17 b0 b7 7f 81 3b ff +08 cf b9 2a af 6e cf 6b f2 54 7e 97 fd f9 21 bf +87 9d 77 da 09 5a 25 4a a9 e0 c3 8f cf 4a ae 37 +9c b2 82 c6 d1 e2 31 0c a6 c8 85 93 0c 20 0f f0 +a5 13 70 5f 68 08 7b 9c 48 1a 88 f6 a5 a2 c8 9d +a2 6c 8b fc 7b 26 31 82 fe cd 9f 0c 0f 35 51 66 +2a 12 7f 2b 9d 97 d1 89 41 d7 52 1f 07 94 57 cd +ac af 04 db 43 f5 70 01 f4 7e 7b ee 3c fb 9b b0 +ea 56 0b a0 85 b4 68 ec ac 19 4c 23 a8 97 b6 53 +20 36 13 c0 86 a0 0d 09 14 15 e9 0b e7 3e 0f 49 +7c ac 94 2f 0e 78 c3 28 c6 13 b8 f7 9a e8 19 78 +a3 c3 d2 41 a3 42 34 b4 70 6b 89 ba ee 5b 7a 5a +dc 65 19 8f 5f 3c 99 0b a4 18 e2 c5 00 bd 20 2e +26 42 31 8b af 2b 7c 16 b0 cb d8 fb 8e d1 d7 4c +be 59 3b 67 98 c4 fe fc ae 40 04 37 ed 58 c0 6f +98 9e a8 ae 5d 40 c5 89 6e 3e 5f 93 6f 89 28 d5 +79 ca 27 32 7f 85 99 d8 5e 85 53 fc 37 d0 0f 80 +44 2b b0 b5 63 76 ee 99 15 18 f2 20 d7 dd c4 99 +f5 fc 41 34 fd 4e 78 ca 96 ed 0a 34 91 1d e3 83 +a6 3c fc 27 d2 00 7c e0 87 65 87 f0 13 4f e1 18 +12 2a c5 2b e6 a4 21 95 3b 61 71 db c5 6a 19 37 +69 7c da ac fd 86 37 3e da 33 5a cc a3 12 aa c8 +82 0d 20 64 8b c5 56 2b 89 5a 6a 70 cd bb 6b 06 +c7 2b 49 bc 7e dc d9 2b 0a 63 6f ac 48 84 a8 cf +de 2b f0 8f 85 e9 4c 4c b5 23 de a2 0b 97 6d c0 +dd 55 75 22 63 6f 72 69 6a 31 c2 7d 5c e4 b1 3c +e1 82 79 30 ad 0d 12 28 fa 57 5b cb f5 10 76 1f +e4 8e 7e 6b 74 6b a4 e9 2d e0 16 a9 2c 47 51 7f +36 ce 16 e0 8f d1 f6 7b 1a 13 a7 6e 5c b7 63 19 +86 08 e5 2a 66 6e 25 b0 1f a9 7c d9 fb 4f 24 05 +83 c7 39 34 07 33 92 05 06 fa 11 e6 a8 1b a0 3f +d6 c2 b5 2a 3b 08 36 bc 13 4f 9c c8 b4 7f 50 d2 +3d d7 93 1b 4d 30 09 c9 89 34 bc f8 e6 90 b8 97 +4a c9 5b a6 50 11 87 5c e4 d5 34 52 81 41 a9 dd +79 f9 f0 6e 50 53 39 d9 d0 cf 7f 0e 05 33 da a0 +2e 53 fe 07 e5 b8 28 10 b2 f5 a1 d1 4b 7c 39 de +76 38 c0 b2 b0 50 fe c4 40 e1 f7 5a c9 08 ba 39 +70 57 3c 7a 8d b1 05 64 44 2f 8b ba ec 28 c6 cb +03 93 7f 0a b6 c8 0a 73 f9 36 c0 f4 12 28 5e 97 +3a 2d 30 15 8d 88 6f 46 65 42 55 4d 62 6f fa 98 +4b 17 3f e5 b7 2f 71 53 bd fe b5 e8 09 72 ac 1b +f5 cc 36 66 4f aa 92 c6 08 c4 d7 b5 4d 68 ce ea +c5 5b a6 19 40 24 d3 a0 a2 51 6b f3 54 5b 69 c1 +eb 38 55 43 74 6f fb ec 1f 83 e0 82 74 96 c5 e3 +17 4f 4d 05 a5 7b 4a 4d ef a5 df 1b 8b 70 f7 17 +d5 f7 e5 4c 43 ed dd 41 8a d7 df b1 c6 8c d7 19 +98 3a 31 e7 4e 12 90 f2 ca 91 92 74 cf 72 4a e5 +0d 11 15 fd 27 1e 8e da f7 0a b5 c2 c2 dc ce df +33 6f 1c ef 41 6e cb 46 ac 2c dc 4b 65 44 2c 2c +83 b4 7b 97 ef cd 1e 97 6c 69 45 11 98 64 87 c4 +0d d8 65 cc bb 47 eb cb 6a f8 1e 8f 3c 0c 54 cc +cc 4c 3b e1 5e 43 f6 b9 ed 01 be 4d 69 50 6e 7f +f4 12 8d 54 e9 23 77 22 76 92 71 93 bb 3a ac fd +9e 60 11 e2 bd b2 3b 35 39 75 9e 50 f1 14 0d c4 +2d bf 71 49 22 38 39 3d e8 87 18 37 ab 14 e4 39 +72 33 27 ac 24 67 3e 26 35 10 ac 1c c3 ff 2a 08 +be 05 41 9f 66 2a 99 48 a1 40 5a 56 1c bb be 1e +61 9d 8d b5 3c 67 aa 82 50 f4 1d 3f 97 4b fd 9a +ce 41 d1 e3 ae 3d 73 02 5a cb 37 bb e4 fe a4 69 +72 96 7d 21 0d 83 a0 4d 2d d1 9c 38 07 10 c7 87 +7f 3c 16 9b 9d 34 22 05 3f f0 1a cc 23 ab ad 58 +42 ac 80 e5 6c af 37 a4 22 ec 18 1e 4a 48 43 3d +97 7d c9 46 4e 19 5e 16 16 38 96 0d 06 e3 0b cf +d6 d1 eb d5 c3 62 11 5d 17 ab a5 f3 35 08 b2 55 +c8 ad 25 09 60 8e 78 bb 44 1a 37 df eb 38 92 75 +c8 6a 2a 52 8a b6 6f eb 4a 93 c2 f5 80 6e 0e 2c +ef f9 0b fe 40 4b 39 2a 88 67 37 17 d2 a6 a9 d1 +a8 5b 23 0d 80 56 1d d7 c7 42 a8 9a 3d 21 ac 68 +f1 62 ff 8e 56 e0 47 0e 72 1f c9 e4 0d e3 13 2a +0e d5 15 a9 70 61 32 48 7d ef 7e e0 7c 9f 34 9f +1c a0 83 5e c7 ce b8 4f 5c 42 74 17 99 a9 be 1e +92 70 25 de 06 47 c5 c0 78 5d fa 15 c0 ed 76 fe +49 9f 63 e2 6e ac 27 6b 14 d5 6d 1f f4 cd 72 62 +fe 24 4e 70 3d 2c 90 e4 bc 66 3c d2 74 46 ea 00 +d5 88 65 06 33 29 87 88 86 37 ee 33 92 f2 b4 64 +fc 04 10 5f 75 d3 a3 36 51 b0 9c 36 e5 bf 35 f4 +a4 77 91 fc fc b1 12 56 25 6a e0 86 3e 61 89 06 +cb b6 21 bb 0a e1 57 7e f5 ba c5 1d 8e 19 01 68 +4f ac 76 30 dc 7d 77 a1 56 1b b3 51 3b 60 a6 73 +32 e0 37 e0 38 ff 98 9d 28 23 19 1d 57 58 17 22 +b1 43 37 79 57 77 ea 56 ed 1b 69 a3 94 27 0e f8 +e8 b8 22 49 18 36 21 f7 a7 75 e6 57 4c 6c a3 12 +eb e7 3f 78 59 31 cd c3 32 37 e3 b1 8d a9 e6 2c +37 bc 26 a6 48 b0 45 65 d5 2f 3c 9e 42 56 fc 33 +eb ff 5c 3d 44 3d ee 0d e7 c6 90 0d 37 26 ae f2 +19 47 88 1f b4 4d 06 ff c1 61 f1 29 70 01 c3 f9 +f3 68 7f 1f 86 1d d2 07 fb 88 d4 78 13 da b3 dc +e1 cc c3 c0 5b d5 37 7b 09 10 2d 58 e6 e6 61 dd +af 40 5a c1 92 f5 5f 65 98 2c aa 54 e2 98 19 0f +68 97 8c 8c 83 50 c4 a8 16 c0 17 88 47 f5 cb 23 +ea c4 4a 19 43 b4 1f 3b 54 de b2 7d 0c 46 d5 55 +ff f7 ed 16 ea 67 49 01 71 71 db 71 92 af b9 03 +5c 9f aa 2d 65 82 f4 13 34 74 18 7d 17 41 aa 1a +9a e3 0d 83 8c ae 5a f1 a6 e0 0f b4 d8 54 2c 8e +a3 83 c2 f8 3a 9c e2 66 e1 0f 9f aa 2a 2d 87 41 +31 d5 65 93 4f 2e da 0c 51 eb 48 4c f1 3e 4d e6 +bb f2 94 7a bd 63 36 cf 20 df 12 33 35 23 fa d9 +99 f1 68 fa 21 55 53 ea a8 24 9d 8d a7 80 be b3 +b8 31 c6 1a 74 07 e1 65 c9 39 ae 96 91 b0 90 7f +c5 ef 92 74 76 50 23 2b e9 c6 a3 cf d6 ae 24 a8 +5d bc 01 ca 9f 5b e1 67 16 60 93 0c 3c 24 eb b3 +b3 52 4b b5 48 72 d8 95 8c 54 2f 8c 9d 46 63 28 +87 30 c1 d0 ec 44 a9 48 7b a9 35 09 b7 ea 2f 91 +c3 6a 17 65 01 2d c4 7a 97 ba e3 2b a8 c2 4b 90 +cd f7 1b de 88 cf c3 42 6a 17 be e7 46 18 08 f0 +79 f1 e4 43 15 3f c8 23 55 f3 00 b5 b6 3a 2f 01 +2c e1 5c d7 ad 71 89 0e 65 82 ee fa 50 78 e8 53 +ff c5 5b fa 6e da c3 0f a5 ab 42 e9 89 87 d4 94 +3d c7 39 a9 22 44 63 11 8a 61 4c f8 a5 28 4e 77 +44 df e0 7b d1 d1 9b a2 a8 c3 15 be 5a ee bd 79 +d0 41 a9 88 08 40 7c 9d d0 a9 85 36 9d 6d 2e 7c +ce ce f5 88 43 c5 1d 0d d5 5f f2 ac 53 b1 d7 1c +e2 9a 6c 96 57 95 5a a0 e2 5c 9d ae bb bf df b7 +12 20 29 21 60 eb c1 fb fa 22 0d be 07 0d 8d 2a +76 2a 0b ae 44 99 87 e8 bd 6f 84 df 6d f2 1d 23 +f5 d1 3f 4b 33 d6 48 14 99 2f 10 5f 15 1e 75 34 +b1 67 8d 31 d6 04 ca 1f 7b 50 ad 16 20 1a 47 b1 +5c cc 44 cf 05 96 af f3 ae 8d 1c 07 bf bd dd 5f +30 52 d0 2b df 9a 74 fd f1 6b c3 69 ca 5d 4d 32 +21 0f 22 da c3 8a a3 4b aa bb 50 39 35 46 47 43 +59 82 d8 52 77 51 6c c9 ae e7 9a e1 99 4e f1 ff +df 19 f9 76 a5 4e 7c 5d c0 b7 5d b8 90 27 fa 07 +cf a6 ef 72 08 59 31 b0 25 7f 05 42 a4 6f 81 2a +4d 00 01 96 97 78 27 76 73 c5 e5 b0 a3 71 5a ac +3e d5 3a 4b 46 bb e6 86 f8 9d 33 c4 bc 06 7d 2e +a0 21 ab f6 86 b9 48 2c 90 9c e4 00 15 fa ec 94 +bf fb d5 f4 e3 11 b4 cf d7 88 fd c4 d4 db 46 eb +03 1b 9c e6 fb 26 75 58 27 6c 6a b4 7c 29 db 93 +25 9b 07 57 56 bd b2 d1 5f c0 44 33 4d b9 38 fa +ec d5 39 a4 f2 6a dd af e9 5d b1 d9 94 7a a7 62 +6d eb a2 ba 30 22 a3 12 7f 32 53 19 d3 84 9a 57 +4a b0 a1 b8 fd 59 e2 61 8d 78 af 51 d1 66 c7 55 +af 43 8a da 4e d4 a0 1c 35 3b 27 45 0e eb f8 eb +f1 43 06 88 45 42 a7 7b 8a 65 e6 cb 6f 16 28 ea +31 37 fc a6 12 88 fb 8f f1 3f 81 2d e1 9c d2 43 +94 b1 21 8a 3d 15 3c 10 80 6e 81 d7 0c dc 07 45 +1d bb 52 dc 4a fd 70 4a a9 e4 17 89 5f 7c 5f 2c +fc 66 46 cb 52 60 31 22 c4 50 3a 41 98 49 f2 e6 +a4 ac 63 4d f9 c2 98 45 14 e6 37 de ee fa 48 8a +f7 c8 3d f2 24 8c 46 74 a0 a5 7b 47 24 27 5b d3 +24 ed b5 54 92 16 66 04 7e 6c b6 02 73 c5 07 60 +b8 b4 93 3f 2f 17 65 8f 03 11 10 2d 42 ed 7c 79 +9d ab 1a 82 87 70 ba 54 a9 36 72 af 58 f9 55 64 +ac d0 d9 d9 6a 1e ec 10 87 54 20 20 7d 03 7b f3 +32 2c 3c 9b 5a 50 dd ac e9 db 3f 68 1e 6b a3 55 +ef 45 8d d3 25 d2 f0 af 3e 09 0d 9f 21 4e 9b 58 +fa 5c c8 c1 44 f0 72 e2 94 b8 d6 65 20 09 62 4b +e6 bb c5 a8 57 86 96 4a 85 87 50 0b a8 7b e3 8f +5b 43 c7 2b 36 6b 0c cf ca 2e f2 b3 5b db 49 73 +6a 5c 07 1e c2 d2 b2 92 6d da e8 19 0a 8c ad b0 +06 ad 7f b2 22 44 03 8f 29 62 80 ec 00 fc ee c0 +76 10 4d 64 e9 e8 3f 80 b8 2d 62 1e d5 f4 ae 0d +1b 14 a9 a1 67 e0 ff ae 80 b1 90 3b ab 5c 80 00 +bf 89 ab 4e 83 92 33 8b 84 62 10 7f 8e b4 bb 6a +fb 89 31 f6 da f8 4e 73 56 0b ee 1d f9 81 03 42 +0e da 02 d2 5d c4 e8 9a 73 95 cb 17 b0 19 fb fb +b8 da 47 04 b5 e8 d8 b6 b5 ae 72 23 89 54 35 99 +f7 d0 65 17 3d 08 9d c1 a8 77 9f 00 75 0d a1 00 +d2 47 01 8b 28 73 09 e7 2e 0b ea c9 dc ea 5f 57 +12 57 de 8e 39 d7 07 ab 76 2f 20 23 75 0f 90 9c +a4 8d 4b f0 f3 0d a1 97 95 ab 50 fd 60 11 84 7a +9f de 5f 84 47 4a 4b a8 00 37 4d a8 4b 63 43 a9 +12 82 f2 58 08 a1 a5 76 5f 9c bf 95 97 89 53 e0 +b7 e0 b9 31 00 89 e1 25 03 33 c6 d5 37 d7 05 08 +38 f5 3b aa d2 f1 71 a5 0c c8 38 e4 77 fb fb 40 +b8 59 14 39 b8 f2 15 5a b7 fe 2a b9 2d cf c4 a1 +fb 71 e1 3e fb df f3 66 97 10 45 80 e6 13 b2 81 +bd 48 0e fb 56 34 e9 d4 ea 35 a7 db d3 79 5a ea +13 ae a5 1e 87 27 4e ce 85 30 87 22 36 62 95 55 +a3 85 69 7a 2b d6 20 01 9f f8 43 8b 72 ad f1 27 +47 1d 85 ab ba 25 47 f9 fa 6c a0 23 23 e7 81 dd +94 f6 49 87 45 58 81 ca 00 f9 d1 ae 8e 4b fc 76 +1f 91 ab 57 7b c0 79 8a 1f ac da 58 b7 ce 07 40 +d8 5c fc bd 5f 00 33 bd 68 b8 c6 47 0e 5b a0 12 +d4 54 3e f6 9c 0c 09 ed ae 36 da c3 f6 3f ca 1c +eb 4c ac 95 b9 74 d6 df 8a b1 31 d5 f5 9e 3a dd +e9 b3 a0 cf 01 b9 cd 59 b2 74 92 82 48 c5 4e 27 +b2 1a be a2 fd 98 7f 88 6e ee 0f d8 86 e4 14 89 +18 b5 91 c0 bc c4 da 7b bb a4 35 57 76 bb 38 84 +f9 8e b7 a0 5e 0c 44 dc b4 0e 4c f4 82 79 77 5b +1a 19 2b 87 f3 4d ee 1a 3a ba ec 83 68 20 b4 40 +b1 3f 1d 92 1c 8d 22 e2 ca b6 86 27 58 f9 d6 03 +d7 af 6a ae 93 23 b4 39 4b e6 0e c0 0f 0e ed 29 +9e a1 7a 58 4b 40 74 48 8d 2c 65 82 73 6e 8c 00 +2a 05 2e 79 0d ed 14 49 32 00 dc 83 f5 66 63 43 +25 f8 23 b0 35 91 5c 5f 70 6a b1 b2 76 99 23 2d +be 2f 6a a2 f0 e8 3d 18 4b 7f 68 f6 4e 37 bd 9b +df d8 24 59 2c 15 a8 1b 8d 70 2c 40 98 c9 a4 25 +34 0f f3 97 fd 23 48 be f5 99 7d b2 cf a7 0d c6 +30 0c 56 cc 06 f7 14 de c3 fa f8 82 39 0c b3 3d +ff bf 89 b1 91 ea 10 33 6e 35 34 e3 c7 bb c1 65 +b1 72 1f f9 b3 85 26 46 d6 c4 fb 33 ad 81 0f b7 +75 90 49 f2 be 39 bd 48 0c e0 14 35 57 82 cc c8 +ce 62 18 52 43 99 08 5e 00 ee 90 1c 85 5d 91 44 +32 2f 77 f1 ee de 7b 06 a0 83 d8 53 2f ed 0d e3 +49 93 56 c3 89 da 96 e4 21 8a 08 c0 7c 2d 3e 4e +57 3e ba fd a6 1b 68 6f 9c 60 d8 be b4 7a cc 7a +86 b3 b9 77 e3 6e 88 dc a7 fb ff 86 d1 5e 40 e3 +3c e1 89 32 7f 6c f4 41 76 14 cd 63 d8 7c a6 ab +58 db 7d 0d a1 a1 4e 34 a9 f0 db 0e a2 11 3f 54 +30 6c 5b 77 e4 a9 79 29 c7 31 50 0c 8e e3 5e 7b +27 34 44 59 07 9f af af ec e5 64 d6 d6 a1 b3 02 +9a 12 8c 42 5e 74 af 95 51 4c 2f 2a 50 35 4a 6e +1d 78 b6 b9 89 2e d8 50 18 d0 32 7a ff d4 c4 19 +66 16 b4 b3 f4 f0 52 50 a0 40 d5 ba 00 c9 f0 2e +3f 53 f5 27 e6 42 a4 33 2e 51 f1 f0 cd b4 26 cd +4d 13 c8 ca 3a e9 eb c2 f8 e1 48 aa 22 f5 0e cd +92 13 59 d2 f6 b2 e9 41 28 01 25 78 23 f9 3a 3b +46 aa 61 2b fe b5 dc 9b 70 01 c6 c0 13 bc 34 31 +f6 71 04 4e 29 75 1e 07 02 f1 34 42 fd 0b 08 56 +46 04 dd 86 38 f3 f9 cc 52 29 3a b3 a8 02 23 2f +ab 06 d0 8f 37 dc 52 6c f2 1c 13 bc 04 c7 e9 46 +57 55 60 82 40 7f 2c 13 9c dc c4 41 6c cc 82 37 +21 32 38 70 dd 5a 49 a4 cb df 9c 93 0b 69 66 79 +ea ce bd f6 e9 e5 df 5f e6 64 2c ca db 7e 75 c1 +4c 31 b2 0a a8 9a 04 99 0b 6e 3a 96 26 0e da b4 +b6 87 ce 7b 99 ef 6c 32 27 03 2a 66 1e f6 2e c1 +39 da 4a 95 5b 94 1f 05 81 19 a0 1d 55 b9 eb 49 +50 59 cf 63 34 4d 03 5f cb f2 f1 d4 b6 38 63 85 +95 c3 d6 a9 42 70 a8 c2 73 bc 6f fa 9f 66 36 ef +11 ba 0e 3b 1e f1 8a 98 1f a1 fb 37 0f 4c c1 66 +8f 65 15 d2 67 89 d3 cd a1 44 61 92 90 cc 85 d2 +7c d9 20 9e 25 13 cd e6 2b 1a 04 9a e5 c4 c1 06 +df 4d ad 48 59 69 3a c0 39 9c 5c 1f f5 36 9a ae +3b 16 07 9f da bf 54 e7 90 9f e2 df 08 18 78 86 +96 e0 ba 52 3e 83 c4 ac e6 da 88 b9 8e 48 ba 54 +db 0a f5 8c 72 8e 3c db cc 06 56 43 b3 40 c8 e6 +75 58 89 fc 84 fa 17 81 c5 19 d9 20 f4 d0 1f 11 +a3 9e e1 95 b2 77 8b e3 d8 6e e8 80 64 36 91 71 +44 87 cd 86 90 93 c0 0f c0 2b bd 41 27 b8 34 8a +3e 33 7e 5d dd 50 ef 7e 86 33 96 0c 85 3b 44 53 +f2 df 91 02 88 aa 81 05 b0 e4 c3 dd c0 82 20 f8 +74 20 41 d1 01 f2 16 2c 6b ad 1c 91 3f fc cd 56 +e1 a5 fa 01 20 62 55 c0 11 81 47 0d 4a 85 36 e1 +b6 68 f5 a8 42 14 63 b5 e0 29 7b c3 48 b8 01 1f +c9 a5 8b 7b a3 b2 6f 61 c9 73 69 de 3b 3f 9d 45 +41 14 8a b0 33 13 9e 99 04 d1 c0 9c 74 04 2c 3a +11 bf 1f e3 34 3d 3e 4a d8 a8 2f 61 ee d7 36 82 +09 e9 36 4d 8b 0a 7f d5 b4 8c 94 cb 60 3c 78 3d +f8 46 84 16 41 31 26 53 67 ac 7e 83 94 de 5b 99 +6a 4b 38 94 0e 96 6e ca 69 1f b2 e7 25 3c 98 89 +c4 d0 bd 5c 5b 54 37 9d e2 bf 83 ca 2c 16 26 9a +da b7 ee 5a cf 17 33 6f c7 eb 28 0a 9c 1f 46 23 +ec 2b cb f1 52 70 93 f9 8f b9 11 47 69 90 ea 54 +3f fb 11 78 0d 34 43 93 29 9c 6a db 11 15 43 5c +64 10 b3 ae a6 a3 d4 60 bd 88 98 3d 2a 44 d1 dc +ce 28 2a 7e 6d a2 d6 79 1c a1 cc 2c b6 82 06 2e +18 27 3a a5 50 ed 15 ef 7c f2 e5 a8 c3 b7 12 fe +7a 6e 30 f2 d3 d5 2c cc 67 a2 cf c3 48 30 3d 20 +a6 24 2f 65 61 06 3f c3 26 3d 5e 3d 58 bb f2 ae +90 fc 12 ee 88 e5 55 c8 4f bf 96 97 3e d8 ce 6d +5d 99 ed 08 2b f4 89 e7 8e 5e 14 61 6c fa 83 71 +82 c7 7d 13 dc 76 a1 f6 8e b5 6d f0 4f 43 41 2c +b5 10 25 83 b6 40 0b 30 6d ad 8e 11 cb 72 de 3e +86 e0 90 21 30 18 d4 58 d6 a1 32 61 c1 0d c8 9c +22 23 0a aa e8 28 51 16 3d d7 4c d0 59 bc 2c d3 +cc 44 30 ea 21 c8 d9 52 5a 14 c4 3b 31 d3 ac db +3a b7 6c bb 5c c1 29 94 69 3c 0a 63 a6 eb 2f 7c +d1 33 3f 8e fd f5 9c 97 b9 d1 65 ac 01 55 1c dd +56 d9 37 30 f4 e4 df 7f 27 e8 d9 21 f7 13 5a 3d +d5 36 a4 bb 7d f1 40 44 7d 9b cb f6 c3 6e cb 25 +d1 d6 ca 07 6a 66 fd d0 49 15 ab ff 9c 01 f9 e1 +9d 6f 2d 5c 71 71 71 cc 15 c4 91 b9 79 29 ca 84 +e2 11 04 bf f4 af 51 88 6e 19 b0 58 a0 42 1f 9b +e9 a7 af a4 9d 8b 64 d3 04 d9 88 77 a1 60 4c 95 +32 f7 1b 92 32 41 5f 52 98 7e e0 26 fc 02 8d 87 +58 7b 59 bd 09 2f b2 a8 2c 2a 12 63 88 38 71 85 +4f 92 fd 8f 5b 16 2c 23 80 a2 4a f7 c4 c6 b5 bf +36 3b a8 79 19 59 ee fa e6 fa b1 d7 a6 6e e0 d3 +56 d9 a8 98 14 33 a1 6b 1f c8 13 a7 64 76 5e 97 +74 5a cc d9 f5 1c 5f 79 22 f2 12 09 28 9c 56 76 +11 02 c5 cd bf a4 45 25 d6 d0 92 3f 7e 4b 34 1d +3e 6b ab b7 2f af 13 6c 7b a6 d9 6b a3 1f 5a ed +b5 e7 45 55 3d bf 93 b7 4d f1 35 16 d4 48 9d 87 +8b 9a cc 75 20 42 c6 1c 23 f7 46 2b 57 39 76 e0 +1f d8 9f 3a 33 fa d2 bf 02 cd 29 bd 74 2a 0f 51 +f0 c0 0c df 4a 1b eb 5b e3 ec b4 df 7d e2 bc e4 +a5 c8 ea 30 17 5e 49 80 9f 4a 1d b2 53 9c 3c 0c +1f 7c 85 6b 6f f7 1e c1 1c 7f bc b8 1d 3f c4 03 +f1 f4 a9 18 c2 a3 38 2c b9 75 6b 5c 8d 58 26 7c +71 fa 12 9e 6c 0c c0 f6 01 4b f6 77 e3 4b 8d b7 +df 13 b7 d4 ab 91 78 e6 a7 fa 03 3c 4b 2a ca 9c +e0 9c 17 33 bf 8b 58 e5 bc b4 9e 01 73 cd a8 50 +8d a4 65 93 eb 45 f1 b8 1a a5 2a 90 97 91 fe 31 +b2 b6 33 14 42 65 bf 24 f6 fa 5a be 1f aa 02 ef +55 9e 57 98 93 21 88 c9 1b 82 db ef 59 35 39 89 +72 d5 ad 0c 2a f9 c4 34 31 d5 8b 28 6f c8 19 54 +6c 40 5a 47 e4 c7 7e 95 e1 01 b4 a1 d5 e9 e1 35 +26 5e e6 0f bb d5 0a 38 c5 62 38 8a de 01 5a 07 +61 9b e1 b9 6f ee 3c ab 41 b9 dd 95 29 83 4d 0c +24 49 98 f2 90 5d 85 d9 ce 83 24 3c f8 21 5a 62 +d6 2b 74 89 a1 35 17 15 19 aa e8 24 f5 ab 09 36 +70 20 63 f4 4c 53 0f 2c b3 ac 4d 58 bf 44 c1 0a +58 47 eb ac c8 ce aa 23 05 85 5c 73 2b f8 1b d8 +5c 9b 1f 00 11 d8 7a 66 81 3b 39 f4 41 97 ea 69 +16 07 86 02 80 80 59 aa 59 fb 37 e3 cb 7f 6c b1 +dd 11 ec 68 21 18 f0 e7 38 ed 45 d0 3e 6d d1 3b +39 21 62 1c bf 7e 6e d2 73 f6 d6 c7 7a 5d 8b 1f +bb 29 90 67 47 63 c1 09 f5 6a 99 34 7d 42 db 2f +75 46 01 5d af 19 34 53 69 e8 30 24 d5 a5 36 2c +9f e6 ee 01 d3 9b 5e 52 1a 5e 9d 93 0e 2f f2 36 +ac 20 11 c0 e8 63 01 97 05 a5 2c f3 c3 59 be 0a +b3 28 f6 77 64 b5 40 14 d7 b9 68 6a 85 7f da f0 +a0 ae dd 67 ab 52 2a b5 76 57 c8 a6 47 f0 2a 0c +d2 82 9e 47 c5 09 46 ec c0 c9 83 e3 c0 5c 8c 54 +6f bd 55 65 f8 f6 38 8f dc 4c 69 f1 9a e5 a9 85 +ac 2b cd e6 53 55 85 d1 c2 cc 32 b3 42 39 f4 11 +f8 b0 85 a3 61 0f 55 50 26 71 2a 3b 65 23 77 2d +53 0d e2 3f 7d 80 69 60 61 61 d1 74 08 74 17 3f +84 2a 29 eb bd d1 56 5a c4 55 32 d1 f8 cd 14 5b +ab 99 18 b5 4e 7b 23 47 32 aa 2b 41 39 3c f3 87 +4a af 11 84 e5 b4 71 fc 38 c5 7a 66 85 81 56 5d +fc 42 20 8b ef b6 6d c8 4e a5 5b 64 eb 9c b7 3a +5c c6 d6 81 5c 6c 25 90 a5 bc d4 cf 6a b1 a5 43 +32 44 c4 4c 8a 8e 45 0b e2 17 d1 cc f6 6c 85 dc +d1 7b da 34 0c 0e 7e ff 3a 3b d7 3a 78 b2 e0 9e +24 b5 cf 34 9d 95 e7 13 d4 02 16 ac 64 41 b8 ac +8d 3e 86 aa af 11 00 2f bb 68 da 93 b9 ed d2 c9 +3d 22 4e 9c c8 9f 24 84 0a e1 49 13 98 65 dd b9 +ac 0c 81 78 7c 11 e9 66 69 cd 73 8e c6 92 c2 3a +ca 22 01 d8 96 fe 69 44 30 9b 68 2d ff f8 e2 e1 +32 b3 ab f6 a0 c1 4e ba 92 2c c1 04 3b 69 2f 84 +e3 ac 7b 1b bf 0a 8c 97 0b 71 81 4e e1 c2 e7 69 +c3 05 fa 30 06 3c 61 b7 56 b4 42 3e a8 0a 28 9a +37 e2 01 4e 67 31 9d 44 9d 8c 30 43 2f d9 5c ec +a2 59 cb fb 35 f3 8e e4 90 4e b7 f8 72 26 58 32 +53 a4 f0 55 55 86 82 d4 95 05 86 7e da 5f ce ad +a7 ad 7b 88 81 44 e0 1d 7d 70 78 be d5 ee f9 96 +df 7d 29 ca a3 70 0c f6 d3 61 79 a3 b5 66 83 8b +a1 b2 82 46 43 8c 4e 83 8d 25 68 ca bb 4f 6d 5d +39 83 35 ff 18 a7 9a 96 de ce 0d 79 70 b9 3e 32 +50 48 52 dc 71 6d b8 e4 03 ba 14 08 59 78 e2 41 +0e 9e a6 af 3b c9 cb 89 27 7e c4 27 9b ad 7e 0f +75 35 85 c5 4a ca 4c 93 e3 66 e6 59 59 d1 b5 32 +db 3e b4 08 48 e5 13 f1 05 83 c5 ec f4 e7 b3 c0 +2b b2 f2 14 07 7b 20 1c e5 c8 f2 86 42 db b3 93 +5f e1 94 ed b2 65 b3 aa 48 b0 79 0d 2b 29 02 cb +5c 11 0d dc 2c e9 15 29 e0 61 f4 b0 b6 46 91 26 +6c b0 a5 e0 4c 12 13 54 12 84 43 c0 a2 92 24 1d +22 3b e4 8f b9 41 8a 60 5f b3 42 91 0b 87 19 32 +95 d3 d4 91 b4 09 49 6f e1 d1 51 47 be 19 c6 f8 +be a8 9a ef b2 68 66 5f 74 f0 4a 2f 70 c1 52 27 +47 05 4c f5 21 6d 5b 44 c5 a6 28 1d 6d da 15 01 +28 55 c9 b3 2e c0 48 00 4f c5 c3 a5 47 9a 99 bb +a8 6e ae 77 00 bd 63 63 39 29 48 69 83 c5 49 24 +2b 77 e0 28 67 f6 b0 72 c6 1c e0 91 7b 8e 54 aa +5a 86 e7 e8 22 d7 75 e7 93 66 0e 8d 3c 85 ed ab +c0 43 f0 27 33 9a 15 e4 b8 d0 64 4e 9d 7b dc e9 +f6 77 28 41 63 5c 3a 89 76 22 25 53 44 c8 d4 97 +1f f6 f4 00 b8 12 5c 77 a1 47 ea 85 3e f4 48 ae +e9 1a 25 8b 8a e5 b4 ed 20 52 1f b9 0e 32 1a db +92 78 55 e3 d7 49 5e 13 36 95 f1 a3 99 e0 f3 47 +1d f1 8f d9 be 29 f1 cb fd 74 11 79 f1 44 7d 8b +95 f6 af af 59 1a b0 48 14 39 57 2e c3 02 a9 d4 +f6 c7 d0 13 58 d9 9b 37 e3 4c 93 d6 4e ac 3e 82 +34 b3 8a fa bb 5a 56 33 38 d4 b3 d5 f7 f0 bb f9 +03 1c 7a 6e 7c 8d dd 46 38 aa 64 4d 19 56 93 db +89 0e 8a f6 c1 a6 cb 0d e9 46 01 13 90 56 fa 3f +b7 84 bc c2 13 a0 02 fb 86 5c 93 3e 83 8e 48 20 +7b d0 b2 a3 1a 54 ba 4f 00 89 1a 93 4c 29 d3 da +61 36 7d f6 0d 13 0c 35 21 db 46 83 e6 65 77 09 +11 aa 82 4f 0b 33 65 40 94 fa 15 af ec 04 10 2a +2c cd b8 26 32 5f 1c 43 cc 79 30 be 0d 2d c4 0d +5e fc 2e 49 bc a8 00 9c 2d 8d 7b f7 ad 79 3d d8 +59 90 50 d1 92 68 41 ba 74 65 37 0f 2d 60 74 93 +c1 5a 07 9f b0 e7 15 cf 78 b2 25 07 a9 63 56 ac +78 10 12 e4 92 17 c5 7c 85 8c b8 31 45 27 bd ed +b6 e0 2f 52 bb 16 0b 37 62 7b 10 8c 5c b3 f2 f3 +20 b2 b4 85 c0 e2 b1 7c d5 70 ef 9d 2f 8d 76 b4 +d7 c3 05 89 ce 6d 33 3d 0a 0e 64 65 63 ef 2e 10 +b4 c3 85 16 4c 29 d2 f8 95 8d 68 07 30 0e f1 a3 +ad e9 00 8e c1 aa 66 8f c2 40 87 5b 8d 71 f5 f8 +56 cc 77 60 e4 43 15 83 7a db da 4c 89 b4 05 19 +7e 40 87 50 c4 a6 02 4d 9d 03 39 38 0a 6f d9 49 +fa 13 07 4f a7 5a 36 19 04 40 75 05 bc 77 ba 52 +a5 1b f3 6b 1c ab 5d f8 2f 0f 3c 74 d8 91 b9 be +81 a9 8f aa 62 a5 1a bd 96 49 2c a8 58 1f 4d 3d +1c 84 0b 85 0c 53 f7 b9 07 4a eb 5c 98 b6 ca c4 +13 f8 84 f7 17 be 7a 49 b4 00 15 2d 69 a0 f9 49 +7e 0f 45 18 ff f6 25 53 cd 0f fd 59 67 4a 6a fc +c6 21 3f 21 54 5c da 5e cd c6 9c 3e 91 b4 2b 18 +70 8d a8 aa 96 12 23 f5 52 ab 26 03 d7 2c 96 f2 +09 df 49 1d e5 56 db c7 b5 52 f9 d1 63 f4 89 11 +8e 5b 7d 22 76 c6 6e cc de 49 d0 62 23 ec e9 4b +03 56 e6 95 df 2a 48 45 22 2c c3 cd 63 ea 0f ec +f1 4a 21 1a 76 c9 af 86 6e d4 6d 3d c4 d7 6f d3 +a9 76 21 64 b9 74 3e b9 1d 38 11 c8 61 2c 8e 0a +9c 08 63 ae 38 3f f4 f6 75 43 87 f6 64 4e ad d7 +ed 14 59 1f d3 f3 64 db 4e b9 26 08 e3 fe 53 47 +7c 4f 9a 6e ad fc 2c 20 85 54 31 94 60 05 c2 e9 +2d a1 e3 f4 0a 71 c2 a2 83 e7 cc b9 41 8a 71 1d +75 c9 dd fe e3 32 73 a7 b6 07 f3 56 08 7a db e0 +8c 06 b0 bd 12 33 09 6b 23 4b 7a 3f ed 6a bc 6f +83 59 5b 2d 62 eb 43 2e 2e 3b 5e 96 76 3a 88 6d +66 a7 9f 81 5d bc 89 3c 48 b0 cb 85 2e 97 ee fc +18 7b a4 fc 73 a6 60 89 b6 97 e8 0d 8c dd d1 33 +53 e2 0c 1b c3 68 14 4d a8 78 15 04 c3 1a ff 8a +be 01 ed 61 f9 45 91 6d 69 91 e3 a3 df d0 bf 64 +49 61 d4 74 9e 09 8c ae 3b a5 40 2f 85 64 1b 98 +2d 86 09 d8 ae ed 53 f1 cd 7d 88 29 30 07 a8 1e +0a a0 1b 43 21 14 a1 ab 40 16 b8 01 d5 ba 47 a3 +1e 69 b3 43 72 f9 be 53 46 0f e7 a0 af 72 97 94 +36 15 c9 54 40 ba d5 d3 14 ec 67 d2 f3 e0 9f f4 +43 10 bf 07 86 51 fa f1 f3 f4 54 6f 44 eb b0 b3 +54 96 41 b3 95 b1 c3 87 8c 34 7d 15 30 0d 53 a2 +37 bd b2 23 4d 9f 32 3b a9 81 89 34 40 23 94 cb +e0 f4 51 20 1f 8c 01 74 d7 f3 7f c8 c6 a9 16 34 +27 79 ea f2 ec 85 49 69 2e 96 d8 82 fc 82 b8 2c +b3 35 f9 59 fa a7 9b dc 70 f9 d0 6c 9b d4 d4 40 +d9 32 88 4b 3d 6b e8 21 6c 87 97 99 20 16 9c 9e +37 9f 8d f9 66 d3 15 8a cf 26 27 1a 72 85 31 f0 +65 e9 e2 f6 01 3b 58 91 f1 7b 73 f7 1b 18 d1 4a +51 48 64 97 5f 98 a1 06 14 ef 0f d5 31 2b 28 74 +ac ac 71 2b c4 c5 e3 59 92 f3 c0 02 0f 31 a7 98 +86 38 23 d1 f2 59 24 11 80 8a 65 d9 c1 5f 5d 25 +07 e5 30 55 1e 6a 36 65 3b e6 04 07 e7 89 12 8c +7c 23 af 5e 7d 76 3d fa b6 eb 50 bd 16 f5 14 51 +ce 1c db 1f d1 53 a4 14 c5 96 39 4c 9d ec a7 81 +6a 93 76 cf 9f cc 09 9a 34 2b 4a e5 6d e6 34 21 +42 59 65 a5 b0 8e 49 3a ad 0e ef a0 38 db 26 9b +f7 d3 88 5d 05 de 58 d8 14 87 46 8e eb f1 37 92 +4e 5d 68 16 b0 ca 59 c9 2f 85 a0 37 57 16 02 00 +46 18 52 e8 b6 2f 30 c1 0c 7b 79 84 cb 42 6d e7 +76 88 49 a0 ac 6f 93 55 f5 d1 49 1e 2e ca 09 c3 +c2 e1 7e e6 22 c5 2d c0 05 18 ab 12 f7 41 5b 3f +67 cf f3 f4 2d ee a1 c6 56 a0 bb 3e a3 f8 77 83 +ef 94 c6 70 20 6b 5d e9 01 5d c4 20 c4 ec 72 8a +c8 a8 27 e4 9e 1a 73 67 6e 63 cb eb 2b e5 6d 88 +49 17 1d 59 2d 37 cf 06 66 66 eb 42 34 01 94 b2 +d1 74 a8 7d 61 03 82 a7 0e 3b f0 46 1b 33 4b 4f +f7 23 8e 6f 79 62 2e 41 8d 5b 67 24 89 38 0a 4a +46 50 e6 e4 b2 55 db 37 25 72 5f 72 de 4c dd c6 +ea 84 a8 ec 33 74 58 d8 17 15 40 2d 24 57 c5 5e +9d e5 51 28 2f 1d 83 b4 f3 fa 29 3a e7 81 2e b5 +91 a7 2d 82 57 ff 55 e5 ec 91 07 15 c7 86 0f a1 +7d c2 cf 50 a7 d6 5d 35 cc 3f e5 67 73 c5 bc a8 +93 93 0a ed 1d 04 cf 5a 3c f9 5e 92 8b af f7 22 +0a 3e 92 43 07 f8 2f 98 b3 67 44 77 62 ae f7 ba +2c 46 0c ee e0 bb fa 58 d3 27 38 69 52 c3 69 b1 +b7 01 c3 7c 86 55 a4 46 b3 77 24 70 b6 ac 67 c4 +27 97 b6 f9 4b 36 11 7a 18 11 b1 fe 8d 78 3f 88 +bb 91 bd f0 fb 80 d4 8a e4 c1 88 30 87 f3 51 6f +36 30 61 08 5d d9 38 2b d6 23 28 f1 90 45 ac a6 +fb 7c 31 79 59 33 c3 54 48 41 a6 9a 69 60 1c 7b +bf 49 9e 9c 4b 07 9e 43 ac 85 d5 cd 2a 50 b0 62 +6d 38 3c 8a 75 a7 7f ae a1 22 bc 5e 52 a1 55 6c +61 54 8c bf 7e 74 9e 65 b0 82 e0 5f 4b 3f 19 eb +1c dc ab 32 34 7b 4e 1c de f3 b9 ff c7 64 9b 2c +8c 07 e8 01 f2 6e fc 33 f3 63 61 3b 62 48 1b 42 +82 fc 0e d3 c2 c3 99 9c 47 d8 bf ff c1 83 23 f6 +c1 5f cc 47 5b 6f dc b1 fb f3 62 a4 26 56 f8 f4 +a6 e4 75 e6 2f f7 12 c4 e8 d1 42 5f 4d bb b1 38 +b5 c4 b0 0c 15 a5 6e 95 8e 23 8c fe 95 16 24 c8 +7c e5 59 6e 00 5e 56 1d 15 85 64 ab d9 b9 ca db +6f 25 05 a0 e0 52 97 ee 4a b1 04 cb 25 4e ff ac +a8 4a d5 2f fe 1c 79 f3 62 53 d3 b7 b5 5e a1 82 +18 8e d9 c4 e4 13 e1 60 76 a4 7d 26 07 48 a2 8e +d3 1d 42 c5 2a c0 61 d0 19 09 18 76 21 e0 e1 6e +f8 4a f3 5e 35 22 c8 56 51 52 b9 f6 a0 3b b0 64 +ff a9 28 f0 c7 1a 24 89 05 03 3c e1 1a d4 08 40 +9b 0a 1b f3 16 68 ec 8d 5a 7f 2e 6f 57 c4 62 ab +23 1f 5c ab 7e d0 18 08 70 f9 8e 06 f3 74 98 69 +1d 9e 84 be 0a 5d 89 3c f7 63 17 d2 f6 a5 40 49 +8f 0a b2 59 46 80 94 2b cf 27 7f f8 79 ba a6 10 +41 97 2a 17 d9 22 fc 9f 73 97 2a b5 ea 09 31 3f +64 fb b4 63 91 1a 4c af f6 db 12 41 0e 33 6d de +5e 34 ce 41 cc ad 9d 2f 37 79 36 3d 93 d7 e7 fa +ad c3 6c 41 eb 76 68 a2 30 c4 11 c4 78 72 78 eb +14 d6 5a fe b7 42 37 48 b4 38 5e ef ef 25 cc f5 +e3 3c 86 90 00 8c b6 22 ce 84 f8 04 fd 57 4e b2 +e0 ca e0 15 05 98 06 2e c1 79 08 7b 81 2d f1 03 +e3 9e 92 ac 4c fe 16 50 2a d1 2a 96 c5 f1 03 33 +e1 91 18 1d e5 53 cd 19 46 56 cc 74 e9 3d f3 07 +9e 19 24 0e 8d 31 d0 4c 10 7c 0b 8d 1a 41 9d 09 +40 2b f4 ee fb 70 e8 d8 56 b7 90 5e 62 aa fe 23 +38 aa 3b c0 e8 63 e4 0e 34 7e 59 d2 8b c6 7b 26 +cc 52 1e d1 ed 5e a9 4f 5a e7 3c d5 cf 9a 11 71 +64 3c 06 ea 9a 13 f9 50 77 dc f5 e0 d6 dd 80 de +17 b2 fd 90 f7 f6 c7 e1 78 4a 57 01 65 ac db 45 +49 eb 61 42 b8 7c 14 9a 45 7d b7 4f 3f 2a 97 86 +d0 1e 5d b6 4b d6 56 83 27 51 86 11 57 16 45 d5 +8f 00 ce 8f 26 b5 93 08 4f 2b 87 4e b0 4c 6b 42 +52 76 a9 b7 22 49 44 7c ae 92 f3 ea 35 79 79 bb +38 89 d7 90 a8 44 9a 1d 55 8d 7e f2 a7 66 59 d5 +d1 f3 d7 de ad 5b 63 0e ef 8b 70 0e b1 fc 60 38 +d5 59 51 0f 3c 7b 03 11 8d 04 93 74 41 09 84 1d +71 84 4c 76 85 ba 8f 82 9d cb f4 6d c8 af 7e 99 +d7 a5 29 5e a1 a0 50 c7 b7 a3 6d 55 8b 60 7e 02 +86 cc 81 1f 34 5b 50 1f ba 15 78 80 20 75 5e 00 +69 a3 40 57 15 42 c1 5b bd 0e ce 57 e1 7a 0b 55 +0a f9 22 82 61 25 e1 c1 d6 99 6e e4 49 5b c8 77 +27 1c a3 5f 9a 94 0e 41 ab 01 b1 c8 32 2b 94 08 +d3 f9 2c 25 6d 89 3f 8a 4c 29 fe 2a 8a 3e 49 1e +99 0c 89 91 66 78 30 e0 8b ae 4e 8f 56 37 cb 73 +ff 26 58 49 ba 49 0e 54 9b 55 c5 4d 8e 8d 5a bc +dd 46 4a 1e 71 11 45 c8 19 b8 cf 26 f2 89 46 9f +02 a4 25 65 8f 76 49 bb 0e 8a fe 03 32 e9 99 ad +fc b5 77 f9 15 f3 6f 60 a3 a1 53 54 9a 58 51 d1 +0e ce de f7 3c d8 04 39 fd ef e6 01 90 d9 65 fb +2d 0a 56 a7 b3 84 2f 76 ce df 9d e5 b8 ee ee d0 +57 0f 19 aa b0 37 57 ba d5 2f 21 cc a2 ab 18 14 +12 52 6f 9e da ec 9b ff 8a 5f 60 de 6b bd b8 d1 +fa 8b 6b 5f a2 0c 6f c4 3f 2a 6b 9b 8c ef 24 ff +ea 41 d7 46 7b f7 b2 29 97 06 3f a0 5b a1 d1 fe +c3 a0 0f 45 0f 54 13 9c 4d 25 8c 49 6f 6c 5b 0e +f0 83 fc 4a d0 ef 06 1c 09 aa ca 50 ed 9f 47 41 +83 24 7a dc a5 71 f1 c1 f6 72 3b 99 a3 9a 06 12 +b8 36 a0 28 e4 8a 2f a1 13 52 71 5e 21 bc 22 27 +9c 37 8f 45 6f f6 2d 93 d3 41 ff 49 55 26 f7 ae +d4 a0 dc 5b 0a e5 c0 49 84 1e a6 84 69 b0 ed cc +f4 1b 0b 9e 8a 8a 0b da 55 66 c1 52 67 12 bd db +71 ca 0d 71 2a d3 48 65 2c f9 e7 0f aa 35 c8 8d +20 54 6a 5b 31 1d 6b 57 88 04 bb 3a e8 2b e7 ce +3c 5d c0 f3 ae 9a 7f 1a 86 cd c6 da fb 2a f0 fd +b3 72 34 d8 cb f1 7e 58 ba 21 49 6e e2 1b 52 9d +d3 04 bd 49 d0 ef d1 19 59 15 fa e1 13 ac 17 c2 +9b 37 44 68 2f 34 a8 1d da ce a3 63 25 49 71 29 +d8 dd 07 a0 50 65 e3 9e 93 d4 c2 24 22 7f 47 22 +2a 75 2f 5b 1d d2 dd 93 f8 70 dd 43 34 1b 23 1b +84 14 60 3a 5d 75 2d f8 27 bf 0e b3 c2 1c f8 c0 +2f 04 70 0f 88 8f 81 f9 f6 33 84 65 c4 f2 1e f7 +13 44 07 64 7a 31 5e 02 5e ec 42 7c 27 d3 5c 99 +7d ed 90 1a 0e 3f ae 0a 57 8c 59 ec 9c f3 1e 13 +9f 0b 55 48 ac 04 24 10 a1 79 2a 3f 47 b0 44 dd +7e 05 6c 00 e6 3f 0d ca 0c fd 8b 5b 19 ee df af +1c 45 0a c7 4a 11 c8 ea e5 dd c8 aa c7 24 ab 99 +74 21 aa 91 9b d2 f5 cf 55 ce 2f fa 6c 36 dd 35 +a8 b3 7c 84 a6 79 b1 91 61 2e 4c 75 73 92 74 6a +84 17 6e c5 ad 60 d5 5e f9 f0 f4 69 78 8f 24 59 +82 28 a2 48 4a 8d 90 4d e4 b6 21 55 97 8a 9f 96 +76 3f 9f e0 70 ba 24 32 e0 f5 5d ef e4 9f 55 73 +cb 85 cd 88 53 53 f4 7f 7a 80 1d 73 b6 73 51 78 +7f 42 1f ee 85 bb 58 cf fd 5a ac 34 8e 96 d6 da +17 30 15 3c 48 98 cc b1 c9 d5 10 90 16 19 4e 8d +7f 85 8a 7c f5 84 de c3 97 2a 27 07 e4 99 ef 4b +bc c7 2b 4d 93 9b 81 7d 2f 83 49 b3 95 50 d1 93 +ee af 70 db c3 2f d4 05 51 9b 3a b8 16 d8 35 7c +c0 2d 3a 7c 9c 5d 89 0a 6c f6 d5 6f c5 bb 86 6d +fa b0 4b 4d 63 02 7b ce a7 54 e1 d9 47 4d ac dc +a4 cd c3 29 38 b4 bd aa cf 97 33 98 1c 2f 46 9b +b8 2e a0 63 9d aa ea ed 12 36 f1 7f 8c 9c 34 1e +ab 9f c2 44 cb 84 68 f4 b2 79 e3 55 ff 6f 0d 7c +f0 b8 4b cc 3b 8e 11 2d c0 c4 ed 5c 51 87 2a a5 +1f aa b4 c6 c5 86 a1 ba 63 c7 5b 91 d0 5a 99 bb +42 dd cd 7e b7 70 e1 dc fa 63 cc 3c b1 df 61 a3 +be 7e 5e fe d4 a5 f3 86 ab f2 fc 3a 7f b3 40 7e +2c 84 5f 2d 58 d0 ca 09 96 ed 50 3c 28 e5 09 e8 +d7 2d 74 c8 6c f3 33 10 2c aa 14 55 94 6c 45 79 +c6 2c af d8 83 b4 b6 2c 8b ff 53 77 c5 d0 5d 09 +a9 5a c9 ce e9 e3 a1 45 16 e5 60 51 df de 92 ea +90 da f9 6a 3f 63 12 ef 5e 91 0c 1b a4 8b 0a c1 +01 c8 08 a0 5f dd c5 4d 76 0a 6e f4 86 17 88 3b +77 93 de 64 97 58 6e b4 10 b2 ed 9a 86 21 c6 e4 +0d cd e2 04 d6 a9 27 26 c8 9e b9 b5 f7 8a 87 43 +df 6d 1f 56 3e fe be 9c cb 95 2a 15 8b 3c df 6c +9d 96 2e 82 56 85 bf a0 85 c3 dd 19 6c 87 01 70 +28 72 73 42 be 6c b2 24 9d e4 9c fd 3f ea 1e 2b +84 9f cf 51 74 e5 7e 58 36 3b 79 53 83 dc b2 0e +c2 b3 75 53 71 14 e6 b0 33 96 81 0f 44 06 dd b8 +f9 e5 1c 8c 1e 79 aa dc a9 97 26 d8 b4 1b 08 27 +c6 7e 68 6d 47 78 72 d0 0d 53 90 38 2f b8 cf 06 +b5 a2 84 28 de 8b 9b da 33 ce 6b 8d 73 a9 13 79 +30 62 55 5f cf c1 90 da ba d7 ca ac 12 55 2b e4 +ad 69 a6 d8 90 ad c8 f2 c9 be 93 f5 e9 e3 f8 a3 +04 1f 6b 54 32 c7 60 ef 90 42 b0 fe a8 d5 1c 6e +66 3e 01 7a 3b 41 70 60 01 88 22 be 1d 89 a3 48 +34 52 3d d5 ea c1 d8 c9 06 bc 57 20 5d 9e ee f6 +b0 d2 0f b8 1d c0 ac 83 af 28 8f 75 52 3a 02 e1 +39 17 78 cf ac 9c 1f f8 cc 70 48 00 98 93 ef c2 +92 93 e2 86 c0 df 65 6d 30 ad 29 bb 02 e5 54 9d +da 68 21 47 05 1b 90 b6 00 de 8f 67 a8 eb d8 60 +24 1e fd 99 9d bd 26 9d d2 dc 42 c5 71 9e 77 f4 +0c f5 0a 89 4b 5e 15 3e bb a4 74 f8 1e fa e9 7e +09 6b d2 3a 4e 8f 07 70 ff 73 74 bc 01 0d 60 9e +dd ad b2 63 99 de 2a b0 87 dd 33 5f 24 ce e1 cf +24 cc ed ed 6a ea a7 ad eb c5 c6 73 e4 b3 da a9 +a9 d1 e1 99 28 7e 1b 50 1b 59 b6 52 f4 9f 7d 0e +2b 94 31 3e f4 9b bd 10 c8 67 61 99 f7 1e 56 4a +0b 37 dd 2a b7 9c 70 cf 69 4f 70 80 50 6e 03 e6 +2d 1d 76 26 ae 55 40 32 72 ae 5e a6 3a 0d 31 1b +45 76 54 ea 5e 9f 94 bb b3 dc 89 91 b5 87 54 2f +5d 19 c8 90 f3 6b 76 21 1d 47 7a ad 91 1f 9e cf +a1 73 d4 23 b6 f8 32 cb e6 db ce fc 0c f2 d7 c7 +1a b4 7f 6a 75 12 26 6f f9 a7 c5 fd 6f 09 9d 4d +72 43 07 60 22 85 71 ce 3b ba a0 fb 0f 25 13 a5 +91 8f 3c c5 fe 39 ed fa ef d6 d8 f7 ed 2e 0c e9 +d4 e0 b4 0a 8e 6d c7 9d d8 7b e5 fb 41 7c 8b 40 +5c c7 ef 9f 2d 62 29 ca a7 49 f1 5a a3 05 ad f5 +f2 d6 b4 91 65 95 d0 18 6c 50 fc fa 30 98 2d 6b +69 61 49 15 24 51 50 e7 3e d2 1c 06 3c 78 e6 cb +e4 55 61 02 d5 35 ba 83 a4 7c ac fc d9 40 ad f0 +da f0 cf 49 2d e4 0f 23 cb 1b e7 0d ed 51 00 00 +8b 64 b0 31 77 24 47 69 c4 92 12 53 ca b9 af 56 +12 24 68 9e b2 4b be 23 d6 71 81 25 80 09 93 14 +2a ed 95 23 5a 8c fc de ed 15 44 ff 13 c7 8a 22 +ea ad 50 4e 0a d3 8d 48 83 1d 7b dd 18 97 bb e7 +50 64 a3 64 5d 21 17 0b 74 e2 e8 df c2 f5 db 98 +3f 8e 48 99 2a 88 fd bd cd 58 d6 0f 40 f9 96 0c +79 2a a4 a8 74 ba 4b ca 9a 10 3f 38 27 20 bb a5 +28 7d 23 02 d4 95 39 a1 3d 1a 92 10 4d b1 08 f1 +fd e7 61 e6 af 6d 98 43 11 ae af ff 80 c2 c8 20 +a2 d9 5c b8 8c 38 49 1e 54 b3 99 45 ec 6e 92 97 +bb 4a 69 6a cd 39 1b 27 11 cb 20 ae 5e ad 61 da +f0 80 70 0f d4 0f d9 48 eb b4 3c 8e 89 85 8c df +88 fc 07 6b 10 70 60 11 a2 5e fb f5 36 3c 47 bf +11 7a 3e 37 d5 f9 47 fc de 25 68 f6 00 b3 ab c0 +22 c4 c4 94 32 f2 cd c4 ff fd 81 8a 8f 14 f2 33 +f1 d5 d3 9d c9 49 65 3c 7c ec 38 59 78 6f f3 22 +a4 9b f9 6d 90 0a ea f0 8f 75 aa cd 34 58 97 ef +b4 62 38 f4 f8 1d 32 a2 f7 83 54 23 00 b9 3e b1 +02 53 2f af f0 3f f1 6b eb e3 a8 11 fa 09 28 0f +21 eb 9b 08 3d c9 62 50 a3 fb 26 85 c8 6b 9b 03 +a0 e0 0d 5b 5d fc cd e9 c9 63 9b 47 c5 0d f6 f4 +66 4e 1c 28 6f c2 b0 5f 69 57 f4 b9 c9 a8 6a 01 +0c 71 2e 25 eb 2a 70 8a b3 a4 15 87 7d c2 0c 80 +c1 8e 51 fd 45 51 41 f6 9d 22 48 2b f9 56 5b 6f +41 df 2d 5e 55 ac cb ff 3c 12 59 04 d7 6a a6 86 +b5 c9 a6 a6 94 f0 8b 67 74 80 c3 60 47 4a b5 0e +0c ae 16 5b b0 be 8d 6b ed ba f4 33 b1 c7 9c af +09 14 26 6d 4c 22 69 58 da 21 8d 28 6b 8d 5b a9 +f2 06 db bd 9e c1 5e 6f bd b7 e1 c3 de 11 9c 93 +6b 77 22 25 04 23 7b 33 ff dd 5b bf ab 95 9d b0 +55 62 c3 bf 09 86 8f 3b 94 33 90 1b d0 4a 69 09 +06 c2 79 49 7a ed ad 2a 89 30 e4 5a de 64 a8 ae +fb 7e d7 fd 08 fd 75 0b 30 f5 d5 bf 92 4f 5f dc +31 f5 b0 e5 d0 3f 0d dd 20 0a 24 2a db 95 75 fc +b7 7b 59 2c ed f1 a5 2b f2 75 45 7c 7e 63 09 3c +70 25 86 54 93 3b d3 91 ce b9 94 4c 18 54 1b 3a +de 28 4c 58 fd ab f8 e5 c2 dc c1 8a 07 6f 75 d4 +7c 76 07 c9 75 90 2f cb b7 fc ab b3 4f c5 ba 35 +63 eb bc a2 e2 15 f2 77 cf 46 43 59 70 af a5 07 +36 96 23 2a 46 63 93 13 19 82 4b df 72 fb 72 f9 +3f 08 60 c1 c0 7d e2 37 57 61 74 50 1e 03 f4 a6 +0c 34 35 19 fb e8 fb c9 10 16 66 44 84 e9 76 71 +b6 f5 9d ae 25 67 1f 4e ac 91 6a 0e 46 ff d2 6a +ea d3 ae c1 24 6b 07 07 4f 8a 95 c2 c8 50 93 48 +6b b3 6d 84 cd 75 02 15 5a 48 2e 4c 83 ee 3f d7 +d1 1a ba 26 48 a5 d3 8f 6f ac 55 31 92 9e 3c 22 +6a 4e 13 2c 0c 58 6e 93 87 c9 12 02 f7 48 f4 bf +1f 91 51 8a 32 29 dd a9 d6 71 b0 21 80 29 a4 99 +31 39 e5 2e 0a ad 3b 8c df 31 3d 9a a3 54 25 22 +49 05 e8 ba 6f 90 f5 5e 0b 61 e8 87 df 4d 8d 11 +cd 47 29 3c d8 6b d5 7b e2 c2 08 40 52 95 aa e3 +ed ed d6 14 bf d5 51 d6 ac c0 35 69 4f 7b 10 cb +7a bd 33 34 ea 37 4c aa af 86 7b f4 fe 9d 01 ab +5f ce 6a 98 d7 97 f5 a4 d0 87 c3 91 aa 96 91 0c +29 e1 5a fb b9 f7 58 d0 f3 62 b8 6b e9 5f b3 5f +92 8c 3e 5f 49 de c9 04 bb 91 84 6b c1 9e 72 93 +50 00 74 1a 16 be 98 01 91 cc cc 78 a7 a7 a2 20 +2d 6d 7b e0 91 d1 c6 43 a9 4c 05 46 64 c8 14 ed +b5 17 62 2d cc f1 a1 4a e6 88 02 b4 d8 fc 1d 8f +69 30 7f cd f3 37 e9 f0 f8 11 09 68 8e 17 63 33 +64 51 cf dd ab 4e 45 9b eb 63 1a 74 2d 20 39 fd +8f 70 b4 3a 69 a9 a0 dc 0e af 54 5d f2 d4 37 fe +12 be cd 89 da 9f 0f 0c 8d a4 3e 70 9a 2a 08 7b +e2 0c f2 c2 19 16 42 65 a2 34 44 7d 8e 2d 17 7a +3f 81 f0 96 68 8f 3b 70 a2 e9 1c c5 81 b0 63 81 +ad cf 06 00 05 16 0a b0 0d e1 e4 a4 bb 6f a0 b3 +84 f3 44 03 3e 69 1d 97 30 f5 6d b0 46 df e3 03 +5e b0 c6 a3 0c be dd 35 9a d2 96 46 70 fb b1 4e +11 4c 39 d3 88 06 f2 c7 ad fb f5 cf b1 b8 ae 8a +17 2e a7 c8 15 f7 2f 06 73 3a ba 78 d7 55 b4 39 +c3 df 08 b4 a7 ac 57 e6 7b fb 40 bf 7b 00 a5 09 +a4 61 66 f4 e3 e2 33 ea df ce 4b 97 32 79 97 65 +5b 78 0c 26 cc 97 93 50 6b ad c4 3e 03 c6 83 1b +dc d1 2f f7 88 52 b3 eb a1 fb 11 b1 67 e9 4f 02 +36 ea 9e 70 52 c1 98 ce 65 ba 31 fa c8 ee 2f 8f +6d 89 b0 5a 6c 25 87 9f 8d 86 b0 ac 8f ee 8e 43 +61 b2 a9 5c 32 30 32 47 41 39 f9 ce 8e 77 1c a9 +98 d7 ef 61 00 5b 2f 73 a7 df bd 5a 57 a0 0b bf +87 50 16 1d 00 e1 f1 8d 38 83 be ce 70 bc e5 46 +fe 86 ee 7c 7b 19 af f3 75 55 24 2c 96 69 b4 24 +31 46 6c 68 5b 2b f0 bb ee 05 4d 72 37 92 08 0f +ef 40 69 7e c8 48 92 7e 1b ba 47 95 41 cb 20 88 +50 86 9f b2 63 41 51 ed 80 04 0c e0 d3 9e 42 e5 +b1 87 08 a7 cf 14 a2 48 f0 55 33 da d7 24 25 10 +4d 46 fa 7c 5d 9c b4 2a 93 c5 c9 b2 28 b7 77 eb +8b d1 d6 3b 32 b6 4c f7 79 33 28 f8 88 23 e8 9b +c1 04 5b 28 9a 5f 2d 54 3b 67 e1 56 ad 2c 9f 56 +dd 76 c8 23 f2 bf d8 ba a6 f7 e4 52 6b 72 f1 70 +8d 95 15 ab e5 ca f6 00 89 f9 ec b7 0b 75 8a c4 +67 c7 c7 c8 80 ba 47 55 1b 24 d4 c8 d1 75 01 9d +d1 6e 46 5e f4 b1 0c 17 0e 72 8c df 8b 36 83 f4 +7e 2d 91 31 88 c3 31 ff 3b 6d d9 89 4f 66 14 6b +1f ad f5 53 e6 7d f4 40 6a 05 d5 bf 78 63 27 f2 +4e 8c 38 8e 15 2b f5 60 29 5f f2 85 36 7f c3 de +d4 d6 8f 2e 00 eb 21 b0 cf 62 5c 50 fc 3d ee 3e +f7 58 b2 f0 8f 01 5e 54 02 09 1a 29 86 6e e1 9b +77 16 2b 36 d3 76 0a ef 22 0b ed bc be 70 8e 78 +20 c8 fb 73 d2 96 5e e0 10 88 2e d6 e6 d4 aa 89 +27 37 dc 83 82 b7 75 22 4f 45 71 c0 7e fc d8 7c +2e 84 ca bd 84 82 cd 3a 46 49 e8 41 8b b2 0d e2 +9a 5f 38 54 e8 b5 89 04 01 d6 00 6a 8f ca 45 ca +bf 9e 7b 8f 70 37 f8 61 63 01 6d ec a7 e1 1e 0f +a1 a9 c9 ca 2b bd 08 80 3f 79 b6 57 44 45 15 f3 +06 f0 55 77 a5 6e 24 d6 e1 82 fa d3 ab fd 17 6f +7c f1 03 8e e1 b7 13 ce 5a 87 df 7a 88 da 56 68 +ef 73 36 42 0b 0b db bd 99 ac 5f ad af 83 8e cf +92 16 9a 2f e8 65 9a b4 b9 19 9b d5 80 67 4d e6 +53 ed 1e b7 51 11 64 8e ed 0b 9a 08 82 34 8c c6 +2a fa 59 cb 4c 0f 71 2e 49 0d 6b 0e 81 ea 0e b2 +9f da db 97 ad 8b a3 62 3a b8 78 09 87 a2 0b e3 +b1 81 c9 91 b0 28 d4 ed d7 30 28 6a 13 99 7a 93 +d4 80 68 56 10 14 e7 4e 33 ed 71 b2 71 6c d1 17 +50 58 f2 c6 44 15 29 8c 5d f9 d4 2b 5d 31 85 36 +74 67 2f 53 4b 14 9d d2 e1 74 71 d7 f1 68 cf f9 +5c 81 08 10 55 ee 54 b6 da 3d a5 42 cb 49 a9 33 +31 a7 1e 76 3d 80 ae 19 f1 fe 3c 2c d4 94 4d b3 +2d 86 37 64 16 81 54 06 e8 39 57 5c 36 f3 bd ac +b2 98 95 d1 a2 66 f7 f5 24 51 ca 1b ec 31 f8 03 +c7 1e 96 a2 8e b6 d8 37 08 7e de e3 02 61 75 81 +e9 cc 05 d9 40 d7 e0 7e 42 bf 09 b3 a8 75 a3 5e +54 76 b4 31 7e c1 c4 d1 db bf f5 89 f0 b3 a3 01 +13 d7 5e 15 97 a1 c2 1a a2 6a e9 47 65 85 30 bd +fc be d6 89 3f 40 1f 6a 2b 2e 5f 6c 88 70 64 55 +2d f0 1f b8 43 e4 0c b7 39 0a 85 09 1f a5 2b 75 +90 94 d5 8d 8c 4d 1a 9b 96 9b 81 7e ba 2f d5 0b +89 29 bf ed 4a 9c 36 8e 77 e1 dd f8 1d e2 c6 56 +fe b8 5b 6f be 0b c0 86 31 1b 0f 5e e0 1d 70 cb +a0 75 31 d7 4a da c0 75 51 3b 09 cd b3 48 cf 45 +09 9d 29 f6 2a 36 c4 ae 76 f1 19 fc 27 f2 48 48 +ec 96 50 d5 5f 65 22 52 05 93 bd 11 94 f9 3d c4 +8f a5 b0 12 4e f7 49 80 d3 7a e5 e6 54 2d 91 87 +a1 0f fb e2 5f dc e1 4c ba da 4e b3 60 8a f6 57 +31 6a d8 cf 25 13 51 ac 3a 46 b1 28 70 85 5b fc +f6 29 b4 40 d3 7a c9 e4 45 a2 44 1f ce 17 7d 0c +05 d7 8d 82 a7 02 06 d6 67 7d 4b a8 e3 aa 11 2f +49 99 2a 6b 62 e1 b4 93 6f d4 9d 6e 48 3c dd e9 +37 80 2a b5 a3 0b d7 0c 96 57 22 26 85 63 f6 e0 +00 10 b0 3b 27 cf 75 22 bc 77 e9 a3 b9 1a 21 c7 +56 d9 05 fb 8a e2 15 42 64 6f f0 63 36 e5 45 46 +65 60 ad d4 9a eb 26 dd 53 e0 91 47 85 bc 9b a3 +2f c7 ca f3 9d 6c a8 1e e9 6a 76 00 f4 4b d2 64 +a3 c2 96 62 5f 74 6d 26 d8 4d 79 38 53 db 39 ac +e0 a7 f1 c1 18 27 57 6b 7e b2 33 d3 d6 a6 cc 92 +a3 53 33 81 3a d6 bb e5 35 61 40 6f ad 3c e9 da +86 67 ea 41 3f e3 aa 0f a5 76 b9 0c 2a bb 82 5a +ed e7 a5 f8 2d 2a 36 22 23 23 0e e4 94 70 4c 3a +f9 1b 4d 65 e8 02 06 7f 6c 3c f7 ff 33 d1 d0 08 +7d 77 f4 5d 72 7a 2b 0c 41 a9 4f cc fa b0 f8 e1 +ee 43 d9 4d 14 95 7a 7e f4 cf 07 e3 99 f6 43 df +6c 5c 26 c5 c5 bf 77 98 8e b2 fd 97 22 e1 d5 cd +ba be 34 4f ed 42 21 c0 b0 d7 f0 7c 64 ca c0 cb +b7 39 04 fc 87 02 9b 99 db ed 2f 52 08 7e e0 9c +1a dc 79 6e e7 5c 7c 24 3f 80 13 fd 9c cf fe 88 +cc 84 52 e6 e3 7c 2e 3b 36 07 9d ac fb 54 d4 2e +a9 18 35 1b 83 23 a6 94 11 db e6 86 e0 6a 1f 65 +c0 81 06 7b f1 c6 ee b4 e8 13 90 5f 97 e9 c4 bc +33 1b 15 29 6b 22 fc 4f 6d 76 df aa cd 77 fe eb +30 bc d0 54 65 61 e3 4f c3 c9 d0 0b 98 9a f1 07 +79 c2 47 79 5d 1c d9 dd 17 1e b8 25 1f 39 8c 3c +32 0a b2 b4 2c af c3 6f 57 5d c4 fe 89 39 ef 28 +b4 de 6b 14 cc 88 25 b6 c7 5e 8d 51 fe 46 e4 cb +26 b2 a7 0b fc 65 5b ba b9 63 d7 01 40 d3 48 13 +83 c2 69 24 41 5c 82 e6 89 56 43 d6 ef 4f 73 6a +2d b9 a9 b2 07 de 5e 05 67 91 3f 32 88 49 a2 30 +67 1f f1 89 df 7f 77 c8 a3 a9 72 58 c8 3f 0a d1 +dc 1b 5d 55 b2 52 95 d4 ad 68 af dd 52 db 28 1e +b7 8a 0b ed 04 64 59 49 29 37 84 6f d4 3e c4 1a +be 9c 7a c8 d5 0c 5e 4b 76 f8 b8 24 56 d3 fb 9d +0e 56 3f 3a 32 5a b5 c4 91 14 83 6b dd 32 bb 47 +fa 4d 46 17 f9 d3 21 15 c0 58 d1 94 79 69 b2 f9 +bb 70 0e d3 2d 79 d6 c4 3b 52 56 7e 50 24 1f c9 +af 71 1a 3c 37 ec 4c 97 38 30 ac 20 8d c5 85 e4 +87 c6 8f 99 62 17 f8 20 64 02 fb 49 3e 02 b9 7e +76 d8 48 de 11 08 f1 7d f6 a8 f7 5c 9c 59 50 4b +2f 81 c1 51 60 db 21 34 cc 34 56 4d b1 71 eb 2d +7e 10 59 7d 13 8a 4d ab 7f b2 f3 28 95 90 af 35 +1c b1 b2 40 d1 54 82 46 76 15 e5 86 96 ea a2 bb +40 0a 73 0e 78 d7 08 b3 3e f9 08 e1 bd 77 d6 cd +5d ae 43 0a 71 19 cc 64 b0 87 21 36 b4 4e 4e 2a +29 0f a5 1c 05 3a 0d 19 8c 27 f4 6a c3 b6 4c b9 +2a be 40 55 3e 68 bf 1a 78 91 1d 7c 7e c0 2c 82 +b1 8b f7 d0 ee cc 09 0f 99 56 a8 21 42 dd 80 9b +84 f4 1d d1 05 a6 71 6b 86 0b 76 4f 4a d8 9f 24 +fd 1c e0 93 3e 9e bf 9a 86 b0 9e b7 bb d8 87 bc +a3 58 0a c9 6c a2 f8 a1 ac 3f 92 03 fe 39 bb 27 +85 48 88 ec 34 81 46 31 bb 24 63 26 10 ee ab af +48 4e f2 7f e4 35 cb 8e 84 1d 88 15 95 ea 91 e1 +55 ce c3 7b 24 49 dc 9c 12 75 d8 7e 13 61 f6 e6 +8e 73 1a 85 36 b5 67 e2 58 8a c7 1d c5 9a b6 a0 +54 aa a7 cc 1c 24 a6 1e db fa 70 b9 7a 2b 54 24 +87 15 16 2a 6a 6a 4f 8d 06 dd 14 92 b5 82 97 8f +0d 25 f0 22 5e 9a 17 2e 06 81 02 bd 8d 0e c3 ba +1e ed 2d 06 b1 2c 4b cc c8 74 34 48 2a 1f c4 bf +50 cf 5a ec dc 0e 01 a3 5d aa 8e c8 ff ff b2 57 +20 b0 9d 23 99 42 67 5c 03 b8 8f bb ed fd e1 9b +9c 94 79 3b f8 bc 17 d2 c8 8c 90 37 af b5 c4 57 +22 1a ef bd 04 c9 02 7b fd 33 5c 93 a3 32 59 2d +42 e2 48 da 9e d3 10 ca 9b 08 3c e8 f1 f7 f7 60 +8a 76 cc b2 04 f7 fe 16 34 d2 41 f2 05 c0 1b 40 +47 2f 87 65 7e e5 28 cf ac b0 e5 17 85 0e 1a 7b +9f d4 14 e2 53 95 71 96 fd b3 43 b4 d3 a1 ff 3d +46 f8 0d ee 5e c7 be 69 89 7a f2 1a 97 55 42 55 +38 93 6e 1f a8 8d 24 1f 2d 9c 2f 96 6c bc d3 87 +7d bc 08 83 24 0b 00 52 a7 b9 40 54 01 51 a2 d4 +b4 62 52 74 c4 4a af 1a 8b db 53 8d ee 9a 53 bd +76 49 ff 74 76 1c 64 8d 28 59 5f c3 48 69 32 ea +fb fb 65 ab 3d e1 a3 8f c6 83 df 2f 6b 86 d9 18 +5f 3c cb 5a fc 93 9c 84 07 8c d0 c5 8e 6c b8 f9 +1b d1 de af 65 82 8c c3 3d ed 0e 66 e7 34 6e 16 +91 35 f2 13 c7 db 04 eb 1b c4 22 41 fd aa 7b a4 +b1 ec 03 17 6e d3 88 5d 5a b8 db 32 fa 61 a9 90 +35 2c fa 54 35 92 df ab 1f c4 02 8d 28 27 aa ab +9f fe 92 b7 d4 36 84 d1 b2 fd 30 da 8a 1b 1c d4 +ec 44 0b 6a 40 d7 00 81 b9 0a 21 1b 86 1f 86 ed +c6 98 ce 36 c8 a9 a1 33 20 00 c1 38 b2 29 4e 11 +a9 2e 59 7a 70 ed d8 9c f1 ce 1e d2 d6 diff --git a/lib/TinyGSM/extras/test_10k.bin b/lib/TinyGSM/extras/test_10k.bin new file mode 100644 index 0000000..b29e31e Binary files /dev/null and b/lib/TinyGSM/extras/test_10k.bin differ diff --git a/lib/TinyGSM/extras/test_10k.hex b/lib/TinyGSM/extras/test_10k.hex new file mode 100644 index 0000000..2f65c69 --- /dev/null +++ b/lib/TinyGSM/extras/test_10k.hex @@ -0,0 +1,209 @@ +7d b4 24 2f 83 f0 f8 7f 8e e6 1c ae c8 3f 8c 51 +6b ac 77 80 f5 13 90 37 fa 05 ee f5 b5 21 3a 94 +b8 73 d6 7d 22 93 75 44 9b d0 40 fe 98 c5 b1 0d +3a 72 d9 fe 7a d5 6a e0 9a 77 4e 82 66 57 25 93 +09 90 65 fc d7 96 1d 6d 79 f6 38 e7 4a cc ad 03 +05 dd fa b5 1d 4d 28 62 a2 c0 86 40 16 5a e7 08 +99 81 97 81 ec 74 77 d1 7d e4 e0 ea a6 71 6c 53 +19 a0 5c 7b bc 02 76 22 e3 b2 2f 26 4a 09 10 a3 +dd fc 8d 1f 5e a9 f1 7f 22 82 2d ca 71 26 24 7d +95 4e 11 ea ad d9 ac 23 c8 e2 f0 fd b9 30 0a 65 +d7 79 31 ab 29 a2 68 f1 ee 21 50 5d 33 8a 72 97 +34 bd f1 4d 24 f9 62 b7 93 21 80 c6 33 8d bf 5e +40 17 de 57 e5 75 7b 5c f0 80 0f cd 2e f9 83 3e +96 c3 62 60 d7 44 b3 2f df 23 4f 10 d1 cd ac 70 +18 e5 ea 17 29 aa 77 af c5 5b c3 37 6d 26 72 71 +e2 09 b5 be 63 a5 ca 17 d9 2a b9 67 f2 2c b8 f1 +13 4c 41 e2 61 3f 8f 96 8c 7c 61 1d 7e 8e 22 98 +62 57 71 d5 b6 87 39 24 9f 1e 37 27 f3 79 1e 3b +8b 41 28 28 ff 70 e0 31 29 f6 ca 63 82 48 1d 83 +cf 39 91 25 45 cd 9c 40 25 d2 92 dc 20 48 90 bd +89 28 2d 28 c3 83 11 bb 9a 66 b0 fe 6e f5 30 b6 +f4 42 39 2f 4e 30 65 28 c6 bc d0 ac 44 20 0b 91 +da d3 d1 57 5e d9 77 b8 01 48 44 2c c5 3e ea f4 +d4 ed d9 e0 1e d7 f5 89 37 3c b1 57 15 c5 79 79 +38 c4 23 2e cb 64 ec 3c 94 d1 27 7d 4d a4 e0 7f +62 d9 f0 62 b8 7d a6 50 91 3d 90 44 10 01 b0 57 +8a ab 87 4f 41 7e be 28 1b 6a e4 21 89 0e d2 28 +46 a1 a9 ba 29 67 57 31 d2 a1 68 eb 92 5d 06 28 +a5 8b b7 ff aa 91 b7 bd 29 f4 3a 3b 5c 4a 1c 26 +72 37 6b a0 38 03 83 a4 29 7d b1 fc a7 ba 18 5d +b8 a1 70 fe e3 a7 01 1b 8d 65 9e 25 17 3d 94 76 +a5 27 7a c0 31 8e 82 2b c5 8a 1e c0 41 7b bc 82 +1b bb 64 98 55 05 33 e5 03 5a 54 65 d6 2e 0d 4a +9e 59 ac a8 41 a9 b2 6b 8e e9 19 c2 c2 f6 2c a7 +d6 63 c3 59 f7 37 41 5d 97 8f 03 af 5e 74 fb ea +59 05 c3 2a a8 c5 da fd 77 f2 f9 2b ca 04 93 2a +6a da 4a a5 76 27 70 a0 43 a0 0e aa 0d 0c d5 65 +95 d8 5d 27 06 eb f3 42 0a 97 91 aa ab 05 14 c4 +69 54 59 75 2e c0 a6 90 bd a3 40 1c 3a 75 78 b6 +fe d1 7d 9d 1f 51 32 81 63 6e cb a0 a1 fe d0 7e +90 e1 b4 5f 8c d1 ce ab c4 29 43 9f 99 82 2d 28 +3c 0c 28 6f b6 fc 11 e3 6d a1 4b 71 1c 89 4a ea +a7 0d b3 d1 eb 6f 94 73 5a 3e f4 1b e8 82 5d 8e +f3 3b 10 16 9d 22 d8 9c 27 24 10 6c e0 ab 1e 6e +1d 38 d4 65 9d 38 dd 3f 47 ce b4 d8 4f cd fd cc +80 74 54 b6 f7 96 f6 82 43 ae b5 d9 6e ac 26 57 +de b0 d0 21 7b 25 3f a6 36 0b 4c a9 84 c9 44 1f +02 af a1 51 8a d2 18 92 0f 2c 55 a5 19 18 c8 ed +de 9c 24 30 e0 a4 fd 2f 90 e2 9d c5 04 e1 29 6a +e9 d0 81 e4 49 0b cc b8 3c 6a 97 d4 9c 9b 18 86 +97 22 38 4a 16 88 c2 9b 06 fc 3f 4f 7f 4a 3c 1d +37 be 89 07 14 fe 94 a2 13 03 62 38 9e be 92 2a +5c b4 a9 85 3b 14 80 42 c0 85 ec 36 50 ea 4b fd +1b 84 ae f4 9c 98 a4 a3 dc ac 25 b7 1a 36 0f 56 +97 02 e6 4b 20 ff dc f4 8a fd a9 83 3a 4f 02 ed +81 93 b5 fb 39 39 ef dc 46 8f 63 ee 0f b8 a6 06 +58 30 d0 27 59 62 a4 49 96 52 cf a3 26 73 24 c2 +d0 b8 b6 b3 5e 86 e3 73 17 78 7c 89 0f 9b fe ea +92 c4 37 43 6e 77 45 2f a6 da 49 d0 e7 de ee f7 +2b 40 a1 c8 79 d4 63 ff f7 aa 17 15 54 57 af 8d +b3 9c 11 f0 10 c0 ad 2f e1 9f 45 43 ec 32 09 c3 +d1 17 73 99 69 41 b5 ae 31 67 02 83 67 a0 f1 bf +2b b0 20 71 a2 77 69 67 81 d9 27 86 cb 07 f5 f9 +d3 f9 84 ca 98 4d 7e d7 c7 62 57 39 78 36 6b fd +a1 ea 26 26 5a f0 24 a7 5f 7e 14 55 50 39 48 fb +f5 44 9c ad 55 c5 e0 74 ce 36 85 87 f9 9d 41 86 +5d 35 63 4d 0e b4 74 80 97 e7 50 17 cd 62 04 7f +02 32 70 51 20 44 fc 0b b2 e6 4d 5e b4 e9 66 a8 +74 fb c7 cd d8 6a 47 86 35 ac 81 20 71 0e 27 d0 +e8 24 8d 8b 8f ef 4f ac 30 22 55 a3 99 12 8e 48 +13 16 cc 2d c9 95 63 75 e0 4b 1d f1 93 f1 ac 4e +a7 8f de 9c 70 a0 cb 63 e6 3f 36 d8 50 3e 99 16 +85 4b 26 0f 91 23 ac 2a 40 f4 6b 05 28 ec f2 80 +d6 87 0b 90 75 4a 3b 92 ec a6 89 4f 4f 0c 75 9d +7e 73 49 c8 dc e7 55 a6 83 82 c1 08 ec 34 2d 38 +b9 5d be 6a 21 c4 62 87 19 02 9f 6a ad 0b 1f 98 +f2 f6 e5 97 9a dd 32 1f 21 7f 34 a6 75 54 f7 1d +e7 e5 0d 6b 2e 24 64 21 d9 92 5e eb 2f fa c3 95 +40 97 2d 44 b1 fa 2f e8 ea 87 4f fd 10 2b 73 e9 +e1 3b d5 67 b3 64 33 e6 f4 87 7c 18 3b 11 21 a3 +d9 c5 b7 b3 ae c7 52 d4 98 92 17 09 1f 60 4d bd +11 d8 7b 18 2a 89 b9 01 8d c0 f2 c5 e5 19 0b d4 +d1 c9 9c 59 18 f7 f2 2a 6b 2e 9f 74 6a fc e4 44 +82 65 10 72 a3 b5 7a 6a 56 19 27 5d 00 d8 d2 4b +d3 ad 4b 88 9b 45 78 e8 9d ff 33 e5 86 34 7f 70 +b2 f7 33 23 00 14 cb 11 96 18 e3 89 f3 0a ea ee +47 69 c6 3c 33 a1 49 ad d2 97 af a5 f5 35 8e 1e +07 81 ee 48 32 96 e4 b1 26 6b 86 c7 de fd 31 f0 +c0 2b 3f d0 77 11 33 4d 9a 36 d9 df 0d 02 34 43 +ab 97 c8 c4 09 58 96 30 45 a6 52 2d 6c 5f 88 3a +1b d1 63 98 e5 7b d4 d6 c4 21 33 ad 51 3f 4b 9f +3d 58 ab 2b 51 5a 39 77 04 95 7d 8b 43 be 84 15 +1a bb 90 30 2e b6 68 b1 c6 3c 45 cf 85 0c 90 99 +66 f7 04 98 81 1c 66 e9 f0 1e 70 e5 bf 9b e8 32 +36 7d 21 d0 7b 15 74 6c 18 fd 4b 51 5d 65 8b e7 +06 9f 71 0b b9 b5 7d 15 0a e0 11 32 51 08 1f 0b +56 83 69 92 20 61 df bb 1d db 01 28 65 94 86 33 +48 4b 64 6c 51 8f 4e ac 29 04 e6 2b d6 b2 91 a5 +86 9e 22 10 e3 45 2a d7 2f 0c 32 7f ba 66 f3 de +4e c0 c0 2f b3 41 d1 12 fd 7e 6d 15 d4 d0 d6 4b +7f 00 83 17 b4 e8 48 af 34 63 d5 f8 64 2b 22 db +d6 95 93 07 79 51 4d 4d a9 b8 7c 4d 9b 6b f3 d3 +97 87 1b c9 21 45 8d ef 18 c9 54 6a 1b 45 91 0d +77 9c c2 ac bc 12 08 68 fb 54 a6 e8 4d e6 e6 a5 +6e 2f 8b c1 f2 9f d5 d9 e5 19 a2 87 9c 73 c5 b0 +f1 1c 97 f8 85 b7 85 31 12 f4 ca 0d 17 d3 54 4d +b7 63 fe 88 c7 5e f2 05 95 9d d7 17 a1 c5 95 7f +30 9b c2 6a 23 40 cd a1 37 6d 9e d7 90 11 36 41 +30 c9 36 27 76 02 c2 a0 42 26 24 06 c6 f0 ec 0e +76 de 93 96 52 3d fa d4 fe 28 48 99 7d a7 37 e1 +ac eb e2 df 43 08 74 f8 79 15 62 1f ac e2 db 39 +24 69 d6 0f 5c b8 9b 9b 83 5a 7d 21 6e 7b c9 73 +42 d4 37 7e ee 28 2d 1a ec 3f 28 e7 93 8c d0 78 +ea 35 cd 2c ed fb 5b d7 e4 4c c2 fb 3b 62 8f 9c +13 93 59 8a 15 f0 ff c9 99 9d fa 73 56 4a a4 a3 +32 20 09 37 37 de 9f de 5c ed 73 0f 1f 4c 17 58 +31 f0 ad fe 89 9b 34 d4 99 a4 4a 4b 5e 44 22 c0 +b7 9e be d7 3c 59 7a a6 7e 2a 28 8d 89 8a 2e c3 +22 2c c9 f8 b5 7a 81 92 f4 d8 36 14 a3 bb d6 68 +b9 59 48 7d ce 0a 90 cb 53 b9 3c d4 1f 31 e6 42 +ca 2f 48 c0 50 d2 12 19 ce 53 15 fa c0 b3 64 c4 +52 43 56 14 6c 10 c3 6f 16 70 bc ca 38 dd 63 2e +7d 5e 36 d4 ce 61 02 06 72 95 ba f6 5d b6 15 59 +c0 ff 28 5a 3a 74 3e 85 19 18 ee bc 99 bc 67 63 +0f 4e 04 5a f3 d1 f0 85 11 f1 80 7a 37 fd 88 b9 +c0 f5 18 36 16 8b f2 8b 3d 4c 62 1a 14 d9 36 bc +34 3d 1d 6e 44 bf 78 f3 5e 31 d1 2b 5e e5 15 be +cd 3b 48 a5 2b ca 74 93 43 3f 83 25 d0 9a cc d4 +71 b4 83 50 cb 97 92 2d a9 3e b2 0c 9c 1e d8 84 +8d 7e da fb 8b c8 3d 61 97 79 4a 25 d6 de 18 b8 +1e bf 3a 2e 43 82 27 70 3b 5a 41 06 75 29 38 be +97 33 72 98 99 86 f9 41 b8 32 38 be 16 c6 fb d1 +32 4e 0d e0 dc 1d 86 a0 94 29 62 df f3 6f f7 9b +0e 55 fb f3 99 93 34 57 68 c9 d3 a5 d0 6b 8a 2b +9b 5f ca 9f 77 a0 83 22 15 96 80 5b 98 5a d6 68 +7f 75 c5 39 98 6d cb e8 f7 da dd 9f 5d 31 57 da +6b 61 9f c2 f1 d9 7e 03 6a cf 02 29 79 ad 91 25 +6c 2b fb 3a 7d 08 d3 93 7a 58 a0 70 93 1c 5b 4d +42 51 f0 fd b6 da 62 7a 90 4e 2f d1 92 25 9b 59 +24 4c 1c 81 be c3 6f 06 80 67 bb 28 87 e0 f6 b6 +f0 42 33 5c c8 c3 2a 6f 75 e9 c5 74 f8 62 96 8d +0a af 5b 0b 83 dc 68 51 5f 63 16 ab 4c 25 e0 12 +05 f2 6c 95 29 1e 02 af 42 ac 4c 6c 57 8a cc a1 +d7 dd da be ad da 2e 52 87 a1 4b 7d 69 33 70 05 +07 3d ac 48 df 69 56 5d 88 d0 f3 b6 da 54 22 83 +28 b5 ae 98 f9 ab 01 ee f6 7d f1 27 db ed f6 d1 +fe bc 68 aa 70 0a f6 dc 87 23 63 8a 48 27 d5 f8 +a8 90 00 30 de e5 87 22 ee 09 6a 16 a9 3b ca 70 +9c ce 0e d5 d2 55 06 ef e7 c6 31 74 ac 8e 99 d2 +cb 87 ee 92 b9 54 f8 f1 15 95 0c f5 86 56 23 a4 +1d 75 4b ca 6a d9 d0 9d 11 e2 e5 0c a0 7e c7 a9 +84 b6 57 8a 70 ae 44 f4 f9 33 63 1a 2b 4e 35 38 +11 27 08 2f b4 2d f8 66 eb 17 2e 15 53 9e 80 54 +63 d7 67 ba c8 d5 0d db 06 c5 f1 4b 18 6b e6 92 +3d ba 91 24 43 df 03 5b ea cb d8 d7 90 a9 35 f1 +6d d7 a8 87 2f 21 50 e9 f5 1e 82 dc b8 0a 8b f7 +0c 4f f3 a4 64 7c 6e 38 da 4c c7 71 eb a3 f2 fd +4b 76 a1 5b 5f 98 ba 5b 73 66 7e 85 c4 18 be 81 +92 94 e3 00 af 02 40 35 97 6f 37 0f 25 6d f0 ce +71 70 0d 69 43 21 45 22 90 64 62 e0 da de f0 a4 +6d 63 7b 85 f9 b9 7a fa bf 37 52 ed bd 1c c2 4c +ea 2f a2 ca 05 08 ae c2 76 27 76 6b 8b 66 82 26 +28 33 c1 58 8d ef e7 96 83 bb fe e3 6b ba 98 cf +1b e4 cf b1 76 71 31 c2 c7 a0 e9 92 d1 ac d8 d3 +25 3d e5 fb 26 77 2b 1f 4d ee e9 77 71 53 d6 e2 +7e b5 e2 49 9e 37 8d 15 85 33 19 90 d8 a5 2e 02 +31 6a 57 3b 0f 3d e3 7d 88 39 32 6d 8d d2 63 87 +6e 31 59 1a 51 e0 6d 59 b5 5f 90 1a f3 5d 30 94 +f1 1e 11 23 9e ba 53 94 b3 0a a2 32 3e bd 3d 8e +64 59 03 52 ec 27 cb 40 f5 e3 c9 b3 f4 e4 43 b6 +cf 09 76 f6 fe da 22 3f 93 30 77 fa 7d 19 73 72 +51 ed f3 80 3a 19 dd 5e dd 3d 3b 3a 29 10 c6 3b +78 35 1a eb 9b 5c 4d 09 62 8b 73 b9 51 c7 32 28 +c4 94 35 e8 10 51 58 5c 5d ad 6a ad 22 00 73 20 +f0 41 23 04 37 44 bc 5a be 46 be 3f 25 b6 ab ec +ce 8f 9e d5 50 a4 02 12 70 f3 7c a2 8b 19 0c da +b5 85 cb a4 59 27 c1 82 b8 93 da 06 fc aa 59 4e +b7 25 53 07 4a f0 47 46 de ea 2e ce 5e 97 59 1e +42 21 ed 56 ad 53 8f 0c 9b ae cd 99 e7 ad e2 f4 +23 74 eb c0 b3 97 0a 75 f5 16 56 ed 29 79 e2 a5 +45 de cc 4b 98 f4 24 08 35 6f 2e 64 cb 59 df db +f4 67 ce e6 00 af e6 d9 e4 01 5a 92 da d9 5b b0 +03 fb 92 6f 0b 6e 55 19 24 0f 01 f9 c2 85 ee b3 +0f d8 d6 17 f5 e5 7c 35 6a a6 94 a1 c9 c5 b5 4b +6d 66 b5 99 52 d6 77 23 07 d8 fe ab 3c 5f ba 0d +d2 3c 6c 76 aa 3c dd 73 3a 84 04 4a d7 fe 9e c7 +23 07 cf e0 91 57 92 c5 89 ef 53 b7 89 85 f4 71 +cf 66 4e 97 0e 9b 42 0f 6b f7 65 d3 b7 9c b0 0b +c1 c7 7f 48 2e 71 f2 a7 cc 11 50 0c c8 86 e3 03 +30 26 c6 8a c7 c0 23 4b aa 11 3c 23 f6 9d 01 2a +25 18 63 93 48 13 f8 1c 60 ac ad f1 e3 e8 bb 1f +0b fb fe 6a 30 f4 98 8c a2 5c e2 42 a7 28 1d 13 +92 5f e7 57 4a 7f 8d eb 0a ec 57 c8 10 d7 4c 33 +dd 77 6c 95 f1 44 7d 5a 2d 2b 1e 85 4a 1f 19 94 +24 88 fa 3f 6b 84 d6 c4 23 34 fc e7 30 b7 55 ec +f6 fd e5 51 20 b3 6c a0 0c a0 a7 08 8e 15 cf e5 +5f f9 71 52 e8 a5 9c 63 6b 2c c5 83 e7 bc b2 7e +e5 ef 26 11 3d 7f c4 e2 b8 48 d6 90 37 43 e9 e5 +ee 65 ec 65 2c 42 5a a8 33 f1 49 35 8a d1 17 2a +3d e7 83 d3 96 24 b9 42 92 9d 04 35 5d 47 17 9c +48 f3 4f f8 61 3f 17 2f 29 d6 0e 6c 7f 24 04 e4 +5f f2 07 80 00 7c 29 4f 5f 60 b6 a8 32 1c 7a 09 +be f8 3f be d2 82 2d fc f0 71 a0 d9 12 36 3e 91 +af 3b e8 39 74 f4 64 91 2a a3 5a fb 02 d1 2e 73 +7e a6 ed 7b 71 74 7f 48 c9 c7 99 22 f4 a0 2f 08 +68 b5 1c 2b 4b ca 01 54 fd 83 73 3e 56 1f df f3 +aa 0a 02 53 fc 7b e6 36 9b ee 22 b2 ee e2 c1 03 +c6 aa 0b 94 77 6d 51 1c cf 2b c8 c9 51 ec 11 aa +75 24 66 67 ab ff 1f 74 f4 00 82 1f 33 07 4e 70 diff --git a/lib/TinyGSM/extras/test_1k.bin b/lib/TinyGSM/extras/test_1k.bin new file mode 100644 index 0000000..5595509 Binary files /dev/null and b/lib/TinyGSM/extras/test_1k.bin differ diff --git a/lib/TinyGSM/extras/test_1k.hex b/lib/TinyGSM/extras/test_1k.hex new file mode 100644 index 0000000..90a8e66 --- /dev/null +++ b/lib/TinyGSM/extras/test_1k.hex @@ -0,0 +1,21 @@ +cd 3b 48 a5 2b ca 74 93 43 3f 83 25 d0 9a cc d4 +71 b4 83 50 cb 97 92 2d a9 3e b2 0c 9c 1e d8 84 +8d 7e da fb 8b c8 3d 61 97 79 4a 25 d6 de 18 b8 +1e bf 3a 2e 43 82 27 70 3b 5a 41 06 75 29 38 be +97 33 72 98 99 86 f9 41 b8 32 38 be 16 c6 fb d1 +32 4e 0d e0 dc 1d 86 a0 94 29 62 df f3 6f f7 9b +0e 55 fb f3 99 93 34 57 68 c9 d3 a5 d0 6b 8a 2b +9b 5f ca 9f 77 a0 83 22 15 96 80 5b 98 5a d6 68 +7f 75 c5 39 98 6d cb e8 f7 da dd 9f 5d 31 57 da +6b 61 9f c2 f1 d9 7e 03 6a cf 02 29 79 ad 91 25 +6c 2b fb 3a 7d 08 d3 93 7a 58 a0 70 93 1c 5b 4d +42 51 f0 fd b6 da 62 7a 90 4e 2f d1 92 25 9b 59 +24 4c 1c 81 be c3 6f 06 80 67 bb 28 87 e0 f6 b6 +f0 42 33 5c c8 c3 2a 6f 75 e9 c5 74 f8 62 96 8d +0a af 5b 0b 83 dc 68 51 5f 63 16 ab 4c 25 e0 12 +05 f2 6c 95 29 1e 02 af 42 ac 4c 6c 57 8a cc a1 +d7 dd da be ad da 2e 52 87 a1 4b 7d 69 33 70 05 +07 3d ac 48 df 69 56 5d 88 d0 f3 b6 da 54 22 83 +28 b5 ae 98 f9 ab 01 ee f6 7d f1 27 db ed f6 d1 +fe bc 68 aa 70 0a f6 dc 87 23 63 8a 48 27 d5 f8 +a8 90 00 30 de e5 87 22 ee 09 6a 16 a9 3b F diff --git a/lib/TinyGSM/extras/test_1m.bin b/lib/TinyGSM/extras/test_1m.bin new file mode 100644 index 0000000..16666f2 Binary files /dev/null and b/lib/TinyGSM/extras/test_1m.bin differ diff --git a/lib/TinyGSM/extras/test_simple.txt b/lib/TinyGSM/extras/test_simple.txt new file mode 100644 index 0000000..675136a --- /dev/null +++ b/lib/TinyGSM/extras/test_simple.txt @@ -0,0 +1,10 @@ +01 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +02 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +03 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +04 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +05 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +06 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +07 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +08 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +09 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +10 #$%@`"'~^&-*+=!?.,:;_\|/<>()[]{} 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz diff --git a/lib/TinyGSM/library.json b/lib/TinyGSM/library.json index 031a15f..7113c5d 100644 --- a/lib/TinyGSM/library.json +++ b/lib/TinyGSM/library.json @@ -1,8 +1,8 @@ { "name": "TinyGSM", - "version": "0.12.0", + "version": "0.11.7", "description": "A small Arduino library for GPRS modules, that just works. Includes examples for Blynk, MQTT, File Download, and Web Client. Supports many GSM, LTE, and WiFi modules with AT command interfaces.", - "keywords": "GSM, AT commands, AT, SIM800, SIM900, A6, A7, M590, ESP8266, SIM7000, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900A, SIM900D, SIM908, SIM968, M95, MC60, MC60E, BG96, BG95, ublox, Quectel, SIMCOM, AI Thinker, LTE, LTE-M", + "keywords": "GSM, AT commands, AT, SIM800, SIM900, A6, A7, M590, ESP8266, SIM7000, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900A, SIM900D, SIM908, SIM968, M95, MC60, MC60E, BG96, ublox, Quectel, SIMCOM, AI Thinker, LTE, LTE-M", "authors": [ { "name": "Volodymyr Shymanskyy", @@ -26,7 +26,9 @@ "examples/*" ] }, - "frameworks": ["arduino", "energia", "wiringpi"], - "platforms": "*", + "frameworks": "arduino", + "platforms": [ + "espressif32" + ], "headers": "TinyGsmClient.h" } diff --git a/lib/TinyGSM/library.properties b/lib/TinyGSM/library.properties index ef29574..9a93f05 100644 --- a/lib/TinyGSM/library.properties +++ b/lib/TinyGSM/library.properties @@ -1,10 +1,10 @@ name=TinyGSM -version=0.12.0 +version=0.11.7 author=Volodymyr Shymanskyy maintainer=Volodymyr Shymanskyy sentence=A small Arduino library for GPRS modules, that just works. paragraph=Includes examples for Blynk, MQTT, File Download, and Web Client. Supports many GSM, LTE, and WiFi modules with AT command interfaces. category=Communication url=https://github.com/vshymanskyy/TinyGSM -architectures=* +architectures=esp32 includes=TinyGsmClient.h diff --git a/lib/TinyGSM/platformio.ini b/lib/TinyGSM/platformio.ini new file mode 100644 index 0000000..e637881 --- /dev/null +++ b/lib/TinyGSM/platformio.ini @@ -0,0 +1,31 @@ + +[platformio] +lib_dir = . +src_dir = tools/test_build + +[env] +framework = arduino +upload_protocol = esptool +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder, log2file +build_flags = + -Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference + ; -D TINY_GSM_MODEM_SIM7000 + ; -D TINY_GSM_MODEM_SIM7020 + ; -D TINY_GSM_MODEM_SIM7600 + ; -D TINY_GSM_MODEM_A7608 + -D TINY_GSM_MODEM_A7670 + ; -D TINY_GSM_MODEM_SIM7672 +lib_deps= + 89 + 415 + 1202 + 1286 + +[env:esp32dev] +platform = espressif32 +board = esp32dev + +[env:esp32s3box] +platform = espressif32 +board = esp32s3box diff --git a/lib/TinyGSM/src/TinyGsmBattery.tpp b/lib/TinyGSM/src/TinyGsmBattery.tpp index 767ae78..a662682 100644 --- a/lib/TinyGSM/src/TinyGsmBattery.tpp +++ b/lib/TinyGSM/src/TinyGsmBattery.tpp @@ -15,62 +15,21 @@ template class TinyGsmBattery { - /* =========================================== */ - /* =========================================== */ - /* - * Define the interface - */ public: /* * Battery functions */ - - /** - * @brief Get the current battery voltage. - * - * @note Unless you have a battery directly connected to your modem module, - * this will be the input voltage going to the module from your main processor - * board, not the battery voltage of your main processor. - * - * @return *int16_t* The battery voltage measured by the modem module. - */ - int16_t getBattVoltage() { + uint16_t getBattVoltage() { return thisModem().getBattVoltageImpl(); } - - /** - * @brief Get the current battery percent. - * - * @note Unless you have a battery directly connected to your modem module, - * this will be the percent from the input voltage going to the module from - * your main processor board, not the battery percent of your main processor. - * - * @return *int8_t* The current battery percent. - */ int8_t getBattPercent() { return thisModem().getBattPercentImpl(); } - - /** - * @brief Get the battery charging state. - * - * @return *int8_t* The battery charge state. - */ - int8_t getBattChargeState() { + uint8_t getBattChargeState() { return thisModem().getBattChargeStateImpl(); } - - /** - * @brief Get the all battery state - * - * @param chargeState A reference to an int to set to the battery charge state - * @param percent A reference to an int to set to the battery percent - * @param milliVolts A reference to an int to set to the battery voltage - * @return *true* The battery stats were updated by the module. - * @return *false* There was a failure in updating the battery stats from the - * module. - */ - bool getBattStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) { + bool getBattStats(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { return thisModem().getBattStatsImpl(chargeState, percent, milliVolts); } @@ -84,20 +43,13 @@ class TinyGsmBattery { inline modemType& thisModem() { return static_cast(*this); } - ~TinyGsmBattery() {} - - /* =========================================== */ - /* =========================================== */ - /* - * Define the default function implementations - */ /* * Battery functions */ protected: // Use: float vBatt = modem.getBattVoltage() / 1000.0; - int16_t getBattVoltageImpl() { + uint16_t getBattVoltageImpl() { thisModem().sendAT(GF("+CBC")); if (thisModem().waitResponse(GF("+CBC:")) != 1) { return 0; } thisModem().streamSkipUntil(','); // Skip battery charge status @@ -120,7 +72,7 @@ class TinyGsmBattery { return res; } - int8_t getBattChargeStateImpl() { + uint8_t getBattChargeStateImpl() { thisModem().sendAT(GF("+CBC")); if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; } // Read battery charge status @@ -130,8 +82,8 @@ class TinyGsmBattery { return res; } - bool getBattStatsImpl(int8_t& chargeState, int8_t& percent, - int16_t& milliVolts) { + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { thisModem().sendAT(GF("+CBC")); if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; } chargeState = thisModem().streamGetIntBefore(','); diff --git a/lib/TinyGSM/src/TinyGsmBluetooth.tpp b/lib/TinyGSM/src/TinyGsmBluetooth.tpp index b3913d9..ca1cb4e 100644 --- a/lib/TinyGSM/src/TinyGsmBluetooth.tpp +++ b/lib/TinyGSM/src/TinyGsmBluetooth.tpp @@ -15,59 +15,23 @@ template class TinyGsmBluetooth { - /* =========================================== */ - /* =========================================== */ - /* - * Define the interface - */ public: /* * Bluetooth functions */ - - /** - * @brief Enable module Bluetooth - * - * @return *true* Bluetooth was succcessfully enabled - * @return *false* Bluetooth failed to enable - */ bool enableBluetooth() { return thisModem().enableBluetoothImpl(); } - - /** - * @brief Disable module Bluetooth - * - * @return *true* Bluetooth was succcessfully disabled - * @return *false* Bluetooth failed to disable - */ bool disableBluetooth() { return thisModem().disableBluetoothImpl(); } - - /** - * @brief Set the Bluetooth visibility - * - * @param visible True to make the modem visible over Bluetooth, false to make - * it invisible. - * @return *true* Bluetooth visibility was successfully changed. - * @return *false* Bluetooth visibility failed to change - */ bool setBluetoothVisibility(bool visible) { return thisModem().setBluetoothVisibilityImpl(visible); } - - /** - * @brief Set the Bluetooth host name - * - * @param name The name visible to other Bluetooth objects - * @return *true* Bluetooth host name was successfully changed. - * @return *false* Bluetooth host name failed to change - */ bool setBluetoothHostName(const char* name) { - return thisModem().setBluetoothHostNameImpl(name); + return thisModem().setBluetoothHostNameImpl(name); } - + /* * CRTP Helper */ @@ -78,22 +42,15 @@ class TinyGsmBluetooth { inline modemType& thisModem() { return static_cast(*this); } - ~TinyGsmBluetooth() {} - - /* =========================================== */ - /* =========================================== */ - /* - * Define the default function implementations - */ /* * Bluetooth functions */ - bool enableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED; - bool disableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED; - bool setBluetoothVisibilityImpl(bool visible) TINY_GSM_ATTR_NOT_IMPLEMENTED; - bool setBluetoothHostNameImpl(const char* name) TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool enableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool disableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool setBluetoothVisibilityImpl(bool visible) TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool setBluetoothHostNameImpl(const char* name) TINY_GSM_ATTR_NOT_IMPLEMENTED; }; diff --git a/lib/TinyGSM/src/TinyGsmCalling.tpp b/lib/TinyGSM/src/TinyGsmCalling.tpp index 59525fd..413515a 100644 --- a/lib/TinyGSM/src/TinyGsmCalling.tpp +++ b/lib/TinyGSM/src/TinyGsmCalling.tpp @@ -15,55 +15,19 @@ template class TinyGsmCalling { - /* =========================================== */ - /* =========================================== */ - /* - * Define the interface - */ public: /* * Phone Call functions */ - - /** - * @brief Answer an incoming phone call - * - * @return *true* The call was answered. - * @return *false* The call was not answered. - */ bool callAnswer() { return thisModem().callAnswerImpl(); } - - /** - * @brief Make a phone call - * - * @param number The number to call - * @return *true* The call was successfully made - * @return *false* The call wasn't made - */ bool callNumber(const String& number) { return thisModem().callNumberImpl(number); } - - /** - * @brief Hang up an in-progress phone call - * - * @return *true* The call was hung up. - * @return *false* The call was not hung up. - */ bool callHangup() { return thisModem().callHangupImpl(); } - - /** - * @brief Sent a DTMF (dual tone multi frequency) message over a phone call. - * - * @param cmd The command to send - * @param duration_ms The tone duration - * @return *true* The message was sent - * @return *false* The message failed to send - */ bool dtmfSend(char cmd, int duration_ms = 100) { return thisModem().dtmfSendImpl(cmd, duration_ms); } @@ -78,13 +42,6 @@ class TinyGsmCalling { inline modemType& thisModem() { return static_cast(*this); } - ~TinyGsmCalling() {} - - /* =========================================== */ - /* =========================================== */ - /* - * Define the default function implementations - */ /* * Phone Call functions diff --git a/lib/TinyGSM/src/TinyGsmClient.h b/lib/TinyGSM/src/TinyGsmClient.h index cd2a98f..818791c 100644 --- a/lib/TinyGSM/src/TinyGsmClient.h +++ b/lib/TinyGSM/src/TinyGsmClient.h @@ -31,6 +31,12 @@ typedef TinyGsmSim800::GsmClientSim800 TinyGsmClient; typedef TinyGsmSim7000 TinyGsm; typedef TinyGsmSim7000::GsmClientSim7000 TinyGsmClient; +#elif defined(TINY_GSM_MODEM_SIM7020) +#include "TinyGsmClientSIM7020.h" +typedef TinyGsmSim7020 TinyGsm; +typedef TinyGsmSim7020::GsmClientSim7020 TinyGsmClient; +typedef TinyGsmSim7020::GsmClientSecureSim7020 TinyGsmClientSecure; + #elif defined(TINY_GSM_MODEM_SIM7000SSL) #include "TinyGsmClientSIM7000SSL.h" typedef TinyGsmSim7000SSL TinyGsm; @@ -56,6 +62,19 @@ typedef TinyGsmSim5360::GsmClientSim5360 TinyGsmClient; typedef TinyGsmSim7600 TinyGsm; typedef TinyGsmSim7600::GsmClientSim7600 TinyGsmClient; +#elif defined(TINY_GSM_MODEM_A7670) +#include "TinyGsmClientA7670.h" +typedef TinyGsmA7670 TinyGsm; +typedef TinyGsmA7670::GsmClientA7670 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_A7608) +#include "TinyGsmClientA7608.h" +typedef TinyGsmA7608 TinyGsm; +typedef TinyGsmA7608::GsmClientA7608 TinyGsmClient; +#elif defined(TINY_GSM_MODEM_SIM7672) +#include "TinyGsmClientSIM7672.h" +typedef TinyGsmSim7672 TinyGsm; +typedef TinyGsmSim7672::GsmClientSim7672 TinyGsmClient; #elif defined(TINY_GSM_MODEM_UBLOX) #include "TinyGsmClientUBLOX.h" typedef TinyGsmUBLOX TinyGsm; @@ -68,23 +87,15 @@ typedef TinyGsmSaraR4 TinyGsm; typedef TinyGsmSaraR4::GsmClientSaraR4 TinyGsmClient; typedef TinyGsmSaraR4::GsmClientSecureR4 TinyGsmClientSecure; -#elif defined(TINY_GSM_MODEM_SARAR5) -#include "TinyGsmClientSaraR5.h" -typedef TinyGsmSaraR5 TinyGsm; -typedef TinyGsmSaraR5::GsmClientSaraR5 TinyGsmClient; -typedef TinyGsmSaraR5::GsmClientSecureR5 TinyGsmClientSecure; - #elif defined(TINY_GSM_MODEM_M95) #include "TinyGsmClientM95.h" typedef TinyGsmM95 TinyGsm; typedef TinyGsmM95::GsmClientM95 TinyGsmClient; -#elif defined(TINY_GSM_MODEM_BG96) || defined(TINY_GSM_MODEM_BG95) || \ - defined(TINY_GSM_MODEM_BG95SSL) +#elif defined(TINY_GSM_MODEM_BG96) #include "TinyGsmClientBG96.h" -typedef TinyGsmBG96 TinyGsm; -typedef TinyGsmBG96::GsmClientBG96 TinyGsmClient; -typedef TinyGsmBG96::GsmClientSecureBG96 TinyGsmClientSecure; +typedef TinyGsmBG96 TinyGsm; +typedef TinyGsmBG96::GsmClientBG96 TinyGsmClient; #elif defined(TINY_GSM_MODEM_A6) || defined(TINY_GSM_MODEM_A7) #include "TinyGsmClientA6.h" @@ -101,7 +112,7 @@ typedef TinyGsmM590::GsmClientM590 TinyGsmClient; typedef TinyGsmMC60 TinyGsm; typedef TinyGsmMC60::GsmClientMC60 TinyGsmClient; -#elif defined(TINY_GSM_MODEM_ESP8266) || defined(TINY_GSM_MODEM_ESP32) +#elif defined(TINY_GSM_MODEM_ESP8266) #define TINY_GSM_MODEM_HAS_WIFI #include "TinyGsmClientESP8266.h" typedef TinyGsmESP8266 TinyGsm; @@ -120,13 +131,7 @@ typedef TinyGsmXBee::GsmClientSecureXBee TinyGsmClientSecure; typedef TinyGsmSequansMonarch TinyGsm; typedef TinyGsmSequansMonarch::GsmClientSequansMonarch TinyGsmClient; typedef TinyGsmSequansMonarch::GsmClientSecureSequansMonarch - TinyGsmClientSecure; - -#elif defined(TINY_GSM_MODEM_A7672X) -#include "TinyGsmClientA7672x.h" -typedef TinyGsmA7672X TinyGsm; -typedef TinyGsmA7672X::GsmClientA7672X TinyGsmClient; -typedef TinyGsmA7672X::GsmClientSecureA7672X TinyGsmClientSecure; +TinyGsmClientSecure; #else #error "Please define GSM modem model" diff --git a/lib/TinyGSM/src/TinyGsmClientA6.h b/lib/TinyGSM/src/TinyGsmClientA6.h index f486b46..9226960 100644 --- a/lib/TinyGSM/src/TinyGsmClientA6.h +++ b/lib/TinyGSM/src/TinyGsmClientA6.h @@ -14,34 +14,24 @@ #define TINY_GSM_MUX_COUNT 8 #define TINY_GSM_NO_MODEM_BUFFER -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "Ai-Thinker" - -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#if defined(TINY_GSM_MODEM_A7) -#define MODEM_MODEL "A7" -#else -#define MODEM_MODEL "A6" -#endif -#include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" -#include "TinyGsmGPRS.tpp" +#include "TinyGsmBattery.tpp" #include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" #include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" #include "TinyGsmTime.tpp" -#include "TinyGsmBattery.tpp" -enum A6RegStatus { +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { REG_NO_RESULT = -1, REG_UNREGISTERED = 0, REG_SEARCHING = 2, @@ -139,7 +129,7 @@ class TinyGsmA6 : public TinyGsmModem, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientA6")); @@ -165,7 +155,7 @@ class TinyGsmA6 : public TinyGsmModem, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -175,15 +165,6 @@ class TinyGsmA6 : public TinyGsmModem, } } - // Gets the modem serial number - String getModemSerialNumberImpl() { - sendAT(GF("GSN")); // Not CGSN - String res; - if (waitResponse(1000L, res) != 1) { return ""; } - cleanResponseString(res); - return res; - } - bool factoryDefaultImpl() { sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write waitResponse(); @@ -195,7 +176,7 @@ class TinyGsmA6 : public TinyGsmModem, * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { + bool restartImpl(const char* pin = NULL) { if (!testAT()) { return false; } sendAT(GF("+RST=1")); delay(3000); @@ -217,13 +198,13 @@ class TinyGsmA6 : public TinyGsmModem, * Generic network functions */ public: - A6RegStatus getRegistrationStatus() { - return (A6RegStatus)getRegistrationStatusXREG("CREG"); + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); } protected: bool isNetworkConnectedImpl() { - A6RegStatus s = getRegistrationStatus(); + RegStatus s = getRegistrationStatus(); return (s == REG_OK_HOME || s == REG_OK_ROAMING); } @@ -231,27 +212,18 @@ class TinyGsmA6 : public TinyGsmModem, sendAT(GF("+CIFSR")); String res; if (waitResponse(10000L, res) != 1) { return ""; } - cleanResponseString(res); + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); return res; } - /* - * Secure socket layer (SSL) functions - */ - protected: - // No functions of this type supported - - /* - * WiFi functions - */ - // No functions of this type supported - /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); sendAT(GF("+CGATT=1")); @@ -294,7 +266,7 @@ class TinyGsmA6 : public TinyGsmModem, waitResponse(); sendAT(GF("+COPS?")); - if (waitResponse(GF(AT_NL "+COPS:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+COPS:")) != 1) { return ""; } streamSkipUntil('"'); // Skip mode and format String res = stream.readStringUntil('"'); waitResponse(); @@ -307,7 +279,7 @@ class TinyGsmA6 : public TinyGsmModem, protected: String getSimCCIDImpl() { sendAT(GF("+CCID")); - if (waitResponse(GF(AT_NL "+SCID: SIM Card ID:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+SCID: SIM Card ID:")) != 1) { return ""; } String res = stream.readStringUntil('\n'); waitResponse(); res.trim(); @@ -328,16 +300,16 @@ class TinyGsmA6 : public TinyGsmModem, if (waitResponse(5000L) != 1) { return false; } - if (waitResponse(60000L, GF(AT_NL "+CIEV: \"CALL\",1"), - GF(AT_NL "+CIEV: \"CALL\",0"), GFP(GSM_ERROR)) != 1) { + if (waitResponse(60000L, GF(GSM_NL "+CIEV: \"CALL\",1"), + GF(GSM_NL "+CIEV: \"CALL\",0"), GFP(GSM_ERROR)) != 1) { return false; } - int8_t rsp = waitResponse(60000L, GF(AT_NL "+CIEV: \"SOUNDER\",0"), - GF(AT_NL "+CIEV: \"CALL\",0")); + int8_t rsp = waitResponse(60000L, GF(GSM_NL "+CIEV: \"SOUNDER\",0"), + GF(GSM_NL "+CIEV: \"CALL\",0")); - int8_t rsp2 = waitResponse(300L, GF(AT_NL "BUSY" AT_NL), - GF(AT_NL "NO ANSWER" AT_NL)); + int8_t rsp2 = waitResponse(300L, GF(GSM_NL "BUSY" GSM_NL), + GF(GSM_NL "NO ANSWER" GSM_NL)); return rsp == 1 && rsp2 == 0; } @@ -380,7 +352,7 @@ class TinyGsmA6 : public TinyGsmModem, } /* - * Text messaging (SMS) functions + * Messaging functions */ protected: String sendUSSDImpl(const String& code) { @@ -390,7 +362,7 @@ class TinyGsmA6 : public TinyGsmModem, waitResponse(); sendAT(GF("+CUSD=1,\""), code, GF("\",15")); if (waitResponse(10000L) != 1) { return ""; } - if (waitResponse(GF(AT_NL "+CUSD:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+CUSD:")) != 1) { return ""; } streamSkipUntil('"'); String hex = stream.readStringUntil('"'); streamSkipUntil(','); @@ -405,42 +377,23 @@ class TinyGsmA6 : public TinyGsmModem, } } - /* - * GSM Location functions - */ - // No functions of this type supported - - /* - * GPS/GNSS/GLONASS location functions - */ - // No functions of this type supported - /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp + protected: + // Can follow the standard CCLK function in the template // Note - the clock probably has to be set manaually first - /* - * NTP server functions - */ - // No functions of this type supported - - /* - * BLE functions - */ - // No functions of this type supported - /* * Battery functions */ protected: - int16_t getBattVoltageImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + uint16_t getBattVoltageImpl() TINY_GSM_ATTR_NOT_AVAILABLE; // Needs a '?' after CBC, unlike most int8_t getBattPercentImpl() { sendAT(GF("+CBC?")); - if (waitResponse(GF(AT_NL "+CBC:")) != 1) { return false; } + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; } streamSkipUntil(','); // Skip battery charge status // Read battery charge level int8_t res = streamGetIntBefore('\n'); @@ -450,10 +403,10 @@ class TinyGsmA6 : public TinyGsmModem, } // Needs a '?' after CBC, unlike most - bool getBattStatsImpl(int8_t& chargeState, int8_t& percent, - int16_t& milliVolts) { + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { sendAT(GF("+CBC?")); - if (waitResponse(GF(AT_NL "+CBC:")) != 1) { return false; } + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; } chargeState = streamGetIntBefore(','); percent = streamGetIntBefore('\n'); milliVolts = 0; @@ -462,11 +415,6 @@ class TinyGsmA6 : public TinyGsmModem, return true; } - /* - * Temperature functions - */ - // No functions of this type supported - /* * Client related functions */ @@ -477,12 +425,12 @@ class TinyGsmA6 : public TinyGsmModem, uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; sendAT(GF("+CIPSTART="), GF("\"TCP"), GF("\",\""), host, GF("\","), port); - if (waitResponse(timeout_ms, GF(AT_NL "+CIPNUM:")) != 1) { return false; } + if (waitResponse(timeout_ms, GF(GSM_NL "+CIPNUM:")) != 1) { return false; } int8_t newMux = streamGetIntBefore('\n'); - int8_t rsp = waitResponse((timeout_ms - (millis() - startMillis)), - GF("CONNECT OK" AT_NL), GF("CONNECT FAIL" AT_NL), - GF("ALREADY CONNECT" AT_NL)); + int8_t rsp = waitResponse( + (timeout_ms - (millis() - startMillis)), GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), GF("ALREADY CONNECT" GSM_NL)); if (waitResponse() != 1) { return false; } *mux = newMux; @@ -491,10 +439,10 @@ class TinyGsmA6 : public TinyGsmModem, int16_t modemSend(const void* buff, size_t len, uint8_t mux) { sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); - if (waitResponse(2000L, GF(AT_NL ">")) != 1) { return 0; } + if (waitResponse(2000L, GF(GSM_NL ">")) != 1) { return 0; } stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(10000L, GFP(GSM_OK), GF(AT_NL "FAIL")) != 1) { return 0; } + if (waitResponse(10000L, GFP(GSM_OK), GF(GSM_NL "FAIL")) != 1) { return 0; } return len; } @@ -510,37 +458,115 @@ class TinyGsmA6 : public TinyGsmModem, * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF("+CIPRCV:"))) { - int8_t mux = streamGetIntBefore(','); - int16_t len = streamGetIntBefore(','); - int16_t len_orig = len; - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - if (len > sockets[mux]->rx.free()) { - DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free()); - // reset the len to read to the amount free - len = sockets[mux]->rx.free(); - } - while (len--) { moveCharFromStreamToFifo(mux); } - // TODO(?) Deal with missing characters - if (len_orig != sockets[mux]->available()) { - DBG("### Different number of characters received than expected: ", - sockets[mux]->available(), " vs ", len_orig); + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+CIPRCV:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore(','); + int16_t len_orig = len; + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (len > sockets[mux]->rx.free()) { + DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free()); + } else { + DBG("### Got: ", len, "->", sockets[mux]->rx.free()); + } + while (len--) { moveCharFromStreamToFifo(mux); } + // TODO(?) Deal with missing characters + if (len_orig > sockets[mux]->available()) { + DBG("### Fewer characters received than expected: ", + sockets[mux]->available(), " vs ", len_orig); + } + } + data = ""; + } else if (data.endsWith(GF("+TCPCLOSED:"))) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); } } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - DBG("### Got Data: ", len_orig, "on", mux); - return true; - } else if (data.endsWith(GF("+TCPCLOSED:"))) { - int8_t mux = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - data = ""; - DBG("### Closed: ", mux); - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } public: @@ -548,6 +574,7 @@ class TinyGsmA6 : public TinyGsmModem, protected: GsmClientA6* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTA6_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientA7608.h b/lib/TinyGSM/src/TinyGsmClientA7608.h new file mode 100644 index 0000000..1169821 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientA7608.h @@ -0,0 +1,1076 @@ +/** + * @file TinyGsmClientA7608.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTA7608_H_ +#define SRC_TINYGSMCLIENTA7608_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 10 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" +#include "TinyGsmMqttA76xx.h" +#include "TinyGsmHttpsA76xx.h" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, + REG_SMS_ONLY = 6, + REG_EMERGENCY = 11 +}; + +enum NetworkMode { + MODEM_NETWORK_AUTO = 2, + MODEM_NETWORK_GSM = 13, + MODEM_NETWORK_WCDMA = 14, + MODEM_NETWORK_LTE = 38, +}; + +class TinyGsmA7608 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSMS, + public TinyGsmGSMLocation, + public TinyGsmGPS, + public TinyGsmTime, + public TinyGsmNTP, + public TinyGsmBattery, + public TinyGsmTemperature, + public TinyGsmCalling, + public TinyGsmMqttA76xx, + public TinyGsmHttpsA76xx + +{ + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSMS; + friend class TinyGsmGPS; + friend class TinyGsmGSMLocation; + friend class TinyGsmTime; + friend class TinyGsmNTP; + friend class TinyGsmBattery; + friend class TinyGsmTemperature; + friend class TinyGsmCalling; + friend class TinyGsmMqttA76xx; + friend class TinyGsmHttpsA76xx; + + /* + * Inner Client + */ + public: + class GsmClientA7608 : public GsmClient { + friend class TinyGsmA7608; + + public: + GsmClientA7608() {} + + explicit GsmClientA7608(TinyGsmA7608& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmA7608* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + // stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + /*TODO(?)) + class GsmClientSecureSIM7600 : public GsmClientA7608 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmA7608& modem, uint8_t mux = 0) + : public GsmClient(modem, mux) + {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ + + /* + * Constructor + */ + public: + explicit TinyGsmA7608(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientA7608")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Disable time and time zone URC's + sendAT(GF("+CTZR=0")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable automatic time zome update + sendAT(GF("+CTZU=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + String getModemNameImpl() { + String name = "UNKOWN"; + String res; + + sendAT(GF("E0")); // Echo Off + waitResponse(); + + sendAT("I"); + if (waitResponse(10000L, res) != 1) { + DBG("MODEM STRING NO FOUND!"); + return name; + } + int modelIndex = res.indexOf("Model:") + 6; + int nextLineIndex = res.indexOf('\n', modelIndex); + if (nextLineIndex != -1) { + String modelString = res.substring(modelIndex, nextLineIndex); + modelString.trim(); + if(modelString.startsWith("A7608")){ + name = modelString; + DBG("### Modem:", name); + } + } else { + DBG("Model string not found."); + } + return name; + } + + bool factoryDefaultImpl() { + sendAT("&F"); //Set all current parameters to manufacturer defaults + if (waitResponse() != 1) { return false; } + return true; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("+CRESET")); + if (waitResponse(10000L) != 1) { return false; } + delay(5000L); // TODO(?): Test this delay! + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPOF")); + return waitResponse() == 1; + } + + bool radioOffImpl() { + if (!setPhoneFunctionality(4)) { return false; } + delay(3000); + return true; + } + + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+CSCLK="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CGREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + public: + String getNetworkModes() { + int16_t mode = getNetworkMode(); + switch (mode) + { + case MODEM_NETWORK_AUTO: + return "AUTO"; + case MODEM_NETWORK_GSM: + return "GSM"; + case MODEM_NETWORK_WCDMA: + return "WCDMA"; + case MODEM_NETWORK_LTE: + return "LTE"; + default: + break; + } + return "UNKNOWN"; + } + + int16_t getNetworkMode() { + sendAT(GF("+CNMP?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return -1; } + int16_t mode = streamGetIntBefore('\n'); + waitResponse(); + return mode; + } + + bool setNetworkMode(NetworkMode mode) { + switch (mode) + { + case MODEM_NETWORK_AUTO: + case MODEM_NETWORK_GSM: + case MODEM_NETWORK_WCDMA: + case MODEM_NETWORK_LTE: + break; + default: + return false; + } + sendAT(GF("+CNMP="), mode); + return waitResponse() == 1; + } + + String getLocalIPImpl() { + String res; + sendAT(GF("+IPADDR")); // Inquire Socket PDP address + if (waitResponse(GF("+IPADDR: ")) != 1) { return ""; } + waitResponse(1000,res); + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + bool enableNetwork(){ + sendAT(GF("+NETOPEN")); + int res = waitResponse(GF("+NETOPEN: 0"),GF("+IP ERROR: Network is already opened")); + if (res != 1 && res != 2){ + return false; + } + return true; + } + + bool disableNetwork(){ + sendAT(GF("+NETCLOSE")); + if (waitResponse() != 1){ + return false; + } + int res = waitResponse(GF("+NETCLOSE: 0"),GF("+NETCLOSE: 2")); + if (res != 1 || res != 2){ + return false; + } + return true; + } + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); // Make sure we're not connected first + + // Define the PDP context + + // The CGDCONT commands set up the "external" PDP context + + // Set the external authentication + if (user && strlen(user) > 0) { + sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"'); + waitResponse(); + } + + // Define external PDP context 1 + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"', ",\"0.0.0.0\",0,0"); + waitResponse(); + + // Configure TCP parameters + + // Select TCP/IP application mode (command mode) + sendAT(GF("+CIPMODE=0")); + waitResponse(); + + // Set Sending Mode - send without waiting for peer TCP ACK + sendAT(GF("+CIPSENDMODE=0")); + waitResponse(); + + // Configure socket parameters + // AT+CIPCCFG= , , , , , + // , + // NmRetry = number of retransmission to be made for an IP packet + // = 10 (default) + // DelayTm = number of milliseconds to delay before outputting received data + // = 0 (default) + // Ack = sets whether reporting a string "Send ok" = 0 (don't report) + // errMode = mode of reporting error result code = 0 (numberic values) + // HeaderType = which data header of receiving data in multi-client mode + // = 1 (+RECEIVE,,) + // AsyncMode = sets mode of executing commands + // = 0 (synchronous command executing) + // TimeoutVal = minimum retransmission timeout in milliseconds = 75000 + sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000")); + if (waitResponse() != 1) { + // printf("[%u]Configure socket parameters\n ",__LINE__); + return false; + } + + // Configure timeouts for opening and closing sockets + // AT+CIPTIMEOUT= , + sendAT(GF("+CIPTIMEOUT="), 75000, ',', 15000, ',', 15000); + waitResponse(); + + // PDP context activate or deactivate + sendAT("+CGACT=1,1"); + if (waitResponse(30000UL) != 1) { + // printf("[%u]PDP context activate or deactivate\n ",__LINE__); + return false; + } + + // This activates and attaches to the external PDP context that is tied + // to the embedded context for TCP/IP (ie AT+CGACT=1,1 and AT+CGATT=1) + // Response may be an immediate "OK" followed later by "+NETOPEN: 0". + // We to ignore any immediate response and wait for the + // URC to show it's really connected. + sendAT(GF("+NETOPEN")); + if (waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) { + // printf("[%u]NETOPEN\n ",__LINE__); + return false; + } + + return true; + } + + bool gprsDisconnectImpl() { + // Close all sockets and stop the socket service + // Note: On the LTE models, this single command closes all sockets and the + // service + sendAT(GF("+NETCLOSE")); + if (waitResponse(60000L, GF(GSM_NL "+NETCLOSE: 0")) != 1) { return false; } + + return true; + } + + bool isGprsConnectedImpl() { + sendAT(GF("+NETOPEN?")); + // May return +NETOPEN: 1, 0. We just confirm that the first number is 1 + if (waitResponse(GF(GSM_NL "+NETOPEN: 1")) != 1) { return false; } + waitResponse(); + + // sendAT(GF("+IPADDR")); // Inquire Socket PDP address + // // sendAT(GF("+CGPADDR=1")); // Show PDP address + // if (waitResponse() != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Gets the CCID of a sim card via AT+CCID + String getSimCCIDImpl() { + sendAT(GF("+CICCID")); + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + protected: + bool callHangupImpl() { + sendAT(GF("+CHUP")); + return waitResponse() == 1; + } + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GSM Location functions + */ + protected: + // Can return a GSM-based location from CLBS as per the template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // enable GPS + bool enableGPSImpl(int8_t power_en_pin ,uint8_t enable_level) { + if(power_en_pin == GSM_MODEM_AUX_POWER){ + sendAT("+CVAUXS=1"); + waitResponse(); + }else if(power_en_pin != -1){ + sendAT("+CGDRT=",power_en_pin,",1"); + waitResponse(); + sendAT("+CGSETV=",power_en_pin,",",enable_level); + waitResponse(); + } + sendAT(GF("+CGNSSPWR=1")); + if (waitResponse(10000UL,"+CGNSSPWR: READY!") != 1) { return false; } + return true; + } + + bool disableGPSImpl(int8_t power_en_pin ,uint8_t disbale_level) { + if(power_en_pin == GSM_MODEM_AUX_POWER){ + sendAT("+CVAUXS=0"); + waitResponse(); + }else if(power_en_pin != -1){ + sendAT("+CGSETV=",power_en_pin,",",disbale_level); + waitResponse(); + sendAT("+CGDRT=",power_en_pin,",0"); + waitResponse(); + } + sendAT(GF("+CGNSSPWR=0")); + if (waitResponse() != 1) { return false; } + return true; + } + + bool isEnableGPSImpl(){ + sendAT(GF("+CGNSSPWR?")); + if (waitResponse("+CGNSSPWR:") != 1) { return false; } + // +CGNSSPWR:,, + return 1 == streamGetIntBefore(','); + } + + bool enableAGPSImpl(){ + sendAT(GF("+CGNSSPWR?")); + if (waitResponse("+CGNSSPWR:") != 1) { return false; } + // +CGNSSPWR:,, + if(1 == streamGetIntBefore(',')){ + sendAT("+CAGPS"); + if (waitResponse(30000UL,"+AGPS:") != 1) { return false; } + String res = stream.readStringUntil('\n'); + if(res.startsWith(" success.")){ + return true; + } + } + return false; + } + + // get the RAW GPS output + String getGPSrawImpl() { + sendAT(GF("+CGNSSINFO")); + if (waitResponse(GF(GSM_NL "+CGNSSINFO:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + bool getGPSImpl(uint8_t *status,float* lat, float* lon, float* speed = 0, float* alt = 0, + int* vsat = 0, int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, int* hour = 0, + int* minute = 0, int* second = 0) { + sendAT(GF("+CGNSSINFO")); + if (waitResponse(GF(GSM_NL "+CGNSSINFO: ")) != 1) { return false; } + + uint8_t fixMode = streamGetIntBefore(','); // mode 2=2D Fix or 3=3DFix + // TODO(?) Can 1 be returned + if (fixMode == 1 || fixMode == 2 || fixMode == 3) { + // init variables + float ilat = 0; + char north; + float ilon = 0; + char east; + float ispeed = 0; + float ialt = 0; + int ivsat = 0; + int iusat = 0; + float iaccuracy = 0; + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + float secondWithSS = 0; + // 20240513 fixed + // A7600M7_B11V05_231108 + // +CGNSSINFO: 3,13,14,,,xx.xxxx,N,xx.xxxx,E,130524,035736.00,53.6,0.000,,1.7,1.2,1.1, + streamSkipUntil(','); // GPS-SVs satellite valid numbers + streamSkipUntil(','); // BEIDOU-SVs satellite valid numbers + streamSkipUntil(','); // GLONASS-SVs satellite valid numbers + streamSkipUntil(','); // GALILEO-SVs satellite valid numbers + ilat = streamGetFloatBefore(','); // Latitude in ddmm.mmmmmm + north = stream.read(); // N/S Indicator, N=north or S=south + streamSkipUntil(','); + ilon = streamGetFloatBefore(','); // Longitude in ddmm.mmmmmm + east = stream.read(); // E/W Indicator, E=east or W=west + streamSkipUntil(','); + + // Date. Output format is ddmmyy + iday = streamGetIntLength(2); // Two digit day + imonth = streamGetIntLength(2); // Two digit month + iyear = streamGetIntBefore(','); // Two digit year + + // UTC Time. Output format is hhmmss.s + ihour = streamGetIntLength(2); // Two digit hour + imin = streamGetIntLength(2); // Two digit minute + secondWithSS = streamGetFloatBefore(','); // 4 digit second with subseconds + + ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters + ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots. + streamSkipUntil(','); // Course Over Ground. Degrees. + streamSkipUntil(','); // After set, will report GPS every x seconds + iaccuracy = streamGetFloatBefore(','); // Position Dilution Of Precision + streamSkipUntil(','); // Horizontal Dilution Of Precision + streamSkipUntil(','); // Vertical Dilution Of Precision + streamSkipUntil('\n'); // TODO(?) is one more field reported?? + if (status){ + *status = fixMode; + } + // Set pointers + if (lat != NULL){ + *lat = (ilat) * (north == 'N' ? 1 : -1); + } + if (lon != NULL){ + *lon = (ilon) * (east == 'E' ? 1 : -1); + } + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = ivsat; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); + + waitResponse(); + // Sometimes, although fix is displayed, + // the value of longitude and latitude 0 will be set as invalid + if(ilat == 0 || ilon == 0){ + return false; + } + return true; + } + waitResponse(); + return false; + } + + bool setGPSBaudImpl(uint32_t baud){ + sendAT("+CGNSSIPR=",baud); + return waitResponse(1000L) == 1; + } + + bool setGPSModeImpl(uint8_t mode){ + sendAT("+CGNSSMODE=",mode); + return waitResponse(1000L) == 1; + } + + bool setGPSOutputRateImpl(uint8_t rate_hz){ + sendAT("+CGPSNMEARATE=",rate_hz); + return waitResponse(1000L) == 1; + } + + bool enableNMEAImpl(){ + sendAT("+CGNSSTST=1"); + if(waitResponse(1000L) != 1){ + return false; + } + // Select the output port for NMEA sentence + sendAT("+CGNSSPORTSWITCH=0,1"); + if(waitResponse(1000L) != 1){ + return false; + } + + + /* + 20240513 + Manufacturer: INCORPORATED + Model: A7608E-H + Revision: A50C4B11A7600M7 + A7600M7_B11V05_231108 + QCN: + IMEI: 861513066221920 + MEID: + +GCAP: +CGSM,+FCLASS,+DS + DeviceInfo: + A7600M7_B11V05_231108 version removes turning off GPS when NMEA is enabled + REMOVE: + // if(!disableGPSImpl(-1,0)){ + // return false; + // } + * * * * */ + + + /* + 20240507 + A7600M7_B11V05_231108 version will not return <+CGNSSPWR: READY!> + but will be returned in earlier versions. Redirect the NMEA sentence to the AT port and only check whether it returns OK. + + Manufacturer: INCORPORATED + Model: A7608SA-H + Revision: A50C4B11A7600M7 + A7600M7_B11V05_231108 + QCN: + IMEI: XXXXXXXXXXXXXXXXXXX + MEID: + +GCAP: +CGSM,+FCLASS,+DS + DeviceInfo: + // if(!enableGPSImpl(-1,0)){ + // return false; + // } + * + * * */ + // sendAT(GF("+CGNSSPWR=1")); + // if (waitResponse(10000UL) != 1) { return false; } + return true; + } + + bool disableNMEAImpl(){ + sendAT("+CGNSSTST=0"); + waitResponse(1000L); + // Select the output port for NMEA sentence + sendAT("+CGNSSPORTSWITCH=1,0"); + return waitResponse(1000L) == 1; + } + + bool configNMEASentenceImpl(bool CGA,bool GLL,bool GSA,bool GSV,bool RMC,bool VTG,bool ZDA,bool ANT){ + char buffer[32]; + snprintf(buffer,32,"%u,%u,%u,%u,%u,%u,%u,0", CGA, GLL, GSA, GSV, RMC, VTG, ZDA); + sendAT("+CGNSSNMEA=",buffer); + return waitResponse(1000L) == 1; + } + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // returns volts, multiply by 1000 to get mV + uint16_t getBattVoltageImpl() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return 0; } + + // get voltage in VOLTS + float voltage = streamGetFloatBefore('\n'); + // Wait for final OK + waitResponse(); + // Return millivolts + uint16_t res = voltage * 1000; + return res; + } + + int8_t getBattPercentImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + + uint8_t getBattChargeStateImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { + chargeState = 0; + percent = 0; + milliVolts = getBattVoltage(); + return true; + } + + /* + * Temperature functions + */ + protected: + // get temperature in degree celsius + uint16_t getTemperatureImpl() { + sendAT(GF("+CPMUTEMP")); + if (waitResponse(GF(GSM_NL "+CPMUTEMP:")) != 1) { return 0; } + // return temperature in C + uint16_t res = streamGetIntBefore('\n'); + // Wait for final OK + waitResponse(); + return res; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 15) { + if (ssl) { DBG("SSL not yet supported on this module!"); } + // Make sure we'll be getting data manually on this connection + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { return false; } + + // Establish a connection in multi-socket mode + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + sendAT(GF("+CIPOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), + port); + // The reply is OK followed by +CIPOPEN: , where + // is the mux number and should be 0 if there's no error + if (waitResponse(timeout_ms, GF(GSM_NL "+CIPOPEN:")) != 1) { return false; } + uint8_t opened_mux = streamGetIntBefore(','); + uint8_t opened_result = streamGetIntBefore('\n'); + + // printf("\nopened_mux :%u opened_result:%u\n",opened_mux,opened_result); + // switch (opened_result) + // { + // case 0 : printf("operation succeeded\n");break; + // case 1 : printf("Network failure\n");break; + // case 2 : printf("Network not opened\n");break; + // case 3 : printf("Wrong parameter\n");break; + // case 4 : printf("Operation not supported\n");break; + // case 5 : printf("Failed to create socket\n");break; + // case 6 : printf("Failed to bind socket\n");break; + // case 7 : printf("TCP server is already listening\n");break; + // case 8 : printf("Busy\n");break; + // case 9 : printf("Sockets opened\n");break; + // case 10 : printf(" Timeout\n");break; + // case 11 : printf(" DNS parse failed for AT+CIPOPEN\n");break; + // case 12 : printf(" Unknown error\n");break; + // default: + // break; + // } + if (opened_mux != mux || opened_result != 0) return false; + return true; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip requested bytes to send + // TODO(?): make sure requested and confirmed bytes match + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux/cid (connecion id) + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ The data length which not read in the buffer + for (int i = 0; i < len_requested; i++) { + uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); +#endif + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + sockets[mux]->sock_available = len_confirmed; + waitResponse(); + return len_requested; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); + } + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + // Read the status of all sockets at once + sendAT(GF("+CIPCLOSE?")); + if (waitResponse(GF("+CIPCLOSE:")) != 1) { + // return false; // TODO: Why does this not read correctly? + } + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // +CIPCLOSE:,,..., + bool muxState = stream.parseInt(); + if (sockets[muxNo]) { sockets[muxNo]->sock_connected = muxState; } + } + waitResponse(); // Should be an OK at the end + if (!sockets[mux]) return false; + return sockets[mux]->sock_connected; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + // putchar(a); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+IPCLOSE:"))) { + int8_t mux = streamGetIntBefore(','); + streamSkipUntil('\n'); // Skip the reason code + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+CIPEVENT:"))) { + // Need to close all open sockets and release the network library. + // User will then need to reconnect. + DBG("### Network error!"); + if (!isGprsConnected()) { gprsDisconnect(); } + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientA7608* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTA7608_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientA7670.h b/lib/TinyGSM/src/TinyGsmClientA7670.h new file mode 100644 index 0000000..1c1b376 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientA7670.h @@ -0,0 +1,1019 @@ +/** + * @file TinyGsmClientA7670.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTA7670_H_ +#define SRC_TINYGSMCLIENTA7670_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 10 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" +#include "TinyGsmMqttA76xx.h" +#include "TinyGsmHttpsA76xx.h" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, + REG_SMS_ONLY = 6, + REG_EMERGENCY = 11 +}; + +enum NetworkMode { + MODEM_NETWORK_AUTO = 2, + MODEM_NETWORK_GSM = 13, + MODEM_NETWORK_WCDMA = 14, + MODEM_NETWORK_LTE = 38, +}; + +class TinyGsmA7670 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSMS, + public TinyGsmGSMLocation, + public TinyGsmGPS, + public TinyGsmTime, + public TinyGsmNTP, + public TinyGsmBattery, + public TinyGsmTemperature, + public TinyGsmCalling, + public TinyGsmMqttA76xx, + public TinyGsmHttpsA76xx + { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSMS; + friend class TinyGsmGPS; + friend class TinyGsmGSMLocation; + friend class TinyGsmTime; + friend class TinyGsmNTP; + friend class TinyGsmBattery; + friend class TinyGsmTemperature; + friend class TinyGsmCalling; + friend class TinyGsmMqttA76xx; + friend class TinyGsmHttpsA76xx; + + /* + * Inner Client + */ + public: + class GsmClientA7670 : public GsmClient { + friend class TinyGsmA7670; + + public: + GsmClientA7670() {} + + explicit GsmClientA7670(TinyGsmA7670& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmA7670* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + // stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + /*TODO(?)) + class GsmClientSecureA7670 : public GsmClientA7670 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmA7670& modem, uint8_t mux = 0) + : public GsmClient(modem, mux) + {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ + + /* + * Constructor + */ + public: + explicit TinyGsmA7670(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientA7670")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Disable time and time zone URC's + sendAT(GF("+CTZR=0")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable automatic time zome update + sendAT(GF("+CTZU=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + String getModemNameImpl() { + String name = "UNKOWN"; + String res; + + sendAT(GF("E0")); // Echo Off + waitResponse(); + + sendAT("I"); + if (waitResponse(10000L, res) != 1) { + DBG("MODEM STRING NO FOUND!"); + return name; + } + int modelIndex = res.indexOf("Model:") + 6; + int nextLineIndex = res.indexOf('\n', modelIndex); + if (nextLineIndex != -1) { + String modelString = res.substring(modelIndex, nextLineIndex); + modelString.trim(); + if(modelString.startsWith("A7670")){ + name = modelString; + DBG("### Modem:", name); + } + } else { + DBG("Model string not found."); + } + return name; + } + + bool factoryDefaultImpl() { + sendAT("&F"); //Set all current parameters to manufacturer defaults + if (waitResponse() != 1) { return false; } + return true; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("+CRESET")); + if (waitResponse(10000L) != 1) { return false; } + delay(5000L); // TODO(?): Test this delay! + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPOF")); + return waitResponse() == 1; + } + + bool radioOffImpl() { + if (!setPhoneFunctionality(4)) { return false; } + delay(3000); + return true; + } + + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+CSCLK="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CGREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + public: + String getNetworkModes() { + int16_t mode = getNetworkMode(); + switch (mode) + { + case MODEM_NETWORK_AUTO: + return "AUTO"; + case MODEM_NETWORK_GSM: + return "GSM"; + case MODEM_NETWORK_WCDMA: + return "WCDMA"; + case MODEM_NETWORK_LTE: + return "LTE"; + default: + break; + } + return "UNKNOWN"; + } + + int16_t getNetworkMode() { + sendAT(GF("+CNMP?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return -1; } + int16_t mode = streamGetIntBefore('\n'); + waitResponse(); + return mode; + } + + bool setNetworkMode(NetworkMode mode) { + switch (mode) + { + case MODEM_NETWORK_AUTO: + case MODEM_NETWORK_GSM: + case MODEM_NETWORK_WCDMA: + case MODEM_NETWORK_LTE: + break; + default: + return false; + } + sendAT(GF("+CNMP="), mode); + return waitResponse() == 1; + } + + String getLocalIPImpl() { + String res; + sendAT(GF("+IPADDR")); // Inquire Socket PDP address + if (waitResponse(GF("+IPADDR: ")) != 1) { return ""; } + waitResponse(1000,res); + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + + bool enableNetwork(){ + sendAT(GF("+NETOPEN")); + int res = waitResponse(GF("+NETOPEN: 0"),GF("+IP ERROR: Network is already opened")); + if (res != 1 && res != 2){ + return false; + } + return true; + } + + bool disableNetwork(){ + sendAT(GF("+NETCLOSE")); + if (waitResponse() != 1){ + return false; + } + int res = waitResponse(GF("+NETCLOSE: 0"),GF("+NETCLOSE: 2")); + if (res != 1 && res != 2){ + return false; + } + return true; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); // Make sure we're not connected first + + // Define the PDP context + + // The CGDCONT commands set up the "external" PDP context + + // Set the external authentication + if (user && strlen(user) > 0) { + sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"'); + waitResponse(); + } + + // Define external PDP context 1 + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"', ",\"0.0.0.0\",0,0"); + waitResponse(); + + // Configure TCP parameters + + // Select TCP/IP application mode (command mode) + sendAT(GF("+CIPMODE=0")); + waitResponse(); + + // Set Sending Mode - send without waiting for peer TCP ACK + sendAT(GF("+CIPSENDMODE=0")); + waitResponse(); + + // Configure socket parameters + // AT+CIPCCFG= , , , , , + // , + // NmRetry = number of retransmission to be made for an IP packet + // = 10 (default) + // DelayTm = number of milliseconds to delay before outputting received data + // = 0 (default) + // Ack = sets whether reporting a string "Send ok" = 0 (don't report) + // errMode = mode of reporting error result code = 0 (numberic values) + // HeaderType = which data header of receiving data in multi-client mode + // = 1 (+RECEIVE,,) + // AsyncMode = sets mode of executing commands + // = 0 (synchronous command executing) + // TimeoutVal = minimum retransmission timeout in milliseconds = 75000 + sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000")); + if (waitResponse() != 1) { + // printf("[%u]Configure socket parameters\n ",__LINE__); + return false; + } + + // Configure timeouts for opening and closing sockets + // AT+CIPTIMEOUT= , + sendAT(GF("+CIPTIMEOUT="), 75000, ',', 15000, ',', 15000); + waitResponse(); + + // PDP context activate or deactivate + sendAT("+CGACT=1,1"); + if (waitResponse(30000UL) != 1) { + // printf("[%u]PDP context activate or deactivate\n ",__LINE__); + return false; + } + + // This activates and attaches to the external PDP context that is tied + // to the embedded context for TCP/IP (ie AT+CGACT=1,1 and AT+CGATT=1) + // Response may be an immediate "OK" followed later by "+NETOPEN: 0". + // We to ignore any immediate response and wait for the + // URC to show it's really connected. + sendAT(GF("+NETOPEN")); + if (waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) { + // printf("[%u]NETOPEN\n ",__LINE__); + return false; + } + + return true; + } + + bool gprsDisconnectImpl() { + // Close all sockets and stop the socket service + // Note: On the LTE models, this single command closes all sockets and the + // service + sendAT(GF("+NETCLOSE")); + if (waitResponse(60000L, GF(GSM_NL "+NETCLOSE: 0")) != 1) { return false; } + + return true; + } + + bool isGprsConnectedImpl() { + sendAT(GF("+NETOPEN?")); + // May return +NETOPEN: 1, 0. We just confirm that the first number is 1 + if (waitResponse(GF(GSM_NL "+NETOPEN: 1")) != 1) { return false; } + waitResponse(); + + // sendAT(GF("+IPADDR")); // Inquire Socket PDP address + // // sendAT(GF("+CGPADDR=1")); // Show PDP address + // if (waitResponse() != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Gets the CCID of a sim card via AT+CCID + String getSimCCIDImpl() { + sendAT(GF("+CICCID")); + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + protected: + bool callHangupImpl() { + sendAT(GF("+CHUP")); + return waitResponse() == 1; + } + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GSM Location functions + */ + protected: + // Can return a GSM-based location from CLBS as per the template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // enable GPS + bool enableGPSImpl(int8_t power_en_pin ,uint8_t enable_level) { + if(power_en_pin!= -1){ + sendAT("+CGDRT=",power_en_pin,",1"); + waitResponse(); + sendAT("+CGSETV=",power_en_pin,",",enable_level); + waitResponse(); + } + sendAT(GF("+CGNSSPWR=1")); + if (waitResponse(10000UL, GF("+CGNSSPWR: READY!")) != 1) { return false; } + return true; + } + + bool disableGPSImpl(int8_t power_en_pin ,uint8_t disbale_level) { + if(power_en_pin!= -1){ + sendAT("+CGSETV=",power_en_pin,",",disbale_level); + waitResponse(); + sendAT("+CGDRT=",power_en_pin,",0"); + waitResponse(); + } + sendAT(GF("+CGNSSPWR=0")); + if (waitResponse() != 1) { return false; } + return true; + } + + bool isEnableGPSImpl(){ + sendAT(GF("+CGNSSPWR?")); + if (waitResponse("+CGNSSPWR:") != 1) { return false; } + // +CGNSSPWR:,, + return 1 == streamGetIntBefore(','); + } + + bool enableAGPSImpl(){ + sendAT(GF("+CGNSSPWR?")); + if (waitResponse("+CGNSSPWR:") != 1) { return false; } + // +CGNSSPWR:,, + if(1 == streamGetIntBefore(',')){ + sendAT("+CAGPS"); + if (waitResponse(30000UL,"+AGPS:") != 1) { return false; } + String res = stream.readStringUntil('\n'); + if(res.startsWith(" success.")){ + return true; + } + } + return false; + } + + // get the RAW GPS output + String getGPSrawImpl() { + sendAT(GF("+CGNSSINFO")); + if (waitResponse(GF(GSM_NL "+CGNSSINFO:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + bool getGPSImpl(uint8_t *status,float* lat, float* lon, float* speed = 0, float* alt = 0, + int* vsat = 0, int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, int* hour = 0, + int* minute = 0, int* second = 0) { + sendAT(GF("+CGNSSINFO")); + if (waitResponse(GF(GSM_NL "+CGNSSINFO:")) != 1) { return false; } + + uint8_t fixMode = streamGetIntBefore(','); // mode 2=2D Fix or 3=3DFix + // TODO(?) Can 1 be returned + if (fixMode == 1 || fixMode == 2 || fixMode == 3) { + // init variables + float ilat = 0; + char north; + float ilon = 0; + char east; + float ispeed = 0; + float ialt = 0; + int ivsat = 0; + int iusat = 0; + float iaccuracy = 0; + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + float secondWithSS = 0; + + streamSkipUntil(','); // GPS satellite valid numbers + streamSkipUntil(','); // GLONASS satellite valid numbers + streamSkipUntil(','); // skip dump , A7670 + streamSkipUntil(','); // BEIDOU satellite valid numbers + ilat = streamGetFloatBefore(','); // Latitude in ddmm.mmmmmm + north = stream.read(); // N/S Indicator, N=north or S=south + streamSkipUntil(','); + ilon = streamGetFloatBefore(','); // Longitude in ddmm.mmmmmm + east = stream.read(); // E/W Indicator, E=east or W=west + streamSkipUntil(','); + + // Date. Output format is ddmmyy + iday = streamGetIntLength(2); // Two digit day + imonth = streamGetIntLength(2); // Two digit month + iyear = streamGetIntBefore(','); // Two digit year + + // UTC Time. Output format is hhmmss.s + ihour = streamGetIntLength(2); // Two digit hour + imin = streamGetIntLength(2); // Two digit minute + secondWithSS = + streamGetFloatBefore(','); // 4 digit second with subseconds + + ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters + ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots. + streamSkipUntil(','); // Course Over Ground. Degrees. + streamSkipUntil(','); // After set, will report GPS every x seconds + iaccuracy = streamGetFloatBefore(','); // Position Dilution Of Precision + streamSkipUntil(','); // Horizontal Dilution Of Precision + streamSkipUntil(','); // Vertical Dilution Of Precision + streamSkipUntil('\n'); // TODO(?) is one more field reported?? + if (status){ + *status = fixMode; + } + // Set pointers + if (lat != NULL){ + *lat = (ilat) * (north == 'N' ? 1 : -1); + } + if (lon != NULL){ + *lon = (ilon) * (east == 'E' ? 1 : -1); + } + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = ivsat; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; + if (iyear < 2000) iyear += 2000; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); + + waitResponse(); + return true; + } + waitResponse(); + return false; + } + + bool setGPSBaudImpl(uint32_t baud){ + sendAT("+CGNSSIPR=",baud); + return waitResponse(1000L) == 1; + } + + bool setGPSModeImpl(uint8_t mode){ + sendAT("+CGNSSMODE=",mode); + return waitResponse(1000L) == 1; + } + + bool setGPSOutputRateImpl(uint8_t rate_hz){ + sendAT("+CGPSNMEARATE=",rate_hz); + return waitResponse(1000L) == 1; + } + + bool enableNMEAImpl(){ + sendAT("+CGNSSTST=1"); + waitResponse(1000L); + // Select the output port for NMEA sentence + sendAT("+CGNSSPORTSWITCH=0,1"); + return waitResponse(1000L) == 1; + } + + bool disableNMEAImpl(){ + sendAT("+CGNSSTST=0"); + waitResponse(1000L); + // Select the output port for NMEA sentence + sendAT("+CGNSSPORTSWITCH=1,0"); + return waitResponse(1000L) == 1; + } + + bool configNMEASentenceImpl(bool CGA,bool GLL,bool GSA,bool GSV,bool RMC,bool VTG,bool ZDA,bool ANT){ + char buffer[32]; + snprintf(buffer,32,"%u,%u,%u,%u,%u,%u,%u,0", CGA, GLL, GSA, GSV, RMC, VTG, ZDA); + sendAT("+CGNSSNMEA=",buffer); + return waitResponse(1000L) == 1; + } + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // returns volts, multiply by 1000 to get mV + uint16_t getBattVoltageImpl() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return 0; } + + // get voltage in VOLTS + float voltage = streamGetFloatBefore('\n'); + // Wait for final OK + waitResponse(); + // Return millivolts + uint16_t res = voltage * 1000; + return res; + } + + int8_t getBattPercentImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + + uint8_t getBattChargeStateImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { + chargeState = 0; + percent = 0; + milliVolts = getBattVoltage(); + return true; + } + + /* + * Temperature functions + */ + protected: + // get temperature in degree celsius + uint16_t getTemperatureImpl() { + sendAT(GF("+CPMUTEMP")); + if (waitResponse(GF(GSM_NL "+CPMUTEMP:")) != 1) { return 0; } + // return temperature in C + uint16_t res = streamGetIntBefore('\n'); + // Wait for final OK + waitResponse(); + return res; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 15) { + if (ssl) { DBG("SSL not yet supported on this module!"); } + // Make sure we'll be getting data manually on this connection + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { return false; } + + // Establish a connection in multi-socket mode + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + sendAT(GF("+CIPOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), + port); + // The reply is OK followed by +CIPOPEN: , where + // is the mux number and should be 0 if there's no error + if (waitResponse(timeout_ms, GF(GSM_NL "+CIPOPEN:")) != 1) { return false; } + uint8_t opened_mux = streamGetIntBefore(','); + uint8_t opened_result = streamGetIntBefore('\n'); + + // printf("\nopened_mux :%u opened_result:%u\n",opened_mux,opened_result); + // switch (opened_result) + // { + // case 0 : printf("operation succeeded\n");break; + // case 1 : printf("Network failure\n");break; + // case 2 : printf("Network not opened\n");break; + // case 3 : printf("Wrong parameter\n");break; + // case 4 : printf("Operation not supported\n");break; + // case 5 : printf("Failed to create socket\n");break; + // case 6 : printf("Failed to bind socket\n");break; + // case 7 : printf("TCP server is already listening\n");break; + // case 8 : printf("Busy\n");break; + // case 9 : printf("Sockets opened\n");break; + // case 10 : printf(" Timeout\n");break; + // case 11 : printf(" DNS parse failed for AT+CIPOPEN\n");break; + // case 12 : printf(" Unknown error\n");break; + // default: + // break; + // } + if (opened_mux != mux || opened_result != 0) return false; + return true; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip requested bytes to send + // TODO(?): make sure requested and confirmed bytes match + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux/cid (connecion id) + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ The data length which not read in the buffer + for (int i = 0; i < len_requested; i++) { + uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); +#endif + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + sockets[mux]->sock_available = len_confirmed; + waitResponse(); + return len_requested; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); + } + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + // Read the status of all sockets at once + sendAT(GF("+CIPCLOSE?")); + if (waitResponse(GF("+CIPCLOSE:")) != 1) { + // return false; // TODO: Why does this not read correctly? + } + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // +CIPCLOSE:,,..., + bool muxState = stream.parseInt(); + if (sockets[muxNo]) { sockets[muxNo]->sock_connected = muxState; } + } + waitResponse(); // Should be an OK at the end + if (!sockets[mux]) return false; + return sockets[mux]->sock_connected; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + // putchar(a); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+IPCLOSE:"))) { + int8_t mux = streamGetIntBefore(','); + streamSkipUntil('\n'); // Skip the reason code + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+CIPEVENT:"))) { + // Need to close all open sockets and release the network library. + // User will then need to reconnect. + DBG("### Network error!"); + if (!isGprsConnected()) { gprsDisconnect(); } + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientA7670* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTA7670_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientA7672x.h b/lib/TinyGSM/src/TinyGsmClientA7672x.h deleted file mode 100644 index b470ac4..0000000 --- a/lib/TinyGSM/src/TinyGsmClientA7672x.h +++ /dev/null @@ -1,775 +0,0 @@ -/** - * @file TinyGsmClientA7672x.h - * @author Giovanni de Rosso Unruh - * @license LGPL-3.0 - * @copyright Copyright (c) 2022 Giovanni de Rosso Unruh - * @date Oct 2022 - */ - -#ifndef SRC_TINYGSMCLIENTA7672X_H_ -#define SRC_TINYGSMCLIENTA7672X_H_ - -// #define TINY_GSM_DEBUG Serial - -#define TINY_GSM_MUX_COUNT 10 -#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "SIMCom" - -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#define MODEM_MODEL "A7672x" - -#include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" -#include "TinyGsmSSL.tpp" -#include "TinyGsmGPRS.tpp" -#include "TinyGsmCalling.tpp" -#include "TinyGsmSMS.tpp" -#include "TinyGsmGSMLocation.tpp" -#include "TinyGsmTime.tpp" -#include "TinyGsmNTP.tpp" -#include "TinyGsmBattery.tpp" -#include "TinyGsmTemperature.tpp" - -enum A7672xRegStatus { - REG_NO_RESULT = -1, - REG_UNREGISTERED = 0, - REG_SEARCHING = 2, - REG_DENIED = 3, - REG_OK_HOME = 1, - REG_OK_ROAMING = 5, - REG_UNKNOWN = 4, -}; -class TinyGsmA7672X : public TinyGsmModem, - public TinyGsmGPRS, - public TinyGsmTCP, - public TinyGsmSSL, - public TinyGsmCalling, - public TinyGsmSMS, - public TinyGsmGSMLocation, - public TinyGsmTime, - public TinyGsmNTP, - public TinyGsmBattery, - public TinyGsmTemperature { - friend class TinyGsmModem; - friend class TinyGsmGPRS; - friend class TinyGsmTCP; - friend class TinyGsmSSL; - friend class TinyGsmCalling; - friend class TinyGsmSMS; - friend class TinyGsmGSMLocation; - friend class TinyGsmTime; - friend class TinyGsmNTP; - friend class TinyGsmBattery; - friend class TinyGsmTemperature; - - /* - * Inner Client - */ - public: - class GsmClientA7672X : public GsmClient { - friend class TinyGsmA7672X; - - public: - GsmClientA7672X() {} - - explicit GsmClientA7672X(TinyGsmA7672X& modem, uint8_t mux = 0) { - init(&modem, mux); - } - - bool init(TinyGsmA7672X* modem, uint8_t mux = 0) { - this->at = modem; - sock_available = 0; - prev_check = 0; - sock_connected = false; - got_data = false; - - if (mux < TINY_GSM_MUX_COUNT) { - this->mux = mux; - } else { - this->mux = (mux % TINY_GSM_MUX_COUNT); - } - at->sockets[this->mux] = this; - - return true; - } - - public: - virtual int connect(const char* host, uint16_t port, int timeout_s) { - stop(); - TINY_GSM_YIELD(); - rx.clear(); - sock_connected = at->modemConnect(host, port, mux, false, timeout_s); - return sock_connected; - } - TINY_GSM_CLIENT_CONNECT_OVERRIDES - - void stop(uint32_t maxWaitMs) { - dumpModemBuffer(maxWaitMs); - at->sendAT(GF("+CIPCLOSE="), mux); - sock_connected = false; - at->waitResponse(); - } - void stop() override { - stop(15000L); - } - - /* - * Extended API - */ - - String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; - }; - - /* - * Inner Secure Client - */ - public: - class GsmClientSecureA7672X : public GsmClientA7672X { - public: - GsmClientSecureA7672X() {} - - explicit GsmClientSecureA7672X(TinyGsmA7672X& modem, uint8_t mux = 0) - : GsmClientA7672X(modem, mux) {} - - public: - bool addCertificate(const char* certificateName, const char* cert, - const uint16_t len) { - return at->addCertificate(certificateName, cert, len); - } - - bool setCertificate(const char* certificateName) { - return at->setCertificate(certificateName, mux); - } - - bool deleteCertificate(const char* certificateName) { - return at->deleteCertificate(certificateName); - } - - int connect(const char* host, uint16_t port, int timeout_s) override { - stop(); - TINY_GSM_YIELD(); - rx.clear(); - sock_connected = at->modemConnect(host, port, mux, true, timeout_s); - return sock_connected; - } - TINY_GSM_CLIENT_CONNECT_OVERRIDES - - void stop(uint32_t maxWaitMs) { - dumpModemBuffer(maxWaitMs); - at->sendAT(GF("+CCHCLOSE="), mux); //, GF(",1")); // Quick close - sock_connected = false; - at->waitResponse(); - } - void stop() override { - stop(15000L); - } - }; - - /* - * Constructor - */ - public: - explicit TinyGsmA7672X(Stream& stream) : stream(stream) { - memset(sockets, 0, sizeof(sockets)); - } - - /* - * Basic functions - */ - protected: - bool initImpl(const char* pin = nullptr) { - DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); - DBG(GF("### TinyGSM Compiled Module: A7672X")); - - if (!testAT(2000)) { return false; } - - // sendAT(GF("&FZ")); // Factory + Reset - // waitResponse(); - - sendAT(GF("E0")); // Echo Off - if (waitResponse() != 1) { return false; } - -#ifdef TINY_GSM_DEBUG - sendAT(GF("V1")); // turn on verbose error codes -#else - sendAT(GF("V0")); // turn off error codes -#endif - waitResponse(); - - DBG(GF("### Modem:"), getModemName()); - - SimStatus ret = getSimStatus(); - // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { - simUnlock(pin); - return (getSimStatus() == SIM_READY); - } else { - // if the sim is ready, or it's locked but no pin has been provided, - // return true - return (ret == SIM_READY || ret == SIM_LOCKED); - } - } - - bool factoryDefaultImpl() { - sendAT(GF("&F")); // Factory + Reset - waitResponse(); - sendAT(GF("+IFC=0,0")); // No Flow Control - waitResponse(); - sendAT(GF("+ICF=2,2")); // 8 data 0 parity 1 stop - waitResponse(); - sendAT(GF("+CSCLK=0")); // Control UART Sleep always work - waitResponse(); - sendAT(GF("&W")); // Write configuration - return waitResponse() == 1; - } - - /* - * Power functions - */ - protected: - bool restartImpl(const char* pin = nullptr) { - if (!testAT()) { return false; } - sendAT(GF("+CRESET")); - waitResponse(); - if (!setPhoneFunctionality(0)) { return false; } - if (!setPhoneFunctionality(1, true)) { return false; } - delay(3000); - return init(pin); - } - - bool powerOffImpl() { - sendAT(GF("+CPOF")); - return waitResponse(10000L) == 1; - } - - // This command is used to enable UART Sleep or always work. If set to 0, - // UART always work. If set to 1, ensure that DTR is pulled high and the - // module can go to DTR sleep. If set to 2, the module will enter RXsleep. RX - // wakeup directly sends data through the serial port (for example: AT) to - // wake up - bool sleepEnableImpl(bool enable = true) { - sendAT(GF("+CSCLK="), - enable ? "2" : "1"); // 2: RXsleep (at wakeup) 1: DTR sleep - return waitResponse() == 1; - } - - // 0 minimum functionality - // 1 full functionality, online mode - // 4 disable phone both transmit and receive RF circuits - // 5 Factory Test Mode (The A7600's 5 and 1 have the same function) - // 6 Reset - // 7 Offline Mode - // 0 do not reset the ME before setting it to power level - // 1 reset the ME before setting it to power level. This - // valueonlytakes effect when equals 1 - bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { - sendAT(GF("+CFUN="), fun, reset ? ",1" : ",0"); - return waitResponse(10000L) == 1; - } - - /* - * Generic network functions - */ - public: - A7672xRegStatus getRegistrationStatus() { - return (A7672xRegStatus)getRegistrationStatusXREG("CREG"); - } - - String getLocalIPImpl() { - if (hasSSL) { - sendAT(GF("+CCHADDR")); - if (waitResponse(GF("+CCHADDR:")) != 1) { return ""; } - } else { - sendAT(GF("+CGPADDR=1")); - if (waitResponse(GF("+CGPADDR:")) != 1) { return ""; } - } - streamSkipUntil(','); // Skip context id - String res = stream.readStringUntil('\r'); - if (waitResponse() != 1) { return ""; } - return res; - } - - protected: - bool isNetworkConnectedImpl() { - A7672xRegStatus s = getRegistrationStatus(); - return (s == REG_OK_HOME || s == REG_OK_ROAMING); - } - - /* - * Secure socket layer (SSL) functions - */ - public: - // The name of the certificate/key/password file. The file name must - // havetype like ".pem" or ".der". - // The certificate like - const char ca_cert[] PROGMEM = R"EOF(-----BEGIN... - // len of certificate like - sizeof(ca_cert) - bool addCertificate(const char* certificateName, const char* cert, - const uint16_t len) { - sendAT(GF("+CCERTDOWN="), certificateName, GF(","), len); - if (waitResponse(GF(">")) != 1) { return false; } - stream.write(cert, len); - stream.flush(); - return waitResponse() == 1; - } - - bool deleteCertificate(const char* certificateName) { // todo test - sendAT(GF("+CCERTDELE="), certificateName); - return waitResponse() == 1; - } - - /* - * WiFi functions - */ - // No functions of this type supported - - /* - * GPRS functions - */ - protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { - gprsDisconnect(); - - // Define the PDP context - sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); - waitResponse(); - - // Activate the PDP context - sendAT(GF("+CGACT=1,1")); - waitResponse(60000L); - - // Attach to GPRS - sendAT(GF("+CGATT=1")); - if (waitResponse(60000L) != 1) { return false; } - - // Set to get data manually - sendAT(GF("+CIPRXGET=1")); - if (waitResponse() != 1) { return false; } - - // Get Local IP Address, only assigned after connection - sendAT(GF("+CGPADDR=1")); - if (waitResponse(10000L) != 1) { return false; } - - // Configure Domain Name Server (DNS) - sendAT(GF("+CDNSCFG=\"8.8.8.8\",\"8.8.4.4\"")); - if (waitResponse() != 1) { return false; } - - return true; - } - - bool gprsDisconnectImpl() { - // Shut the TCP/IP connection - sendAT(GF("+NETCLOSE")); - if (waitResponse(60000L) != 1) { return false; } - - sendAT(GF("+CGATT=0")); // Detach from GPRS - if (waitResponse(60000L) != 1) { return false; } - - return true; - } - - String getProviderImpl() { - sendAT(GF("+CSPN?")); - if (waitResponse(GF("+CSPN:")) != 1) { return ""; } - streamSkipUntil('"'); /* Skip mode and format */ - String res = stream.readStringUntil('"'); - waitResponse(); - return res; - } - - /* - * SIM card functions - */ - protected: - SimStatus getSimStatusImpl(uint32_t timeout_ms = 10000L) { - for (uint32_t start = millis(); millis() - start < timeout_ms;) { - sendAT(GF("+CPIN?")); - if (waitResponse(GF("+CPIN:")) != 1) { - delay(1000); - continue; - } - int8_t status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"), - GF("SIM not inserted"), GF("SIM REMOVED")); - waitResponse(); - switch (status) { - case 2: - case 3: return SIM_LOCKED; - case 1: return SIM_READY; - default: return SIM_ERROR; - } - } - return SIM_ERROR; - } - - String getSimCCIDImpl() { - sendAT(GF("+CICCID")); - if (waitResponse(GF(AT_NL "+ICCID:")) != 1) { return ""; } - String res = stream.readStringUntil('\n'); - waitResponse(); - res.trim(); - return res; - } - - /* - * Phone Call functions - */ - public: - bool setGsmBusy(bool busy = true) { - sendAT(GF("+CCFC=1,"), busy ? 1 : 0); - return waitResponse() == 1; - } - - /* - * Audio functions - */ - // No functions of this type supported - - /* - * Text messaging (SMS) functions - */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp - - /* - * GSM Location functions - */ - // No functions of this type supported - /* - * GPS/GNSS/GLONASS location functions - */ - // No functions of this type supported - - /* - * Time functions - */ - // No functions of this type supported - /* - * NTP server functions - */ - // No functions of this type supported - - /* - * BLE functions - */ - // No functions of this type supported - - /* - * Battery functions - */ - // No functions of this type supported - - /* - * Temperature functions - */ - protected: - float getTemperatureImpl() { - String res = ""; - sendAT(GF("+CPMUTEMP")); - if (waitResponse(1000L, res)) { return 0; } - res = res.substring(res.indexOf(':'), res.indexOf('\r')); - float temp = res.toFloat(); - waitResponse(); - return temp; - } - /* - * Client related functions - */ - protected: - bool modemConnect(const char* host, uint16_t port, uint8_t mux, - bool ssl = false, int timeout_s = 75) { - int8_t rsp; - uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; - - // +CTCPKA:,,, - sendAT(GF("+CTCPKA=1,2,5,1")); - if (waitResponse(2000L) != 1) { return false; } - - if (ssl) { - hasSSL = true; - // set the ssl version - // AT+CSSLCFG="sslversion",, - // PDP context identifier - // 0: QAPI_NET_SSL_PROTOCOL_UNKNOWN - // 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0 - // 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1 - // 3: QAPI_NET_SSL_PROTOCOL_TLS_1_2 - // 4: QAPI_NET_SSL_PROTOCOL_DTLS_1_0 - // 5: QAPI_NET_SSL_PROTOCOL_DTLS_1_2 - // NOTE: despite docs using caps, "sslversion" must be in lower case - sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2 - if (waitResponse(5000L) != 1) return false; - - - if (certificates[mux] != "") { - /* Configure the server root CA of the specified SSL context - AT + CSSLCFG = "cacert", , */ - sendAT(GF("+CSSLCFG=\"cacert\",0,"), certificates[mux].c_str()); - if (waitResponse(5000L) != 1) return false; - } - - // set the SSL SNI (server name indication) - // AT+CSSLCFG="enableSNI",, - // NOTE: despite docs using caps, "sni" must be in lower case - sendAT(GF("+CSSLCFG=\"enableSNI\",0,1")); - if (waitResponse(2000L) != 1) { return false; } - - // Configure the report mode of sending and receiving data - /* +CCHSET=, - * Whether to report result of CCHSEND, the default - * value is 0: 0 No. 1 Yes. Module will report +CCHSEND: - * , to MCU when complete sending data. - * - * The receiving mode, the default value is 0: - * 0 Output the data to MCU whenever received data. - * 1 Module caches the received data and notifies MCU with+CCHEVENT: - * ,RECV EVENT. MCU can use AT+CCHRECV to receive the cached - * data (only in manual receiving mode). - */ - sendAT(GF("+CCHSET=1,1")); - if (waitResponse(2000L) != 1) { return false; } - - sendAT(GF("+CCHSTART")); - if (waitResponse(2000L) != 1) { return false; } - - sendAT(GF("+CCHSSLCFG="), mux, GF(",0")); - if (waitResponse(2000L) != 1) { return false; } - - sendAT(GF("+CCHOPEN="), 0, GF(",\""), host, GF("\","), port, GF(",2")); - rsp = waitResponse(timeout_ms, GF("+CCHOPEN: 0,0" AT_NL), - GF("+CCHOPEN: 0,1" AT_NL), GF("+CCHOPEN: 0,4" AT_NL), - GF("ERROR" AT_NL), GF("CLOSE OK" AT_NL)); - } else { - sendAT(GF("+NETOPEN")); - if (waitResponse(2000L) != 1) { return false; } - - sendAT(GF("+NETOPEN?")); - if (waitResponse(2000L) != 1) { return false; } - - sendAT(GF("+CIPOPEN="), 0, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), - port); - - rsp = waitResponse( - timeout_ms, GF("+CIPOPEN: 0,0" AT_NL), GF("+CIPOPEN: 0,1" AT_NL), - GF("+CIPOPEN: 0,4" AT_NL), GF("ERROR" AT_NL), - GF("CLOSE OK" AT_NL)); // Happens when HTTPS handshake fails - } - - return (1 == rsp); - } - - int16_t modemSend(const void* buff, size_t len, uint8_t mux) { - if (hasSSL) - sendAT(GF("+CCHSEND="), mux, ',', (uint16_t)len); - else - sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); - if (waitResponse(GF(">")) != 1) { return 0; } - stream.write(reinterpret_cast(buff), len); - stream.flush(); - - if (waitResponse() != 1) { return 0; } - if (waitResponse(10000L, GF("+CCHSEND: 0,0" AT_NL), - GF("+CCHSEND: 0,4" AT_NL), GF("+CCHSEND: 0,9" AT_NL), - GF("ERROR" AT_NL), GF("CLOSE OK" AT_NL)) != 1) { - return 0; - } - return len; - } - - size_t modemRead(size_t size, uint8_t mux) { - int16_t len_requested = 0; - int16_t len_confirmed = 0; - if (!sockets[mux]) return 0; - if (hasSSL) { - sendAT(GF("+CCHRECV?")); // TODO(Rosso): Optimize this! - String res = ""; - waitResponse(2000L, res); - int16_t len = - res.substring(res.indexOf(',') + 1, res.lastIndexOf(',')).toInt(); - sendAT(GF("+CCHRECV="), mux, ',', (uint16_t)size); - if (waitResponse(GF("+CCHRECV:")) != 1) { return 0; } - //+CCHRECV: DATA,0, - streamSkipUntil(','); // Skip DATA - streamSkipUntil(','); // Skip mux - len_requested = streamGetIntBefore('\n'); - len_confirmed = len; // streamGetIntBefore(','); - } else { - sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); - if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } - streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX - streamSkipUntil(','); // Skip mux - len_requested = streamGetIntBefore(','); - // ^^ Requested number of data bytes (1-1460 bytes)to be read - len_confirmed = streamGetIntBefore('\n'); - // ^^ Confirmed number of data bytes to be read, which may be less than - // requested. 0 indicates that no data can be read. - // SRGD NOTE: Contrary to above (which is copied from AT command manual) - // this is actually be the number of bytes that will be remaining in the - // buffer after the read. - } - for (int i = 0; i < len_requested; i++) { - uint32_t startMillis = millis(); - while (!stream.available() && - (millis() - startMillis < sockets[mux]->_timeout)) { - TINY_GSM_YIELD(); - } - char c = stream.read(); - sockets[mux]->rx.put(c); - } - // DBG("### READ:", len_requested, " bytes from connection ", mux); - // sockets[mux]->sock_available = modemGetAvailable(mux); - sockets[mux]->sock_available = len_confirmed; - waitResponse(); - return len_requested; - } - - size_t modemGetAvailable(uint8_t mux) { - if (!sockets[mux]) return 0; - size_t result = 0; - if (hasSSL) { - sendAT(GF("+CCHRECV?")); // TODO(Rosso): Optimize this! - String res = ""; - waitResponse(2000L, res); - result = - res.substring(res.indexOf(',') + 1, res.lastIndexOf(',')).toInt(); - } else { - sendAT(GF("+CIPRXGET=4,"), mux); - result = 0; - if (waitResponse(GF("+CIPRXGET:")) == 1) { - streamSkipUntil(','); // Skip mode 4 - streamSkipUntil(','); // Skip mux - result = streamGetIntBefore('\n'); - waitResponse(); - } - } - // DBG("### Available:", result, "on", mux); - if (result == 0) { sockets[mux]->sock_connected = modemGetConnected(mux); } - return result; - } - - bool modemGetConnected(uint8_t mux) { - int8_t res = 0; - if (hasSSL) { - bool connected = this->sockets[mux]->sock_connected; - // DBG("### Connected:", connected); - return connected; - } else { - sendAT(GF("+CIPACK="), mux); - waitResponse(GF("+CIPACK:")); - res = waitResponse(2000L); //(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), - // GF(",\"CLOSING\""), GF(",\"REMOTE - // CLOSING\""), GF(",\"INITIAL\"")); - waitResponse(); - } - return 1 == res; - } - - /* - * Utilities - */ - public: - bool handleURCs(String& data) { - if (data.endsWith(GF(AT_NL "+CIPRXGET:"))) { - int8_t mode = streamGetIntBefore(','); - if (mode == 1) { - int8_t mux = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - } - data = ""; - DBG("### Got Data:", mux); - return true; - } else { - data += mode; - return false; - } - } else if (data.endsWith(GF("RECV EVENT" AT_NL))) { - sendAT(GF("+CCHRECV?")); - String res = ""; - waitResponse(2000L, res); - int8_t mux = res.substring(res.lastIndexOf(',') + 1).toInt(); - int16_t len = - res.substring(res.indexOf(',') + 1, res.lastIndexOf(',')).toInt(); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } - } - data = ""; - DBG("### Got Data:", len, "on", mux); - return true; - } else if (data.endsWith(GF("+CCHRECV: 0,0" AT_NL))) { - int8_t mux = data.substring(data.lastIndexOf(',') + 1).toInt(); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = true; - } - data = ""; - DBG("### ACK:", mux); - return true; - } else if (data.endsWith(GF("+IPCLOSE:"))) { - int8_t mux = streamGetIntBefore(','); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - data = ""; - streamSkipUntil('\n'); - DBG("### TCP Closed: ", mux); - return true; - } else if (data.endsWith(GF("+CCHCLOSE:"))) { - int8_t mux = streamGetIntBefore(','); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - data = ""; - streamSkipUntil('\n'); - DBG("### SSL Closed: ", mux); - return true; - } else if (data.endsWith(GF("+CCH_PEER_CLOSED:"))) { - int8_t mux = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - data = ""; - DBG("### SSL Closed: ", mux); - return true; - } else if (data.endsWith(GF("*PSNWID:"))) { - streamSkipUntil('\n'); // Refresh network name by network - data = ""; - DBG("### Network name updated."); - return true; - } else if (data.endsWith(GF("*PSUTTZ:"))) { - streamSkipUntil('\n'); // Refresh time and time zone by network - data = ""; - DBG("### Network time and time zone updated."); - return true; - } else if (data.endsWith(GF("+CTZV:"))) { - streamSkipUntil('\n'); // Refresh network time zone by network - data = ""; - DBG("### Network time zone updated."); - return true; - } else if (data.endsWith(GF("DST:"))) { - streamSkipUntil('\n'); // Refresh Network Daylight Saving Time by network - data = ""; - DBG("### Daylight savings time state updated."); - return true; - } - return false; - } - - public: - Stream& stream; - - protected: - GsmClientA7672X* sockets[TINY_GSM_MUX_COUNT]; - bool hasSSL = false; - String certificates[TINY_GSM_MUX_COUNT]; -}; - -#endif // SRC_TINYGSMCLIENTA7672X_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientBG96.h b/lib/TinyGSM/src/TinyGsmClientBG96.h index 7ee1959..a123f86 100644 --- a/lib/TinyGSM/src/TinyGsmClientBG96.h +++ b/lib/TinyGSM/src/TinyGsmClientBG96.h @@ -1,9 +1,9 @@ /** * @file TinyGsmClientBG96.h - * @author Volodymyr Shymanskyy and Aurelien BOUIN (SSL) + * @author Volodymyr Shymanskyy * @license LGPL-3.0 * @copyright Copyright (c) 2016 Volodymyr Shymanskyy - * @date Apr 2018, Aug 2023 (SSL) + * @date Apr 2018 */ #ifndef SRC_TINYGSMCLIENTBG96_H_ @@ -14,38 +14,27 @@ #define TINY_GSM_MUX_COUNT 12 #define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "Quectel" -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#if defined(TINY_GSM_MODEM_BG95) || defined(TINY_GSM_MODEM_BG95SSL) -#define MODEM_MODEL "BG95" -#else -#define MODEM_MODEL "BG96" -#endif - -#include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" -#include "TinyGsmSSL.tpp" -#include "TinyGsmGPRS.tpp" +#include "TinyGsmBattery.tpp" #include "TinyGsmCalling.tpp" -#include "TinyGsmSMS.tpp" +#include "TinyGsmGPRS.tpp" #include "TinyGsmGPS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" #include "TinyGsmTime.tpp" #include "TinyGsmNTP.tpp" -#include "TinyGsmBattery.tpp" -#include "TinyGsmTemperature.tpp" -enum BG96RegStatus { +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { REG_NO_RESULT = -1, REG_UNREGISTERED = 0, REG_SEARCHING = 2, @@ -58,23 +47,21 @@ enum BG96RegStatus { class TinyGsmBG96 : public TinyGsmModem, public TinyGsmGPRS, public TinyGsmTCP, - public TinyGsmSSL, public TinyGsmCalling, public TinyGsmSMS, - public TinyGsmGPS, public TinyGsmTime, public TinyGsmNTP, + public TinyGsmGPS, public TinyGsmBattery, public TinyGsmTemperature { friend class TinyGsmModem; friend class TinyGsmGPRS; friend class TinyGsmTCP; - friend class TinyGsmSSL; friend class TinyGsmCalling; friend class TinyGsmSMS; - friend class TinyGsmGPS; friend class TinyGsmTime; friend class TinyGsmNTP; + friend class TinyGsmGPS; friend class TinyGsmBattery; friend class TinyGsmTemperature; @@ -89,7 +76,6 @@ class TinyGsmBG96 : public TinyGsmModem, GsmClientBG96() {} explicit GsmClientBG96(TinyGsmBG96& modem, uint8_t mux = 0) { - ssl_sock = false; init(&modem, mux); } @@ -110,16 +96,17 @@ class TinyGsmBG96 : public TinyGsmModem, return true; } + public: virtual int connect(const char* host, uint16_t port, int timeout_s) { stop(); TINY_GSM_YIELD(); rx.clear(); - sock_connected = at->modemConnect(host, port, mux, timeout_s); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); return sock_connected; } TINY_GSM_CLIENT_CONNECT_OVERRIDES - virtual void stop(uint32_t maxWaitMs) { + void stop(uint32_t maxWaitMs) { uint32_t startMillis = millis(); dumpModemBuffer(maxWaitMs); at->sendAT(GF("+QICLOSE="), mux); @@ -135,39 +122,34 @@ class TinyGsmBG96 : public TinyGsmModem, */ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; - - protected: - bool ssl_sock; }; /* * Inner Secure Client */ - public: - class GsmClientSecureBG96 : public GsmClientBG96 { - public: - GsmClientSecureBG96() {} - explicit GsmClientSecureBG96(TinyGsmBG96& modem, uint8_t mux = 0) - : GsmClientBG96(modem, mux) { - ssl_sock = true; - } + /* + class GsmClientSecureBG96 : public GsmClientBG96 + { + public: + GsmClientSecure() {} - bool setCertificate(const String& certificateName) { - return at->setCertificate(certificateName, mux); - } + GsmClientSecure(TinyGsmBG96& modem, uint8_t mux = 0) + : public GsmClient(modem, mux) + {} - void stop(uint32_t maxWaitMs) override { - uint32_t startMillis = millis(); - dumpModemBuffer(maxWaitMs); - at->sendAT(GF("+QSSLCLOSE="), mux); - sock_connected = false; - at->waitResponse((maxWaitMs - (millis() - startMillis))); - } - void stop() override { - stop(15000L); + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; } + TINY_GSM_CLIENT_CONNECT_OVERRIDES }; + */ /* * Constructor @@ -181,7 +163,7 @@ class TinyGsmBG96 : public TinyGsmModem, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientBG96")); @@ -209,7 +191,7 @@ class TinyGsmBG96 : public TinyGsmModem, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -223,7 +205,7 @@ class TinyGsmBG96 : public TinyGsmModem, * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { + bool restartImpl(const char* pin = NULL) { if (!testAT()) { return false; } if (!setPhoneFunctionality(1, true)) { return false; } waitResponse(10000L, GF("APP RDY")); @@ -246,51 +228,40 @@ class TinyGsmBG96 : public TinyGsmModem, return waitResponse() == 1; } - bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false, - uint32_t timeout_ms = 15500L) { + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); - return waitResponse(timeout_ms, GF("OK")) == 1; + return waitResponse(10000L, GF("OK")) == 1; } /* * Generic network functions */ public: - BG96RegStatus getRegistrationStatus() { + RegStatus getRegistrationStatus() { // Check first for EPS registration - BG96RegStatus epsStatus = (BG96RegStatus)getRegistrationStatusXREG("CEREG"); + RegStatus epsStatus = (RegStatus)getRegistrationStatusXREG("CEREG"); // If we're connected on EPS, great! if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) { return epsStatus; } else { // Otherwise, check generic network status - return (BG96RegStatus)getRegistrationStatusXREG("CREG"); + return (RegStatus)getRegistrationStatusXREG("CREG"); } } protected: bool isNetworkConnectedImpl() { - BG96RegStatus s = getRegistrationStatus(); + RegStatus s = getRegistrationStatus(); return (s == REG_OK_HOME || s == REG_OK_ROAMING); } - /* - * Secure socket layer (SSL) functions - */ - // Follows functions as inherited from TinyGsmSSL.tpp - - /* - * WiFi functions - */ - // No functions of this type supported - /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); // Configure the TCPIP Context @@ -316,22 +287,13 @@ class TinyGsmBG96 : public TinyGsmModem, return true; } - String getProviderImpl() { - sendAT(GF("+QSPN?")); - if (waitResponse(GF("+QSPN:")) != 1) { return ""; } - streamSkipUntil('"'); // Skip mode and format - String res = stream.readStringUntil('"'); // read the provider - waitResponse(); // skip anything else - return res; - } - /* * SIM card functions */ protected: String getSimCCIDImpl() { sendAT(GF("+QCCID")); - if (waitResponse(GF(AT_NL "+QCCID:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+QCCID:")) != 1) { return ""; } String res = stream.readStringUntil('\n'); waitResponse(); res.trim(); @@ -341,17 +303,14 @@ class TinyGsmBG96 : public TinyGsmModem, /* * Phone Call functions */ - // Follows all phone call functions as inherited from TinyGsmCalling.tpp - - /* - * Audio functions - */ - // No functions of this type supported + protected: + // Can follow all of the phone call functions from the template /* - * Text messaging (SMS) functions + * Messaging functions */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp + protected: + // Follows all messaging functions per template /* * GSM Location functions @@ -367,13 +326,13 @@ class TinyGsmBG96 : public TinyGsmModem, */ protected: // enable GPS - bool enableGPSImpl() { + bool enableGPSImpl(int8_t power_en_pin ,uint8_t enable_level) { sendAT(GF("+QGPS=1")); if (waitResponse() != 1) { return false; } return true; } - bool disableGPSImpl() { + bool disableGPSImpl(int8_t power_en_pin ,uint8_t disbale_level) { sendAT(GF("+QGPSEND")); if (waitResponse() != 1) { return false; } return true; @@ -382,7 +341,7 @@ class TinyGsmBG96 : public TinyGsmModem, // get the RAW GPS output String getGPSrawImpl() { sendAT(GF("+QGPSLOC=2")); - if (waitResponse(10000L, GF(AT_NL "+QGPSLOC: ")) != 1) { return ""; } + if (waitResponse(10000L, GF(GSM_NL "+QGPSLOC:")) != 1) { return ""; } String res = stream.readStringUntil('\n'); waitResponse(); res.trim(); @@ -390,12 +349,12 @@ class TinyGsmBG96 : public TinyGsmModem, } // get GPS informations - bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, + bool getGPSImpl(uint8_t *status,float* lat, float* lon, float* speed = 0, float* alt = 0, int* vsat = 0, int* usat = 0, float* accuracy = 0, int* year = 0, int* month = 0, int* day = 0, int* hour = 0, int* minute = 0, int* second = 0) { sendAT(GF("+QGPSLOC=2")); - if (waitResponse(10000L, GF(AT_NL "+QGPSLOC: ")) != 1) { + if (waitResponse(10000L, GF(GSM_NL "+QGPSLOC:")) != 1) { // NOTE: Will return an error if the position isn't fixed return false; } @@ -437,20 +396,20 @@ class TinyGsmBG96 : public TinyGsmModem, // 0, it is the type of error. // Set pointers - if (lat != nullptr) *lat = ilat; - if (lon != nullptr) *lon = ilon; - if (speed != nullptr) *speed = ispeed; - if (alt != nullptr) *alt = ialt; - if (vsat != nullptr) *vsat = 0; - if (usat != nullptr) *usat = iusat; - if (accuracy != nullptr) *accuracy = iaccuracy; + if (lat != NULL) *lat = ilat; + if (lon != NULL) *lon = ilon; + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = 0; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; if (iyear < 2000) iyear += 2000; - if (year != nullptr) *year = iyear; - if (month != nullptr) *month = imonth; - if (day != nullptr) *day = iday; - if (hour != nullptr) *hour = ihour; - if (minute != nullptr) *minute = imin; - if (second != nullptr) *second = static_cast(secondWithSS); + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); waitResponse(); // Final OK return true; @@ -478,49 +437,6 @@ class TinyGsmBG96 : public TinyGsmModem, return res; } - // The BG96 returns UTC time instead of local time as other modules do in - // response to CCLK, so we're using QLTS where we can specifically request - // local time. - bool getNetworkUTCTimeImpl(int* year, int* month, int* day, int* hour, - int* minute, int* second, float* timezone) { - sendAT(GF("+QLTS=1")); - if (waitResponse(2000L, GF("+QLTS: \"")) != 1) { return false; } - - int iyear = 0; - int imonth = 0; - int iday = 0; - int ihour = 0; - int imin = 0; - int isec = 0; - int itimezone = 0; - - // Date & Time - iyear = streamGetIntBefore('/'); - imonth = streamGetIntBefore('/'); - iday = streamGetIntBefore(','); - ihour = streamGetIntBefore(':'); - imin = streamGetIntBefore(':'); - isec = streamGetIntLength(2); - char tzSign = stream.read(); - itimezone = streamGetIntBefore(','); - if (tzSign == '-') { itimezone = itimezone * -1; } - streamSkipUntil('\n'); // DST flag - - // Set pointers - if (iyear < 2000) iyear += 2000; - if (year != nullptr) *year = iyear; - if (month != nullptr) *month = imonth; - if (day != nullptr) *day = iday; - if (hour != nullptr) *hour = ihour; - if (minute != nullptr) *minute = imin; - if (second != nullptr) *second = isec; - if (timezone != nullptr) *timezone = static_cast(itimezone) / 4.0; - - // Final OK - waitResponse(); // Ends with OK - return true; - } - // The BG96 returns UTC time instead of local time as other modules do in // response to CCLK, so we're using QLTS where we can specifically request // local time. @@ -551,13 +467,13 @@ class TinyGsmBG96 : public TinyGsmModem, // Set pointers if (iyear < 2000) iyear += 2000; - if (year != nullptr) *year = iyear; - if (month != nullptr) *month = imonth; - if (day != nullptr) *day = iday; - if (hour != nullptr) *hour = ihour; - if (minute != nullptr) *minute = imin; - if (second != nullptr) *second = isec; - if (timezone != nullptr) *timezone = static_cast(itimezone) / 4.0; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = isec; + if (timezone != NULL) *timezone = static_cast(itimezone) / 4.0; // Final OK waitResponse(); // Ends with OK @@ -568,7 +484,7 @@ class TinyGsmBG96 : public TinyGsmModem, * NTP server functions */ - byte NTPServerSyncImpl(String server = "pool.ntp.org", byte = -5) { + byte NTPServerSyncImpl(const String& server = "pool.ntp.org", byte = -5) { // Request network synchronization // AT+QNTP=,[,][,] sendAT(GF("+QNTP=1,\""), server, '"'); @@ -585,15 +501,11 @@ class TinyGsmBG96 : public TinyGsmModem, String ShowNTPErrorImpl(byte error) TINY_GSM_ATTR_NOT_IMPLEMENTED; - /* - * BLE functions - */ - // No functions of this type supported - /* * Battery functions */ - // Follows all battery functions as inherited from TinyGsmBattery.tpp + protected: + // Can follow CBC as in the template /* * Temperature functions @@ -602,7 +514,7 @@ class TinyGsmBG96 : public TinyGsmModem, // get temperature in degree celsius uint16_t getTemperatureImpl() { sendAT(GF("+QTEMP")); - if (waitResponse(GF(AT_NL "+QTEMP:")) != 1) { return 0; } + if (waitResponse(GF(GSM_NL "+QTEMP:")) != 1) { return 0; } // return temperature in C uint16_t res = streamGetIntBefore(','); // read PMIC (primary ic) temperature @@ -618,121 +530,39 @@ class TinyGsmBG96 : public TinyGsmModem, */ protected: bool modemConnect(const char* host, uint16_t port, uint8_t mux, - int timeout_s = 150) { - uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; - bool ssl = sockets[mux]->ssl_sock; - - if (ssl) { - // set the ssl version - // AT+QSSLCFG="sslversion",, - // PDP context identifier - // 0: QAPI_NET_SSL_3.0 - // 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0 - // 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1 - // 3: QAPI_NET_SSL_PROTOCOL_TLS_1_2 - // 4: ALL - // NOTE: despite docs using caps, "sslversion" must be in lower case - sendAT(GF("+QSSLCFG=\"sslversion\",0,3")); // TLS 1.2 - if (waitResponse(5000L) != 1) return false; - // set the ssl cipher_suite - // AT+QSSLCFG="ciphersuite",, - // PDP context identifier - // 0: TODO - // 1: TODO - // 0X0035: TLS_RSA_WITH_AES_256_CBC_SHA - // 0XFFFF: ALL - // NOTE: despite docs using caps, "sslversion" must be in lower case - sendAT(GF( - "+QSSLCFG=\"ciphersuite\",0,0X0035")); // TLS_RSA_WITH_AES_256_CBC_SHA - if (waitResponse(5000L) != 1) return false; - // set the ssl sec level - // AT+QSSLCFG="seclevel",, - // PDP context identifier - // 0: TODO - // 1: TODO - // 0X0035: TLS_RSA_WITH_AES_256_CBC_SHA - // 0XFFFF: ALL - // NOTE: despite docs using caps, "sslversion" must be in lower case - sendAT(GF("+QSSLCFG=\"seclevel\",0,1")); - if (waitResponse(5000L) != 1) return false; - - - if (certificates[mux] != "") { - // apply the correct certificate to the connection - // AT+QSSLCFG="cacert",, - // PDP context identifier - // certificate name - - // sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", - // certificates[mux].c_str(), - // "\""); - sendAT(GF("+QSSLCFG=\"cacert\",0,\""), certificates[mux].c_str(), - GF("\"")); - if (waitResponse(5000L) != 1) return false; - } + bool ssl = false, int timeout_s = 150) { + if (ssl) { DBG("SSL not yet supported on this module!"); } - // (1-16), (0-11), - // "TCP/UDP/TCP LISTENER/UDPSERVICE", "/", - // ,,(0-2; 0=buffer) - // may need previous AT+QSSLCFG - sendAT(GF("+QSSLOPEN=1,1,"), mux, GF(",\""), host, GF("\","), port, - GF(",0")); - waitResponse(); + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; - if (waitResponse(timeout_ms, GF(AT_NL "+QSSLOPEN:")) != 1) { - return false; - } - // 20230629 -> +QSSLOPEN: , - // clientID is mux - // err must be 0 - if (streamGetIntBefore(',') != mux) { return false; } - // Read status - return (0 == streamGetIntBefore('\n')); - } else { - // AT+QIOPEN=1,0,"TCP","220.180.239.212",8009,0,0 - // (1-16), (0-11), - // "TCP/UDP/TCP LISTENER/UDPSERVICE", "/", - // ,,(0-2; 0=buffer) - sendAT(GF("+QIOPEN=1,"), mux, GF(",\""), GF("TCP"), GF("\",\""), host, - GF("\","), port, GF(",0,0")); - waitResponse(); + // (1-16), (0-11), + // "TCP/UDP/TCP LISTENER/UDPSERVICE", "/", + // ,,(0-2; 0=buffer) + sendAT(GF("+QIOPEN=1,"), mux, GF(",\""), GF("TCP"), GF("\",\""), host, + GF("\","), port, GF(",0,0")); + waitResponse(); - if (waitResponse(timeout_ms, GF(AT_NL "+QIOPEN:")) != 1) { return false; } + if (waitResponse(timeout_ms, GF(GSM_NL "+QIOPEN:")) != 1) { return false; } - if (streamGetIntBefore(',') != mux) { return false; } - } + if (streamGetIntBefore(',') != mux) { return false; } // Read status return (0 == streamGetIntBefore('\n')); } int16_t modemSend(const void* buff, size_t len, uint8_t mux) { - bool ssl = sockets[mux]->ssl_sock; - if (ssl) { - sendAT(GF("+QSSLSEND="), mux, ',', (uint16_t)len); - } else { - sendAT(GF("+QISEND="), mux, ',', (uint16_t)len); - } + sendAT(GF("+QISEND="), mux, ',', (uint16_t)len); if (waitResponse(GF(">")) != 1) { return 0; } stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(GF(AT_NL "SEND OK")) != 1) { return 0; } - // TODO(?): Wait for ACK? (AT+QISEND=id,0 or AT+QSSLSEND=id,0) + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; } + // TODO(?): Wait for ACK? AT+QISEND=id,0 return len; } size_t modemRead(size_t size, uint8_t mux) { if (!sockets[mux]) return 0; - bool ssl = sockets[mux]->ssl_sock; - if (ssl) { - sendAT(GF("+QSSLRECV="), mux, ',', (uint16_t)size); - if (waitResponse(GF("+QSSLRECV:")) != 1) { - DBG("### READ: For unknown reason close"); - return 0; - } - } else { - sendAT(GF("+QIRD="), mux, ',', (uint16_t)size); - if (waitResponse(GF("+QIRD:")) != 1) { return 0; } - } + sendAT(GF("+QIRD="), mux, ',', (uint16_t)size); + if (waitResponse(GF("+QIRD:")) != 1) { return 0; } int16_t len = streamGetIntBefore('\n'); for (int i = 0; i < len; i++) { moveCharFromStreamToFifo(mux); } @@ -744,98 +574,146 @@ class TinyGsmBG96 : public TinyGsmModem, size_t modemGetAvailable(uint8_t mux) { if (!sockets[mux]) return 0; - bool ssl = sockets[mux]->ssl_sock; + sendAT(GF("+QIRD="), mux, GF(",0")); size_t result = 0; - if (ssl) { - sendAT(GF("+QSSLRECV="), mux, GF(",0")); - if (waitResponse(GF("+QSSLRECV:")) == 1) { - streamSkipUntil(','); // Skip total received - streamSkipUntil(','); // Skip have read - result = streamGetIntBefore('\n'); - if (result) { DBG("### DATA AVAILABLE:", result, "on", mux); } - waitResponse(); - } - } else { - sendAT(GF("+QIRD="), mux, GF(",0")); - if (waitResponse(GF("+QIRD:")) == 1) { - streamSkipUntil(','); // Skip total received - streamSkipUntil(','); // Skip have read - result = streamGetIntBefore('\n'); - if (result) { DBG("### DATA AVAILABLE:", result, "on", mux); } - waitResponse(); - } + if (waitResponse(GF("+QIRD:")) == 1) { + streamSkipUntil(','); // Skip total received + streamSkipUntil(','); // Skip have read + result = streamGetIntBefore('\n'); + if (result) { DBG("### DATA AVAILABLE:", result, "on", mux); } + waitResponse(); } if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } return result; } bool modemGetConnected(uint8_t mux) { - bool ssl = sockets[mux]->ssl_sock; - if (ssl) { - sendAT(GF("+QSSLSTATE=1,"), mux); - // +QSSLSTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" - - if (waitResponse(GF("+QSSLSTATE:")) != 1) { return false; } - - streamSkipUntil(','); // Skip clientID - streamSkipUntil(','); // Skip "SSLClient" - streamSkipUntil(','); // Skip remote ip - streamSkipUntil(','); // Skip remote port - streamSkipUntil(','); // Skip local port - int8_t res = streamGetIntBefore(','); // socket state - - waitResponse(); - - // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing - return 2 == res; - } else { - sendAT(GF("+QISTATE=1,"), mux); - // +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" + sendAT(GF("+QISTATE=1,"), mux); + // +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" - if (waitResponse(GF("+QISTATE:")) != 1) { return false; } + if (waitResponse(GF("+QISTATE:")) != 1) { return false; } - streamSkipUntil(','); // Skip mux - streamSkipUntil(','); // Skip socket type - streamSkipUntil(','); // Skip remote ip - streamSkipUntil(','); // Skip remote port - streamSkipUntil(','); // Skip local port - int8_t res = streamGetIntBefore(','); // socket state + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip socket type + streamSkipUntil(','); // Skip remote ip + streamSkipUntil(','); // Skip remote port + streamSkipUntil(','); // Skip local port + int8_t res = streamGetIntBefore(','); // socket state - waitResponse(); + waitResponse(); - // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing - return 2 == res; - } + // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing + return 2 == res; } /* * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF(AT_NL "+QIURC:"))) { - streamSkipUntil('\"'); - String urc = stream.readStringUntil('\"'); - streamSkipUntil(','); - if (urc == "recv") { - int8_t mux = streamGetIntBefore('\n'); - DBG("### URC RECV:", mux); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - } - } else if (urc == "closed") { - int8_t mux = streamGetIntBefore('\n'); - DBG("### URC CLOSE:", mux); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+QIURC:"))) { + streamSkipUntil('\"'); + String urc = stream.readStringUntil('\"'); + streamSkipUntil(','); + if (urc == "recv") { + int8_t mux = streamGetIntBefore('\n'); + DBG("### URC RECV:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + } else if (urc == "closed") { + int8_t mux = streamGetIntBefore('\n'); + DBG("### URC CLOSE:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + } else { + streamSkipUntil('\n'); + } + data = ""; } - } else { - streamSkipUntil('\n'); } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } public: @@ -843,7 +721,7 @@ class TinyGsmBG96 : public TinyGsmModem, protected: GsmClientBG96* sockets[TINY_GSM_MUX_COUNT]; - String certificates[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTBG96_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientESP8266.h b/lib/TinyGSM/src/TinyGsmClientESP8266.h index 85f352c..92763a0 100644 --- a/lib/TinyGSM/src/TinyGsmClientESP8266.h +++ b/lib/TinyGSM/src/TinyGsmClientESP8266.h @@ -14,57 +14,38 @@ #define TINY_GSM_MUX_COUNT 5 #define TINY_GSM_NO_MODEM_BUFFER -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "Espressif" - -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#if defined(TINY_GSM_MODEM_ESP8266) -#define MODEM_MODEL "ESP8266" -#else -#define MODEM_MODEL "ESP32" -#endif #include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" #include "TinyGsmSSL.tpp" +#include "TinyGsmTCP.tpp" #include "TinyGsmWifi.tpp" -static uint8_t TINY_GSM_TCP_KEEP_ALIVE = 120; +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +static uint8_t TINY_GSM_TCP_KEEP_ALIVE = 120; // status of ESP8266 station interface -// 0: ESP8266 station is not initialized. -// 1: ESP8266 station is initialized, but not started a Wi-Fi connection yet. // 2 : ESP8266 station connected to an AP and has obtained IP // 3 : ESP8266 station created a TCP or UDP transmission // 4 : the TCP or UDP transmission of ESP8266 station disconnected // 5 : ESP8266 station did NOT connect to an AP -enum ESP8266RegStatus { - REG_UNINITIALIZED = 0, - REG_UNREGISTERED = 1, - REG_OK_IP = 2, - REG_OK_TCP = 3, - REG_OK_NO_TCP = 4, - REG_DENIED = 5, - REG_UNKNOWN = 6, +enum RegStatus { + REG_OK_IP = 2, + REG_OK_TCP = 3, + REG_OK_NO_TCP = 4, + REG_DENIED = 5, + REG_UNKNOWN = 6, }; class TinyGsmESP8266 : public TinyGsmModem, + public TinyGsmWifi, public TinyGsmTCP, - public TinyGsmSSL, - public TinyGsmWifi { + public TinyGsmSSL { friend class TinyGsmModem; - friend class TinyGsmTCP; - friend class TinyGsmSSL; friend class TinyGsmWifi; + friend class TinyGsmTCP; + friend class TinyGsmSSL; /* * Inner Client @@ -156,7 +137,7 @@ class TinyGsmESP8266 : public TinyGsmModem, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientESP8266")); @@ -178,7 +159,11 @@ class TinyGsmESP8266 : public TinyGsmModem, return true; } - bool setBaudImpl(uint32_t baud) { + String getModemNameImpl() { + return "ESP8266"; + } + + void setBaudImpl(uint32_t baud) { sendAT(GF("+UART_CUR="), baud, "8,1,0,0"); if (waitResponse() != 1) { sendAT(GF("+UART="), baud, @@ -186,69 +171,35 @@ class TinyGsmESP8266 : public TinyGsmModem, // if (waitResponse() != 1) { // sendAT(GF("+IPR="), baud); // First release firmwares might need // this - return waitResponse() == 1; + waitResponse(); // } } - return false; - } - - String getModemInfoImpl() { - sendAT(GF("+GMR")); - String res; - if (waitResponse(1000L, res) != 1) { return ""; } - cleanResponseString(res); - return res; } - // Gets the modem hardware version - String getModemManufacturerImpl() { - return MODEM_MANUFACTURER; + bool factoryDefaultImpl() { + sendAT(GF("+RESTORE")); + return waitResponse() == 1; } - // Gets the modem hardware version - String getModemModelImpl() { - String model = MODEM_MODEL; + String getModemInfoImpl() { sendAT(GF("+GMR")); - streamSkipUntil('\n'); // skip the AT version - streamSkipUntil('\n'); // skip the SDK version - streamSkipUntil('\n'); // skip the compile time - // read the hardware from the Bin version - streamSkipUntil('('); // skip the text "Bin version" - String wroom = stream.readStringUntil( - ')'); // read the WRoom version in the parethesis - streamSkipUntil('('); // skip the bin version itself - if (waitResponse(1000L) == 1) { // wait for the ending OK - return wroom; - } - return model; - } - - // Gets the modem firmware version - String getModemRevisionImpl() { - sendAT(GF("GMR")); // GMR instead of CGMR String res; if (waitResponse(1000L, res) != 1) { return ""; } - cleanResponseString(res); + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, " "); + res.trim(); return res; } - // Gets the modem serial number - String getModemSerialNumberImpl() TINY_GSM_ATTR_NOT_AVAILABLE; - - bool factoryDefaultImpl() { - sendAT(GF("+RESTORE")); - return waitResponse() == 1; - } - /* * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { + bool restartImpl(const char* pin = NULL) { if (!testAT()) { return false; } sendAT(GF("+RST")); if (waitResponse(10000L) != 1) { return false; } - if (waitResponse(10000L, GF(AT_NL "ready" AT_NL)) != 1) { return false; } + if (waitResponse(10000L, GF(GSM_NL "ready" GSM_NL)) != 1) { return false; } delay(500); return init(pin); } @@ -269,18 +220,13 @@ class TinyGsmESP8266 : public TinyGsmModem, * Generic network functions */ public: - ESP8266RegStatus getRegistrationStatus() { + RegStatus getRegistrationStatus() { sendAT(GF("+CIPSTATUS")); if (waitResponse(3000, GF("STATUS:")) != 1) return REG_UNKNOWN; - // after "STATUS:" it should return the status number (0,1,2,3,4,5), - // followed by an OK - // Since there are more possible status number codes than the arguments for - // waitResponse, we'll capture the response in a string and then parse it. - String res; - if (waitResponse(3000L, res) != 1) { return REG_UNKNOWN; } - res.trim(); - int8_t status = res.toInt(); - return (ESP8266RegStatus)status; + int8_t status = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"), + GF("5")); + waitResponse(); // Returns an OK after the status + return (RegStatus)status; } protected: @@ -306,7 +252,7 @@ class TinyGsmESP8266 : public TinyGsmModem, } bool isNetworkConnectedImpl() { - ESP8266RegStatus s = getRegistrationStatus(); + RegStatus s = getRegistrationStatus(); if (s == REG_OK_IP || s == REG_OK_TCP) { // with these, we're definitely connected return true; @@ -339,12 +285,6 @@ class TinyGsmESP8266 : public TinyGsmModem, return res2; } - /* - * Secure socket layer (SSL) functions - */ - // Follows functions as inherited from TinyGsmSSL.tpp - - /* * WiFi functions */ @@ -353,9 +293,9 @@ class TinyGsmESP8266 : public TinyGsmModem, // attempt first without than with the 'current' flag used in some firmware // versions sendAT(GF("+CWJAP=\""), ssid, GF("\",\""), pwd, GF("\"")); - if (waitResponse(30000L, GFP(GSM_OK), GF(AT_NL "FAIL" AT_NL)) != 1) { + if (waitResponse(30000L, GFP(GSM_OK), GF(GSM_NL "FAIL" GSM_NL)) != 1) { sendAT(GF("+CWJAP_CUR=\""), ssid, GF("\",\""), pwd, GF("\"")); - if (waitResponse(30000L, GFP(GSM_OK), GF(AT_NL "FAIL" AT_NL)) != 1) { + if (waitResponse(30000L, GFP(GSM_OK), GF(GSM_NL "FAIL" GSM_NL)) != 1) { return false; } } @@ -370,62 +310,6 @@ class TinyGsmESP8266 : public TinyGsmModem, return retVal; } - - /* - * GPRS functions - */ - // No functions of this type supported - - /* - * SIM card functions - */ - // No functions of this type supported - - /* - * Audio functions - */ - // No functions of this type supported - - /* - * Text messaging (SMS) functions - */ - // No functions of this type supported - - /* - * GSM Location functions - */ - // No functions of this type supported - - /* - * GPS/GNSS/GLONASS location functions - */ - // No functions of this type supported - - /* - * Time functions - */ - // No functions of this type supported - - /* - * NTP server functions - */ - // No functions of this type supported - - /* - * BLE functions - */ - // No functions of this type supported - - /* - * Battery functions - */ - // No functions of this type supported - - /* - * Temperature functions - */ - // No functions of this type supported - /* * Client related functions */ @@ -453,19 +337,18 @@ class TinyGsmESP8266 : public TinyGsmModem, if (waitResponse(GF(">")) != 1) { return 0; } stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(10000L, GF(AT_NL "SEND OK" AT_NL)) != 1) { return 0; } + if (waitResponse(10000L, GF(GSM_NL "SEND OK" GSM_NL)) != 1) { return 0; } return len; } bool modemGetConnected(uint8_t mux) { sendAT(GF("+CIPSTATUS")); if (waitResponse(3000, GF("STATUS:")) != 1) { return false; } - // after "STATUS:" it should return the status number (0,1,2,3,4,5), - // followed by an OK - // Hopefully we'll catch the "3" here, but fall back to the OK or Error - int8_t status = waitResponse(GF("3"), GFP(GSM_OK), GFP(GSM_ERROR)); - // if the status is anything but 3, there are no connections open - if (status != 1) { + int8_t status = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"), + GF("5")); + if (status != 3) { + // if the status is anything but 3, there are no connections open + waitResponse(); // Returns an OK after the status for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { if (sockets[muxNo]) { sockets[muxNo]->sock_connected = false; } } @@ -499,41 +382,96 @@ class TinyGsmESP8266 : public TinyGsmModem, * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF("+IPD,"))) { - int8_t mux = streamGetIntBefore(','); - int16_t len = streamGetIntBefore(':'); - int16_t len_orig = len; - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - if (len > sockets[mux]->rx.free()) { - DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free()); - // reset the len to read to the amount free - len = sockets[mux]->rx.free(); - } - while (len--) { moveCharFromStreamToFifo(mux); } - // TODO(SRGDamia1): deal with buffer overflow/missed characters - if (len_orig != sockets[mux]->available()) { - DBG("### Different number of characters received than expected: ", - sockets[mux]->available(), " vs ", len_orig); + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), GsmConstStr r3 = NULL, + GsmConstStr r4 = NULL, GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+IPD,"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore(':'); + int16_t len_orig = len; + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (len > sockets[mux]->rx.free()) { + DBG("### Buffer overflow: ", len, "received vs", + sockets[mux]->rx.free(), "available"); + } else { + // DBG("### Got Data: ", len, "on", mux); + } + while (len--) { moveCharFromStreamToFifo(mux); } + // TODO(SRGDamia1): deal with buffer overflow/missed characters + if (len_orig > sockets[mux]->available()) { + DBG("### Fewer characters received than expected: ", + sockets[mux]->available(), " vs ", len_orig); + } + } + data = ""; + } else if (data.endsWith(GF("CLOSED"))) { + int8_t muxStart = + TinyGsmMax(0, data.lastIndexOf(GSM_NL, data.length() - 8)); + int8_t coma = data.indexOf(',', muxStart); + int8_t mux = data.substring(muxStart, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); } } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - DBG("### Got Data: ", len_orig, "on", mux); - return true; - } else if (data.endsWith(GF("CLOSED"))) { - int8_t muxStart = TinyGsmMax(0, - data.lastIndexOf(AT_NL, data.length() - 8)); - int8_t coma = data.indexOf(',', muxStart); - int8_t mux = data.substring(muxStart, coma).toInt(); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - streamSkipUntil('\n'); // throw away the new line - data = ""; - DBG("### Closed: ", mux); - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), GsmConstStr r3 = NULL, + GsmConstStr r4 = NULL, GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), GsmConstStr r3 = NULL, + GsmConstStr r4 = NULL, GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } public: @@ -541,6 +479,7 @@ class TinyGsmESP8266 : public TinyGsmModem, protected: GsmClientESP8266* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTESP8266_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientM590.h b/lib/TinyGSM/src/TinyGsmClientM590.h index 1458613..e921447 100644 --- a/lib/TinyGSM/src/TinyGsmClientM590.h +++ b/lib/TinyGSM/src/TinyGsmClientM590.h @@ -14,28 +14,22 @@ #define TINY_GSM_MUX_COUNT 2 #define TINY_GSM_NO_MODEM_BUFFER -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "Neoway" - -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#define MODEM_MODEL "M590" -#include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" #include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" #include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" #include "TinyGsmTime.tpp" -enum M590RegStatus { +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { REG_NO_RESULT = -1, REG_UNREGISTERED = 0, REG_SEARCHING = 3, @@ -112,11 +106,6 @@ class TinyGsmM590 : public TinyGsmModem, String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; }; - /* - * Inner Secure Client - */ - // NOT SUPPORTED - /* * Constructor */ @@ -129,7 +118,7 @@ class TinyGsmM590 : public TinyGsmModem, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientM590")); @@ -149,7 +138,7 @@ class TinyGsmM590 : public TinyGsmModem, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -159,50 +148,9 @@ class TinyGsmM590 : public TinyGsmModem, } } - // This is extracted from the modem info + // Doesn't support CGMI String getModemNameImpl() { - sendAT(GF("I")); - String factory = stream.readStringUntil('\n'); // read the factory - factory.trim(); - String model = stream.readStringUntil('\n'); // read the model - model.trim(); - streamSkipUntil('\n'); // skip the revision - waitResponse(); // wait for the OK - return factory + String(" ") + model; - } - - // This is extracted from the modem info - String getModemManufacturerImpl() { - sendAT(GF("I")); - String factory = stream.readStringUntil('\n'); // read the factory - factory.trim(); - streamSkipUntil('\n'); // skip the model - streamSkipUntil('\n'); // skip the revision - if (waitResponse() == 1) { return factory; } - return MODEM_MANUFACTURER; - } - - // This is extracted from the modem info - String getModemModelImpl() { - sendAT(GF("I")); - streamSkipUntil('\n'); // skip the factory - String model = stream.readStringUntil('\n'); // read the model - model.trim(); - streamSkipUntil('\n'); // skip the revision - if (waitResponse() == 1) { return model; } - return MODEM_MODEL; - } - - // Gets the modem firmware version - // This is extracted from the modem info - String getModemRevisionImpl() { - sendAT(GF("I")); - streamSkipUntil('\n'); // skip the factory - streamSkipUntil('\n'); // skip the model - String res = stream.readStringUntil('\n'); // read the revision - res.trim(); - waitResponse(); // wait for the OK - return res; + return "Neoway M590"; } // Extra stuff here - pwr save, internal stack @@ -223,11 +171,11 @@ class TinyGsmM590 : public TinyGsmModem, * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { + bool restartImpl(const char* pin = NULL) { if (!testAT()) { return false; } if (!setPhoneFunctionality(15)) { return false; } // MODEM:STARTUP - waitResponse(60000L, GF(AT_NL "+PBREADY" AT_NL)); + waitResponse(60000L, GF(GSM_NL "+PBREADY" GSM_NL)); return init(pin); } @@ -250,19 +198,19 @@ class TinyGsmM590 : public TinyGsmModem, * Generic network functions */ public: - M590RegStatus getRegistrationStatus() { - return (M590RegStatus)getRegistrationStatusXREG("CREG"); + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); } protected: bool isNetworkConnectedImpl() { - M590RegStatus s = getRegistrationStatus(); + RegStatus s = getRegistrationStatus(); return (s == REG_OK_HOME || s == REG_OK_ROAMING); } String getLocalIPImpl() { sendAT(GF("+XIIC?")); - if (waitResponse(GF(AT_NL "+XIIC:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) { return ""; } streamSkipUntil(','); String res = stream.readStringUntil('\n'); waitResponse(); @@ -270,22 +218,12 @@ class TinyGsmM590 : public TinyGsmModem, return res; } - /* - * Secure socket layer (SSL) functions - */ - // No functions of this type supported - - /* - * WiFi functions - */ - // No functions of this type supported - /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); sendAT(GF("+XISP=0")); @@ -330,7 +268,7 @@ class TinyGsmM590 : public TinyGsmModem, bool isGprsConnectedImpl() { sendAT(GF("+XIIC?")); - if (waitResponse(GF(AT_NL "+XIIC:")) != 1) { return false; } + if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) { return false; } int8_t res = streamGetIntBefore(','); waitResponse(); return res == 1; @@ -340,69 +278,20 @@ class TinyGsmM590 : public TinyGsmModem, * SIM card functions */ protected: - // Able to follow all SIM card functions as inherited from TinyGsmGPRS.tpp - + // Able to follow all SIM card functions as inherited from the template /* - * Phone Call functions - */ - // No functions of this type supported - - /* - * Audio functions - */ - // No functions of this type supported - - /* - * Text messaging (SMS) functions + * Messaging functions */ protected: bool sendSMS_UTF16Impl(const String& number, const void* text, size_t len) TINY_GSM_ATTR_NOT_AVAILABLE; - /* - * GSM Location functions - */ - // No functions of this type supported - - /* - * GPS/GNSS/GLONASS location functions - */ - // No functions of this type supported - /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp - /* - * NTP server functions - */ - // Follows all NTP server functions as inherited from TinyGsmNTP.tpp - - /* - * BLE functions - */ - // No functions of this type supported - - /* - * NTP server functions - */ - // No functions of this type supported - - /* - * BLE functions - */ - // No functions of this type supported - - /* - * Battery functions - */ - // No functions of this type supported - - /* - * Temperature functions - */ - // No functions of this type supported + protected: + // Can follow the standard CCLK function in the template /* * Client related functions @@ -415,8 +304,9 @@ class TinyGsmM590 : public TinyGsmModem, String ip = dnsIpQuery(host); sendAT(GF("+TCPSETUP="), mux, GF(","), ip, GF(","), port); - int8_t rsp = waitResponse(timeout_ms, GF(",OK" AT_NL), GF(",FAIL" AT_NL), - GF("+TCPSETUP:Error" AT_NL)); + int8_t rsp = waitResponse(timeout_ms, GF(",OK" GSM_NL), + GF(",FAIL" GSM_NL), + GF("+TCPSETUP:Error" GSM_NL)); if (1 == rsp) { return true; } else if (3 == rsp) { @@ -434,7 +324,7 @@ class TinyGsmM590 : public TinyGsmModem, stream.write(reinterpret_cast(buff), len); stream.write(static_cast(0x0D)); stream.flush(); - if (waitResponse(30000L, GF(AT_NL "+TCPSEND:")) != 1) { return 0; } + if (waitResponse(30000L, GF(GSM_NL "+TCPSEND:")) != 1) { return 0; } streamSkipUntil('\n'); return len; } @@ -449,9 +339,9 @@ class TinyGsmM590 : public TinyGsmModem, String dnsIpQuery(const char* host) { sendAT(GF("+DNS=\""), host, GF("\"")); - if (waitResponse(10000L, GF(AT_NL "+DNS:")) != 1) { return ""; } + if (waitResponse(10000L, GF(GSM_NL "+DNS:")) != 1) { return ""; } String res = stream.readStringUntil('\n'); - waitResponse(GF("+DNS:OK" AT_NL)); + waitResponse(GF("+DNS:OK" GSM_NL)); res.trim(); return res; } @@ -460,38 +350,116 @@ class TinyGsmM590 : public TinyGsmModem, * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF("+TCPRECV:"))) { - int8_t mux = streamGetIntBefore(','); - int16_t len = streamGetIntBefore(','); - int16_t len_orig = len; - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - if (len > sockets[mux]->rx.free()) { - DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free()); - // reset the len to read to the amount free - len = sockets[mux]->rx.free(); - } - while (len--) { moveCharFromStreamToFifo(mux); } - // TODO(?): Handle lost characters - if (len_orig != sockets[mux]->available()) { - DBG("### Different number of characters received than expected: ", - sockets[mux]->available(), " vs ", len_orig); + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+TCPRECV:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore(','); + int16_t len_orig = len; + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (len > sockets[mux]->rx.free()) { + DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free()); + } else { + DBG("### Got: ", len, "->", sockets[mux]->rx.free()); + } + while (len--) { moveCharFromStreamToFifo(mux); } + // TODO(?): Handle lost characters + if (len_orig > sockets[mux]->available()) { + DBG("### Fewer characters received than expected: ", + sockets[mux]->available(), " vs ", len_orig); + } + } + data = ""; + } else if (data.endsWith(GF("+TCPCLOSE:"))) { + int8_t mux = streamGetIntBefore(','); + streamSkipUntil('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); } } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - DBG("### Got Data: ", len_orig, "on", mux); - return true; - } else if (data.endsWith(GF("+TCPCLOSE:"))) { - int8_t mux = streamGetIntBefore(','); - streamSkipUntil('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - data = ""; - DBG("### Closed: ", mux); - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } public: @@ -499,6 +467,7 @@ class TinyGsmM590 : public TinyGsmModem, protected: GsmClientM590* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTM590_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientM95.h b/lib/TinyGSM/src/TinyGsmClientM95.h index 0104392..538969a 100644 --- a/lib/TinyGSM/src/TinyGsmClientM95.h +++ b/lib/TinyGSM/src/TinyGsmClientM95.h @@ -15,31 +15,25 @@ #define TINY_GSM_MUX_COUNT 6 #define TINY_GSM_BUFFER_READ_NO_CHECK -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "Quectel" - -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#define MODEM_MODEL "M95" -#include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" -#include "TinyGsmGPRS.tpp" +#include "TinyGsmBattery.tpp" #include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" #include "TinyGsmSMS.tpp" -#include "TinyGsmTime.tpp" -#include "TinyGsmBattery.tpp" +#include "TinyGsmTCP.tpp" #include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif -enum M95RegStatus { +enum RegStatus { REG_NO_RESULT = -1, REG_UNREGISTERED = 0, REG_SEARCHING = 2, @@ -127,7 +121,29 @@ class TinyGsmM95 : public TinyGsmModem, /* * Inner Secure Client */ - // NOT SUPPORTED + + /* + class GsmClientSecureM95 : public GsmClientM95 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmm95& modem, uint8_t mux = 0) + : GsmClient(modem, mux) + {} + + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ /* * Constructor @@ -141,7 +157,7 @@ class TinyGsmM95 : public TinyGsmModem, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientM95")); @@ -168,7 +184,7 @@ class TinyGsmM95 : public TinyGsmModem, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -182,7 +198,7 @@ class TinyGsmM95 : public TinyGsmModem, * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { + bool restartImpl(const char* pin = NULL) { if (!testAT()) { return false; } sendAT(GF("+CFUN=0")); if (waitResponse(10000L, GF("NORMAL POWER DOWN"), GF("OK"), GF("FAIL")) == @@ -218,13 +234,13 @@ class TinyGsmM95 : public TinyGsmModem, * Generic network functions */ public: - M95RegStatus getRegistrationStatus() { - return (M95RegStatus)getRegistrationStatusXREG("CREG"); + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); } protected: bool isNetworkConnectedImpl() { - M95RegStatus s = getRegistrationStatus(); + RegStatus s = getRegistrationStatus(); return (s == REG_OK_HOME || s == REG_OK_ROAMING); } @@ -245,22 +261,12 @@ class TinyGsmM95 : public TinyGsmModem, return res; } - /* - * Secure socket layer (SSL) functions - */ - // No functions of this type supported - - /* - * WiFi functions - */ - // No functions of this type supported - /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); // select foreground context 0 = VIRTUAL_UART_1 @@ -334,22 +340,13 @@ class TinyGsmM95 : public TinyGsmModem, return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1; } - String getProviderImpl() { - sendAT(GF("+QSPN?")); - if (waitResponse(GF("+QSPN:")) != 1) { return ""; } - streamSkipUntil('"'); // Skip mode and format - String res = stream.readStringUntil('"'); // read the provider - waitResponse(); // skip anything else - return res; - } - /* * SIM card functions */ protected: String getSimCCIDImpl() { sendAT(GF("+QCCID")); - if (waitResponse(GF(AT_NL "+QCCID:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+QCCID:")) != 1) { return ""; } String res = stream.readStringUntil('\n'); waitResponse(); res.trim(); @@ -359,17 +356,14 @@ class TinyGsmM95 : public TinyGsmModem, /* * Phone Call functions */ - // Follows all phone call functions as inherited from TinyGsmCalling.tpp - + protected: + // Can follow all of the phone call functions from the template /* - * Audio functions + * Messaging functions */ - // No functions of this type supported - /* - * Text messaging (SMS) functions - */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp + protected: + // Can follow all template functions public: /** Delete all SMS */ @@ -381,45 +375,16 @@ class TinyGsmM95 : public TinyGsmModem, return false; } - /* - * GSM Location functions - */ - // No functions of this type supported - - /* - * GPS/GNSS/GLONASS location functions - */ - // No functions of this type supported - /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp - - /* - * NTP server functions - */ - // Follows all NTP server functions as inherited from TinyGsmNTP.tpp - - /* - * BLE functions - */ - // No functions of this type supported - - /* - * NTP server functions - */ - // No functions of this type supported - - /* - * BLE functions - */ - // No functions of this type supported + protected: + // Can follow the standard CCLK function in the template /* * Battery functions */ - // Follows all battery functions as inherited from TinyGsmBattery.tpp + // Can follow the battery functions in the template /* * Temperature functions @@ -427,7 +392,7 @@ class TinyGsmM95 : public TinyGsmModem, protected: float getTemperatureImpl() { sendAT(GF("+QTEMP?")); - if (waitResponse(GF(AT_NL "+QTEMP:")) != 1) { + if (waitResponse(GF(GSM_NL "+QTEMP:")) != 1) { return static_cast(-9999); } streamSkipUntil(','); // Skip mode @@ -450,9 +415,9 @@ class TinyGsmM95 : public TinyGsmModem, uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; sendAT(GF("+QIOPEN="), mux, GF(",\""), GF("TCP"), GF("\",\""), host, GF("\","), port); - int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" AT_NL), - GF("CONNECT FAIL" AT_NL), - GF("ALREADY CONNECT" AT_NL)); + int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL)); return (1 == rsp); } @@ -461,13 +426,13 @@ class TinyGsmM95 : public TinyGsmModem, if (waitResponse(GF(">")) != 1) { return 0; } stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(GF(AT_NL "SEND OK")) != 1) { return 0; } + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; } // bool allAcknowledged = false; // // bool failed = false; // while ( !allAcknowledged ) { // sendAT( GF("+QISACK")); - // if (waitResponse(5000L, GF(AT_NL "+QISACK:")) != 1) { + // if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) { // return -1; // } else { // streamSkipUntil(','); // Skip total length sent on connection @@ -550,36 +515,114 @@ class TinyGsmM95 : public TinyGsmModem, * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF(AT_NL "+QIRDI:"))) { - streamSkipUntil(','); // Skip the context - streamSkipUntil(','); // Skip the role - int8_t mux = streamGetIntBefore('\n'); - // DBG("### Got Data:", mux); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - // We have no way of knowing how much data actually came in, so - // we set the value to 1500, the maximum possible size. - sockets[mux]->sock_available = 1500; - } - data = ""; - return true; - } else if (data.endsWith(GF("CLOSED" AT_NL))) { - int8_t nl = data.lastIndexOf(AT_NL, data.length() - 8); - int8_t coma = data.indexOf(',', nl + 2); - int8_t mux = data.substring(nl + 2, coma).toInt(); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+QIRDI:"))) { + streamSkipUntil(','); // Skip the context + streamSkipUntil(','); // Skip the role + int8_t mux = streamGetIntBefore('\n'); + // DBG("### Got Data:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + // We have no way of knowing how much data actually came in, so + // we set the value to 1500, the maximum possible size. + sockets[mux]->sock_available = 1500; + } + data = ""; + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+QNITZ:"))) { + streamSkipUntil('\n'); // URC for time sync + data = ""; + DBG("### Network time updated."); + } } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - DBG("### Closed: ", mux); - return true; - } else if (data.endsWith(GF("+QNITZ:"))) { - streamSkipUntil('\n'); // URC for time sync - data = ""; - DBG("### Network time updated."); - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } public: @@ -587,6 +630,7 @@ class TinyGsmM95 : public TinyGsmModem, protected: GsmClientM95* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTM95_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientMC60.h b/lib/TinyGSM/src/TinyGsmClientMC60.h index 1156068..abc5488 100644 --- a/lib/TinyGSM/src/TinyGsmClientMC60.h +++ b/lib/TinyGSM/src/TinyGsmClientMC60.h @@ -17,34 +17,24 @@ #define TINY_GSM_MUX_COUNT 6 #define TINY_GSM_BUFFER_READ_NO_CHECK -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "Quectel" - -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#if defined(TINY_GSM_MODEM_MC60E) -#define MODEM_MODEL "MC60E" -#else -#define MODEM_MODEL "MC60" -#endif -#include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" -#include "TinyGsmGPRS.tpp" +#include "TinyGsmBattery.tpp" #include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" #include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" #include "TinyGsmTime.tpp" -#include "TinyGsmBattery.tpp" -enum MC60RegStatus { +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { REG_NO_RESULT = -1, REG_UNREGISTERED = 0, REG_SEARCHING = 2, @@ -130,7 +120,29 @@ class TinyGsmMC60 : public TinyGsmModem, /* * Inner Secure Client */ - // NOT SUPPORTED + + /* + class GsmClientSecureMC60 : public GsmClientMC60 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmMC60& modem, uint8_t mux = 0) + : GsmClient(modem, mux) + {} + + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ /* * Constructor @@ -144,7 +156,7 @@ class TinyGsmMC60 : public TinyGsmModem, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientMC60")); @@ -171,7 +183,7 @@ class TinyGsmMC60 : public TinyGsmModem, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -185,7 +197,7 @@ class TinyGsmMC60 : public TinyGsmModem, * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { + bool restartImpl(const char* pin = NULL) { if (!testAT()) { return false; } if (!setPhoneFunctionality(0)) { return false; } if (!setPhoneFunctionality(1, true)) { return false; } @@ -217,13 +229,13 @@ class TinyGsmMC60 : public TinyGsmModem, * Generic network functions */ public: - MC60RegStatus getRegistrationStatus() { - return (MC60RegStatus)getRegistrationStatusXREG("CREG"); + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); } protected: bool isNetworkConnectedImpl() { - MC60RegStatus s = getRegistrationStatus(); + RegStatus s = getRegistrationStatus(); return (s == REG_OK_HOME || s == REG_OK_ROAMING); } @@ -235,22 +247,12 @@ class TinyGsmMC60 : public TinyGsmModem, return res; } - /* - * Secure socket layer (SSL) functions - */ - // No functions of this type supported - - /* - * WiFi functions - */ - // No functions of this type supported - /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); // select foreground context 0 = VIRTUAL_UART_1 @@ -307,15 +309,6 @@ class TinyGsmMC60 : public TinyGsmModem, return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1; } - String getProviderImpl() { - sendAT(GF("+QSPN?")); - if (waitResponse(GF("+QSPN:")) != 1) { return ""; } - streamSkipUntil('"'); // Skip mode and format - String res = stream.readStringUntil('"'); // read the provider - waitResponse(); // skip anything else - return res; - } - /* * SIM card functions */ @@ -323,7 +316,7 @@ class TinyGsmMC60 : public TinyGsmModem, SimStatus getSimStatusImpl(uint32_t timeout_ms = 10000L) { for (uint32_t start = millis(); millis() - start < timeout_ms;) { sendAT(GF("+CPIN?")); - if (waitResponse(GF(AT_NL "+CPIN:")) != 1) { + if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) { delay(1000); continue; } @@ -346,17 +339,14 @@ class TinyGsmMC60 : public TinyGsmModem, /* * Phone Call functions */ - // Follows all phone call functions as inherited from TinyGsmCalling.tpp - - /* - * Audio functions - */ - // No functions of this type supported + protected: + // Can follow all of the phone call functions from the template /* - * Text messaging (SMS) functions + * Messaging functions */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp + protected: + // Can follow all template functions public: /** Delete all SMS */ @@ -368,50 +358,16 @@ class TinyGsmMC60 : public TinyGsmModem, return false; } - /* - * GSM Location functions - */ - // No functions of this type supported - - /* - * GPS/GNSS/GLONASS location functions - */ - // No functions of this type supported - /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp - - /* - * NTP server functions - */ - // No functions of this type supported - - /* - * BLE functions - */ - // No functions of this type supported - - /* - * NTP server functions - */ - // No functions of this type supported - - /* - * BLE functions - */ - // No functions of this type supported + protected: + // Can follow the standard CCLK function in the template /* * Battery functions */ - // Follows all battery functions as inherited from TinyGsmBattery.tpp - - /* - * Temperature functions - */ - // No functions of this type supported + // Can follow battery functions as in the template /* * Client related functions @@ -432,9 +388,9 @@ class TinyGsmMC60 : public TinyGsmModem, uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; sendAT(GF("+QIOPEN="), mux, GF(",\""), GF("TCP"), GF("\",\""), host, GF("\","), port); - int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" AT_NL), - GF("CONNECT FAIL" AT_NL), - GF("ALREADY CONNECT" AT_NL)); + int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL)); return (1 == rsp); } @@ -443,14 +399,14 @@ class TinyGsmMC60 : public TinyGsmModem, if (waitResponse(GF(">")) != 1) { return 0; } stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(GF(AT_NL "SEND OK")) != 1) { return 0; } + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; } bool allAcknowledged = false; // bool failed = false; while (!allAcknowledged) { sendAT(GF("+QISACK="), mux); // If 'mux' is not specified, MC60 returns // 'ERRROR' (for QIMUX == 1) - if (waitResponse(5000L, GF(AT_NL "+QISACK:")) != 1) { + if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) { return -1; } else { streamSkipUntil(','); /** Skip total */ @@ -532,43 +488,127 @@ class TinyGsmMC60 : public TinyGsmModem, * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF(AT_NL "+QIRDI:"))) { // TODO(?): QIRD? or QIRDI? - // +QIRDI: ,,,,,< tlen> - streamSkipUntil(','); // Skip the context - streamSkipUntil(','); // Skip the role - // read the connection id - int8_t mux = streamGetIntBefore(','); - // read the number of packets in the buffer - int8_t num_packets = streamGetIntBefore(','); - // read the length of the current packet - streamSkipUntil( - ','); // Skip the length of the current package in the buffer - int16_t len_total = - streamGetIntBefore('\n'); // Total length of all packages - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux] && - num_packets >= 0 && len_total >= 0) { - sockets[mux]->sock_available = len_total; - } - data = ""; - // DBG("### Got Data:", len_total, "on", mux); - return true; - } else if (data.endsWith(GF("CLOSED" AT_NL))) { - int8_t nl = data.lastIndexOf(AT_NL, data.length() - 8); - int8_t coma = data.indexOf(',', nl + 2); - int8_t mux = data.substring(nl + 2, coma).toInt(); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + String r6s(r6); r6s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s, ",", r6s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (r6 && data.endsWith(r6)) { + index = 6; + goto finish; + } else if (data.endsWith( + GF(GSM_NL "+QIRDI:"))) { // TODO(?): QIRD? or QIRDI? + // +QIRDI: ,,,,,< tlen> + streamSkipUntil(','); // Skip the context + streamSkipUntil(','); // Skip the role + // read the connection id + int8_t mux = streamGetIntBefore(','); + // read the number of packets in the buffer + int8_t num_packets = streamGetIntBefore(','); + // read the length of the current packet + streamSkipUntil( + ','); // Skip the length of the current package in the buffer + int16_t len_total = + streamGetIntBefore('\n'); // Total length of all packages + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux] && + num_packets >= 0 && len_total >= 0) { + sockets[mux]->sock_available = len_total; + } + data = ""; + // DBG("### Got Data:", len_total, "on", mux); + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+QNITZ:"))) { + streamSkipUntil('\n'); // URC for time sync + DBG("### Network time updated."); + data = ""; + } } - data = ""; - DBG("### Closed: ", mux); - return true; - } else if (data.endsWith(GF("+QNITZ:"))) { - streamSkipUntil('\n'); // URC for time sync - DBG("### Network time updated."); + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5, r6); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5, r6); } public: @@ -576,6 +616,7 @@ class TinyGsmMC60 : public TinyGsmModem, protected: GsmClientMC60* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTMC60_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM5360.h b/lib/TinyGSM/src/TinyGsmClientSIM5360.h index 14e9d86..f181036 100644 --- a/lib/TinyGSM/src/TinyGsmClientSIM5360.h +++ b/lib/TinyGSM/src/TinyGsmClientSIM5360.h @@ -14,41 +14,26 @@ #define TINY_GSM_MUX_COUNT 10 #define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE -#ifdef AT_NL -#undef AT_NL -#endif -#define AT_NL "\r\n" - -#ifdef MODEM_MANUFACTURER -#undef MODEM_MANUFACTURER -#endif -#define MODEM_MANUFACTURER "SIMCom" - -#ifdef MODEM_MODEL -#undef MODEM_MODEL -#endif -#if defined(TINY_GSM_MODEM_SIM5320) -#define MODEM_MODEL "SIM5320"; -#elif defined(TINY_GSM_MODEM_SIM5300) -#define MODEM_MODEL "SIM5300"; -#elif defined(TINY_GSM_MODEM_SIM7100) -#define MODEM_MODEL "SIM7100"; -#else -#define MODEM_MODEL "SIM5360"; -#endif -#include "TinyGsmModem.tpp" -#include "TinyGsmTCP.tpp" +#include "TinyGsmBattery.tpp" #include "TinyGsmGPRS.tpp" -#include "TinyGsmSMS.tpp" #include "TinyGsmGSMLocation.tpp" -#include "TinyGsmGPS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" #include "TinyGsmTime.tpp" #include "TinyGsmNTP.tpp" -#include "TinyGsmBattery.tpp" -#include "TinyGsmTemperature.tpp" -enum SIM5360RegStatus { +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { REG_NO_RESULT = -1, REG_UNREGISTERED = 0, REG_SEARCHING = 2, @@ -62,20 +47,18 @@ class TinyGsmSim5360 : public TinyGsmModem, public TinyGsmGPRS, public TinyGsmTCP, public TinyGsmSMS, - public TinyGsmGSMLocation, - public TinyGsmGPS, public TinyGsmTime, public TinyGsmNTP, + public TinyGsmGSMLocation, public TinyGsmBattery, public TinyGsmTemperature { friend class TinyGsmModem; friend class TinyGsmGPRS; friend class TinyGsmTCP; friend class TinyGsmSMS; - friend class TinyGsmGSMLocation; - friend class TinyGsmGPS; friend class TinyGsmTime; friend class TinyGsmNTP; + friend class TinyGsmGSMLocation; friend class TinyGsmBattery; friend class TinyGsmTemperature; @@ -140,7 +123,27 @@ class TinyGsmSim5360 : public TinyGsmModem, /* * Inner Secure Client */ - // NOT SUPPORTED + + // TODO(?): Add SSL support + /* + class GsmClientSecureSim5360 : public GsmClientSim5360 { + public: + GsmClientSecureSim5360() {} + + explicit GsmClientSecureSim5360(TinyGsmSim5360& modem, uint8_t mux = 0) + : GsmClientSim5360(modem, mux) {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ /* * Constructor @@ -154,7 +157,7 @@ class TinyGsmSim5360 : public TinyGsmModem, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM5360")); @@ -182,7 +185,7 @@ class TinyGsmSim5360 : public TinyGsmModem, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -192,6 +195,21 @@ class TinyGsmSim5360 : public TinyGsmModem, } } + String getModemNameImpl() { + String name = "SIMCom SIM5360"; + + sendAT(GF("+CGMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { return name; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.replace("_", " "); + res2.trim(); + + name = res2; + DBG("### Modem:", name); + return name; + } + bool factoryDefaultImpl() { // these commands aren't supported return false; } @@ -200,14 +218,14 @@ class TinyGsmSim5360 : public TinyGsmModem, * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { + bool restartImpl(const char* pin = NULL) { if (!testAT()) { return false; } sendAT(GF("+REBOOT")); // Should return an 'OK' after reboot command is sent if (waitResponse(10000L) != 1) { return false; } // After booting, modem sends out messages as each of its // internal modules loads. The final message is "PB DONE". - if (waitResponse(40000L, GF(AT_NL "PB DONE")) != 1) { return false; } + if (waitResponse(40000L, GF(GSM_NL "PB DONE")) != 1) { return false; } return init(pin); } @@ -236,20 +254,20 @@ class TinyGsmSim5360 : public TinyGsmModem, * Generic network functions */ public: - SIM5360RegStatus getRegistrationStatus() { - return (SIM5360RegStatus)getRegistrationStatusXREG("CGREG"); + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CGREG"); } protected: bool isNetworkConnectedImpl() { - SIM5360RegStatus s = getRegistrationStatus(); + RegStatus s = getRegistrationStatus(); return (s == REG_OK_HOME || s == REG_OK_ROAMING); } public: String getNetworkModes() { sendAT(GF("+CNMP=?")); - if (waitResponse(GF(AT_NL "+CNMP:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; } String res = stream.readStringUntil('\n'); waitResponse(); return res; @@ -257,7 +275,7 @@ class TinyGsmSim5360 : public TinyGsmModem, int16_t getNetworkMode() { sendAT(GF("+CNMP?")); - if (waitResponse(GF(AT_NL "+CNMP:")) != 1) { return false; } + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } int16_t mode = streamGetIntBefore('\n'); waitResponse(); return mode; @@ -273,26 +291,18 @@ class TinyGsmSim5360 : public TinyGsmModem, // sendAT(GF("+CGPADDR=1")); // Show PDP address String res; if (waitResponse(10000L, res) != 1) { return ""; } - cleanResponseString(res); + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); return res; } - /* - * Secure socket layer (SSL) functions - */ - // No functions of this type supported - - /* - * WiFi functions - */ - // No functions of this type supported - /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); // Make sure we're not connected first // Define the PDP context @@ -366,7 +376,7 @@ class TinyGsmSim5360 : public TinyGsmModem, // We to ignore any immediate response and wait for the // URC to show it's really connected. sendAT(GF("+NETOPEN")); - if (waitResponse(75000L, GF(AT_NL "+NETOPEN: 0")) != 1) { return false; } + if (waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) { return false; } return true; } @@ -382,7 +392,7 @@ class TinyGsmSim5360 : public TinyGsmModem, // Note: all sockets should be closed first - on 3G/4G models the sockets // must be closed manually sendAT(GF("+NETCLOSE")); - if (waitResponse(60000L, GF(AT_NL "+NETCLOSE: 0")) != 1) { return false; } + if (waitResponse(60000L, GF(GSM_NL "+NETCLOSE: 0")) != 1) { return false; } return true; } @@ -390,7 +400,7 @@ class TinyGsmSim5360 : public TinyGsmModem, bool isGprsConnectedImpl() { sendAT(GF("+NETOPEN?")); // May return +NETOPEN: 1, 0. We just confirm that the first number is 1 - if (waitResponse(GF(AT_NL "+NETOPEN: 1")) != 1) { return false; } + if (waitResponse(GF(GSM_NL "+NETOPEN: 1")) != 1) { return false; } waitResponse(); sendAT(GF("+IPADDR")); // Inquire Socket PDP address @@ -400,15 +410,6 @@ class TinyGsmSim5360 : public TinyGsmModem, return true; } - String getProviderImpl() { - sendAT(GF("+CSPN?")); - if (waitResponse(GF("+CSPN:")) != 1) { return ""; } - streamSkipUntil('"'); /* Skip mode and format */ - String res = stream.readStringUntil('"'); - waitResponse(); - return res; - } - /* * SIM card functions */ @@ -416,7 +417,7 @@ class TinyGsmSim5360 : public TinyGsmModem, // Gets the CCID of a sim card via AT+CCID String getSimCCIDImpl() { sendAT(GF("+CICCID")); - if (waitResponse(GF(AT_NL "+ICCID:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { return ""; } String res = stream.readStringUntil('\n'); waitResponse(); res.trim(); @@ -424,148 +425,37 @@ class TinyGsmSim5360 : public TinyGsmModem, } /* - * Phone Call functions + * Messaging functions */ - // No functions of this type supported - - /* - * Audio functions - */ - // No functions of this type supported - - /* - * Text messaging (SMS) functions - */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp + protected: + // Follows all messaging functions per template /* * GSM Location functions */ + protected: // SIM5360 and SIM7100 can return a GSM-based location from CLBS as per the // template; SIM5320 doesn't not appear to be able to - /* - * GPS/GNSS/GLONASS location functions - */ - protected: - // enable GPS - bool enableGPSImpl() { - sendAT(GF("+CGPS=1")); - if (waitResponse() != 1) { return false; } - return true; - } - - bool disableGPSImpl() { - sendAT(GF("+CGPS=0")); - if (waitResponse() != 1) { return false; } - return true; - } - - // get the RAW GPS output - String getGPSrawImpl() { - sendAT(GF("+CGPSINFO")); - if (waitResponse(GF(AT_NL "+CGPSINFO:")) != 1) { return ""; } - String res = stream.readStringUntil('\n'); - waitResponse(); - res.trim(); - return res; - } - - // get GPS informations - bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, - int* vsat = 0, int* usat = 0, float* accuracy = 0, - int* year = 0, int* month = 0, int* day = 0, int* hour = 0, - int* minute = 0, int* second = 0) { - sendAT(GF("+CGPSINFO")); - if (waitResponse(GF(AT_NL "+CGPSINFO:")) != 1) { return false; } - delay(30); - - float ilat = 0; - char north; - float ilon = 0; - char east; - float ispeed = 0; - float ialt = 0; - int ivsat = 0; - int iusat = 0; - int iyear = 0; - int imonth = 0; - int iday = 0; - int ihour = 0; - int imin = 0; - float secondWithSS = 0; - - ilat = streamGetFloatBefore(','); // Latitude in ddmm.mmmmmm - north = stream.read(); // N/S Indicator, N=north or S=south - streamSkipUntil(','); // BEIDOU satellite valid numbers - ilon = streamGetFloatBefore(','); // Longitude in dddmm.mmmmmm - east = stream.read(); // E/W Indicator, E=east or W=west - streamSkipUntil(','); // BEIDOU satellite valid numbers - - // Date. Output format is ddmmyy - iday = streamGetIntLength(2); // Two digit day - imonth = streamGetIntLength(2); // Two digit month - iyear = streamGetIntBefore(','); // Two digit year - - // UTC Time. Output format is hhmmss.s - ihour = streamGetIntLength(2); // Two digit hour - imin = streamGetIntLength(2); // Two digit minute - secondWithSS = streamGetFloatBefore(','); // 4 digit second with subseconds - - ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters - ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots. - - if (ilat != -9999.0F) { - if (lat != nullptr) - *lat = (floor(ilat / 100) + fmod(ilat, 100.) / 60) * - (north == 'N' ? 1 : -1); - if (lon != nullptr) - *lon = (floor(ilon / 100) + fmod(ilon, 100.) / 60) * - (east == 'E' ? 1 : -1); - if (speed != nullptr) *speed = ispeed; - if (alt != nullptr) *alt = ialt; - if (vsat != nullptr) *vsat = ivsat; - if (usat != nullptr) *usat = iusat; - if (accuracy != nullptr) *accuracy = -9999; - if (iyear < 2000) iyear += 2000; - if (year != nullptr) *year = iyear; - if (month != nullptr) *month = imonth; - if (day != nullptr) *day = iday; - if (hour != nullptr) *hour = ihour; - if (minute != nullptr) *minute = imin; - if (second != nullptr) *second = static_cast(secondWithSS); - - waitResponse(); - return true; - } - - waitResponse(); - return false; - } - /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp + protected: + // Can follow the standard CCLK function in the template /* * NTP server functions */ - // Follows all NTP server functions as inherited from TinyGsmNTP.tpp - - /* - * BLE functions - */ - // No functions of this type supported + // Can sync with server using CNTP as per template /* * Battery functions */ protected: // SRGD Note: Returns voltage in VOLTS instead of millivolts - int16_t getBattVoltageImpl() { + uint16_t getBattVoltageImpl() { sendAT(GF("+CBC")); - if (waitResponse(GF(AT_NL "+CBC:")) != 1) { return 0; } + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return 0; } streamSkipUntil(','); // Skip battery charge status streamSkipUntil(','); // Skip battery charge level // get voltage in VOLTS @@ -578,10 +468,10 @@ class TinyGsmSim5360 : public TinyGsmModem, } // SRGD Note: Returns voltage in VOLTS instead of millivolts - bool getBattStatsImpl(int8_t& chargeState, int8_t& percent, - int16_t& milliVolts) { + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { sendAT(GF("+CBC")); - if (waitResponse(GF(AT_NL "+CBC:")) != 1) { return false; } + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; } chargeState = streamGetIntBefore(','); percent = streamGetIntBefore(','); // get voltage in VOLTS @@ -603,7 +493,7 @@ class TinyGsmSim5360 : public TinyGsmModem, if (waitResponse() != 1) { return 0; } // Get Temparature Value sendAT(GF("+CMTE?")); - if (waitResponse(GF(AT_NL "+CMTE:")) != 1) { return false; } + if (waitResponse(GF(GSM_NL "+CMTE:")) != 1) { return false; } float res = streamGetFloatBefore('\n'); // Wait for final OK waitResponse(); @@ -626,7 +516,7 @@ class TinyGsmSim5360 : public TinyGsmModem, sendAT(GF("+CIPOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), port); // The reply is +CIPOPEN: ## of socket created - if (waitResponse(timeout_ms, GF(AT_NL "+CIPOPEN:")) != 1) { return false; } + if (waitResponse(timeout_ms, GF(GSM_NL "+CIPOPEN:")) != 1) { return false; } return true; } @@ -635,7 +525,7 @@ class TinyGsmSim5360 : public TinyGsmModem, if (waitResponse(GF(">")) != 1) { return 0; } stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(GF(AT_NL "+CIPSEND:")) != 1) { return 0; } + if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) { return 0; } streamSkipUntil(','); // Skip mux streamSkipUntil(','); // Skip requested bytes to send // TODO(?): make sure requested and confirmed bytes match @@ -669,7 +559,7 @@ class TinyGsmSim5360 : public TinyGsmModem, }; buf[0] = stream.read(); buf[1] = stream.read(); - char c = strtol(buf, nullptr, 16); + char c = strtol(buf, NULL, 16); #else while (!stream.available() && (millis() - startMillis < sockets[mux]->_timeout)) { @@ -719,49 +609,125 @@ class TinyGsmSim5360 : public TinyGsmModem, * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF(AT_NL "+CIPRXGET:"))) { - int8_t mode = streamGetIntBefore(','); - if (mode == 1) { - int8_t mux = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+IPCLOSE:"))) { + int8_t mux = streamGetIntBefore(','); + streamSkipUntil('\n'); // Skip the reason code + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+CIPEVENT:"))) { + // Need to close all open sockets and release the network library. + // User will then need to reconnect. + DBG("### Network error!"); + if (!isGprsConnected()) { gprsDisconnect(); } + data = ""; } - data = ""; - // DBG("### Got Data:", mux); - return true; - } else { - data += mode; - return false; - } - } else if (data.endsWith(GF(AT_NL "+RECEIVE:"))) { - int8_t mux = streamGetIntBefore(','); - int16_t len = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - // DBG("### Got Data:", len, "on", mux); - return true; - } else if (data.endsWith(GF("+IPCLOSE:"))) { - int8_t mux = streamGetIntBefore(','); - streamSkipUntil('\n'); // Skip the reason code - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - data = ""; - DBG("### Closed: ", mux); - return true; - } else if (data.endsWith(GF("+CIPEVENT:"))) { - // Need to close all open sockets and release the network library. - // User will then need to reconnect. - DBG("### Network error!"); - if (!isGprsConnected()) { gprsDisconnect(); } - data = ""; - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } public: @@ -769,6 +735,7 @@ class TinyGsmSim5360 : public TinyGsmModem, protected: GsmClientSim5360* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTSIM5360_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7000.h b/lib/TinyGSM/src/TinyGsmClientSIM7000.h index dc2ab41..fc5f773 100644 --- a/lib/TinyGSM/src/TinyGsmClientSIM7000.h +++ b/lib/TinyGSM/src/TinyGsmClientSIM7000.h @@ -17,29 +17,12 @@ #include "TinyGsmClientSIM70xx.h" #include "TinyGsmTCP.tpp" -#include "TinyGsmSMS.tpp" -#include "TinyGsmGSMLocation.tpp" -#include "TinyGsmTime.tpp" -#include "TinyGsmNTP.tpp" -#include "TinyGsmBattery.tpp" + class TinyGsmSim7000 : public TinyGsmSim70xx, - public TinyGsmTCP, - public TinyGsmSMS, - public TinyGsmTime, - public TinyGsmNTP, - public TinyGsmGSMLocation, - public TinyGsmBattery { + public TinyGsmTCP { friend class TinyGsmSim70xx; - friend class TinyGsmModem; - friend class TinyGsmGPRS; friend class TinyGsmTCP; - friend class TinyGsmSMS; - friend class TinyGsmGSMLocation; - friend class TinyGsmGPS; - friend class TinyGsmTime; - friend class TinyGsmNTP; - friend class TinyGsmBattery; /* * Inner Client @@ -102,7 +85,7 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, /* * Inner Secure Client */ - // NOTE: Use modem TinyGsmSim7000SSL for a secure client! + // NOTE: Use modem TINYGSMSIM7000SSL for a secure client! /* * Constructor @@ -117,7 +100,7 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7000")); @@ -145,7 +128,7 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -158,7 +141,8 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, /* * Power functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h + protected: + // Follows the SIM70xx template /* * Generic network functions @@ -168,26 +152,34 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, sendAT(GF("+CIFSR;E0")); String res; if (waitResponse(10000L, res) != 1) { return ""; } - cleanResponseString(res); + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); return res; } - /* - * Secure socket layer (SSL) functions - */ - // NOTE: Use modem TinyGsmSim7000SSL for a secure client! + bool setNetworkActiveImpl(){ + sendAT(GF("+CNACT=1")); + if (waitResponse(10000L) != 1) { return false; } + return true; + } - /* - * WiFi functions - */ - // No functions of this type supported + String getNetworkActiveImpl(){ + sendAT(GF("+CNACT?")); + String res; + if (waitResponse(GF(GSM_NL "+CNACT: 1")) != 1) { return ""; } + streamSkipUntil('\"'); + res = stream.readStringUntil('\"'); + waitResponse(); + return res; + } /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); // Bearer settings for applications based on IP @@ -273,58 +265,36 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, /* * SIM card functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h - - /* - * Phone Call functions - */ - // No functions of this type supported - - /* - * Audio functions - */ - // No functions of this type supported - - /* - * Text messaging (SMS) functions - */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp + protected: + // Follows the SIM70xx template /* - * GSM Location functions + * Messaging functions */ - // Follows all GSM-based location functions as inherited from - // TinyGsmGSMLocation.tpp + protected: + // Follows all messaging functions per template /* * GPS/GNSS/GLONASS location functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h + protected: + // Follows the SIM70xx template /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp + // Can follow CCLK as per template /* * NTP server functions */ - // Follows all NTP server functions as inherited from TinyGsmNTP.tpp - - /* - * BLE functions - */ - // No functions of this type supported + // Can sync with server using CNTP as per template /* * Battery functions */ - // Follows all battery functions as inherited from TinyGsmBattery.tpp - - /* - * Temperature functions - */ - // No functions of this type supported + protected: + // Follows all battery functions per template /* * Client related functions @@ -339,9 +309,10 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), port); return (1 == - waitResponse(timeout_ms, GF("CONNECT OK" AT_NL), - GF("CONNECT FAIL" AT_NL), GF("ALREADY CONNECT" AT_NL), - GF("ERROR" AT_NL), GF("CLOSE OK" AT_NL))); + waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL), GF("ERROR" GSM_NL), + GF("CLOSE OK" GSM_NL))); } int16_t modemSend(const void* buff, size_t len, uint8_t mux) { @@ -351,9 +322,7 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(GF(AT_NL "DATA ACCEPT:"), GF("SEND FAIL")) != 1) { - return 0; - } + if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { return 0; } streamSkipUntil(','); // Skip mux return streamGetIntBefore('\n'); } @@ -390,7 +359,7 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, }; buf[0] = stream.read(); buf[1] = stream.read(); - char c = strtol(buf, nullptr, 16); + char c = strtol(buf, NULL, 16); #else while (!stream.available() && (millis() - startMillis < sockets[mux]->_timeout)) { @@ -437,68 +406,142 @@ class TinyGsmSim7000 : public TinyGsmSim70xx, * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF(AT_NL "+CIPRXGET:"))) { - int8_t mode = streamGetIntBefore(','); - if (mode == 1) { - int8_t mux = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + // putchar(a); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST: "))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); } - data = ""; - // DBG("### Got Data:", mux); - return true; - } else { - data += mode; - return false; } - } else if (data.endsWith(GF(AT_NL "+RECEIVE:"))) { - int8_t mux = streamGetIntBefore(','); - int16_t len = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } - } - data = ""; - // DBG("### Got Data:", len, "on", mux); - return true; - } else if (data.endsWith(GF("CLOSED" AT_NL))) { - int8_t nl = data.lastIndexOf(AT_NL, data.length() - 8); - int8_t coma = data.indexOf(',', nl + 2); - int8_t mux = data.substring(nl + 2, coma).toInt(); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->sock_connected = false; - } - data = ""; - DBG("### Closed: ", mux); - return true; - } else if (data.endsWith(GF("*PSNWID:"))) { - streamSkipUntil('\n'); // Refresh network name by network - data = ""; - DBG("### Network name updated."); - return true; - } else if (data.endsWith(GF("*PSUTTZ:"))) { - streamSkipUntil('\n'); // Refresh time and time zone by network + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - DBG("### Network time and time zone updated."); - return true; - } else if (data.endsWith(GF("+CTZV:"))) { - streamSkipUntil('\n'); // Refresh network time zone by network - data = ""; - DBG("### Network time zone updated."); - return true; - } else if (data.endsWith(GF("DST: "))) { - streamSkipUntil('\n'); // Refresh Network Daylight Saving Time by network - data = ""; - DBG("### Daylight savings time state updated."); - return true; - } else if (data.endsWith(GF(AT_NL "SMS Ready" AT_NL))) { - data = ""; - DBG("### Unexpected module reset!"); - init(); - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } protected: diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h b/lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h index 2170e49..2f88e89 100644 --- a/lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h +++ b/lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h @@ -18,32 +18,14 @@ #include "TinyGsmClientSIM70xx.h" #include "TinyGsmTCP.tpp" #include "TinyGsmSSL.tpp" -#include "TinyGsmSMS.tpp" -#include "TinyGsmGSMLocation.tpp" -#include "TinyGsmTime.tpp" -#include "TinyGsmNTP.tpp" -#include "TinyGsmBattery.tpp" class TinyGsmSim7000SSL : public TinyGsmSim70xx, public TinyGsmTCP, - public TinyGsmSSL, - public TinyGsmSMS, - public TinyGsmGSMLocation, - public TinyGsmTime, - public TinyGsmNTP, - public TinyGsmBattery { + public TinyGsmSSL { friend class TinyGsmSim70xx; friend class TinyGsmTCP; - friend class TinyGsmSSL; - friend class TinyGsmModem; - friend class TinyGsmGPRS; - friend class TinyGsmSMS; - friend class TinyGsmGSMLocation; - friend class TinyGsmGPS; - friend class TinyGsmNTP; - friend class TinyGsmTime; - friend class TinyGsmBattery; + friend class TinyGsmSSL; /* * Inner Client @@ -106,7 +88,7 @@ class TinyGsmSim7000SSL /* * Inner Secure Client */ - public: + class GsmClientSecureSIM7000SSL : public GsmClientSim7000SSL { public: GsmClientSecureSIM7000SSL() {} @@ -115,6 +97,7 @@ class TinyGsmSim7000SSL uint8_t mux = 0) : GsmClientSim7000SSL(modem, mux) {} + public: bool setCertificate(const String& certificateName) { return at->setCertificate(certificateName, mux); } @@ -135,7 +118,8 @@ class TinyGsmSim7000SSL */ public: explicit TinyGsmSim7000SSL(Stream& stream) - : TinyGsmSim70xx(stream) { + : TinyGsmSim70xx(stream), + certificates() { memset(sockets, 0, sizeof(sockets)); } @@ -143,7 +127,7 @@ class TinyGsmSim7000SSL * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7000SSL")); @@ -171,7 +155,7 @@ class TinyGsmSim7000SSL SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -195,13 +179,14 @@ class TinyGsmSim7000SSL // modemGetAvailable checks all socks, so we only want to do it once // modemGetAvailable calls modemGetConnected(), which also checks allf if (check_socks) { modemGetAvailable(0); } - while (stream.available()) { waitResponse(15, nullptr, nullptr); } + while (stream.available()) { waitResponse(15, NULL, NULL); } } /* * Power functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h + protected: + // Follows the SIM70xx template /* * Generic network functions @@ -209,7 +194,7 @@ class TinyGsmSim7000SSL protected: String getLocalIPImpl() { sendAT(GF("+CNACT?")); - if (waitResponse(GF(AT_NL "+CNACT:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; } streamSkipUntil('\"'); String res = stream.readStringUntil('\"'); waitResponse(); @@ -217,21 +202,21 @@ class TinyGsmSim7000SSL } /* - * Secure socket layer (SSL) functions + * Secure socket layer functions */ - // Follows functions as inherited from TinyGsmSSL.tpp - - /* - * WiFi functions - */ - // No functions of this type supported + protected: + bool setCertificate(const String& certificateName, const uint8_t mux = 0) { + if (mux >= TINY_GSM_MUX_COUNT) return false; + certificates[mux] = certificateName; + return true; + } /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); // Define the PDP context @@ -257,7 +242,7 @@ class TinyGsmSim7000SSL // 2: CHAP // 3: PAP or CHAP if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) { - sendAT(GF("+CNCFG=1,\""), apn, "\",\"", "\",\"", user, pwd, "\",3"); + sendAT(GF("+CNCFG=1,\""), apn, "\",\"", "\",\"", user, pwd, '"'); waitResponse(); } else if (user && strlen(user) > 0) { // Set the user name only @@ -280,8 +265,8 @@ class TinyGsmSim7000SSL int ntries = 0; while (!res && ntries < 5) { sendAT(GF("+CNACT=1,\""), apn, GF("\"")); - res = waitResponse(60000L, GF(AT_NL "+APP PDP: ACTIVE"), - GF(AT_NL "+APP PDP: DEACTIVE")) == 1; + res = waitResponse(60000L, GF(GSM_NL "+APP PDP: ACTIVE"), + GF(GSM_NL "+APP PDP: DEACTIVE")) == 1; waitResponse(); ntries++; } @@ -304,57 +289,36 @@ class TinyGsmSim7000SSL /* * SIM card functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h - - /* - * Phone Call functions - */ - // No functions of this type supported - - /* - * Audio functions - */ - // No functions of this type supported + protected: + // Follows the SIM70xx template /* - * Text messaging (SMS) functions - */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp - /* - * GSM Location functions + * Messaging functions */ - // Follows all GSM-based location functions as inherited from - // TinyGsmGSMLocation.tpp + protected: + // Follows all messaging functions per template /* * GPS/GNSS/GLONASS location functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h + protected: + // Follows the SIM70xx template /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp + // Can follow CCLK as per template /* * NTP server functions */ - // Follows all NTP server functions as inherited from TinyGsmNTP.tpp - - /* - * BLE functions - */ - // No functions of this type supported + // Can sync with server using CNTP as per template /* * Battery functions */ - // Follows all battery functions as inherited from TinyGsmBattery.tpp - - /* - * Temperature functions - */ - // No functions of this type supported + protected: + // Follows all battery functions per template /* * Client related functions @@ -372,9 +336,7 @@ class TinyGsmSim7000SSL if (ssl) { // set the ssl version // AT+CSSLCFG="SSLVERSION",, - // PDP context identifier - for reasons not understood by me, - // use PDP context identifier of 0 for what we defined as 1 in - // the gprsConnect function + // PDP context identifier // 0: QAPI_NET_SSL_PROTOCOL_UNKNOWN // 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0 // 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1 @@ -397,11 +359,8 @@ class TinyGsmSim7000SSL if (ssl) { // set the PDP context to apply SSL to // AT+CSSLCFG="CTXINDEX", - // PDP context identifier - for reasons not understood by me, - // use PDP context identifier of 0 for what we defined as 1 in - // the gprsConnect function - // NOTE: despite docs using "CRINDEX" in all caps, the module only - // accepts the command "ctxindex" and it must be in lower case + // PDP context identifier + // NOTE: despite docs using caps, "ctxindex" must be in lower case sendAT(GF("+CSSLCFG=\"ctxindex\",0")); if (waitResponse(5000L, GF("+CSSLCFG:")) != 1) return false; streamSkipUntil('\n'); // read out the certificate information @@ -423,12 +382,8 @@ class TinyGsmSim7000SSL waitResponse(); // set the SSL SNI (server name indication) - // AT+CSSLCFG="SNI",, - // PDP context identifier - for reasons not understood by me, - // use PDP context identifier of 0 for what we defined as 1 in - // the gprsConnect function // NOTE: despite docs using caps, "sni" must be in lower case - sendAT(GF("+CSSLCFG=\"sni\",0,"), GF("\""), host, GF("\"")); + sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\"")); waitResponse(); } @@ -438,7 +393,7 @@ class TinyGsmSim7000SSL // "TCP" or "UDP" // NOTE: the "TCP" can't be included sendAT(GF("+CAOPEN="), mux, GF(",\""), host, GF("\","), port); - if (waitResponse(timeout_ms, GF(AT_NL "+CAOPEN:")) != 1) { return 0; } + if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; } // returns OK/r/n/r/n+CAOPEN: , // 0: Success // 1: Socket error @@ -475,7 +430,7 @@ class TinyGsmSim7000SSL // after posting data, module responds with: //+CASEND: ,, - if (waitResponse(GF(AT_NL "+CASEND:")) != 1) { return 0; } + if (waitResponse(GF(GSM_NL "+CASEND:")) != 1) { return 0; } streamSkipUntil(','); // Skip mux if (streamGetIntBefore(',') != 0) { return 0; } // If result != success return streamGetIntBefore('\n'); @@ -633,67 +588,143 @@ class TinyGsmSim7000SSL * Utilities */ public: - bool handleURCs(String& data) { - if (data.endsWith(GF("+CARECV:"))) { - int8_t mux = streamGetIntBefore(','); - int16_t len = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } - } - data = ""; - DBG("### Got Data:", len, "on", mux); - return true; - } else if (data.endsWith(GF("+CADATAIND:"))) { - int8_t mux = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - } - data = ""; - DBG("### Got Data:", mux); - return true; - } else if (data.endsWith(GF("+CASTATE:"))) { - int8_t mux = streamGetIntBefore(','); - int8_t state = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - if (state != 1) { - sockets[mux]->sock_connected = false; - DBG("### Closed: ", mux); + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+CARECV:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+CADATAIND:"))) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + DBG("### Got Data:", mux); + } else if (data.endsWith(GF("+CASTATE:"))) { + int8_t mux = streamGetIntBefore(','); + int8_t state = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (state != 1) { + sockets[mux]->sock_connected = false; + DBG("### Closed: ", mux); + } + } + data = ""; + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST: "))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); + data = ""; } } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } data = ""; - return true; - } else if (data.endsWith(GF("*PSNWID:"))) { - streamSkipUntil('\n'); // Refresh network name by network - data = ""; - DBG("### Network name updated."); - return true; - } else if (data.endsWith(GF("*PSUTTZ:"))) { - streamSkipUntil('\n'); // Refresh time and time zone by network - data = ""; - DBG("### Network time and time zone updated."); - return true; - } else if (data.endsWith(GF("+CTZV:"))) { - streamSkipUntil('\n'); // Refresh network time zone by network - data = ""; - DBG("### Network time zone updated."); - return true; - } else if (data.endsWith(GF("DST: "))) { - streamSkipUntil('\n'); // Refresh Network Daylight Saving Time by network - data = ""; - DBG("### Daylight savings time state updated."); - return true; - } else if (data.endsWith(GF(AT_NL "SMS Ready" AT_NL))) { - data = ""; - DBG("### Unexpected module reset!"); - init(); - return true; } - return false; + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); } protected: GsmClientSim7000SSL* sockets[TINY_GSM_MUX_COUNT]; + String certificates[TINY_GSM_MUX_COUNT]; }; #endif // SRC_TINYGSMCLIENTSIM7000SSL_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7020.h b/lib/TinyGSM/src/TinyGsmClientSIM7020.h new file mode 100644 index 0000000..e96f6f1 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM7020.h @@ -0,0 +1,860 @@ +/** + * @file TinyGsmClientSIM7020.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM7020_H_ +#define SRC_TINYGSMCLIENTSIM7020_H_ +// #pragma message("TinyGSM: TinyGsmClientSIM7020") + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 5 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmSSL.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; +class TinyGsmSim7020 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSSL, + public TinyGsmCalling, + public TinyGsmSMS, + public TinyGsmGSMLocation, + public TinyGsmTime, + public TinyGsmNTP, + public TinyGsmBattery +{ + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + friend class TinyGsmCalling; + friend class TinyGsmSMS; + friend class TinyGsmGSMLocation; + friend class TinyGsmTime; + friend class TinyGsmNTP; + friend class TinyGsmBattery; + + /* + * Inner Client + */ +public: + class GsmClientSim7020 : public GsmClient + { + friend class TinyGsmSim7020; + + public: + GsmClientSim7020() {} + + explicit GsmClientSim7020(TinyGsmSim7020 &modem, uint8_t mux = 0) + { + init(&modem, mux); + } + + bool init(TinyGsmSim7020 *modem, uint8_t mux = 0) + { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char *host, uint16_t port, int timeout_s) + { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) + { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CIPCLOSE="), mux, GF(",1")); // Quick close + sock_connected = false; + at->waitResponse(); + } + void stop() override + { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ +public: + class GsmClientSecureSim7020 : public GsmClientSim7020 + { + public: + GsmClientSecureSim7020() {} + + explicit GsmClientSecureSim7020(TinyGsmSim7020 &modem, uint8_t mux = 0) + : GsmClientSim7020(modem, mux) {} + + public: + int connect(const char *host, uint16_t port, int timeout_s) override + { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ +public: + explicit TinyGsmSim7020(Stream &stream) : stream(stream) + { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ +protected: + bool initImpl(const char *pin = NULL) + { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7020")); + + if (!testAT()) { + return false; + } + + // sendAT(GF("&FZ")); // Factory + Reset + // waitResponse(); + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { + return false; + } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable Local Time Stamp for getting network time + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { + return false; + } + + // Enable battery checks + sendAT(GF("+CBATCHK=1")); + waitResponse(); + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + String getModemNameImpl() + { + String name = "SIMCom SIM7020"; + + sendAT(GF("+GMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { + return name; + } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.replace("_", " "); + res2.trim(); + + name = res2; + DBG("### Modem:", name); + return name; + } + + bool factoryDefaultImpl() + { + sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + waitResponse(); + sendAT(GF("+IPR=0")); // Auto-baud + waitResponse(); + sendAT(GF("+IFC=0,0")); // No Flow Control + waitResponse(); + sendAT(GF("+ICF=3,3")); // 8 data 0 parity 1 stop + waitResponse(); + sendAT(GF("+CSCLK=0")); // Disable Slow Clock + waitResponse(); + sendAT(GF("&W")); // Write configuration + return waitResponse() == 1; + } + + /* + bool thisHasSSL() { + #if defined(TINY_GSM_MODEM_SIM900) + return false; + #else + sendAT(GF("+CIPSSL=?")); + if (waitResponse(GF(GSM_NL "+CIPSSL:")) != 1) { return false; } + return waitResponse() == 1; + #endif + } + */ + + /* + * Power functions + */ +protected: + bool restartImpl(const char *pin = NULL) + { + if (!testAT()) { + return false; + } + sendAT(GF("&W")); + waitResponse(); + if (!setPhoneFunctionality(0)) { + return false; + } + if (!setPhoneFunctionality(1, true)) { + return false; + } + delay(3000); + return init(pin); + } + + bool powerOffImpl() + { + sendAT(GF("+CPOWD=1")); + return waitResponse(10000L, GF("NORMAL POWER DOWN")) == 1; + } + + // During sleep, the SIM7020 module has its serial communication disabled. In + // order to reestablish communication pull the DRT-pin of the SIM7020 module + // LOW for at least 50ms. Then use this function to disable sleep mode. The + // DTR-pin can then be released again. + bool sleepEnableImpl(bool enable = true) + { + sendAT(GF("+CSCLK="), enable); + return waitResponse() == 1; + } + + // 0 Minimum functionality + // 1 Full functionality (Default) + // 4 Disable phone both transmit and receive RF circuits. + // Reset the MT before setting it to power level. + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) + { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ +public: + RegStatus getRegistrationStatus() + { + return (RegStatus)getRegistrationStatusXREG("CGREG"); + } + +protected: + bool isNetworkConnectedImpl() + { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + String getLocalIPImpl() + { + sendAT(GF("+IPCONFIG")); + String res; + if (waitResponse(10000L, res) != 1) { + return ""; + } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + /* + * GPRS functions + */ +protected: + bool gprsConnectImpl(const char *apn, const char *user = NULL, + const char *pwd = NULL) + { + gprsDisconnect(); + + // Bearer settings for applications based on IP + // Set the connection type to GPRS + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); + waitResponse(); + + // Set the APN + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); + waitResponse(); + + // Set the user name + if (user && strlen(user) > 0) { + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); + waitResponse(); + } + // Set the password + if (pwd && strlen(pwd) > 0) { + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); + waitResponse(); + } + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Activate the PDP context + sendAT(GF("+CGACT=1,1")); + waitResponse(60000L); + + // Open the definied GPRS bearer context + sendAT(GF("+SAPBR=1,1")); + waitResponse(85000L); + // Query the GPRS bearer context status + sendAT(GF("+SAPBR=2,1")); + if (waitResponse(30000L) != 1) { + return false; + } + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { + return false; + } + + // Set to multi-IP + sendAT(GF("+CIPMUX=1")); + if (waitResponse() != 1) { + return false; + } + + // Put in "quick send" mode (thus no extra "Send OK") + sendAT(GF("+CIPQSEND=1")); + if (waitResponse() != 1) { + return false; + } + + // Set to get data manually + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { + return false; + } + + // Start Task and Set APN, USER NAME, PASSWORD + sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); + if (waitResponse(60000L) != 1) { + return false; + } + + // Bring Up Wireless Connection with GPRS or CSD + sendAT(GF("+CIICR")); + if (waitResponse(60000L) != 1) { + return false; + } + + // Get Local IP Address, only assigned after connection + sendAT(GF("+CIFSR;E0")); + if (waitResponse(10000L) != 1) { + return false; + } + + // Configure Domain Name Server (DNS) + sendAT(GF("+CDNSCFG=\"8.8.8.8\",\"8.8.4.4\"")); + if (waitResponse() != 1) { + return false; + } + + return true; + } + + bool gprsDisconnectImpl() + { + // Shut the TCP/IP connection + // CIPSHUT will close *all* open connections + sendAT(GF("+CIPSHUT")); + if (waitResponse(60000L) != 1) { + return false; + } + + sendAT(GF("+CGATT=0")); // Detach from GPRS + if (waitResponse(60000L) != 1) { + return false; + } + + return true; + } + + /* + * SIM card functions + */ +protected: + // May not return the "+CCID" before the number + String getSimCCIDImpl() + { + sendAT(GF("+CCID")); + if (waitResponse(GF(GSM_NL)) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + // Trim out the CCID header in case it is there + res.replace("CCID:", ""); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ +public: + bool setGsmBusy(bool busy = true) + { + sendAT(GF("+GSMBUSY="), busy ? 1 : 0); + return waitResponse() == 1; + } + + /* + * Messaging functions + */ +protected: + // Follows all messaging functions per template + + /* + * GSM Location functions + */ +protected: + // Depending on the exacty model and firmware revision, should return a + // GSM-based location from CLBS as per the template + // TODO(?): Check number of digits in year (2 or 4) + + /* + * GPS/GNSS/GLONASS location functions + */ +protected: + // No functions of this type supported + + /* + * Audio functions + */ +public: + bool setVolume(uint8_t volume = 50) + { + // Set speaker volume + sendAT(GF("+CLVL="), volume); + return waitResponse() == 1; + } + + uint8_t getVolume() + { + // Get speaker volume + sendAT(GF("+CLVL?")); + if (waitResponse(GF(GSM_NL)) != 1) { + return 0; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.replace("+CLVL:", ""); + res.trim(); + return res.toInt(); + } + + bool setMicVolume(uint8_t channel, uint8_t level) + { + if (channel > 4) { + return 0; + } + sendAT(GF("+CMIC="), level); + return waitResponse() == 1; + } + + bool setAudioChannel(uint8_t channel) + { + sendAT(GF("+CHFA="), channel); + return waitResponse() == 1; + } + + bool playToolkitTone(uint8_t tone, uint32_t duration) + { + sendAT(GF("STTONE="), 1, tone); + delay(duration); + sendAT(GF("STTONE="), 0); + return waitResponse(); + } + + /* + * Time functions + */ +protected: + // Can follow the standard CCLK function in the template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ +protected: + // Follows all battery functions per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Client related functions + */ +protected: + bool modemConnect(const char *host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) + { + int8_t rsp; + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; +#if !defined(TINY_GSM_MODEM_SIM900) + sendAT(GF("+CIPSSL="), ssl); + rsp = waitResponse(); + if (ssl && rsp != 1) { + return false; + } +#ifdef TINY_GSM_SSL_CLIENT_AUTHENTICATION + // set SSL options + // +SSLOPT=, + // + // 0 (default) ignore invalid certificate + // 1 client authentication + // + // 0 (default) close + // 1 open + sendAT(GF("+CIPSSL=1,1")); + if (waitResponse() != 1) return false; +#endif +#endif + sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host, + GF("\","), port); + rsp = waitResponse( + timeout_ms, GF("CONNECT OK" GSM_NL), GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL), GF("ERROR" GSM_NL), + GF("CLOSE OK" GSM_NL)); // Happens when HTTPS handshake fails + return (1 == rsp); + } + + int16_t modemSend(const void *buff, size_t len, uint8_t mux) + { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { + return 0; + } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { + return 0; + } + streamSkipUntil(','); // Skip mux + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) + { + if (!sockets[mux]) return 0; +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { + return 0; + } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { + return 0; + } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ Confirmed number of data bytes to be read, which may be less than + // requested. 0 indicates that no data can be read. + // SRGD NOTE: Contrary to above (which is copied from AT command manual) + // this is actually be the number of bytes that will be remaining in the + // buffer after the read. + for (int i = 0; i < len_requested; i++) { + uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); +#endif + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + sockets[mux]->sock_available = len_confirmed; + waitResponse(); + return len_requested; + } + + size_t modemGetAvailable(uint8_t mux) + { + if (!sockets[mux]) return 0; + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); + } + // DBG("### Available:", result, "on", mux); + if (!result) { + sockets[mux]->sock_connected = modemGetConnected(mux); + } + return result; + } + + bool modemGetConnected(uint8_t mux) + { + sendAT(GF("+CIPSTATUS="), mux); + waitResponse(GF("+CIPSTATUS")); + int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), + GF(",\"CLOSING\""), GF(",\"REMOTE CLOSING\""), + GF(",\"INITIAL\"")); + waitResponse(); + return 1 == res; + } + + /* + * Utilities + */ +public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String &data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) + { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + // putchar(a); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { + sockets[mux]->sock_available = len; + } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST:"))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } + } + } while (millis() - startMillis < timeout_ms); +finish: + if (!index) { + data.trim(); + if (data.length()) { + DBG("### Unhandled:", data); + } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) + { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) + { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + +public: + Stream &stream; + +protected: + GsmClientSim7020 *sockets[TINY_GSM_MUX_COUNT]; + const char *gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTSIM7020_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7080.h b/lib/TinyGSM/src/TinyGsmClientSIM7080.h index 8af5465..a6a875c 100644 --- a/lib/TinyGSM/src/TinyGsmClientSIM7080.h +++ b/lib/TinyGSM/src/TinyGsmClientSIM7080.h @@ -18,31 +18,13 @@ #include "TinyGsmClientSIM70xx.h" #include "TinyGsmTCP.tpp" #include "TinyGsmSSL.tpp" -#include "TinyGsmSMS.tpp" -#include "TinyGsmGSMLocation.tpp" -#include "TinyGsmTime.tpp" -#include "TinyGsmNTP.tpp" -#include "TinyGsmBattery.tpp" class TinyGsmSim7080 : public TinyGsmSim70xx, public TinyGsmTCP, - public TinyGsmSSL, - public TinyGsmSMS, - public TinyGsmGSMLocation, - public TinyGsmTime, - public TinyGsmNTP, - public TinyGsmBattery { + public TinyGsmSSL { friend class TinyGsmSim70xx; - friend class TinyGsmModem; - friend class TinyGsmGPRS; friend class TinyGsmTCP; - friend class TinyGsmSSL; - friend class TinyGsmSMS; - friend class TinyGsmGSMLocation; - friend class TinyGsmGPS; - friend class TinyGsmTime; - friend class TinyGsmNTP; - friend class TinyGsmBattery; + friend class TinyGsmSSL; /* * Inner Client @@ -105,7 +87,7 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, /* * Inner Secure Client */ - public: + class GsmClientSecureSIM7080 : public GsmClientSim7080 { public: GsmClientSecureSIM7080() {} @@ -113,11 +95,13 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, explicit GsmClientSecureSIM7080(TinyGsmSim7080& modem, uint8_t mux = 0) : GsmClientSim7080(modem, mux) {} + public: bool setCertificate(const String& certificateName) { return at->setCertificate(certificateName, mux); } - int connect(const char* host, uint16_t port, int timeout_s) override { + virtual int connect(const char* host, uint16_t port, + int timeout_s) override { stop(); TINY_GSM_YIELD(); rx.clear(); @@ -132,7 +116,8 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, */ public: explicit TinyGsmSim7080(Stream& stream) - : TinyGsmSim70xx(stream) { + : TinyGsmSim70xx(stream), + certificates() { memset(sockets, 0, sizeof(sockets)); } @@ -140,26 +125,14 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, * Basic functions */ protected: - bool initImpl(const char* pin = nullptr) { + bool initImpl(const char* pin = NULL) { DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7080")); - bool gotATOK = false; - for (uint32_t start = millis(); millis() - start < 10000L;) { - sendAT(GF("")); - int8_t resp = waitResponse(200L, GFP(GSM_OK), GFP(GSM_ERROR), GF("AT")); - if (resp == 1) { - gotATOK = true; - break; - } else if (resp == 3) { - waitResponse(200L); // get the OK - sendAT(GF("E0")); // Echo Off - DBG(GF("## Turning off echo!")); - waitResponse(2000L); - } - delay(100); - } - if (!gotATOK) { return false; } + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } #ifdef TINY_GSM_DEBUG sendAT(GF("+CMEE=2")); // turn on verbose error codes @@ -180,7 +153,7 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, SimStatus ret = getSimStatus(); // if the sim isn't ready and a pin has been provided, try to unlock the sim - if (ret != SIM_READY && pin != nullptr && strlen(pin) > 0) { + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); return (getSimStatus() == SIM_READY); } else { @@ -204,39 +177,14 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, // modemGetAvailable checks all socks, so we only want to do it once // modemGetAvailable calls modemGetConnected(), which also checks allf if (check_socks) { modemGetAvailable(0); } - while (stream.available()) { waitResponse(15, nullptr, nullptr); } + while (stream.available()) { waitResponse(15, NULL, NULL); } } /* * Power functions */ protected: - bool restartImpl(const char* pin = nullptr) { - bool success = true; - - bool gotATOK = false; - for (uint32_t start = millis(); millis() - start < 10000L;) { - sendAT(GF("")); - int8_t resp = waitResponse(200L, GFP(GSM_OK), GFP(GSM_ERROR), GF("AT")); - if (resp == 1) { - gotATOK = true; - break; - } else if (resp == 3) { - waitResponse(200L); // get the OK - DBG(GF("## Turning off echo!")); - sendAT(GF("E0")); // Echo Off - waitResponse(2000L); - } - delay(100); - } - if (!gotATOK) { return false; } - - sendAT(GF("+CREBOOT")); // Reboot - success &= waitResponse() == 1; - waitResponse(30000L, GF("SMS Ready")); - success &= initImpl(pin); - return success; - } + // Follows the SIM70xx template /* * Generic network functions @@ -244,29 +192,47 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, protected: String getLocalIPImpl() { sendAT(GF("+CNACT?")); - if (waitResponse(GF(AT_NL "+CNACT:")) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; } streamSkipUntil('\"'); String res = stream.readStringUntil('\"'); waitResponse(); return res; } + + + bool setNetworkActiveImpl(){ + sendAT(GF("+CNACT=0,1")); + if (waitResponse(60000L, GF(GSM_NL "+APP PDP: 0,ACTIVE"), + GF(GSM_NL "+APP PDP: 0,DEACTIVE")) != 1) { return false; } + return true; + } - /* - * Secure socket layer (SSL) functions - */ - // Follows functions as inherited from TinyGsmSSL.tpp + String getNetworkActiveImpl(){ + sendAT(GF("+CNACT?")); + String res; + if (waitResponse(GF(GSM_NL "+CNACT: 0")) != 1) { return ""; } + streamSkipUntil('\"'); + res = stream.readStringUntil('\"'); + waitResponse(); + return res; + } /* - * WiFi functions + * Secure socket layer functions */ - // No functions of this type supported + protected: + bool setCertificate(const String& certificateName, const uint8_t mux = 0) { + if (mux >= TINY_GSM_MUX_COUNT) return false; + certificates[mux] = certificateName; + return true; + } /* * GPRS functions */ protected: - bool gprsConnectImpl(const char* apn, const char* user = nullptr, - const char* pwd = nullptr) { + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { gprsDisconnect(); // Define the PDP context @@ -299,7 +265,7 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, // 2: CHAP // 3: PAP or CHAP if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) { - sendAT(GF("+CNCFG=0,1,\""), apn, "\",\"", user, "\",\"", pwd, "\",3"); + sendAT(GF("+CNCFG=0,1,\""), apn, "\",\"", user, "\",\"", pwd, '"'); waitResponse(); } else if (user && strlen(user) > 0) { // Set the user name only @@ -322,8 +288,8 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, int ntries = 0; while (!res && ntries < 5) { sendAT(GF("+CNACT=0,1")); - res = waitResponse(60000L, GF(AT_NL "+APP PDP: 0,ACTIVE"), - GF(AT_NL "+APP PDP: 0,DEACTIVE")); + res = waitResponse(60000L, GF(GSM_NL "+APP PDP: 0,ACTIVE"), + GF(GSM_NL "+APP PDP: 0,DEACTIVE")); waitResponse(); ntries++; } @@ -346,96 +312,36 @@ class TinyGsmSim7080 : public TinyGsmSim70xx, /* * SIM card functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h - - /* - * Phone Call functions - */ - // No functions of this type supported - - /* - * Audio functions - */ - // No functions of this type supported - - /* - * Text messaging (SMS) functions - */ - // Follows all text messaging (SMS) functions as inherited from TinyGsmSMS.tpp + protected: + // Follows the SIM70xx template /* - * GSM Location functions + * Messaging functions */ - // Follows all GSM-based location functions as inherited from - // TinyGsmGSMLocation.tpp + protected: + // Follows all messaging functions per template /* * GPS/GNSS/GLONASS location functions */ - // Follows functions as inherited from TinyGsmClientSIM70xx.h + protected: + // Follows the SIM70xx template /* * Time functions */ - // Follows all clock functions as inherited from TinyGsmTime.tpp + // Can follow CCLK as per template /* * NTP server functions */ - protected: - byte NTPServerSyncImpl(String server = "pool.ntp.org", int TimeZone = 0) { - // Set GPRS bearer profile to associate with NTP sync - // this may fail, it's not supported by all modules - sendAT(GF("+CNTPCID=0")); // CID must be 0. With 1 (like other modules) - // does not work! - waitResponse(10000L); - - // Set NTP server and timezone - sendAT(GF("+CNTP=\""), server, "\",", String(TimeZone)); - if (waitResponse(10000L) != 1) { return -1; } - - // Request network synchronization - sendAT(GF("+CNTP")); - if (waitResponse(10000L, GF("+CNTP:"))) { - String result = stream.readStringUntil('\n'); - // Check for ',' in case the module appends the time next to the return - // code. Eg: +CNTP: [,