1 /*
2 * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 // FreeModbus Master Example ESP32
8
9 #include <string.h>
10 #include <sys/queue.h>
11 #include "esp_log.h"
12 #include "esp_system.h"
13 #include "esp_wifi.h"
14 #include "esp_event.h"
15 #include "esp_log.h"
16 #include "nvs_flash.h"
17 #include "esp_netif.h"
18 #include "mdns.h"
19 #include "protocol_examples_common.h"
20
21 #include "modbus_params.h" // for modbus parameters structures
22 #include "mbcontroller.h"
23 #include "sdkconfig.h"
24
25 #define MB_TCP_PORT (CONFIG_FMB_TCP_PORT_DEFAULT) // TCP port used by example
26
27 // The number of parameters that intended to be used in the particular control process
28 #define MASTER_MAX_CIDS num_device_parameters
29
30 // Number of reading of parameters from slave
31 #define MASTER_MAX_RETRY (30)
32
33 // Timeout to update cid over Modbus
34 #define UPDATE_CIDS_TIMEOUT_MS (500)
35 #define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_RATE_MS)
36
37 // Timeout between polls
38 #define POLL_TIMEOUT_MS (1)
39 #define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_RATE_MS)
40 #define MB_MDNS_PORT (502)
41
42 // The macro to get offset for parameter in the appropriate structure
43 #define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
44 #define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1))
45 #define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1))
46 #define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
47 #define STR(fieldname) ((const char*)( fieldname ))
48
49 // Options can be used as bit masks or parameter limits
50 #define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
51
52 #define MB_ID_BYTE0(id) ((uint8_t)(id))
53 #define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
54 #define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
55 #define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
56
57 #define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
58
59 #if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
60 #define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
61 #else
62 #define MB_DEVICE_ID (uint32_t)0x00112233
63 #endif
64
65 #define MB_MDNS_INSTANCE(pref) pref"mb_master_tcp"
66 static const char *TAG = "MASTER_TEST";
67
68 // Enumeration of modbus device addresses accessed by master device
69 // Each address in the table is a index of TCP slave ip address in mb_communication_info_t::tcp_ip_addr table
70 enum {
71 MB_DEVICE_ADDR1 = 1, // Slave address 1
72 MB_DEVICE_ADDR2 = 200,
73 MB_DEVICE_ADDR3 = 35
74 };
75
76 // Enumeration of all supported CIDs for device (used in parameter definition table)
77 enum {
78 CID_INP_DATA_0 = 0,
79 CID_HOLD_DATA_0,
80 CID_INP_DATA_1,
81 CID_HOLD_DATA_1,
82 CID_INP_DATA_2,
83 CID_HOLD_DATA_2,
84 CID_RELAY_P1,
85 CID_RELAY_P2,
86 CID_COUNT
87 };
88
89 // Example Data (Object) Dictionary for Modbus parameters:
90 // The CID field in the table must be unique.
91 // Modbus Slave Addr field defines slave address of the device with correspond parameter.
92 // Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
93 // Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
94 // The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
95 // Data Type, Data Size specify type of the characteristic and its data size.
96 // Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
97 // Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
98 const mb_parameter_descriptor_t device_parameters[] = {
99 // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
100 { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
101 INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
102 { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
103 HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
104 { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2,
105 INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
106 { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 2, 2,
107 HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
108 { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 4, 2,
109 INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
110 { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR3, MB_PARAM_HOLDING, 4, 2,
111 HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
112 { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8,
113 COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
114 { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8,
115 COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
116 };
117
118 // Calculate number of parameters in the table
119 const uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
120
121 // This table represents slave IP addresses that correspond to the short address field of the slave in device_parameters structure
122 // Modbus TCP stack shall use these addresses to be able to connect and read parameters from slave
123 char* slave_ip_address_table[] = {
124 #if CONFIG_MB_SLAVE_IP_FROM_STDIN
125 "FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user
126 "FROM_STDIN", // Corresponds to characteristic MB_DEVICE_ADDR2
127 "FROM_STDIN", // Corresponds to characteristic MB_DEVICE_ADDR3
128 NULL // End of table condition (must be included)
129 #elif CONFIG_MB_MDNS_IP_RESOLVER
130 NULL,
131 NULL,
132 NULL,
133 NULL
134 #endif
135 };
136
137 const size_t ip_table_sz = (size_t)(sizeof(slave_ip_address_table) / sizeof(slave_ip_address_table[0]));
138
139 #if CONFIG_MB_SLAVE_IP_FROM_STDIN
140
141 // Scan IP address according to IPV settings
master_scan_addr(int * index,char * buffer)142 char* master_scan_addr(int* index, char* buffer)
143 {
144 char* ip_str = NULL;
145 unsigned int a[8] = {0};
146 int buf_cnt = 0;
147 #if !CONFIG_EXAMPLE_CONNECT_IPV6
148 buf_cnt = sscanf(buffer, "IP%d="IPSTR, index, &a[0], &a[1], &a[2], &a[3]);
149 if (buf_cnt == 5) {
150 if (-1 == asprintf(&ip_str, IPSTR, a[0], a[1], a[2], a[3])) {
151 abort();
152 }
153 }
154 #else
155 buf_cnt = sscanf(buffer, "IP%d="IPV6STR, index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7]);
156 if (buf_cnt == 9) {
157 if (-1 == asprintf(&ip_str, IPV6STR, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
158 abort();
159 }
160 }
161 #endif
162 return ip_str;
163 }
164
master_get_slave_ip_stdin(char ** addr_table)165 static int master_get_slave_ip_stdin(char** addr_table)
166 {
167 char buf[128];
168 int index;
169 char* ip_str = NULL;
170 int buf_cnt = 0;
171 int ip_cnt = 0;
172
173 if (!addr_table) {
174 return 0;
175 }
176
177 ESP_ERROR_CHECK(example_configure_stdin_stdout());
178 while(1) {
179 if (addr_table[ip_cnt] && strcmp(addr_table[ip_cnt], "FROM_STDIN") == 0) {
180 printf("Waiting IP%d from stdin:\r\n", ip_cnt);
181 while (fgets(buf, sizeof(buf), stdin) == NULL) {
182 fputs(buf, stdout);
183 }
184 buf_cnt = strlen(buf);
185 buf[buf_cnt - 1] = '\0';
186 fputc('\n', stdout);
187 ip_str = master_scan_addr(&index, buf);
188 if (ip_str != NULL) {
189 ESP_LOGI(TAG, "IP(%d) = [%s] set from stdin.", ip_cnt, ip_str);
190 if ((ip_cnt >= ip_table_sz) || (index != ip_cnt)) {
191 addr_table[ip_cnt] = NULL;
192 break;
193 }
194 addr_table[ip_cnt++] = ip_str;
195 } else {
196 // End of configuration
197 addr_table[ip_cnt++] = NULL;
198 break;
199 }
200 } else {
201 if (addr_table[ip_cnt]) {
202 ESP_LOGI(TAG, "Leave IP(%d) = [%s] set manually.", ip_cnt, addr_table[ip_cnt]);
203 ip_cnt++;
204 } else {
205 ESP_LOGI(TAG, "IP(%d) is not set in the table.", ip_cnt);
206 break;
207 }
208 }
209 }
210 return ip_cnt;
211 }
212
213 #elif CONFIG_MB_MDNS_IP_RESOLVER
214
215 typedef struct slave_addr_entry_s {
216 uint16_t index;
217 char* ip_address;
218 uint8_t slave_addr;
219 void* p_data;
220 LIST_ENTRY(slave_addr_entry_s) entries;
221 } slave_addr_entry_t;
222
223 LIST_HEAD(slave_addr_, slave_addr_entry_s) slave_addr_list = LIST_HEAD_INITIALIZER(slave_addr_list);
224
225 // convert MAC from binary format to string
gen_mac_str(const uint8_t * mac,char * pref,char * mac_str)226 static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str)
227 {
228 sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
229 return mac_str;
230 }
231
gen_id_str(char * service_name,char * slave_id_str)232 static inline char* gen_id_str(char* service_name, char* slave_id_str)
233 {
234 sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
235 return slave_id_str;
236 }
237
master_start_mdns_service()238 static void master_start_mdns_service()
239 {
240 char temp_str[32] = {0};
241 uint8_t sta_mac[6] = {0};
242 ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA));
243 char* hostname = gen_mac_str(sta_mac, MB_MDNS_INSTANCE("")"_", temp_str);
244 // initialize mDNS
245 ESP_ERROR_CHECK(mdns_init());
246 // set mDNS hostname (required if you want to advertise services)
247 ESP_ERROR_CHECK(mdns_hostname_set(hostname));
248 ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
249
250 // set default mDNS instance name
251 ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_")));
252
253 // structure with TXT records
254 mdns_txt_item_t serviceTxtData[] = {
255 {"board","esp32"}
256 };
257
258 // initialize service
259 ESP_ERROR_CHECK(mdns_service_add(MB_MDNS_INSTANCE(""), "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1));
260 // add mac key string text item
261 ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str)));
262 // add slave id key txt item
263 ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str)));
264 }
265
master_get_slave_ip_str(mdns_ip_addr_t * address,mb_tcp_addr_type_t addr_type)266 static char* master_get_slave_ip_str(mdns_ip_addr_t* address, mb_tcp_addr_type_t addr_type)
267 {
268 mdns_ip_addr_t* a = address;
269 char* slave_ip_str = NULL;
270
271 while (a) {
272 if ((a->addr.type == ESP_IPADDR_TYPE_V6) && (addr_type == MB_IPV6)) {
273 if (-1 == asprintf(&slave_ip_str, IPV6STR, IPV62STR(a->addr.u_addr.ip6))) {
274 abort();
275 }
276 } else if ((a->addr.type == ESP_IPADDR_TYPE_V4) && (addr_type == MB_IPV4)) {
277 if (-1 == asprintf(&slave_ip_str, IPSTR, IP2STR(&(a->addr.u_addr.ip4)))) {
278 abort();
279 }
280 }
281 if (slave_ip_str) {
282 break;
283 }
284 a = a->next;
285 }
286 return slave_ip_str;
287 }
288
master_resolve_slave(uint8_t short_addr,mdns_result_t * result,char ** resolved_ip,mb_tcp_addr_type_t addr_type)289 static esp_err_t master_resolve_slave(uint8_t short_addr, mdns_result_t* result, char** resolved_ip,
290 mb_tcp_addr_type_t addr_type)
291 {
292 if (!short_addr || !result || !resolved_ip) {
293 return ESP_ERR_INVALID_ARG;
294 }
295 mdns_result_t* r = result;
296 int t;
297 char* slave_ip = NULL;
298 char slave_name[22] = {0};
299
300 if (sprintf(slave_name, "mb_slave_tcp_%02X", short_addr) < 0) {
301 ESP_LOGE(TAG, "Fail to create instance name for index: %d", short_addr);
302 abort();
303 }
304 for (; r ; r = r->next) {
305 if ((r->ip_protocol == MDNS_IP_PROTOCOL_V4) && (addr_type == MB_IPV6)) {
306 continue;
307 } else if ((r->ip_protocol == MDNS_IP_PROTOCOL_V6) && (addr_type == MB_IPV4)) {
308 continue;
309 }
310 // Check host name for Modbus short address and
311 // append it into slave ip address table
312 if ((strcmp(r->instance_name, slave_name) == 0) && (r->port == CONFIG_FMB_TCP_PORT_DEFAULT)) {
313 printf(" PTR : %s\n", r->instance_name);
314 if (r->txt_count) {
315 printf(" TXT : [%u] ", r->txt_count);
316 for ( t = 0; t < r->txt_count; t++) {
317 printf("%s=%s; ", r->txt[t].key, r->txt[t].value?r->txt[t].value:"NULL");
318 }
319 printf("\n");
320 }
321 slave_ip = master_get_slave_ip_str(r->addr, addr_type);
322 if (slave_ip) {
323 ESP_LOGI(TAG, "Resolved slave %s[%s]:%u", r->hostname, slave_ip, r->port);
324 *resolved_ip = slave_ip;
325 return ESP_OK;
326 }
327 }
328 }
329 *resolved_ip = NULL;
330 ESP_LOGD(TAG, "Fail to resolve slave: %s", slave_name);
331 return ESP_ERR_NOT_FOUND;
332 }
333
master_create_slave_list(mdns_result_t * results,char ** addr_table,int addr_table_size,mb_tcp_addr_type_t addr_type)334 static int master_create_slave_list(mdns_result_t* results, char** addr_table,
335 int addr_table_size, mb_tcp_addr_type_t addr_type)
336 {
337 if (!results) {
338 return -1;
339 }
340 int i, slave_addr, cid_resolve_cnt = 0;
341 int ip_index = 0;
342 const mb_parameter_descriptor_t* pdescr = &device_parameters[0];
343 char** ip_table = addr_table;
344 char* slave_ip = NULL;
345 slave_addr_entry_t *it;
346
347 for (i = 0; (i < num_device_parameters && pdescr); i++, pdescr++)
348 {
349 slave_addr = pdescr->mb_slave_addr;
350
351 it = NULL;
352 // Is the slave address already registered?
353 LIST_FOREACH(it, &slave_addr_list, entries) {
354 if (slave_addr == it->slave_addr) {
355 break;
356 }
357 }
358 if (!it) {
359 // Resolve new slave IP address using its short address
360 esp_err_t err = master_resolve_slave(slave_addr, results, &slave_ip, addr_type);
361 if (err != ESP_OK) {
362 ESP_LOGE(TAG, "Index: %d, sl_addr: %d, failed to resolve!", i, slave_addr);
363 // Set correspond index to NULL indicate host not resolved
364 ip_table[ip_index] = NULL;
365 continue;
366 }
367 // Register new slave address information
368 slave_addr_entry_t* new_slave_entry = (slave_addr_entry_t*) heap_caps_malloc(sizeof(slave_addr_entry_t),
369 MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
370 MB_RETURN_ON_FALSE((new_slave_entry != NULL), ESP_ERR_NO_MEM,
371 TAG, "Can not allocate memory for slave entry.");
372 new_slave_entry->index = i;
373 new_slave_entry->ip_address = slave_ip;
374 new_slave_entry->slave_addr = slave_addr;
375 new_slave_entry->p_data = NULL;
376 LIST_INSERT_HEAD(&slave_addr_list, new_slave_entry, entries);
377 ip_table[ip_index] = slave_ip;
378 ESP_LOGI(TAG, "Index: %d, sl_addr: %d, resolved to IP: [%s]",
379 i, slave_addr, slave_ip);
380 cid_resolve_cnt++;
381 if (ip_index < addr_table_size) {
382 ip_index++;
383 }
384 } else {
385 ip_table[ip_index] = it ? it->ip_address : ip_table[ip_index];
386 ESP_LOGI(TAG, "Index: %d, sl_addr: %d, set to IP: [%s]",
387 i, slave_addr, ip_table[ip_index]);
388 cid_resolve_cnt++;
389 }
390 }
391 ESP_LOGI(TAG, "Resolved %d cids, with %d IP addresses", cid_resolve_cnt, ip_index);
392 return cid_resolve_cnt;
393 }
394
master_query_slave_service(const char * service_name,const char * proto,mb_tcp_addr_type_t addr_type)395 static int master_query_slave_service(const char * service_name, const char * proto,
396 mb_tcp_addr_type_t addr_type)
397 {
398 ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto);
399
400 mdns_result_t* results = NULL;
401 int count = 0;
402
403 esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results);
404 if(err){
405 ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
406 return count;
407 }
408 if(!results){
409 ESP_LOGW(TAG, "No results found!");
410 return count;
411 }
412
413 count = master_create_slave_list(results, slave_ip_address_table, ip_table_sz, addr_type);
414
415 mdns_query_results_free(results);
416 return count;
417 }
418 #endif
419
master_destroy_slave_list(char ** table,size_t ip_table_size)420 static void master_destroy_slave_list(char** table, size_t ip_table_size)
421 {
422 #if CONFIG_MB_MDNS_IP_RESOLVER
423 slave_addr_entry_t *it;
424 LIST_FOREACH(it, &slave_addr_list, entries) {
425 LIST_REMOVE(it, entries);
426 free(it);
427 }
428 #endif
429 for (int i = 0; ((i < ip_table_size) && table[i] != NULL); i++) {
430 if (table[i]) {
431 #if CONFIG_MB_SLAVE_IP_FROM_STDIN
432 free(table[i]);
433 table[i] = "FROM_STDIN";
434 #elif CONFIG_MB_MDNS_IP_RESOLVER
435 table[i] = NULL;
436 #endif
437 }
438 }
439 }
440
441 // The function to get pointer to parameter storage (instance) according to parameter description table
master_get_param_data(const mb_parameter_descriptor_t * param_descriptor)442 static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor)
443 {
444 assert(param_descriptor != NULL);
445 void* instance_ptr = NULL;
446 if (param_descriptor->param_offset != 0) {
447 switch(param_descriptor->mb_param_type)
448 {
449 case MB_PARAM_HOLDING:
450 instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1);
451 break;
452 case MB_PARAM_INPUT:
453 instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1);
454 break;
455 case MB_PARAM_COIL:
456 instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1);
457 break;
458 case MB_PARAM_DISCRETE:
459 instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1);
460 break;
461 default:
462 instance_ptr = NULL;
463 break;
464 }
465 } else {
466 ESP_LOGE(TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid);
467 assert(instance_ptr != NULL);
468 }
469 return instance_ptr;
470 }
471
472 // User operation function to read slave values and check alarm
master_operation_func(void * arg)473 static void master_operation_func(void *arg)
474 {
475 esp_err_t err = ESP_OK;
476 float value = 0;
477 bool alarm_state = false;
478 const mb_parameter_descriptor_t* param_descriptor = NULL;
479
480 ESP_LOGI(TAG, "Start modbus test...");
481
482 for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
483 // Read all found characteristics from slave(s)
484 for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++)
485 {
486 // Get data from parameters description table
487 // and use this information to fill the characteristics description table
488 // and having all required fields in just one table
489 err = mbc_master_get_cid_info(cid, ¶m_descriptor);
490 if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
491 void* temp_data_ptr = master_get_param_data(param_descriptor);
492 assert(temp_data_ptr);
493 uint8_t type = 0;
494 err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
495 (uint8_t*)&value, &type);
496 if (err == ESP_OK) {
497 *(float*)temp_data_ptr = value;
498 if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
499 (param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
500 ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.",
501 param_descriptor->cid,
502 (char*)param_descriptor->param_key,
503 (char*)param_descriptor->param_units,
504 value,
505 *(uint32_t*)temp_data_ptr);
506 if (((value > param_descriptor->param_opts.max) ||
507 (value < param_descriptor->param_opts.min))) {
508 alarm_state = true;
509 break;
510 }
511 } else {
512 uint16_t state = *(uint16_t*)temp_data_ptr;
513 const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
514 ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.",
515 param_descriptor->cid,
516 (char*)param_descriptor->param_key,
517 (char*)param_descriptor->param_units,
518 (const char*)rw_str,
519 *(uint16_t*)temp_data_ptr);
520 if (state & param_descriptor->param_opts.opt1) {
521 alarm_state = true;
522 break;
523 }
524 }
525 } else {
526 ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = %d (%s).",
527 param_descriptor->cid,
528 (char*)param_descriptor->param_key,
529 (int)err,
530 (char*)esp_err_to_name(err));
531 }
532 vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
533 }
534 }
535 vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
536 }
537
538 if (alarm_state) {
539 ESP_LOGI(TAG, "Alarm triggered by cid #%d.",
540 param_descriptor->cid);
541 } else {
542 ESP_LOGE(TAG, "Alarm is not triggered after %d retries.",
543 MASTER_MAX_RETRY);
544 }
545 ESP_LOGI(TAG, "Destroy master...");
546 vTaskDelay(100);
547 }
548
init_services(mb_tcp_addr_type_t ip_addr_type)549 static esp_err_t init_services(mb_tcp_addr_type_t ip_addr_type)
550 {
551 esp_err_t result = nvs_flash_init();
552 if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
553 ESP_ERROR_CHECK(nvs_flash_erase());
554 result = nvs_flash_init();
555 }
556 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
557 TAG,
558 "nvs_flash_init fail, returns(0x%x).",
559 (uint32_t)result);
560 result = esp_netif_init();
561 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
562 TAG,
563 "esp_netif_init fail, returns(0x%x).",
564 (uint32_t)result);
565 result = esp_event_loop_create_default();
566 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
567 TAG,
568 "esp_event_loop_create_default fail, returns(0x%x).",
569 (uint32_t)result);
570 #if CONFIG_MB_MDNS_IP_RESOLVER
571 // Start mdns service and register device
572 master_start_mdns_service();
573 #endif
574 // This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
575 // Read "Establishing Wi-Fi or Ethernet Connection" section in
576 // examples/protocols/README.md for more information about this function.
577 result = example_connect();
578 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
579 TAG,
580 "example_connect fail, returns(0x%x).",
581 (uint32_t)result);
582 #if CONFIG_EXAMPLE_CONNECT_WIFI
583 result = esp_wifi_set_ps(WIFI_PS_NONE);
584 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
585 TAG,
586 "esp_wifi_set_ps fail, returns(0x%x).",
587 (uint32_t)result);
588 #endif
589 #if CONFIG_MB_MDNS_IP_RESOLVER
590 int res = 0;
591 for (int retry = 0; (res < num_device_parameters) && (retry < 10); retry++) {
592 res = master_query_slave_service("_modbus", "_tcp", ip_addr_type);
593 }
594 if (res < num_device_parameters) {
595 ESP_LOGE(TAG, "Could not resolve one or more slave IP addresses, resolved: %d out of %d.", res, num_device_parameters );
596 ESP_LOGE(TAG, "Make sure you configured all slaves according to device parameter table and they alive in the network.");
597 return ESP_ERR_NOT_FOUND;
598 }
599 mdns_free();
600 #elif CONFIG_MB_SLAVE_IP_FROM_STDIN
601 int ip_cnt = master_get_slave_ip_stdin(slave_ip_address_table);
602 if (ip_cnt) {
603 ESP_LOGI(TAG, "Configured %d IP addresse(s).", ip_cnt);
604 } else {
605 ESP_LOGE(TAG, "Fail to get IP address from stdin. Continue.");
606 return ESP_ERR_NOT_FOUND;
607 }
608 #endif
609 return ESP_OK;
610 }
611
destroy_services(void)612 static esp_err_t destroy_services(void)
613 {
614 esp_err_t err = ESP_OK;
615 master_destroy_slave_list(slave_ip_address_table, ip_table_sz);
616
617 err = example_disconnect();
618 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
619 TAG,
620 "example_disconnect fail, returns(0x%x).",
621 (uint32_t)err);
622 err = esp_event_loop_delete_default();
623 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
624 TAG,
625 "esp_event_loop_delete_default fail, returns(0x%x).",
626 (uint32_t)err);
627 err = esp_netif_deinit();
628 MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_STATE,
629 TAG,
630 "esp_netif_deinit fail, returns(0x%x).",
631 (uint32_t)err);
632 err = nvs_flash_deinit();
633 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
634 TAG,
635 "nvs_flash_deinit fail, returns(0x%x).",
636 (uint32_t)err);
637 return err;
638 }
639
640 // Modbus master initialization
master_init(mb_communication_info_t * comm_info)641 static esp_err_t master_init(mb_communication_info_t* comm_info)
642 {
643 void* master_handler = NULL;
644
645 esp_err_t err = mbc_master_init_tcp(&master_handler);
646 MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE,
647 TAG,
648 "mb controller initialization fail.");
649 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
650 TAG,
651 "mb controller initialization fail, returns(0x%x).",
652 (uint32_t)err);
653
654 err = mbc_master_setup((void*)comm_info);
655 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
656 TAG,
657 "mb controller setup fail, returns(0x%x).",
658 (uint32_t)err);
659
660 err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters);
661 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
662 TAG,
663 "mb controller set descriptor fail, returns(0x%x).",
664 (uint32_t)err);
665 ESP_LOGI(TAG, "Modbus master stack initialized...");
666
667 err = mbc_master_start();
668 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
669 TAG,
670 "mb controller start fail, returns(0x%x).",
671 (uint32_t)err);
672 vTaskDelay(5);
673 return err;
674 }
675
master_destroy(void)676 static esp_err_t master_destroy(void)
677 {
678 esp_err_t err = mbc_master_destroy();
679 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
680 TAG,
681 "mbc_master_destroy fail, returns(0x%x).",
682 (uint32_t)err);
683 ESP_LOGI(TAG, "Modbus master stack destroy...");
684 return err;
685 }
686
app_main(void)687 void app_main(void)
688 {
689 mb_tcp_addr_type_t ip_addr_type;
690 #if !CONFIG_EXAMPLE_CONNECT_IPV6
691 ip_addr_type = MB_IPV4;
692 #else
693 ip_addr_type = MB_IPV6;
694 #endif
695 ESP_ERROR_CHECK(init_services(ip_addr_type));
696
697 mb_communication_info_t comm_info = { 0 };
698 comm_info.ip_port = MB_TCP_PORT;
699 comm_info.ip_addr_type = ip_addr_type;
700 comm_info.ip_mode = MB_MODE_TCP;
701 comm_info.ip_addr = (void*)slave_ip_address_table;
702 comm_info.ip_netif_ptr = (void*)get_example_netif();
703
704 ESP_ERROR_CHECK(master_init(&comm_info));
705 vTaskDelay(50);
706
707 master_operation_func(NULL);
708 ESP_ERROR_CHECK(master_destroy());
709 ESP_ERROR_CHECK(destroy_services());
710 }
711