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