2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
20 #if defined(USE_TELEMETRY) && defined(USE_TELEMETRY_SIM)
24 #include "common/printf.h"
25 #include "common/olc.h"
27 #include "drivers/time.h"
29 #include "fc/fc_core.h"
30 #include "fc/runtime_config.h"
32 #include "flight/imu.h"
35 #include "io/serial.h"
37 #include "navigation/navigation.h"
39 #include "sensors/battery.h"
40 #include "sensors/sensors.h"
42 #include "common/string_light.h"
43 #include "common/typeconversion.h"
45 #include "telemetry/sim.h"
46 #include "telemetry/telemetry.h"
48 #define SIM_AT_COMMAND_MAX_SIZE 255
49 #define SIM_RESPONSE_BUFFER_SIZE 255
50 #define SIM_CYCLE_MS 5000 // wait between sim command cycles
51 #define SIM_AT_COMMAND_DELAY_MS 3000
52 #define SIM_AT_COMMAND_DELAY_MIN_MS 500
53 #define SIM_STARTUP_DELAY_MS 10000
54 #define SIM_SMS_COMMAND_RTH "RTH"
55 #define SIM_LOW_ALT_WARNING_MODES (NAV_ALTHOLD_MODE | NAV_RTH_MODE | NAV_WP_MODE | FAILSAFE_MODE)
57 #define SIM_RESPONSE_CODE_OK ('O' << 24 | 'K' << 16)
58 #define SIM_RESPONSE_CODE_ERROR ('E' << 24 | 'R' << 16 | 'R' << 8 | 'O')
59 #define SIM_RESPONSE_CODE_RING ('R' << 24 | 'I' << 16 | 'N' << 8 | 'G')
60 #define SIM_RESPONSE_CODE_CLIP ('C' << 24 | 'L' << 16 | 'I' << 8 | 'P')
61 #define SIM_RESPONSE_CODE_CREG ('C' << 24 | 'R' << 16 | 'E' << 8 | 'G')
62 #define SIM_RESPONSE_CODE_CSQ ('C' << 24 | 'S' << 16 | 'Q' << 8 | ':')
63 #define SIM_RESPONSE_CODE_CMT ('C' << 24 | 'M' << 16 | 'T' << 8 | ':')
67 SIM_MODULE_NOT_DETECTED
= 0,
68 SIM_MODULE_NOT_REGISTERED
,
69 SIM_MODULE_REGISTERED
,
75 SIM_STATE_INIT_ENTER_PIN
,
78 SIM_STATE_SEND_SMS_ENTER_MESSAGE
79 } simTelemetryState_e
;
84 SIM_AT_WAITING_FOR_RESPONSE
85 } simATCommandState_e
;
88 SIM_READSTATE_RESPONSE
= 0,
97 } simTransmissionState_e
;
107 static serialPort_t
*simPort
;
108 static serialPortConfig_t
*portConfig
;
109 static bool simEnabled
= false;
111 static uint8_t atCommand
[SIM_AT_COMMAND_MAX_SIZE
];
112 static int simTelemetryState
= SIM_STATE_INIT
;
113 static timeMs_t sim_t_stateChange
= 0;
114 static uint8_t simResponse
[SIM_RESPONSE_BUFFER_SIZE
+ 1];
115 static int atCommandStatus
= SIM_AT_OK
;
116 static bool simWaitAfterResponse
= false;
117 static uint8_t readState
= SIM_READSTATE_RESPONSE
;
118 static timeMs_t t_lastMessageSent
= 0;
119 static uint8_t lastMessageTriggeredBy
= 0;
120 static uint8_t simModuleState
= SIM_MODULE_NOT_DETECTED
;
123 static uint8_t accEvent
= ACC_EVENT_NONE
;
124 static char* accEventDescriptions
[] = { "", "HIT! ", "DROP ", "HIT " };
125 static char* modeDescriptions
[] = { "MAN", "ACR", "AIR", "ANG", "HOR", "ALH", "POS", "RTH", "WP", "CRS", "LAU", "FS" };
126 static const char gpsFixIndicators
[] = { '!', '*', ' ' };
128 static bool checkGroundStationNumber(uint8_t* rv
)
131 const char* gsn
= telemetryConfig()->simGroundStationNumber
;
133 int digitsToCheck
= strlen((char*)gsn
);
135 digitsToCheck
-= 5; // ignore country code (max 4 digits)
136 } else if (gsn
[0] == '0') { // ignore trunk prefixes: '0', '8', 01', '02', '06'
138 if (gsn
[1] == '1' || gsn
[1] == '2' || gsn
[1] == '6') {
141 } else if (gsn
[0] == '8') {
145 for (i
= 0; i
< 16 && *gsn
!= '\0'; i
++) gsn
++;
148 for (i
= 0; i
< 16 && *rv
!= '\"'; i
++) rv
++;
151 for (i
= 0; i
< digitsToCheck
; i
++) {
152 if (*rv
!= *gsn
) return false;
159 static void readOriginatingNumber(uint8_t* rv
)
162 char* gsn
= telemetryConfigMutable()->simGroundStationNumber
;
165 for (i
= 0; i
< 15 && rv
[i
] != '\"'; i
++)
170 static void readTransmitFlags(const uint8_t* fs
)
174 uint8_t transmitFlags
= 0;
175 for (i
= 0; i
< SIM_N_TX_FLAGS
&& fs
[i
] != '\0'; i
++) {
178 transmitFlags
|= SIM_TX_FLAG
;
181 transmitFlags
|= SIM_TX_FLAG_FAILSAFE
;
184 transmitFlags
|= SIM_TX_FLAG_GPS
;
187 transmitFlags
|= SIM_TX_FLAG_LOW_ALT
;
190 transmitFlags
|= SIM_TX_FLAG_ACC
;
195 telemetryConfigMutable()->simTransmitFlags
= transmitFlags
;
198 static void requestSendSMS(uint8_t trigger
)
200 lastMessageTriggeredBy
= trigger
;
201 if (simTelemetryState
== SIM_STATE_SEND_SMS_ENTER_MESSAGE
)
202 return; // sending right now, don't reissue AT command
203 simTelemetryState
= SIM_STATE_SEND_SMS
;
204 if (atCommandStatus
!= SIM_AT_WAITING_FOR_RESPONSE
)
205 sim_t_stateChange
= 0; // send immediately
208 static void readSMS(void)
210 if (sl_strcasecmp((char*)simResponse
, SIM_SMS_COMMAND_RTH
) == 0) {
211 if (getStateOfForcedRTH() == RTH_IDLE
) {
217 readTransmitFlags(simResponse
);
219 requestSendSMS(SIM_TX_FLAG_RESPONSE
);
222 static void readSimResponse(void)
224 if (readState
== SIM_READSTATE_SKIP
) {
225 readState
= SIM_READSTATE_RESPONSE
;
227 } else if (readState
== SIM_READSTATE_SMS
) {
229 readState
= SIM_READSTATE_RESPONSE
;
233 uint8_t* resp
= simResponse
;
234 uint32_t responseCode
= 0;
235 if (simResponse
[0] == '+') {
238 responseCode
= *resp
++;
239 responseCode
<<= 8; responseCode
|= *resp
++;
240 responseCode
<<= 8; responseCode
|= *resp
++;
241 responseCode
<<= 8; responseCode
|= *resp
++;
243 if (responseCode
== SIM_RESPONSE_CODE_OK
) {
245 atCommandStatus
= SIM_AT_OK
;
246 if (!simWaitAfterResponse
) {
247 sim_t_stateChange
= millis() + SIM_AT_COMMAND_DELAY_MIN_MS
;
250 } else if (responseCode
== SIM_RESPONSE_CODE_ERROR
) {
252 atCommandStatus
= SIM_AT_ERROR
;
253 if (!simWaitAfterResponse
) {
254 sim_t_stateChange
= millis() + SIM_AT_COMMAND_DELAY_MIN_MS
;
257 } else if (responseCode
== SIM_RESPONSE_CODE_RING
) {
259 } else if (responseCode
== SIM_RESPONSE_CODE_CSQ
) {
261 simRssi
= fastA2I((char*)&simResponse
[6]);
262 } else if (responseCode
== SIM_RESPONSE_CODE_CLIP
) {
263 // we always get this after a RING when a call is incoming
264 // +CLIP: "+3581234567"
265 readOriginatingNumber(&simResponse
[8]);
266 if (checkGroundStationNumber(&simResponse
[8])) {
267 requestSendSMS(SIM_TX_FLAG_RESPONSE
);
269 } else if (responseCode
== SIM_RESPONSE_CODE_CREG
) {
271 if (simResponse
[9] == '1' || simResponse
[9] == '5') {
272 simModuleState
= SIM_MODULE_REGISTERED
;
274 simModuleState
= SIM_MODULE_NOT_REGISTERED
;
276 } else if (responseCode
== SIM_RESPONSE_CODE_CMT
) {
277 // +CMT: <oa>,[<alpha>],<scts>[,<tooa>,<fo>,<pid>,<dcs>,<sca>,<tosca>,<length>]<CR><LF><data>
278 // +CMT: "+3581234567","","19/02/12,14:57:24+08"
279 readOriginatingNumber(&simResponse
[7]);
280 if (checkGroundStationNumber(&simResponse
[7])) {
281 readState
= SIM_READSTATE_SMS
; // next simResponse line will be SMS content
283 readState
= SIM_READSTATE_SKIP
; // skip SMS content
288 static int16_t getAltitudeMeters(void)
290 return getEstimatedActualPosition(Z
) / 100;
293 static void transmit(void)
295 timeMs_t timeSinceMsg
= millis() - t_lastMessageSent
;
296 uint8_t triggers
= SIM_TX_FLAG
;
297 uint32_t accSq
= sq(imuMeasuredAccelBF
.x
) + sq(imuMeasuredAccelBF
.y
) + sq(imuMeasuredAccelBF
.z
);
299 if (telemetryConfig()->accEventThresholdHigh
> 0 && accSq
> sq(telemetryConfig()->accEventThresholdHigh
)) {
300 triggers
|= SIM_TX_FLAG_ACC
;
301 accEvent
= ACC_EVENT_HIGH
;
302 } else if (accSq
< sq(telemetryConfig()->accEventThresholdLow
)) {
303 triggers
|= SIM_TX_FLAG_ACC
;
304 accEvent
= ACC_EVENT_LOW
;
305 } else if (telemetryConfig()->accEventThresholdNegX
> 0 && imuMeasuredAccelBF
.x
< -telemetryConfig()->accEventThresholdNegX
) {
306 triggers
|= SIM_TX_FLAG_ACC
;
307 accEvent
= ACC_EVENT_NEG_X
;
310 if ((lastMessageTriggeredBy
& SIM_TX_FLAG_ACC
) && timeSinceMsg
< 2000)
311 accEvent
= ACC_EVENT_NONE
;
313 if (FLIGHT_MODE(FAILSAFE_MODE
))
314 triggers
|= SIM_TX_FLAG_FAILSAFE
;
315 if (!navigationPositionEstimateIsHealthy())
316 triggers
|= SIM_TX_FLAG_GPS
;
317 if (gpsSol
.fixType
!= GPS_NO_FIX
&& FLIGHT_MODE(SIM_LOW_ALT_WARNING_MODES
) && getAltitudeMeters() < telemetryConfig()->simLowAltitude
)
318 triggers
|= SIM_TX_FLAG_LOW_ALT
;
320 triggers
&= telemetryConfig()->simTransmitFlags
;
324 if (!ARMING_FLAG(WAS_EVER_ARMED
))
327 if ((triggers
& ~lastMessageTriggeredBy
) // if new trigger activated after last msg, don't wait
328 || timeSinceMsg
> 1000 * MAX(SIM_MIN_TRANSMIT_INTERVAL
, telemetryConfig()->simTransmitInterval
)) {
329 requestSendSMS(triggers
);
333 static void sendATCommand(const char* command
)
335 atCommandStatus
= SIM_AT_WAITING_FOR_RESPONSE
;
336 uint8_t len
= MIN((uint8_t)strlen(command
), SIM_AT_COMMAND_MAX_SIZE
);
337 serialWriteBuf(simPort
, (const uint8_t*) command
, len
);
340 static void sendSMS(void)
342 char pluscode_url
[20];
343 int16_t groundSpeed
= 0;
344 uint16_t vbat
= getBatteryVoltage();
345 int16_t amps
= isAmperageConfigured() ? getAmperage() / 10 : 0; // 1 = 100 milliamps
346 uint16_t avgSpeed
= lrintf(10 * calculateAverageSpeed());
347 uint32_t now
= millis();
349 ZERO_FARRAY(pluscode_url
);
351 if (sensors(SENSOR_GPS
) && STATE(GPS_FIX
)) {
352 groundSpeed
= gpsSol
.groundSpeed
/ 100;
355 olc_encode(gpsSol
.llh
.lat
, gpsSol
.llh
.lon
, 11, buf
, sizeof(buf
));
357 // URLencode plus code (replace plus sign with %2B)
358 for (char *in
= buf
, *out
= pluscode_url
; *in
; ) {
371 // \x1a sends msg, \x1b cancels
372 uint8_t len
= tfp_sprintf((char*)atCommand
, "%s%d.%02dV %d.%dA ALT:%d SPD:%d/%d.%d DIS:%lu/%lu HDG:%d SAT:%d%c SIG:%d %s https://maps.google.com/?q=%s\x1a",
373 accEventDescriptions
[accEvent
],
374 vbat
/ 100, vbat
% 100,
375 amps
/ 10, amps
% 10,
377 groundSpeed
, avgSpeed
/ 10, avgSpeed
% 10,
378 (unsigned long)GPS_distanceToHome
, getTotalTravelDistance() / 100ul,
379 (int)DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
),
380 gpsSol
.numSat
, gpsFixIndicators
[gpsSol
.fixType
],
382 getStateOfForcedRTH() == RTH_IDLE
? modeDescriptions
[getFlightModeForTelemetry()] : "RTH",
385 serialWriteBuf(simPort
, atCommand
, len
);
386 t_lastMessageSent
= now
;
387 accEvent
= ACC_EVENT_NONE
;
388 atCommandStatus
= SIM_AT_WAITING_FOR_RESPONSE
;
391 void handleSimTelemetry(void)
393 static uint16_t simResponseIndex
= 0;
394 uint32_t now
= millis();
401 while (serialRxBytesWaiting(simPort
) > 0) {
402 uint8_t c
= serialRead(simPort
);
403 if (c
== '\n' || simResponseIndex
== SIM_RESPONSE_BUFFER_SIZE
) {
404 simResponse
[simResponseIndex
] = '\0';
405 if (simResponseIndex
> 0) simResponseIndex
--;
406 if (simResponse
[simResponseIndex
] == '\r') simResponse
[simResponseIndex
] = '\0';
407 simResponseIndex
= 0; //data ok
411 simResponse
[simResponseIndex
] = c
;
418 if (now
< sim_t_stateChange
)
421 sim_t_stateChange
= now
+ SIM_AT_COMMAND_DELAY_MS
; // by default, if OK or ERROR not received, wait this long
422 simWaitAfterResponse
= false; // by default, if OK or ERROR received, go to next state immediately.
423 switch (simTelemetryState
) {
425 sendATCommand("AT\r");
426 simTelemetryState
= SIM_STATE_INIT2
;
428 case SIM_STATE_INIT2
:
429 sendATCommand("ATE0\r");
430 simTelemetryState
= SIM_STATE_INIT_ENTER_PIN
;
432 case SIM_STATE_INIT_ENTER_PIN
:
433 sendATCommand("AT+CPIN=");
434 sendATCommand((char*)telemetryConfig()->simPin
);
436 simTelemetryState
= SIM_STATE_SET_MODES
;
438 case SIM_STATE_SET_MODES
:
439 sendATCommand("AT+CMGF=1;+CNMI=3,2;+CLIP=1;+CSQ\r");
440 simTelemetryState
= SIM_STATE_INIT
;
441 sim_t_stateChange
= now
+ SIM_CYCLE_MS
;
442 simWaitAfterResponse
= true;
444 case SIM_STATE_SEND_SMS
:
445 sendATCommand("AT+CMGS=\"");
446 sendATCommand((char*)telemetryConfig()->simGroundStationNumber
);
447 sendATCommand("\"\r");
448 simTelemetryState
= SIM_STATE_SEND_SMS_ENTER_MESSAGE
;
449 sim_t_stateChange
= now
+ 100;
451 case SIM_STATE_SEND_SMS_ENTER_MESSAGE
:
453 simTelemetryState
= SIM_STATE_INIT
;
454 sim_t_stateChange
= now
+ SIM_CYCLE_MS
;
455 simWaitAfterResponse
= true;
460 void initSimTelemetry(void)
462 portConfig
= findSerialPortConfig(FUNCTION_TELEMETRY_SIM
);
465 static void configureSimTelemetryPort(void)
470 baudRate_e baudRateIndex
= portConfig
->telemetry_baudrateIndex
;
471 simPort
= openSerialPort(portConfig
->identifier
, FUNCTION_TELEMETRY_SIM
, NULL
, NULL
,
472 baudRates
[baudRateIndex
], MODE_RXTX
, SERIAL_NOT_INVERTED
);
478 sim_t_stateChange
= millis() + SIM_STARTUP_DELAY_MS
;
479 simTelemetryState
= SIM_STATE_INIT
;
480 readState
= SIM_READSTATE_RESPONSE
;
484 void checkSimTelemetryState(void)
489 configureSimTelemetryPort();