5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
22 #include "io/frsky_firmware_update.h"
25 #define BLUETOOTH_COMMAND_NAME "TTM:REN-"
26 #define BLUETOOTH_ANSWER_NAME "TTM:REN"
27 #define BLUETOOTH_COMMAND_BAUD_115200 "TTM:BPS-115200"
29 #define BLUETOOTH_COMMAND_NAME "AT+NAME"
30 #define BLUETOOTH_ANSWER_NAME "OK+"
31 #define BLUETOOTH_COMMAND_BAUD_115200 "AT+BAUD115200"
35 #define SWAP32(val) (_byteswap_ulong(val))
36 #elif defined(__GNUC__ )
37 #define SWAP32(val) (__builtin_bswap32(val))
40 extern Fifo
<uint8_t, BT_TX_FIFO_SIZE
> btTxFifo
;
41 extern Fifo
<uint8_t, BT_RX_FIFO_SIZE
> btRxFifo
;
45 void Bluetooth::write(const uint8_t * data
, uint8_t length
)
48 for (int i
=0; i
<length
; i
++) {
49 TRACE_NOCRLF(" %02X", data
[i
]);
50 while (btTxFifo
.isFull()) {
51 if (!bluetoothIsWriting())
52 bluetoothWriteWakeup();
55 btTxFifo
.push(data
[i
]);
58 bluetoothWriteWakeup();
61 void Bluetooth::writeString(const char * str
)
65 btTxFifo
.push(*str
++);
69 bluetoothWriteWakeup();
72 char * Bluetooth::readline(bool error_reset
)
77 if (!btRxFifo
.pop(byte
)) {
79 // X9E BT module can get unresponsive
80 TRACE("NO RESPONSE FROM BT MODULE");
85 TRACE_NOCRLF("%02X ", byte
);
88 if (error_reset
&& byte
== 'R' && bufferIndex
== 4 && memcmp(buffer
, "ERRO", 4)) {
89 #if defined(PCBX9E) // X9E enter BT reset loop if following code is implemented
95 state
= BLUETOOTH_STATE_OFF
;
96 wakeupTime
= get_tmr10ms() + 100; /* 1s */
104 if (bufferIndex
> 2 && buffer
[bufferIndex
-1] == '\r') {
105 buffer
[bufferIndex
-1] = '\0';
107 TRACE("BT< %s", buffer
);
108 if (error_reset
&& !strcmp((char *)buffer
, "ERROR")) {
109 #if defined(PCBX9E) // X9E enter BT reset loop if following code is implemented
110 TRACE("BT Error...");
112 TRACE("BT Reset...");
114 state
= BLUETOOTH_STATE_OFF
;
115 wakeupTime
= get_tmr10ms() + 100; /* 1s */
120 if (!memcmp(buffer
, "Central:", 8))
121 strcpy(localAddr
, (char *) buffer
+ 8);
122 else if (!memcmp(buffer
, "Peripheral:", 11))
123 strcpy(localAddr
, (char *) buffer
+ 11);
124 return (char *) buffer
;
132 buffer
[bufferIndex
++] = byte
;
133 bufferIndex
&= (BLUETOOTH_LINE_LENGTH
-1);
138 void Bluetooth::processTrainerFrame(const uint8_t * buffer
)
142 for (uint8_t channel
=0, i
=1; channel
<8; channel
+=2, i
+=3) {
143 // +-500 != 512, but close enough.
144 ppmInput
[channel
] = buffer
[i
] + ((buffer
[i
+1] & 0xf0) << 4) - 1500;
145 ppmInput
[channel
+1] = ((buffer
[i
+1] & 0x0f) << 4) + ((buffer
[i
+2] & 0xf0) >> 4) + ((buffer
[i
+2] & 0x0f) << 8) - 1500;
148 ppmInputValidityTimer
= PPM_IN_VALID_TIMEOUT
;
151 void Bluetooth::appendTrainerByte(uint8_t data
)
153 if (bufferIndex
< BLUETOOTH_LINE_LENGTH
) {
154 buffer
[bufferIndex
++] = data
;
155 // we check for "DisConnected", but the first byte could be altered (if received in state STATE_DATA_XOR)
157 if (!strncmp((char *)&buffer
[bufferIndex
-13], "isConnected", 11)) {
158 TRACE("BT< DisConnected");
159 state
= BLUETOOTH_STATE_DISCONNECTED
;
161 wakeupTime
+= 200; // 1s
167 void Bluetooth::processTrainerByte(uint8_t data
)
169 static uint8_t dataState
= STATE_DATA_IDLE
;
172 case STATE_DATA_START
:
173 if (data
== START_STOP
) {
174 dataState
= STATE_DATA_IN_FRAME
;
178 appendTrainerByte(data
);
182 case STATE_DATA_IN_FRAME
:
183 if (data
== BYTE_STUFF
) {
184 dataState
= STATE_DATA_XOR
; // XOR next byte
186 else if (data
== START_STOP
) {
187 dataState
= STATE_DATA_IN_FRAME
;
191 appendTrainerByte(data
);
196 appendTrainerByte(data
^ STUFF_MASK
);
197 dataState
= STATE_DATA_IN_FRAME
;
200 case STATE_DATA_IDLE
:
201 if (data
== START_STOP
) {
203 dataState
= STATE_DATA_START
;
206 appendTrainerByte(data
);
211 if (bufferIndex
>= BLUETOOTH_PACKET_SIZE
) {
213 for (int i
=0; i
<13; i
++) {
216 if (crc
== buffer
[13]) {
217 if (buffer
[0] == 0x80) {
218 processTrainerFrame(buffer
);
221 dataState
= STATE_DATA_IDLE
;
225 void Bluetooth::pushByte(uint8_t byte
)
228 if (byte
== START_STOP
|| byte
== BYTE_STUFF
) {
229 buffer
[bufferIndex
++] = BYTE_STUFF
;
232 buffer
[bufferIndex
++] = byte
;
235 void Bluetooth::sendTrainer()
237 int16_t PPM_range
= g_model
.extendedLimits
? 640*2 : 512*2;
239 int firstCh
= g_model
.trainerData
.channelsStart
;
240 int lastCh
= firstCh
+ 8;
242 uint8_t * cur
= buffer
;
246 buffer
[bufferIndex
++] = START_STOP
; // start byte
247 pushByte(0x80); // trainer frame type?
248 for (int channel
=0; channel
<lastCh
; channel
+=2, cur
+=3) {
249 uint16_t channelValue1
= PPM_CH_CENTER(channel
) + limit((int16_t)-PPM_range
, channelOutputs
[channel
], (int16_t)PPM_range
) / 2;
250 uint16_t channelValue2
= PPM_CH_CENTER(channel
+1) + limit((int16_t)-PPM_range
, channelOutputs
[channel
+1], (int16_t)PPM_range
) / 2;
251 pushByte(channelValue1
& 0x00ff);
252 pushByte(((channelValue1
& 0x0f00) >> 4) + ((channelValue2
& 0x00f0) >> 4));
253 pushByte(((channelValue2
& 0x000f) << 4) + ((channelValue2
& 0x0f00) >> 8));
255 buffer
[bufferIndex
++] = crc
;
256 buffer
[bufferIndex
++] = START_STOP
; // end byte
258 write(buffer
, bufferIndex
);
262 void Bluetooth::forwardTelemetry(const uint8_t * packet
)
266 buffer
[bufferIndex
++] = START_STOP
; // start byte
267 for (uint8_t i
=0; i
<sizeof(SportTelemetryPacket
); i
++) {
270 buffer
[bufferIndex
++] = crc
;
271 buffer
[bufferIndex
++] = START_STOP
; // end byte
273 if (bufferIndex
>= 2*FRSKY_SPORT_PACKET_SIZE
) {
274 write(buffer
, bufferIndex
);
279 void Bluetooth::receiveTrainer()
284 if (!btRxFifo
.pop(byte
)) {
288 TRACE_NOCRLF("%02X ", byte
);
290 processTrainerByte(byte
);
295 void Bluetooth::wakeup(void)
298 if (!g_eeGeneral
.bluetoothMode
) {
299 if (state
!= BLUETOOTH_INIT
) {
301 state
= BLUETOOTH_INIT
;
305 static tmr10ms_t waitEnd
= 0;
306 if (state
!= BLUETOOTH_STATE_IDLE
) {
307 if (state
== BLUETOOTH_INIT
) {
308 bluetoothInit(BLUETOOTH_DEFAULT_BAUDRATE
, true);
310 char * cur
= strAppend(command
, BLUETOOTH_COMMAND_NAME
);
311 uint8_t len
= ZLEN(g_eeGeneral
.bluetoothName
);
313 for (int i
= 0; i
< len
; i
++) {
314 *cur
++ = char2lower(zchar2char(g_eeGeneral
.bluetoothName
[i
]));
319 cur
= strAppend(cur
, FLAVOUR
);
321 writeString(command
);
322 state
= BLUETOOTH_WAIT_TTM
;
323 waitEnd
= get_tmr10ms() + 25; // 250ms
325 else if (state
== BLUETOOTH_WAIT_TTM
) {
326 if (get_tmr10ms() > waitEnd
) {
327 char * line
= readline();
328 if (strncmp(line
, "OK+", 3)) {
329 state
= BLUETOOTH_STATE_IDLE
;
332 bluetoothInit(BLUETOOTH_FACTORY_BAUDRATE
, true);
333 writeString("TTM:BPS-115200");
334 state
= BLUETOOTH_WAIT_BAUDRATE_CHANGE
;
335 waitEnd
= get_tmr10ms() + 250; // 2.5s
339 else if (state
== BLUETOOTH_WAIT_BAUDRATE_CHANGE
) {
340 if (get_tmr10ms() > waitEnd
) {
341 state
= BLUETOOTH_INIT
;
345 else if (IS_BLUETOOTH_TRAINER()){
346 state
= BLUETOOTH_STATE_CONNECTED
;
347 bluetoothWriteWakeup();
354 void Bluetooth::wakeup()
356 if (state
!= BLUETOOTH_STATE_OFF
) {
357 bluetoothWriteWakeup();
358 if (bluetoothIsWriting()) {
363 tmr10ms_t now
= get_tmr10ms();
365 if (now
< wakeupTime
)
368 wakeupTime
= now
+ 5; /* 50ms default */
370 if (state
== BLUETOOTH_STATE_FLASH_FIRMWARE
) {
374 if (g_eeGeneral
.bluetoothMode
== BLUETOOTH_OFF
|| (g_eeGeneral
.bluetoothMode
== BLUETOOTH_TRAINER
&& !IS_BLUETOOTH_TRAINER())) {
375 if (state
!= BLUETOOTH_STATE_OFF
) {
377 state
= BLUETOOTH_STATE_OFF
;
379 wakeupTime
= now
+ 10; /* 100ms */
381 else if (state
== BLUETOOTH_STATE_OFF
) {
382 bluetoothInit(BLUETOOTH_FACTORY_BAUDRATE
, true);
383 state
= BLUETOOTH_STATE_FACTORY_BAUDRATE_INIT
;
386 if (state
== BLUETOOTH_STATE_FACTORY_BAUDRATE_INIT
) {
387 writeString("AT+BAUD4");
388 state
= BLUETOOTH_STATE_BAUDRATE_SENT
;
389 wakeupTime
= now
+ 10; /* 100ms */
391 else if (state
== BLUETOOTH_STATE_BAUDRATE_SENT
) {
392 bluetoothInit(BLUETOOTH_DEFAULT_BAUDRATE
, true);
393 state
= BLUETOOTH_STATE_BAUDRATE_INIT
;
395 wakeupTime
= now
+ 10; /* 100ms */
397 else if (state
== BLUETOOTH_STATE_CONNECTED
) {
398 if (g_eeGeneral
.bluetoothMode
== BLUETOOTH_TRAINER
&& g_model
.trainerData
.mode
== TRAINER_MODE_MASTER_BLUETOOTH
) {
402 if (g_eeGeneral
.bluetoothMode
== BLUETOOTH_TRAINER
&& g_model
.trainerData
.mode
== TRAINER_MODE_SLAVE_BLUETOOTH
) {
404 wakeupTime
= now
+ 2; /* 20ms */
406 readline(); // to deal with "ERROR"
410 char * line
= readline();
411 if (state
== BLUETOOTH_STATE_BAUDRATE_INIT
) {
413 char * cur
= strAppend(command
, BLUETOOTH_COMMAND_NAME
);
414 uint8_t len
= ZLEN(g_eeGeneral
.bluetoothName
);
416 for (int i
= 0; i
< len
; i
++) {
417 *cur
++ = char2lower(zchar2char(g_eeGeneral
.bluetoothName
[i
]));
422 cur
= strAppend(cur
, FLAVOUR
);
424 writeString(command
);
425 state
= BLUETOOTH_STATE_NAME_SENT
;
427 else if (state
== BLUETOOTH_STATE_NAME_SENT
&& (!strncmp(line
, "OK+", 3) || !strncmp(line
, "Central:", 8) || !strncmp(line
, "Peripheral:", 11))) {
428 writeString("AT+TXPW0");
429 state
= BLUETOOTH_STATE_POWER_SENT
;
431 else if (state
== BLUETOOTH_STATE_POWER_SENT
&& (!strncmp(line
, "Central:", 8) || !strncmp(line
, "Peripheral:", 11))) {
432 if (g_eeGeneral
.bluetoothMode
== BLUETOOTH_TRAINER
&& g_model
.trainerData
.mode
== TRAINER_MODE_MASTER_BLUETOOTH
)
433 writeString("AT+ROLE1");
435 writeString("AT+ROLE0");
436 state
= BLUETOOTH_STATE_ROLE_SENT
;
438 else if (state
== BLUETOOTH_STATE_ROLE_SENT
&& (!strncmp(line
, "Central:", 8) || !strncmp(line
, "Peripheral:", 11))) {
439 state
= BLUETOOTH_STATE_IDLE
;
441 else if (state
== BLUETOOTH_STATE_DISCOVER_REQUESTED
) {
442 writeString("AT+DISC?");
443 state
= BLUETOOTH_STATE_DISCOVER_SENT
;
445 else if (state
== BLUETOOTH_STATE_DISCOVER_SENT
&& !strcmp(line
, "OK+DISCS")) {
446 state
= BLUETOOTH_STATE_DISCOVER_START
;
448 else if (state
== BLUETOOTH_STATE_DISCOVER_START
&& !strncmp(line
, "OK+DISC:", 8)) {
449 if (strlen(line
) < 8 + LEN_BLUETOOTH_ADDR
&& reusableBuffer
.moduleSetup
.bt
.devicesCount
< MAX_BLUETOOTH_DISTANT_ADDR
) {
450 strncpy(reusableBuffer
.moduleSetup
.bt
.devices
[reusableBuffer
.moduleSetup
.bt
.devicesCount
], &line
[8], LEN_BLUETOOTH_ADDR
);
451 ++reusableBuffer
.moduleSetup
.bt
.devicesCount
;
454 #if defined(PCBHORUS)
455 else if (state
== BLUETOOTH_STATE_DISCOVER_START
&& !strcmp(line
, "OK+DISCE")) {
456 state
= BLUETOOTH_STATE_DISCOVER_END
;
459 else if (state
== BLUETOOTH_STATE_CLEAR_REQUESTED
) {
460 char command
[] = "AT+CLEAR";
461 writeString(command
);
462 state
= BLUETOOTH_STATE_IDLE
;
464 else if (state
== BLUETOOTH_STATE_BIND_REQUESTED
) {
466 strAppend(strAppend(command
, "AT+CON"), distantAddr
);
467 writeString(command
);
468 state
= BLUETOOTH_STATE_CONNECT_SENT
;
470 else if ((state
== BLUETOOTH_STATE_IDLE
|| state
== BLUETOOTH_STATE_DISCONNECTED
|| state
== BLUETOOTH_STATE_CONNECT_SENT
) && !strncmp(line
, "Connected:", 10)) {
471 strcpy(distantAddr
, &line
[10]); // TODO quick & dirty
472 state
= BLUETOOTH_STATE_CONNECTED
;
473 if (g_model
.trainerData
.mode
== TRAINER_MODE_SLAVE_BLUETOOTH
) {
474 wakeupTime
+= 500; // it seems a 5s delay is needed before sending the 1st frame
477 else if (state
== BLUETOOTH_STATE_DISCONNECTED
&& !line
) {
479 strAppend(strAppend(command
, "AT+CON"), distantAddr
);
480 writeString(command
);
481 wakeupTime
= now
+ 200; /* 2s */
487 uint8_t Bluetooth::bootloaderChecksum(uint8_t command
, const uint8_t * data
, uint8_t size
)
489 uint8_t sum
= command
;
490 for (uint8_t i
= 0; i
< size
; i
++) {
496 uint8_t Bluetooth::read(uint8_t * data
, uint8_t size
, uint32_t timeout
)
498 watchdogSuspend(timeout
/ 10);
502 uint32_t elapsed
= 0;
504 while (!btRxFifo
.pop(byte
)) {
505 if (elapsed
++ >= timeout
) {
515 #define BLUETOOTH_ACK 0xCC
516 #define BLUETOOTH_NACK 0x33
518 const char * Bluetooth::bootloaderWaitCommandResponse(uint32_t timeout
)
521 if (read(response
, sizeof(response
), timeout
) != sizeof(response
)) {
522 return "Bluetooth timeout";
525 if (response
[0] != 0x00) {
526 return "Bluetooth error";
529 if (response
[1] == BLUETOOTH_ACK
|| response
[1] == BLUETOOTH_NACK
) {
533 return "Bluetooth error";
536 const char * Bluetooth::bootloaderWaitResponseData(uint8_t *data
, uint8_t size
)
539 if (read(header
, 2) != 2) {
540 return "Bluetooth timeout";
543 uint8_t len
= header
[0] - 2;
544 uint8_t checksum
= header
[1];
547 return "Bluetooth error";
550 if (read(data
, len
) != len
) {
551 return "Bluetooth timeout";
554 if (bootloaderChecksum(0, data
, len
) != checksum
) {
555 return "Bluetooth CRC error";
561 const char * Bluetooth::bootloaderSetAutoBaud()
563 uint8_t packet
[2] = {
567 write(packet
, sizeof(packet
));
568 return bootloaderWaitCommandResponse();
571 void Bluetooth::bootloaderSendCommand(uint8_t command
, const void *data
, uint8_t size
)
573 uint8_t packet
[3] = {
575 bootloaderChecksum(command
, (uint8_t *) data
, size
),
578 write(packet
, sizeof(packet
));
580 write((uint8_t *)data
, size
);
584 void Bluetooth::bootloaderSendCommandResponse(uint8_t response
)
586 uint8_t packet
[2] = {
590 write(packet
, sizeof(packet
));
595 CMD_GET_STATUS
= 0x23,
596 CMD_SEND_DATA
= 0x24,
597 CMD_SECTOR_ERASE
= 0x26,
598 CMD_GET_CHIP_ID
= 0x28,
602 CMD_RET_SUCCESS
= 0x40,
605 constexpr uint32_t CC26XX_BOOTLOADER_SIZE
= 0x00001000;
606 constexpr uint32_t CC26XX_FIRMWARE_BASE
= CC26XX_BOOTLOADER_SIZE
;
608 constexpr uint32_t CC26XX_PAGE_ERASE_SIZE
= 0x1000;
609 constexpr uint32_t CC26XX_MAX_BYTES_PER_TRANSFER
= 252;
611 const char * Bluetooth::bootloaderSendData(const uint8_t * data
, uint8_t size
)
613 bootloaderSendCommand(CMD_SEND_DATA
, data
, size
);
614 return bootloaderWaitCommandResponse();
617 const char * Bluetooth::bootloaderReadStatus(uint8_t &status
)
619 bootloaderSendCommand(CMD_GET_STATUS
);
620 const char * result
= bootloaderWaitCommandResponse();
623 result
= bootloaderWaitResponseData(&status
, 1);
624 bootloaderSendCommandResponse(result
== nullptr ? BLUETOOTH_ACK
: BLUETOOTH_NACK
);
628 const char * Bluetooth::bootloaderCheckStatus()
631 const char * result
= bootloaderReadStatus(status
);
634 if (status
!= CMD_RET_SUCCESS
)
635 return "Wrong status";
639 const char * Bluetooth::bootloaderEraseFlash(uint32_t start
, uint32_t size
)
641 uint32_t address
= start
;
642 uint32_t end
= start
+ size
;
643 while (address
< end
) {
644 uint32_t addressBigEndian
= SWAP32(address
);
645 bootloaderSendCommand(CMD_SECTOR_ERASE
, &addressBigEndian
, sizeof(addressBigEndian
));
646 const char * result
= bootloaderWaitCommandResponse();
649 result
= bootloaderCheckStatus();
652 address
+= CC26XX_PAGE_ERASE_SIZE
;
657 const char * Bluetooth::bootloaderStartWriteFlash(uint32_t start
, uint32_t size
)
659 uint32_t cmdArgs
[2] = {
663 bootloaderSendCommand(CMD_DOWNLOAD
, cmdArgs
, sizeof(cmdArgs
));
664 const char * result
= bootloaderWaitCommandResponse();
667 result
= bootloaderCheckStatus();
673 const char * Bluetooth::bootloaderWriteFlash(const uint8_t * data
, uint32_t size
)
676 uint32_t len
= min
<uint32_t>(size
, CC26XX_MAX_BYTES_PER_TRANSFER
);
677 const char * result
= bootloaderSendData(data
, len
);
680 result
= bootloaderCheckStatus();
689 const char * Bluetooth::doFlashFirmware(const char * filename
)
693 uint8_t buffer
[CC26XX_MAX_BYTES_PER_TRANSFER
* 4];
697 bootloaderSendCommand(0);
698 result
= bootloaderWaitCommandResponse(0);
700 result
= bootloaderSetAutoBaud();
705 bootloaderSendCommand(CMD_GET_CHIP_ID
);
706 result
= bootloaderWaitCommandResponse();
710 result
= bootloaderWaitResponseData(id
, 4);
711 bootloaderSendCommandResponse(result
== nullptr ? BLUETOOTH_ACK
: BLUETOOTH_NACK
);
713 if (f_open(&file
, filename
, FA_READ
) != FR_OK
) {
714 return "Error opening file";
717 FrSkyFirmwareInformation
* information
= (FrSkyFirmwareInformation
*)buffer
;
718 if (f_read(&file
, buffer
, sizeof(FrSkyFirmwareInformation
), &count
) != FR_OK
|| count
!= sizeof(FrSkyFirmwareInformation
)) {
720 return "Format error";
723 drawProgressScreen(getBasename(filename
), STR_FLASH_ERASE
, 0, 0);
725 result
= bootloaderEraseFlash(CC26XX_FIRMWARE_BASE
, information
->size
);
731 uint32_t size
= information
->size
;
732 drawProgressScreen(getBasename(filename
), STR_FLASH_WRITE
, 0, size
);
734 result
= bootloaderStartWriteFlash(CC26XX_FIRMWARE_BASE
, size
);
740 drawProgressScreen(getBasename(filename
), STR_FLASH_WRITE
, done
, size
);
741 if (f_read(&file
, buffer
, min
<uint32_t>(sizeof(buffer
), size
- done
), &count
) != FR_OK
) {
743 return "Error reading file";
745 result
= bootloaderWriteFlash(buffer
, count
);
756 const char * Bluetooth::flashFirmware(const char * filename
)
758 drawProgressScreen(getBasename(filename
), STR_MODULE_RESET
, 0, 0);
760 state
= BLUETOOTH_STATE_FLASH_FIRMWARE
;
764 bluetoothInit(BLUETOOTH_BOOTLOADER_BAUDRATE
, true); // normal mode
765 watchdogSuspend(500 /*5s*/);
768 bluetoothInit(BLUETOOTH_BOOTLOADER_BAUDRATE
, false); // bootloader mode
769 watchdogSuspend(500 /*5s*/);
772 const char * result
= doFlashFirmware(filename
);
774 AUDIO_PLAY(AU_SPECIAL_SOUND_BEEP1
);
778 POPUP_WARNING(STR_FIRMWARE_UPDATE_ERROR
);
779 SET_WARNING_INFO(result
, strlen(result
), 0);
782 POPUP_INFORMATION(STR_FIRMWARE_UPDATE_SUCCESS
);
785 drawProgressScreen(getBasename(filename
), STR_MODULE_RESET
, 0, 0);
788 watchdogSuspend(500 /*5s*/);
791 state
= BLUETOOTH_STATE_OFF
;