Firmware SDK
twr_cmwx1zzabz.c
1 #include <twr_cmwx1zzabz.h>
2 #include <twr_log.h>
3 #include <twr_timer.h>
4 
5 /*
6 
7 Supported Murata firmware versions
8 
9 1.0.02
10 1.1.03 - added FRMCNT
11 1.1.06 - added JOINDC
12 
13 */
14 
15 #define TWR_CMWX1ZZABZ_DELAY_RUN 100
16 #define TWR_CMWX1ZZABZ_DELAY_INITIALIZATION_RESET_H 100
17 #define TWR_CMWX1ZZABZ_DELAY_INITIALIZATION_AT_COMMAND 100 // ! when using longer AT responses
18 #define TWR_CMWX1ZZABZ_DELAY_CONFIG_SAVE 100
19 #define TWR_CMWX1ZZABZ_DELAY_INITIALIZATION_REBOOT 500
20 #define TWR_CMWX1ZZABZ_DELAY_INITIALIZATION_AT_RESPONSE 100
21 #define TWR_CMWX1ZZABZ_DELAY_SEND_MESSAGE_RESPONSE 1500
22 #define TWR_CMWX1ZZABZ_DELAY_JOIN_RESPONSE 500 //8000
23 #define TWR_CMWX1ZZABZ_DELAY_LINK_CHECK_RESPONSE 4000
24 #define TWR_CMWX1ZZABZ_DELAY_CUSTOM_COMMAND_RESPONSE 100
25 
26 #define TWR_CMWX1ZZABZ_TIMEOUT_CUSTOM_COMMAND_RESPONSE 500
27 #define TWR_CMWX1ZZABZ_TIMEOUT_LNCHECK 20000
28 #define TWR_CMWX1ZZABZ_TIMEOUT_JOIN 120000
29 
30 // Apply changes to the factory configuration
31 const char *_init_commands[] =
32 {
33  "AT+REBOOT\r",
34  "AT\r",
35  "AT+VER?\r",
36  "AT+DEV?\r",
37  "AT+DFORMAT=0\r",
38  "AT+DUTYCYCLE=0\r",
39  "AT+JOINDC=0\r",
40  "AT+DWELL=0,0\r",
41  "AT+DEVADDR?\r",
42  "AT+DEVEUI?\r",
43  "AT+APPEUI?\r",
44  "AT+NWKSKEY?\r",
45  "AT+APPSKEY?\r",
46  "AT+APPKEY?\r",
47  "AT+BAND?\r",
48  "AT+MODE?\r",
49  "AT+CLASS?\r",
50  "AT+RX2?\r",
51  "AT+NWK?\r",
52  "AT+ADR?\r",
53  "AT+DR?\r",
54  "AT+REP?\r",
55  "AT+RTYNUM?\r",
56  NULL
57 };
58 
59 static void _twr_cmwx1zzabz_task(void *param);
60 
61 static bool _twr_cmwx1zzabz_read_response(twr_cmwx1zzabz_t *self);
62 
63 static void _twr_cmwx1zzabz_save_config(twr_cmwx1zzabz_t *self, twr_cmwx1zzabz_config_index_t config_index);
64 
65 static void _uart_event_handler(twr_uart_channel_t channel, twr_uart_event_t event, void *param);
66 
68 {
69  memset(self, 0, sizeof(*self));
70 
71  self->_uart_channel = uart_channel;
72  self->_tx_port = 2;
73 
74  twr_fifo_init(&self->_tx_fifo, self->_tx_fifo_buffer, sizeof(self->_tx_fifo_buffer));
75  twr_fifo_init(&self->_rx_fifo, self->_rx_fifo_buffer, sizeof(self->_rx_fifo_buffer));
76 
78  twr_uart_set_async_fifo(self->_uart_channel, &self->_tx_fifo, &self->_rx_fifo);
79  twr_uart_async_read_start(self->_uart_channel, TWR_TICK_INFINITY);
80  twr_uart_set_event_handler(self->_uart_channel, _uart_event_handler, self);
81 
82  self->_task_id = twr_scheduler_register(_twr_cmwx1zzabz_task, self, TWR_CMWX1ZZABZ_DELAY_RUN);
83  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE;
84 }
85 
87 {
88  // Check if init was called previously
89  if (self->_task_id)
90  {
91  twr_scheduler_unregister(self->_task_id);
92  twr_uart_deinit(self->_uart_channel);
93  }
94 }
95 
97 {
98  // Clear state flags
99  self->_custom_command = false;
100  self->_link_check_command = false;
101  self->_join_command = false;
102 
103  // Set initialization state and force task to run in case it is not planned
104  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE;
105  twr_scheduler_plan_now(self->_task_id);
106 }
107 
108 static void _uart_event_handler(twr_uart_channel_t channel, twr_uart_event_t event, void *param)
109 {
110  (void) channel;
111  twr_cmwx1zzabz_t *self = (twr_cmwx1zzabz_t*)param;
112 
113  if (event == TWR_UART_EVENT_ASYNC_READ_DATA && self->_state == TWR_CMWX1ZZABZ_STATE_IDLE)
114  {
115  twr_scheduler_plan_relative(self->_task_id, 100);
116  self->_state = TWR_CMWX1ZZABZ_STATE_RECEIVE;
117  }
118 }
119 
120 void twr_cmwx1zzabz_set_event_handler(twr_cmwx1zzabz_t *self, void (*event_handler)(twr_cmwx1zzabz_t *, twr_cmwx1zzabz_event_t, void *), void *event_param)
121 {
122  self->_event_handler = event_handler;
123  self->_event_param = event_param;
124 }
125 
127 {
128  if (self->_state == TWR_CMWX1ZZABZ_STATE_IDLE)
129  {
130  return true;
131  }
132 
133  return false;
134 }
135 
136 bool twr_cmwx1zzabz_send_message(twr_cmwx1zzabz_t *self, const void *buffer, size_t length)
137 {
138  if (!twr_cmwx1zzabz_is_ready(self) || length == 0 || length > TWR_CMWX1ZZABZ_TX_MAX_PACKET_SIZE)
139  {
140  return false;
141  }
142 
143  self->_message_length = length;
144 
145  memcpy(self->_message_buffer, buffer, self->_message_length);
146 
147  self->_state = TWR_CMWX1ZZABZ_STATE_SEND_MESSAGE_COMMAND;
148 
149  twr_scheduler_plan_now(self->_task_id);
150 
151  return true;
152 }
153 
154 bool twr_cmwx1zzabz_send_message_confirmed(twr_cmwx1zzabz_t *self, const void *buffer, size_t length)
155 {
156  if (!twr_cmwx1zzabz_is_ready(self) || length == 0 || length > TWR_CMWX1ZZABZ_TX_MAX_PACKET_SIZE)
157  {
158  return false;
159  }
160 
161  self->_message_length = length;
162 
163  memcpy(self->_message_buffer, buffer, self->_message_length);
164 
165  self->_state = TWR_CMWX1ZZABZ_STATE_SEND_MESSAGE_CONFIRMED_COMMAND;
166 
167  twr_scheduler_plan_now(self->_task_id);
168 
169  return true;
170 }
171 
173 {
174  self->_debug = debug;
175 }
176 
177 static size_t _twr_cmwx1zzabz_async_write(twr_cmwx1zzabz_t *self, const void *buffer, size_t length)
178 {
179  size_t ret = twr_uart_async_write(self->_uart_channel, buffer, length);
180 
181  if (self->_debug)
182  {
183  twr_log_debug("LoRa TX: %s", (const char*)buffer);
184  }
185 
186  return ret;
187 }
188 
189 static size_t _twr_cmwx1zzabz_write(twr_cmwx1zzabz_t *self, const void *buffer, size_t length)
190 {
191  size_t ret = twr_uart_write(self->_uart_channel, buffer, length);
192 
193  if (self->_debug)
194  {
195  twr_log_debug("LoRa syncTX: %s", (const char*)buffer);
196  }
197 
198  return ret;
199 }
200 
201 static void _twr_cmwx1zzabz_task(void *param)
202 {
203  twr_cmwx1zzabz_t *self = param;
204 
205  while (true)
206  {
207  switch (self->_state)
208  {
209  case TWR_CMWX1ZZABZ_STATE_READY:
210  {
211  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
212 
213  if (self->_event_handler != NULL)
214  {
215  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_READY, self->_event_param);
216  }
217 
218  continue;
219  }
220  case TWR_CMWX1ZZABZ_STATE_IDLE:
221  {
222  if (self->_save_config_mask != 0)
223  {
224  self->_state = TWR_CMWX1ZZABZ_STATE_CONFIG_SAVE_SEND;
225  continue;
226  }
227 
228  if(self->_join_command)
229  {
230  self->_state = TWR_CMWX1ZZABZ_STATE_JOIN_SEND;
231  continue;
232  }
233 
234  if(self->_custom_command)
235  {
236  self->_state = TWR_CMWX1ZZABZ_STATE_CUSTOM_COMMAND_SEND;
237  continue;
238  }
239 
240  if(self->_link_check_command)
241  {
242  self->_state = TWR_CMWX1ZZABZ_STATE_LINK_CHECK_SEND;
243  continue;
244  }
245 
246  return;
247  }
248  case TWR_CMWX1ZZABZ_STATE_RECEIVE:
249  {
250  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
251 
252  while (_twr_cmwx1zzabz_read_response(self))
253  {
254  if (memcmp(self->_response, "+RECV=", 5) == 0)
255  {
256  self->_message_port = atoi(&self->_response[6]);
257 
258  char *comma_search = strchr(self->_response, ',');
259  if (!comma_search)
260  {
261  continue;
262  }
263 
264  // Parse from the next character
265  self->_message_length = atoi(++comma_search);
266 
267  // Dummy read three \r\n\r characters
268  char dummy[3];
269  uint32_t bytes = twr_uart_async_read(self->_uart_channel, &dummy, 3);
270  if (bytes != 3)
271  {
272  continue;
273  }
274 
275  // Received data is bigger than library message buffer
276  if (self->_message_length > sizeof(self->_message_buffer))
277  {
278  continue;
279  }
280 
281  // Read the received message
282  bytes = twr_uart_async_read(self->_uart_channel, self->_message_buffer, self->_message_length);
283  if (bytes != self->_message_length)
284  {
285  continue;
286  }
287 
288  if (self->_event_handler != NULL)
289  {
290  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_MESSAGE_RECEIVED, self->_event_param);
291  }
292  }
293  else if (memcmp(self->_response, "+ACK", 4) == 0)
294  {
295  if (self->_event_handler != NULL)
296  {
297  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_MESSAGE_CONFIRMED, self->_event_param);
298  }
299  }
300  else if (memcmp(self->_response, "+NOACK", 4) == 0)
301  {
302  if (self->_event_handler != NULL)
303  {
304  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_MESSAGE_NOT_CONFIRMED, self->_event_param);
305  }
306  }
307  else if (memcmp(self->_response, "+EVENT=2,2", 10) == 0)
308  {
309  if (self->_event_handler != NULL)
310  {
311  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_MESSAGE_RETRANSMISSION, self->_event_param);
312  }
313  }
314  // This is used when band changes or factory reset command
315  // 1.0.0.2 & 1.1.0.6 sends +EVENT=0,0 boot event
316  // 1.1.0.3 & 1.1.0.6 sends +EVENT=0,1 factory reset event
317  else if (memcmp(self->_response, "+EVENT=0,1", 10) == 0 || memcmp(self->_response, "+EVENT=0,0", 10) == 0)
318  {
319  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE;
320  self->_save_config_mask = 0;
321 
322  if (self->_event_handler != NULL)
323  {
324  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_MODEM_FACTORY_RESET, self->_event_param);
325  }
326  }
327  }
328 
329  continue;
330  }
331  case TWR_CMWX1ZZABZ_STATE_ERROR:
332  {
333  self->_save_config_mask = 0;
334 
335  if (self->_event_handler != NULL)
336  {
337  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_ERROR, self->_event_param);
338  }
339 
340  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE;
342  return;
343  }
344  case TWR_CMWX1ZZABZ_STATE_INITIALIZE:
345  {
346  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
347  self->_init_command_index = 0;
348 
349  twr_fifo_purge(&self->_rx_fifo);
350  twr_fifo_purge(&self->_tx_fifo);
351 
352  // Test AT command at 9600 baud
353  char cmd_at[] = "AT\r";
354  size_t length = strlen(cmd_at);
355 
356  _twr_cmwx1zzabz_write(self, cmd_at, length);
357 
358  twr_timer_start();
359  twr_timer_delay(50000);
360  twr_timer_stop();
361 
362  // Purge RX FIFO
363  twr_fifo_purge(&self->_rx_fifo);
364 
365  if (_twr_cmwx1zzabz_async_write(self, cmd_at, length) != length)
366  {
367  continue;
368  }
369 
370  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE_AT_RESPONSE;
371 
373  return;
374  }
375 
376  case TWR_CMWX1ZZABZ_STATE_INITIALIZE_AT_RESPONSE:
377  {
378  _twr_cmwx1zzabz_read_response(self);
379 
380  if (strcmp(self->_response, "+OK\r") == 0)
381  {
382  // Modem is replying @9600 baud
383  if (self->_debug)
384  {
385  twr_log_debug("OK response @9600");
386  }
387  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE_COMMAND_SEND;
388  continue;
389  }
390 
391  // No repsponse from modem, try to recover baudrate
392  if (self->_debug)
393  {
394  twr_log_debug("NO response @9600");
395  }
396  self->_state = TWR_CMWX1ZZABZ_STATE_RECOVER_BAUDRATE_UART;
397  continue;
398  }
399 
400  case TWR_CMWX1ZZABZ_STATE_RECOVER_BAUDRATE_UART:
401  {
402  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
403 
404  // 19200 baud
405  if (self->_debug)
406  {
407  twr_log_debug("Higher baudrate 19200");
408  }
409  twr_uart_deinit(self->_uart_channel);
411 
412 
413  twr_timer_init();
414 
415  char cmd_at[] = "AT\r";
416  _twr_cmwx1zzabz_write(self, cmd_at, strlen(cmd_at));
417 
418  twr_timer_start();
419  twr_timer_delay(50000);
420  twr_timer_stop();
421 
422  char cmd_at_baud[] = "AT+UART=9600\r";
423  _twr_cmwx1zzabz_write(self, cmd_at_baud, strlen(cmd_at_baud));
424 
425  twr_timer_start();
426  twr_timer_delay(50000);
427  twr_timer_stop();
428 
429  char cmd_at_reboot[] = "AT+REBOOT\r";
430  _twr_cmwx1zzabz_write(self, cmd_at_reboot, strlen(cmd_at_reboot));
431 
432  // 9600 baud
433  if (self->_debug)
434  {
435  twr_log_debug("Lower baudrate 9600");
436  }
437  twr_uart_deinit(self->_uart_channel);
439 
440  twr_fifo_init(&self->_tx_fifo, self->_tx_fifo_buffer, sizeof(self->_tx_fifo_buffer));
441  twr_fifo_init(&self->_rx_fifo, self->_rx_fifo_buffer, sizeof(self->_rx_fifo_buffer));
442 
443  twr_uart_set_async_fifo(self->_uart_channel, &self->_tx_fifo, &self->_rx_fifo);
444  twr_uart_async_read_start(self->_uart_channel, TWR_TICK_INFINITY);
445  twr_uart_set_event_handler(self->_uart_channel, _uart_event_handler, self);
446 
447  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE;
448 
450  return;
451  }
452 
453  case TWR_CMWX1ZZABZ_STATE_INITIALIZE_COMMAND_SEND:
454  {
455  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
456 
457  // Purge RX FIFO
458  twr_fifo_purge(&self->_rx_fifo);
459 
460  strcpy(self->_command, _init_commands[self->_init_command_index]);
461  size_t length = strlen(self->_command);
462 
463  if (_twr_cmwx1zzabz_async_write(self, self->_command, length) != length)
464  {
465  continue;
466  }
467 
468  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE_COMMAND_RESPONSE;
469  self->_timeout = twr_tick_get();
470 
471  if(strcmp(self->_command, "AT+REBOOT\r") == 0)
472  {
473  // Longer delay after reboot command
474  twr_scheduler_plan_current_from_now(TWR_CMWX1ZZABZ_DELAY_INITIALIZATION_REBOOT);
475  }
476  else
477  {
478  twr_scheduler_plan_current_from_now(TWR_CMWX1ZZABZ_DELAY_INITIALIZATION_AT_COMMAND);
479  }
480 
481  return;
482  }
483  case TWR_CMWX1ZZABZ_STATE_INITIALIZE_COMMAND_RESPONSE:
484  {
485  if (!_twr_cmwx1zzabz_read_response(self))
486  {
488  if (twr_tick_get() > (self->_timeout + TWR_CMWX1ZZABZ_TIMEOUT_CUSTOM_COMMAND_RESPONSE))
489  {
490  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
491  }
492  return;
493  }
494 
495  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
496  uint8_t response_handled = 0;
497 
498  // Compare first 4 cahracters from response
499  uint32_t response_valid = (memcmp(self->_response, "+OK=", 4) == 0);
500  // Pointer to the last send command to know the context of the answer
501  const char *last_command = _init_commands[self->_init_command_index];
502  // Pointer to the first character of response value after +OK=
503  char *response_string_value = &self->_response[4];
504 
505  if (strcmp(last_command, "AT+DEVADDR?\r") == 0 && response_valid)
506  {
507  // Check if user did not filled this structure to save configuration, oterwise it would be overwritten
508  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_DEVADDR) == 0)
509  {
510  memcpy(self->_config.devaddr, response_string_value, 8);
511  self->_config.devaddr[8] = '\0';
512  }
513  response_handled = 1;
514  }
515  else if (strcmp(last_command, "AT+DEVEUI?\r") == 0 && response_valid)
516  {
517  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_DEVEUI) == 0)
518  {
519  memcpy(self->_config.deveui, response_string_value, 16);
520  self->_config.deveui[16] = '\0';
521  }
522  response_handled = 1;
523  }
524  else if (strcmp(last_command, "AT+APPEUI?\r") == 0 && response_valid)
525  {
526  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_APPEUI) == 0)
527  {
528  memcpy(self->_config.appeui, response_string_value, 16);
529  self->_config.appeui[16] = '\0';
530  }
531  response_handled = 1;
532  }
533  else if (strcmp(last_command, "AT+NWKSKEY?\r") == 0 && response_valid)
534  {
535  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_NWKSKEY) == 0)
536  {
537  memcpy(self->_config.nwkskey, response_string_value, 32);
538  self->_config.nwkskey[32] = '\0';
539  }
540  response_handled = 1;
541  }
542  else if (strcmp(last_command, "AT+APPSKEY?\r") == 0 && response_valid)
543  {
544  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_APPSKEY) == 0)
545  {
546  memcpy(self->_config.appskey, response_string_value, 32);
547  self->_config.appskey[32] = '\0';
548  }
549  response_handled = 1;
550  }
551  else if (strcmp(last_command, "AT+APPKEY?\r") == 0 && response_valid)
552  {
553  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_APPKEY) == 0)
554  {
555  memcpy(self->_config.appkey, response_string_value, 32);
556  self->_config.appkey[32] = '\0';
557  }
558  response_handled = 1;
559  }
560  else if (strcmp(last_command, "AT+BAND?\r") == 0 && response_valid)
561  {
562  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_BAND) == 0)
563  {
564  self->_config.band = response_string_value[0] - '0';
565  }
566  response_handled = 1;
567  }
568  else if (strcmp(last_command, "AT+MODE?\r") == 0 && response_valid)
569  {
570  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_MODE) == 0)
571  {
572  self->_config.mode = response_string_value[0] - '0';
573  }
574  response_handled = 1;
575  }
576  else if (strcmp(last_command, "AT+CLASS?\r") == 0 && response_valid)
577  {
578  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_CLASS) == 0)
579  {
580  self->_config.class = response_string_value[0] - '0';
581  }
582  response_handled = 1;
583  }
584  else if (strcmp(last_command, "AT+RX2?\r") == 0 && response_valid)
585  {
586  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_RX2) == 0)
587  {
588  self->_config.rx2_frequency = atoi(response_string_value);
589 
590  char *comma_search = strchr(response_string_value, ',');
591  if (!comma_search)
592  {
593  continue;
594  }
595 
596  self->_config.rx2_datarate = atoi(++comma_search);
597  }
598  response_handled = 1;
599  }
600  else if (strcmp(last_command, "AT+NWK?\r") == 0 && response_valid)
601  {
602  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_NWK) == 0)
603  {
604  self->_config.nwk_public = response_string_value[0] - '0';
605  }
606  response_handled = 1;
607  }
608  else if (strcmp(last_command, "AT+ADR?\r") == 0 && response_valid)
609  {
610  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_ADAPTIVE_DATARATE) == 0)
611  {
612  self->_config.adaptive_datarate = response_string_value[0] == '1';
613  }
614  response_handled = 1;
615  }
616  else if (strcmp(last_command, "AT+DR?\r") == 0 && response_valid)
617  {
618  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_DATARATE) == 0)
619  {
620  self->_config.datarate = atoi(response_string_value);
621  }
622  response_handled = 1;
623  }
624  else if (strcmp(last_command, "AT+REP?\r") == 0 && response_valid)
625  {
626  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_REP) == 0)
627  {
628  self->_config.repetition_unconfirmed = atoi(response_string_value);
629  }
630  response_handled = 1;
631  }
632  else if (strcmp(last_command, "AT+RTYNUM?\r") == 0 && response_valid)
633  {
634  if ((self->_save_config_mask & 1 << TWR_CMWX1ZZABZ_CONFIG_INDEX_RTYNUM) == 0)
635  {
636  self->_config.repetition_confirmed = atoi(response_string_value);
637  }
638  response_handled = 1;
639  }
640  else if (strcmp(last_command, "AT+DUTYCYCLE=0\r") == 0 && strcmp(self->_response, "+ERR=-17\r") == 0)
641  {
642  // DUTYCYLE is unusable in some band configuration, ignore this err response
643  response_handled = 1;
644  }
645  else if (strcmp(last_command, "AT+DWELL=0,0\r") == 0 && strcmp(self->_response, "+ERR=-17\r") == 0)
646  {
647  // DWELL is used only in AS923
648  response_handled = 1;
649  }
650  else if (strcmp(last_command, "AT+JOINDC=0\r") == 0 && (strcmp(self->_response, "+ERR=-1\r") == 0 || strcmp(self->_response, "+ERR=-14\r") == 0 || strcmp(self->_response, "+ERR=-17\r") == 0))
651  {
652  // JOINDC is in the firmware 1.1.06 and higher
653  // +ERR=-1 case is there for older firmwares
654  // +ERR=-14 case is there for 1.1.06 in case modem has set MODE=0 (ABP)
655  // +ERR=-17 case command is not supported in current band
656  response_handled = 1;
657  }
658  else if (strcmp(last_command, "AT+VER?\r") == 0 && memcmp(self->_response, "+OK=", 4) == 0)
659  {
660  // Grab firmware version, only first part eg. 1.1.06
661  // +OK=1.1.06,Aug 24 2020 16:11:57<\r>
662  char *comma = strchr(self->_response, ',');
663  if (comma)
664  {
665  char *first_character = &self->_response[4];
666  memset(self->_fw_version, '\0', sizeof(self->_fw_version));
667  int len = comma - first_character;
668  memcpy(self->_fw_version, first_character, len);
669  }
670  response_handled = 1;
671  }
672  else if ( strcmp(last_command, "AT\r") == 0 &&
673  strcmp(self->_response, "+OK\r") == 0
674  )
675  {
676  response_handled = 1;
677  }
678  // Generic OK response to other commands
679  else if (memcmp(self->_response, "+OK", 3) == 0)
680  {
681  response_handled = 1;
682  }
683 
684  if (!response_handled)
685  {
686  continue;
687  }
688 
689  self->_init_command_index++;
690 
691  if (_init_commands[self->_init_command_index] == NULL)
692  {
693  // If configuration was changed and flag set, save them
694  if (self->_save_config_mask)
695  {
696  self->_state = TWR_CMWX1ZZABZ_STATE_CONFIG_SAVE_SEND;
697  self->_save_command_index = 0;
698  }
699  else
700  {
701  self->_state = TWR_CMWX1ZZABZ_STATE_READY;
702  }
703  }
704  else
705  {
706  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE_COMMAND_SEND;
707  }
708 
709  continue;
710  }
711  case TWR_CMWX1ZZABZ_STATE_SEND_MESSAGE_COMMAND:
712  case TWR_CMWX1ZZABZ_STATE_SEND_MESSAGE_CONFIRMED_COMMAND:
713  {
714  if (self->_state == TWR_CMWX1ZZABZ_STATE_SEND_MESSAGE_CONFIRMED_COMMAND)
715  {
716  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+PCTX %d,%d\r", self->_tx_port, self->_message_length);
717  }
718  else
719  {
720  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+PUTX %d,%d\r", self->_tx_port, self->_message_length);
721  }
722 
723  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
724 
725  uint8_t command_length = strlen(self->_command);
726 
727  for (size_t i = 0; i < self->_message_length; i++)
728  {
729  // put binary data directly to the "string" buffer
730  self->_command[command_length + i] = self->_message_buffer[i];
731  }
732 
733  self->_command[command_length + self->_message_length] = '\r';
734 
735  size_t length = command_length + self->_message_length + 1; // 1 for \n
736 
737  if (_twr_cmwx1zzabz_async_write(self, self->_command, length) != length)
738  {
739  continue;
740  }
741 
742  self->_state = TWR_CMWX1ZZABZ_STATE_SEND_MESSAGE_RESPONSE;
743 
744  if (self->_event_handler != NULL)
745  {
746  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_SEND_MESSAGE_START, self->_event_param);
747  }
748 
749  twr_scheduler_plan_current_from_now(TWR_CMWX1ZZABZ_DELAY_SEND_MESSAGE_RESPONSE);
750 
751  return;
752  }
753  case TWR_CMWX1ZZABZ_STATE_SEND_MESSAGE_RESPONSE:
754  {
755  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
756 
757  if (!_twr_cmwx1zzabz_read_response(self))
758  {
759  continue;
760  }
761 
762  if (strcmp(self->_response, "+OK\r") != 0)
763  {
764  continue;
765  }
766 
767  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
768 
769  if (self->_event_handler != NULL)
770  {
771  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_SEND_MESSAGE_DONE, self->_event_param);
772  }
773 
774  continue;
775  }
776  case TWR_CMWX1ZZABZ_STATE_CONFIG_SAVE_SEND:
777  {
778  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
779 
780  // There are no more config items to send
781  if (self->_save_config_mask == 0)
782  {
783  if (self->_event_handler != NULL)
784  {
785  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_CONFIG_SAVE_DONE, self->_event_param);
786  }
787 
788  self->_state = TWR_CMWX1ZZABZ_STATE_READY;
789  continue;
790  }
791 
792  // Find config item that has been changed
793  for (uint8_t i = 0; i < TWR_CMWX1ZZABZ_CONFIG_INDEX_LAST_ITEM; i++)
794  {
795  if (self->_save_config_mask & 1 << i)
796  {
797  self->_save_command_index = i;
798  break;
799  }
800  }
801 
802  // Purge RX FIFO
803  twr_fifo_purge(&self->_rx_fifo);
804 
805  switch (self->_save_command_index)
806  {
807  case TWR_CMWX1ZZABZ_CONFIG_INDEX_DEVADDR:
808  {
809  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+DEVADDR=%s\r", self->_config.devaddr);
810  break;
811  }
812  case TWR_CMWX1ZZABZ_CONFIG_INDEX_DEVEUI:
813  {
814  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+DEVEUI=%s\r", self->_config.deveui);
815  break;
816  }
817  case TWR_CMWX1ZZABZ_CONFIG_INDEX_APPEUI:
818  {
819  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+APPEUI=%s\r", self->_config.appeui);
820  break;
821  }
822  case TWR_CMWX1ZZABZ_CONFIG_INDEX_NWKSKEY:
823  {
824  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+NWKSKEY=%s\r", self->_config.nwkskey);
825  break;
826  }
827  case TWR_CMWX1ZZABZ_CONFIG_INDEX_APPSKEY:
828  {
829  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+APPSKEY=%s\r", self->_config.appskey);
830  break;
831  }
832  case TWR_CMWX1ZZABZ_CONFIG_INDEX_APPKEY:
833  {
834  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+APPKEY=%s\r", self->_config.appkey);
835  break;
836  }
837  case TWR_CMWX1ZZABZ_CONFIG_INDEX_BAND:
838  {
839  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+BAND=%d\r", self->_config.band);
840  break;
841  }
842  case TWR_CMWX1ZZABZ_CONFIG_INDEX_MODE:
843  {
844  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+MODE=%d\r", self->_config.mode);
845  break;
846  }
847  case TWR_CMWX1ZZABZ_CONFIG_INDEX_CLASS:
848  {
849  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+CLASS=%d\r", self->_config.class);
850  break;
851  }
852  case TWR_CMWX1ZZABZ_CONFIG_INDEX_RX2:
853  {
854  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+RX2=%d,%d\r", (int) self->_config.rx2_frequency, self->_config.rx2_datarate);
855  break;
856  }
857  case TWR_CMWX1ZZABZ_CONFIG_INDEX_NWK:
858  {
859  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+NWK=%d\r", (int) self->_config.nwk_public);
860  break;
861  }
862  case TWR_CMWX1ZZABZ_CONFIG_INDEX_ADAPTIVE_DATARATE:
863  {
864  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+ADR=%d\r", self->_config.adaptive_datarate ? 1 : 0);
865  break;
866  }
867  case TWR_CMWX1ZZABZ_CONFIG_INDEX_DATARATE:
868  {
869  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+DR=%d\r", (int) self->_config.datarate);
870  break;
871  }
872  case TWR_CMWX1ZZABZ_CONFIG_INDEX_REP:
873  {
874  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+REP=%d\r", (int) self->_config.repetition_unconfirmed);
875  break;
876  }
877  case TWR_CMWX1ZZABZ_CONFIG_INDEX_RTYNUM:
878  {
879  snprintf(self->_command, TWR_CMWX1ZZABZ_TX_FIFO_BUFFER_SIZE, "AT+RTYNUM=%d\r", (int) self->_config.repetition_confirmed);
880  break;
881  }
882 
883  default:
884  {
885  break;
886  }
887  }
888 
889  size_t length = strlen(self->_command);
890 
891  if (_twr_cmwx1zzabz_async_write(self, self->_command, length) != length)
892  {
893  continue;
894  }
895 
896  self->_state = TWR_CMWX1ZZABZ_STATE_CONFIG_SAVE_RESPONSE;
897  twr_scheduler_plan_current_from_now(TWR_CMWX1ZZABZ_DELAY_CONFIG_SAVE);
898  return;
899  }
900 
901  case TWR_CMWX1ZZABZ_STATE_CONFIG_SAVE_RESPONSE:
902  {
903  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
904 
905  if (!_twr_cmwx1zzabz_read_response(self))
906  {
907  continue;
908  }
909 
910  // Jump to error state when response is not OK
911  if (memcmp(self->_response, "+OK", 3) != 0)
912  {
913  continue;
914  }
915 
916  // Clean bit mask
917  self->_save_config_mask &= ~(1 << self->_save_command_index);
918 
919  self->_state = TWR_CMWX1ZZABZ_STATE_CONFIG_SAVE_SEND;
920  continue;
921 
922  }
923 
924  case TWR_CMWX1ZZABZ_STATE_JOIN_SEND:
925  {
926  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
927 
928  // Purge RX FIFO
929  twr_fifo_purge(&self->_rx_fifo);
930 
931  strcpy(self->_command, "AT+JOIN\r");
932 
933  size_t length = strlen(self->_command);
934  if (_twr_cmwx1zzabz_async_write(self, self->_command, length) != length)
935  {
936  continue;
937  }
938 
939  // Clear join command flag
940  self->_join_command = false;
941 
942  self->_state = TWR_CMWX1ZZABZ_STATE_JOIN_RESPONSE;
943  self->_timeout = twr_tick_get();
944  twr_scheduler_plan_current_from_now(TWR_CMWX1ZZABZ_DELAY_JOIN_RESPONSE);
945  return;
946  }
947 
948  case TWR_CMWX1ZZABZ_STATE_JOIN_RESPONSE:
949  {
950  bool join_successful = false;
951 
952  if (!_twr_cmwx1zzabz_read_response(self))
953  {
955  if (twr_tick_get() > (self->_timeout + TWR_CMWX1ZZABZ_TIMEOUT_JOIN))
956  {
957  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
958  }
959  return;
960  }
961 
962  if (memcmp(self->_response, "+OK", 3) == 0)
963  {
965  return;
966  }
967 
968  // Fix bug when loraMAC is stuck with -7 answer
969  if (memcmp(self->_response, "+ERR=-7", 7) == 0)
970  {
971  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
972  continue;
973  }
974 
975  // Response EVENT=1,1 means JOIN was successful
976  if (memcmp(self->_response, "+EVENT=1,1", 10) == 0)
977  {
978  join_successful = true;
979  }
980 
981  if (join_successful)
982  {
983  if (self->_event_handler != NULL)
984  {
985  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_JOIN_SUCCESS, self->_event_param);
986  }
987  }
988  else
989  {
990  if (self->_event_handler != NULL)
991  {
992  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_JOIN_ERROR, self->_event_param);
993  }
994  }
995 
996  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
997  continue;
998  }
999 
1000  case TWR_CMWX1ZZABZ_STATE_LINK_CHECK_SEND:
1001  {
1002  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
1003 
1004  // Purge RX FIFO
1005  twr_fifo_purge(&self->_rx_fifo);
1006 
1007  strcpy(self->_command, "AT+LNCHECK\r");
1008 
1009  size_t length = strlen(self->_command);
1010  if (_twr_cmwx1zzabz_async_write(self, self->_command, length) != length)
1011  {
1012  continue;
1013  }
1014 
1015  self->_link_check_command = false;
1016 
1017  self->_state = TWR_CMWX1ZZABZ_STATE_LINK_CHECK_RESPONSE;
1018  self->_timeout = twr_tick_get();
1019  twr_scheduler_plan_current_from_now(TWR_CMWX1ZZABZ_DELAY_LINK_CHECK_RESPONSE);
1020  return;
1021  }
1022 
1023  case TWR_CMWX1ZZABZ_STATE_LINK_CHECK_RESPONSE:
1024  {
1025  if (!_twr_cmwx1zzabz_read_response(self))
1026  {
1028  if (twr_tick_get() > (self->_timeout + TWR_CMWX1ZZABZ_TIMEOUT_LNCHECK))
1029  {
1030  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
1031  }
1032  return;
1033  }
1034 
1035  if (memcmp(self->_response, "+OK", 3) == 0)
1036  {
1038  return;
1039  }
1040 
1041  // Check for response event
1042  if (strstr(self->_response, "+EVENT=2,") > (char *)0)
1043  {
1044  // MAC answer
1045  char *event_str = strchr(self->_response, ',');
1046  event_str++;
1047 
1048  uint8_t link_check_event = atoi(event_str);
1049 
1050  // Clean in case MAC response is 0 and we do not receive +ANS
1051  self->_cmd_link_check_gwcnt = 0;
1052  self->_cmd_link_check_margin = 0;
1053 
1054  if (link_check_event == 1)
1055  {
1056  self->_state = TWR_CMWX1ZZABZ_STATE_LINK_CHECK_RESPONSE_ANS;
1057  // Since +ANS was added in 1.1.03 we will wait until this URC arrives
1059  return;
1060  }
1061  }
1062  else
1063  {
1064  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
1065  continue;
1066  }
1067 
1068  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
1069 
1070  if (self->_event_handler != NULL)
1071  {
1072  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_LINK_CHECK_NOK, self->_event_param);
1073  }
1074 
1076  return;
1077  }
1078 
1079  case TWR_CMWX1ZZABZ_STATE_LINK_CHECK_RESPONSE_ANS:
1080  {
1081  // Did we received +ANS response? FW 1.0.02 don't have it
1082  if (_twr_cmwx1zzabz_read_response(self))
1083  {
1084  // Search for ANS
1085  char *ans = strstr(self->_response, "+ANS=2,");
1086 
1087  if (ans)
1088  {
1089  ans += 7; // skip "+ANS=2,"
1090  self->_cmd_link_check_margin = atoi(ans);
1091 
1092  // MAC answer
1093  char *gwcnt_str = strchr(ans, ',');
1094  gwcnt_str++;
1095 
1096  self->_cmd_link_check_gwcnt = atoi(gwcnt_str);
1097  }
1098  }
1099 
1100  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
1101 
1102  if (self->_event_handler != NULL)
1103  {
1104  self->_event_handler(self, TWR_CMWX1ZZABZ_EVENT_LINK_CHECK_OK, self->_event_param);
1105  }
1106 
1108  return;
1109  }
1110 
1111  case TWR_CMWX1ZZABZ_STATE_CUSTOM_COMMAND_SEND:
1112  {
1113  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
1114 
1115  // Purge RX FIFO
1116  twr_fifo_purge(&self->_rx_fifo);
1117 
1118  strcpy(self->_command, self->_custom_command_buf);
1119 
1120  size_t length = strlen(self->_command);
1121  if (_twr_cmwx1zzabz_async_write(self, self->_command, length) != length)
1122  {
1123  continue;
1124  }
1125 
1126  self->_state = TWR_CMWX1ZZABZ_STATE_CUSTOM_COMMAND_RESPONSE;
1127  self->_timeout = twr_tick_get();
1128  twr_scheduler_plan_current_from_now(TWR_CMWX1ZZABZ_DELAY_CUSTOM_COMMAND_RESPONSE);
1129  return;
1130  }
1131 
1132  case TWR_CMWX1ZZABZ_STATE_CUSTOM_COMMAND_RESPONSE:
1133  {
1134  if (!_twr_cmwx1zzabz_read_response(self))
1135  {
1137  if (twr_tick_get() > (self->_timeout + TWR_CMWX1ZZABZ_TIMEOUT_CUSTOM_COMMAND_RESPONSE))
1138  {
1139  self->_custom_command = false;
1140  self->_state = TWR_CMWX1ZZABZ_STATE_ERROR;
1141  // If factory reset command, then reinitialize modem
1142  if (strcmp(self->_command, "AT+FACNEW\r") == 0)
1143  {
1145  self->_state = TWR_CMWX1ZZABZ_STATE_INITIALIZE;
1146  }
1147  }
1148  return;
1149  }
1150 
1151  // LoRa Module sometimes don't answer on RFQ after JOIN, go to idle instead of error loop
1152  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE; //TWR_CMWX1ZZABZ_STATE_ERROR;
1153  self->_custom_command = false;
1154 
1155  // In 1.0.02 fw FRMCNT is not supported, handle this in case this command
1156  // and others in future versions fails gracefully and jump to idle instead of ERROR state
1157  if (memcmp(self->_response, "+ERR=-1", 7) == 0)
1158  {
1159  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
1160  self->_cmd_frmcnt_uplink = 0;
1161  self->_cmd_frmcnt_downlink = 0;
1162  continue;
1163  }
1164 
1165  if (memcmp(self->_response, "+OK=", 4) == 0)
1166  {
1168 
1169  // If we don't know the response, it must be custom AT command
1170  twr_cmwx1zzabz_event_t event = TWR_CMWX1ZZABZ_EVENT_CUSTOM_AT;
1171 
1172  if (strcmp(self->_custom_command_buf, "AT+RFQ?\r") == 0)
1173  {
1174  // RFQ request
1175  char *rssi_str = strchr(self->_response, '=');
1176  rssi_str++;
1177 
1178  char *snr_str = strchr(self->_response, ',');
1179  snr_str++;
1180 
1181  self->_cmd_rfq_rssi = atoi(rssi_str);
1182  self->_cmd_rfq_snr = atoi(snr_str);
1183  event = TWR_CMWX1ZZABZ_EVENT_RFQ;
1184  }
1185 
1186  if (strcmp(self->_custom_command_buf, "AT+FRMCNT?\r") == 0)
1187  {
1188  // Framecounter request
1189  char *uplink_str = strchr(self->_response, '=');
1190  uplink_str++;
1191 
1192  char *downlink_str = strchr(self->_response, ',');
1193  downlink_str++;
1194 
1195  self->_cmd_frmcnt_uplink = atoi(uplink_str);
1196  self->_cmd_frmcnt_downlink = atoi(downlink_str);
1198  }
1199 
1200  self->_state = TWR_CMWX1ZZABZ_STATE_IDLE;
1201 
1202  if (self->_event_handler != NULL)
1203  {
1204  self->_event_handler(self, event, self->_event_param);
1205  }
1206  return;
1207  }
1208 
1209  continue;
1210  }
1211 
1212  case TWR_CMWX1ZZABZ_STATE_RECOVER_BAUDRATE_REBOOT:
1213  {
1214  // TODO
1215  break;
1216  }
1217 
1218  default:
1219  {
1220  break;
1221  }
1222  }
1223  }
1224 }
1225 
1227 {
1228  self->_join_command = true;
1229  twr_scheduler_plan_now(self->_task_id);
1230 }
1231 
1233 {
1234  self->_tx_port = port;
1235 }
1236 
1238 {
1239  return self->_tx_port;
1240 }
1241 
1243 {
1244  strncpy(self->_config.devaddr, devaddr, 8+1);
1245 
1246  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_DEVADDR);
1247 }
1248 
1250 {
1251  strncpy(devaddr, self->_config.devaddr, 8+1);
1252 }
1253 
1255 {
1256  strncpy(self->_config.deveui, deveui, 16+1);
1257 
1258  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_DEVEUI);
1259 }
1260 
1262 {
1263  strncpy(deveui, self->_config.deveui, 16+1);
1264 }
1265 
1267 {
1268  strncpy(self->_config.appeui, appeui, 16+1);
1269 
1270  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_APPEUI);
1271 }
1272 
1274 {
1275  strncpy(appeui, self->_config.appeui, 16+1);
1276 }
1277 
1279 {
1280  strncpy(self->_config.nwkskey, nwkskey, 32);
1281 
1282  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_NWKSKEY);
1283 }
1284 
1286 {
1287  strncpy(nwkskey, self->_config.nwkskey, 32+1);
1288 }
1289 
1291 {
1292  strncpy(self->_config.appskey, appskey, 32);
1293 
1294  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_APPSKEY);
1295 }
1296 
1298 {
1299  strncpy(appskey, self->_config.appskey, 32+1);
1300 }
1301 
1303 {
1304  strncpy(self->_config.appkey, appkey, 32+1);
1305 
1306  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_APPKEY);
1307 }
1308 
1310 {
1311  strncpy(appkey, self->_config.appkey, 32+1);
1312 }
1313 
1315 {
1316  self->_config.band = band;
1317 
1318  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_BAND);
1319 }
1320 
1322 {
1323  return self->_config.band;
1324 }
1325 
1327 {
1328  self->_config.mode = mode;
1329 
1330  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_MODE);
1331 }
1332 
1334 {
1335  return self->_config.mode;
1336 }
1337 
1339 {
1340  self->_config.class = class;
1341 
1342  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_CLASS);
1343 }
1344 
1346 {
1347  return self->_config.class;
1348 }
1349 
1351 {
1352  return self->_message_port;
1353 }
1354 
1356 {
1357  return self->_message_length;
1358 }
1359 
1360 uint32_t twr_cmwx1zzabz_get_received_message_data(twr_cmwx1zzabz_t *self, uint8_t *buffer, uint32_t buffer_size)
1361 {
1362  if (self->_message_length > buffer_size)
1363  {
1364  return 0;
1365  }
1366 
1367  memcpy(buffer, self->_message_buffer, self->_message_length);
1368 
1369  return self->_message_length;
1370 }
1371 
1372 void twr_cmwx1zzabz_set_rx2(twr_cmwx1zzabz_t *self, uint32_t frequency, uint8_t datarate)
1373 {
1374  self->_config.rx2_frequency = frequency;
1375 
1376  self->_config.rx2_datarate = datarate;
1377 
1378  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_RX2);
1379 }
1380 
1381 void twr_cmwx1zzabz_get_rx2(twr_cmwx1zzabz_t *self, uint32_t *frequency, uint8_t *datarate)
1382 {
1383  *frequency = self->_config.rx2_frequency;
1384  *datarate = self->_config.rx2_datarate;
1385 }
1386 
1388 {
1389  self->_config.nwk_public = public;
1390 
1391  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_NWK);
1392 }
1393 
1395 {
1396  return self->_config.nwk_public;
1397 }
1398 
1400 {
1401  self->_config.adaptive_datarate = enable;
1402 
1403  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_ADAPTIVE_DATARATE);
1404 }
1405 
1407 {
1408  return self->_config.adaptive_datarate;
1409 }
1410 
1411 void twr_cmwx1zzabz_set_datarate(twr_cmwx1zzabz_t *self, uint8_t datarate)
1412 {
1413  self->_config.datarate = datarate;
1414 
1415  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_DATARATE);
1416 }
1417 
1419 {
1420  return self->_config.datarate;
1421 }
1422 
1424 {
1425  self->_config.repetition_unconfirmed = repeat;
1426 
1427  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_REP);
1428 }
1429 
1431 {
1432  return self->_config.repetition_unconfirmed;
1433 }
1434 
1436 {
1437  self->_config.repetition_confirmed = repeat;
1438 
1439  _twr_cmwx1zzabz_save_config(self, TWR_CMWX1ZZABZ_CONFIG_INDEX_RTYNUM);
1440 }
1441 
1443 {
1444  return self->_config.repetition_confirmed;
1445 }
1446 
1448 {
1449  return self->_command;
1450 }
1451 
1453 {
1454  return self->_response;
1455 }
1456 
1457 bool twr_cmwx1zzabz_custom_at(twr_cmwx1zzabz_t *self, char *at_command)
1458 {
1459  if (self->_custom_command)
1460  {
1461  return false;
1462  }
1463 
1464  self->_custom_command = true;
1465  snprintf(self->_custom_command_buf, sizeof(self->_custom_command_buf), "%s\r", at_command);
1466 
1467  twr_scheduler_plan_now(self->_task_id);
1468 
1469  return true;
1470 }
1471 
1473 {
1474  if (self->_custom_command)
1475  {
1476  return false;
1477  }
1478 
1479  self->_custom_command = true;
1480  strcpy(self->_custom_command_buf, "AT+RFQ?\r");
1481 
1482  twr_scheduler_plan_now(self->_task_id);
1483 
1484  return true;
1485 }
1486 
1487 bool twr_cmwx1zzabz_get_rfq(twr_cmwx1zzabz_t *self, int32_t *rssi, int32_t *snr)
1488 {
1489  *rssi = self->_cmd_rfq_rssi;
1490  *snr = self->_cmd_rfq_snr;
1491 
1492  return true;
1493 }
1494 
1496 {
1497  if (self->_custom_command)
1498  {
1499  return false;
1500  }
1501 
1502  self->_custom_command = true;
1503  strcpy(self->_custom_command_buf, "AT+FRMCNT?\r");
1504 
1505  twr_scheduler_plan_now(self->_task_id);
1506 
1507  return true;
1508 }
1509 
1510 bool twr_cmwx1zzabz_get_frame_counter(twr_cmwx1zzabz_t *self, uint32_t *uplink, uint32_t *downlink)
1511 {
1512  *uplink = self->_cmd_frmcnt_uplink;
1513  *downlink = self->_cmd_frmcnt_downlink;
1514 
1515  return true;
1516 }
1517 
1519 {
1520  if (self->_custom_command)
1521  {
1522  return false;
1523  }
1524 
1525  self->_custom_command = true;
1526  strcpy(self->_custom_command_buf, "AT+FACNEW\r");
1527 
1528  twr_scheduler_plan_now(self->_task_id);
1529 
1530  return true;
1531 }
1532 
1534 {
1535  return self->_fw_version;
1536 }
1537 
1539 {
1540  if (self->_state != TWR_CMWX1ZZABZ_STATE_IDLE)
1541  {
1542  return false;
1543  }
1544 
1545  self->_link_check_command = true;
1546 
1547  twr_scheduler_plan_now(self->_task_id);
1548 
1549  return true;
1550 }
1551 
1552 bool twr_cmwx1zzabz_get_link_check(twr_cmwx1zzabz_t *self, uint8_t *margin, uint8_t *gateway_count)
1553 {
1554  *margin = self->_cmd_link_check_margin;
1555  *gateway_count = self->_cmd_link_check_gwcnt;
1556 
1557  return true;
1558 }
1559 
1560 static bool _twr_cmwx1zzabz_read_response(twr_cmwx1zzabz_t *self)
1561 {
1562  while (true)
1563  {
1564  char rx_character;
1565 
1566  if (twr_uart_async_read(self->_uart_channel, &rx_character, 1) == 0)
1567  {
1568  return false;
1569  }
1570 
1571  if (rx_character == '\n')
1572  {
1573  continue;
1574  }
1575 
1576  self->_response[self->_response_length++] = rx_character;
1577 
1578  if (rx_character == '\r')
1579  {
1580  if (self->_response_length == 1)
1581  {
1582  self->_response_length = 0;
1583 
1584  continue;
1585  }
1586 
1587  self->_response[self->_response_length] = '\0';
1588 
1589  break;
1590  }
1591 
1592  if (self->_response_length == sizeof(self->_response) - 1)
1593  {
1594  return false;
1595  }
1596  }
1597 
1598  if (self->_debug)
1599  {
1600  twr_log_debug("LoRa RX: %s", (const char*)self->_response);
1601  }
1602 
1603  self->_response_length = 0;
1604 
1605  return true;
1606 }
1607 
1608 static void _twr_cmwx1zzabz_save_config(twr_cmwx1zzabz_t *self, twr_cmwx1zzabz_config_index_t config_index)
1609 {
1610  self->_save_config_mask |= 1 << config_index;
1611 
1612  if (self->_state == TWR_CMWX1ZZABZ_STATE_IDLE)
1613  {
1614  twr_scheduler_plan_now(self->_task_id);
1615  }
1616 }
void twr_cmwx1zzabz_set_adaptive_datarate(twr_cmwx1zzabz_t *self, bool enable)
Set the configuration adaptive data rate.
bool twr_cmwx1zzabz_link_check(twr_cmwx1zzabz_t *self)
Request send of link check packet.
void twr_cmwx1zzabz_set_appeui(twr_cmwx1zzabz_t *self, char *appeui)
Set APPEUI.
void twr_cmwx1zzabz_get_deveui(twr_cmwx1zzabz_t *self, char *deveui)
Get DEVEUI.
void twr_cmwx1zzabz_init(twr_cmwx1zzabz_t *self, twr_uart_channel_t uart_channel)
Initialize CMWX1ZZABZ.
char * twr_cmwx1zzabz_get_error_command(twr_cmwx1zzabz_t *self)
Get pointer to string containg last sent command.
uint8_t twr_cmwx1zzabz_get_nwk_public(twr_cmwx1zzabz_t *self)
Get the configuration if public networks are enabled.
void twr_cmwx1zzabz_set_rx2(twr_cmwx1zzabz_t *self, uint32_t frequency, uint8_t datarate)
Set the frequency and datarate for RX2 receive window.
void twr_cmwx1zzabz_set_nwkskey(twr_cmwx1zzabz_t *self, char *nwkskey)
Set NWKSKEY.
void twr_cmwx1zzabz_get_appkey(twr_cmwx1zzabz_t *self, char *appkey)
Get APPKEY.
uint8_t twr_cmwx1zzabz_get_datarate(twr_cmwx1zzabz_t *self)
Get the configuration of datarate.
bool twr_cmwx1zzabz_send_message(twr_cmwx1zzabz_t *self, const void *buffer, size_t length)
Send LoRa message.
void twr_cmwx1zzabz_set_debug(twr_cmwx1zzabz_t *self, bool debug)
Set debugging flag which prints modem communication to twr_log.
void twr_cmwx1zzabz_get_nwkskey(twr_cmwx1zzabz_t *self, char *nwkskey)
Set NWKSKEY.
void twr_cmwx1zzabz_set_event_handler(twr_cmwx1zzabz_t *self, void(*event_handler)(twr_cmwx1zzabz_t *, twr_cmwx1zzabz_event_t, void *), void *event_param)
Set callback function.
bool twr_cmwx1zzabz_is_ready(twr_cmwx1zzabz_t *self)
Check if modem is ready for commands.
bool twr_cmwx1zzabz_get_rfq(twr_cmwx1zzabz_t *self, int32_t *rssi, int32_t *snr)
Get RF quality values in callback.
void twr_cmwx1zzabz_set_band(twr_cmwx1zzabz_t *self, twr_cmwx1zzabz_config_band_t band)
Set BAND.
void twr_cmwx1zzabz_set_repeat_unconfirmed(twr_cmwx1zzabz_t *self, uint8_t repeat)
Set number of transmissions of unconfirmed message.
bool twr_cmwx1zzabz_frame_counter(twr_cmwx1zzabz_t *self)
Request frame counter value.
twr_cmwx1zzabz_config_mode_t
LoRa mode ABP/OTAA.
bool twr_cmwx1zzabz_get_link_check(twr_cmwx1zzabz_t *self, uint8_t *margin, uint8_t *gateway_count)
Get link check values.
void twr_cmwx1zzabz_set_devaddr(twr_cmwx1zzabz_t *self, char *devaddr)
Set DEVADDR.
void twr_cmwx1zzabz_join(twr_cmwx1zzabz_t *self)
Start LoRa OTAA join procedure.
char * twr_cmwx1zzabz_get_fw_version(twr_cmwx1zzabz_t *self)
Get firmware version string.
twr_cmwx1zzabz_config_band_t twr_cmwx1zzabz_get_band(twr_cmwx1zzabz_t *self)
Get BAND.
void twr_cmwx1zzabz_set_port(twr_cmwx1zzabz_t *self, uint8_t port)
Set the port for the transmission of the messages.
void twr_cmwx1zzabz_get_devaddr(twr_cmwx1zzabz_t *self, char *devaddr)
Get DEVADDR.
bool twr_cmwx1zzabz_rfq(twr_cmwx1zzabz_t *self)
Request RF quality of the last received packet (JOIN, LNPARAM, confirmed message)
void twr_cmwx1zzabz_set_class(twr_cmwx1zzabz_t *self, twr_cmwx1zzabz_config_class_t class)
Set device class.
uint32_t twr_cmwx1zzabz_get_received_message_data(twr_cmwx1zzabz_t *self, uint8_t *buffer, uint32_t buffer_size)
Get received message data.
twr_cmwx1zzabz_config_class_t
LoRa device class A or C.
void twr_cmwx1zzabz_reboot(twr_cmwx1zzabz_t *self)
Reboot and initialize CMWX1ZZABZ.
void twr_cmwx1zzabz_get_appskey(twr_cmwx1zzabz_t *self, char *appskey)
Get APPSKEY.
uint32_t twr_cmwx1zzabz_get_received_message_length(twr_cmwx1zzabz_t *self)
Get length of the received message.
void twr_cmwx1zzabz_set_nwk_public(twr_cmwx1zzabz_t *self, uint8_t public)
Set the configuration enabling public networks.
void twr_cmwx1zzabz_set_deveui(twr_cmwx1zzabz_t *self, char *deveui)
Set DEVEUI.
twr_cmwx1zzabz_config_band_t
Frequency modes and standards.
uint8_t twr_cmwx1zzabz_get_port(twr_cmwx1zzabz_t *self)
Get the port for the transmission of the messages.
uint8_t twr_cmwx1zzabz_get_repeat_confirmed(twr_cmwx1zzabz_t *self)
Get number of transmissions of confirmed message.
bool twr_cmwx1zzabz_custom_at(twr_cmwx1zzabz_t *self, char *at_command)
Send custom AT command to LoRa Module.
void twr_cmwx1zzabz_deinit(twr_cmwx1zzabz_t *self)
Deinitialize CMWX1ZZABZ.
uint8_t twr_cmwx1zzabz_get_repeat_unconfirmed(twr_cmwx1zzabz_t *self)
Get number of transmissions of unconfirmed message.
void twr_cmwx1zzabz_set_datarate(twr_cmwx1zzabz_t *self, uint8_t datarate)
Set the configuration of datarate.
void twr_cmwx1zzabz_set_appkey(twr_cmwx1zzabz_t *self, char *appkey)
Set APPKEY.
void twr_cmwx1zzabz_set_appskey(twr_cmwx1zzabz_t *self, char *appskey)
Set APPSKEY.
twr_cmwx1zzabz_config_mode_t twr_cmwx1zzabz_get_mode(twr_cmwx1zzabz_t *self)
Get ABP/OTAA mode.
void twr_cmwx1zzabz_get_appeui(twr_cmwx1zzabz_t *self, char *appeui)
Get APPEUI.
bool twr_cmwx1zzabz_get_adaptive_datarate(twr_cmwx1zzabz_t *self)
Get the configuration if adaptive data rate are enabled.
twr_cmwx1zzabz_config_class_t twr_cmwx1zzabz_get_class(twr_cmwx1zzabz_t *self)
Get device class.
bool twr_cmwx1zzabz_factory_reset(twr_cmwx1zzabz_t *self)
Send factory reset command to LoRa Module.
struct twr_cmwx1zzabz_t twr_cmwx1zzabz_t
CMWX1ZZABZ instance.
bool twr_cmwx1zzabz_get_frame_counter(twr_cmwx1zzabz_t *self, uint32_t *uplink, uint32_t *downlink)
Get frame counter value.
void twr_cmwx1zzabz_get_rx2(twr_cmwx1zzabz_t *self, uint32_t *frequency, uint8_t *datarate)
Get the frequency and datarate for RX2 receive window.
bool twr_cmwx1zzabz_send_message_confirmed(twr_cmwx1zzabz_t *self, const void *buffer, size_t length)
Send LoRa confirmed message.
void twr_cmwx1zzabz_set_mode(twr_cmwx1zzabz_t *self, twr_cmwx1zzabz_config_mode_t mode)
Set ABP/OTAA mode.
char * twr_cmwx1zzabz_get_error_response(twr_cmwx1zzabz_t *self)
Get pointer to string containg response on last sent command.
uint8_t twr_cmwx1zzabz_get_received_message_port(twr_cmwx1zzabz_t *self)
Get port of the received message.
twr_cmwx1zzabz_event_t
Callback events.
void twr_cmwx1zzabz_set_repeat_confirmed(twr_cmwx1zzabz_t *self, uint8_t repeat)
Set number of transmissions of confirmed message.
@ TWR_CMWX1ZZABZ_EVENT_ERROR
Error event.
@ TWR_CMWX1ZZABZ_EVENT_CONFIG_SAVE_DONE
Configuration save done.
@ TWR_CMWX1ZZABZ_EVENT_MESSAGE_RECEIVED
Received message.
@ TWR_CMWX1ZZABZ_EVENT_FRAME_COUNTER
Frame counter response.
@ TWR_CMWX1ZZABZ_EVENT_SEND_MESSAGE_DONE
RF frame transmission finished event.
@ TWR_CMWX1ZZABZ_EVENT_READY
Ready event.
@ TWR_CMWX1ZZABZ_EVENT_MESSAGE_NOT_CONFIRMED
Sent message not confirmed.
@ TWR_CMWX1ZZABZ_EVENT_MESSAGE_CONFIRMED
Sent message confirmed.
@ TWR_CMWX1ZZABZ_EVENT_JOIN_ERROR
OTAA join error.
@ TWR_CMWX1ZZABZ_EVENT_SEND_MESSAGE_START
RF frame transmission started event.
@ TWR_CMWX1ZZABZ_EVENT_JOIN_SUCCESS
OTAA join success.
@ TWR_CMWX1ZZABZ_EVENT_RFQ
RF quality response.
@ TWR_CMWX1ZZABZ_EVENT_MESSAGE_RETRANSMISSION
Retransmission of the confirmed message.
void twr_fifo_purge(twr_fifo_t *fifo)
Purge FIFO buffer.
Definition: twr_fifo.c:12
void twr_fifo_init(twr_fifo_t *fifo, void *buffer, size_t size)
Initialize FIFO buffer.
Definition: twr_fifo.c:4
void void twr_log_debug(const char *format,...) __attribute__((format(printf
Log DEBUG message (annotated in log as <D>)
void twr_scheduler_plan_current_from_now(twr_tick_t tick)
Schedule current task to tick relative from now.
void twr_scheduler_unregister(twr_scheduler_task_id_t task_id)
Unregister specified task.
Definition: twr_scheduler.c:77
void twr_scheduler_plan_now(twr_scheduler_task_id_t task_id)
Schedule specified task for immediate execution.
void twr_scheduler_plan_current_now(void)
Schedule current task for immediate execution.
void twr_scheduler_plan_relative(twr_scheduler_task_id_t task_id, twr_tick_t tick)
Schedule specified task to tick relative from current spin.
twr_scheduler_task_id_t twr_scheduler_register(void(*task)(void *), void *param, twr_tick_t tick)
Register task in scheduler.
Definition: twr_scheduler.c:53
#define TWR_TICK_INFINITY
Maximum timestamp value.
Definition: twr_tick.h:12
twr_tick_t twr_tick_get(void)
Get absolute timestamp since start of program.
Definition: twr_tick.c:7
void twr_timer_init(void)
Initialize timer.
Definition: twr_timer.c:23
void twr_timer_delay(uint16_t microseconds)
Relative delay.
Definition: twr_timer.c:59
void twr_timer_stop(void)
Stop timer.
Definition: twr_timer.c:42
void twr_timer_start(void)
Start timer.
Definition: twr_timer.c:28
twr_uart_channel_t
UART channels.
Definition: twr_uart.h:14
void twr_uart_init(twr_uart_channel_t channel, twr_uart_baudrate_t baudrate, twr_uart_setting_t setting)
Initialize UART channel.
Definition: twr_uart.c:54
size_t twr_uart_async_write(twr_uart_channel_t channel, const void *buffer, size_t length)
Add data to be transmited in async mode.
Definition: twr_uart.c:418
void twr_uart_set_async_fifo(twr_uart_channel_t channel, twr_fifo_t *write_fifo, twr_fifo_t *read_fifo)
Set buffers for async transfers.
Definition: twr_uart.c:412
size_t twr_uart_write(twr_uart_channel_t channel, const void *buffer, size_t length)
Write data to UART channel (blocking call)
Definition: twr_uart.c:314
bool twr_uart_async_read_start(twr_uart_channel_t channel, twr_tick_t timeout)
Start async reading.
Definition: twr_uart.c:465
twr_uart_event_t
Callback events.
Definition: twr_uart.h:128
void twr_uart_set_event_handler(twr_uart_channel_t channel, void(*event_handler)(twr_uart_channel_t, twr_uart_event_t, void *), void *event_param)
Set callback function.
Definition: twr_uart.c:406
size_t twr_uart_async_read(twr_uart_channel_t channel, void *buffer, size_t length)
Get data that has been received in async mode.
Definition: twr_uart.c:561
void twr_uart_deinit(twr_uart_channel_t channel)
Deinitialize UART channel.
Definition: twr_uart.c:237
@ TWR_UART_EVENT_ASYNC_READ_DATA
Event is reading done.
Definition: twr_uart.h:133
@ TWR_UART_BAUDRATE_19200
UART baudrat 19200 bps.
Definition: twr_uart.h:34
@ TWR_UART_BAUDRATE_9600
UART baudrat 9600 bps.
Definition: twr_uart.h:31
@ TWR_UART_SETTING_8N1
8N1: 8 data bits, none parity bit, 1 stop bit
Definition: twr_uart.h:70