diff --git a/mb2halv2/Makefile b/mb2halv2/Makefile new file mode 100755 index 0000000000000000000000000000000000000000..81bdd5cb9c4a6504264b8aa0ef6dda3207399156 --- /dev/null +++ b/mb2halv2/Makefile @@ -0,0 +1,28 @@ +all: mb2halv2 + +CFLAGS=-I/usr/include/linuxcnc -DULAPI -DINIFILE_LEGACY + +%.o: %.c + g++ -c $(CFLAGS) $(CPPFLAGS) $< -o $@ + +mb2halv2: mb2hal.o mb2hal_init.o mb2hal_modbus.o mb2hal_hal.o + g++ $(CFLAGS) $^ -o $@ -llinuxcnchal -llinuxcncini -lpthread -lmodbus + +mb2hal.o: mb2hal.c + +mb2hal_init.o: mb2hal_init.c + +mb2hal_modbus.o: mb2hal_modbus.c + +mb2hal_hal.o: mb2hal_hal.c + +install: + sudo install -m 0755 -g root -o root mb2halv2 /usr/local/bin + +valid: mb2halv2 + cd tests; python test05.py + +clean: + rm -f mb2halv2 *.o + + diff --git a/mb2halv2/mb2hal.c b/mb2halv2/mb2hal.c new file mode 100755 index 0000000000000000000000000000000000000000..c54877cf02c774199a4005a2183b1fd5e727f52c --- /dev/null +++ b/mb2halv2/mb2hal.c @@ -0,0 +1,461 @@ +/* + * mb2hal.c + * Userspace HAL component to communicate with one or more Modbus devices. + * + * Victor Rocco, adapted from Les Newell's modbuscomms.c which is + * Copyright (C) 2009-2012 Les Newell + * source code in http://wiki.linuxcnc.org/cgi-bin/wiki.pl?ContributedComponents + * + * Copyright (C) 2012 Victor Rocco + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + */ + +#include "mb2hal.h" + +gbl_t gbl; + +/* + * Main: init global params, parse args, open ini file, parse ini file + * (transaction strcutures), init links (links structures), init and + * create hal pins, create a thread for each link , and wait forever + */ + +int main(int argc, char **argv) +{ + const char *fnct_name = "main"; + pthread_attr_t thrd_attr; + int counter; + int ret; + + set_init_gbl_params(); + + if (parse_main_args(argc, argv) != 0) { + ERR(gbl.init_dbg, "Unable to parse arguments"); + return -1; + } + + gbl.ini_file_ptr = fopen(gbl.ini_file_path, "r"); + if (gbl.ini_file_ptr == NULL) { + ERR(gbl.init_dbg, "Unable to open INI file [%s]", gbl.ini_file_path); + return -1; + } + + if (parse_ini_file() != 0) { + ERR(gbl.init_dbg, "Unable to parse INI file [%s]", gbl.ini_file_path); + goto QUIT_CLEANUP; + } + OK(gbl.init_dbg, "parse_ini_file done OK"); + + if (init_mb_links() != retOK) { + ERR(gbl.init_dbg, "init_mb_links failed"); + goto QUIT_CLEANUP; + } + OK(gbl.init_dbg, "init_gbl.mb_link done OK"); + + if (init_mb_tx() != retOK) { + ERR(gbl.init_dbg, "init_mb_tx failed"); + goto QUIT_CLEANUP; + } + OK(gbl.init_dbg, "init_gbl.mb_tx done OK"); + + gbl.hal_mod_id = hal_init(gbl.hal_mod_name); + if (gbl.hal_mod_id < 0) { + ERR(gbl.init_dbg, "Unable to initialize HAL component [%s]", gbl.hal_mod_name); + goto QUIT_CLEANUP; + } + if (create_HAL_pins() != retOK) { + ERR(gbl.init_dbg, "Unable to create HAL pins"); + goto QUIT_CLEANUP; + } + hal_ready(gbl.hal_mod_id); + OK(gbl.init_dbg, "HAL components created OK"); + + gbl.quit_flag = 0; //tell the threads to quit (SIGTERM o SIGQUIT) (unloadusr mb2hal). + signal(SIGINT, quit_signal); + //unloadusr and unload commands of halrun + signal(SIGTERM, quit_signal); + + /* Each link has it's own thread */ + pthread_attr_init(&thrd_attr); + pthread_attr_setdetachstate(&thrd_attr, PTHREAD_CREATE_DETACHED); + for (counter = 0; counter < gbl.tot_mb_links; counter++) { + ret = pthread_create(&gbl.mb_links[counter].thrd, &thrd_attr, link_loop_and_logic, (void *) &gbl.mb_links[counter].mb_link_num); + if (ret != 0) { + ERR(gbl.init_dbg, "Unable to start thread for link number %d", counter); + } + OK(gbl.init_dbg, "Link thread loop and logic %d created OK", counter); + } + + OK(gbl.init_dbg, "%s is running", gbl.hal_mod_name); + while (gbl.quit_flag == 0) { + sleep(1); + } + +QUIT_CLEANUP: + quit_cleanup(); + OK(gbl.init_dbg, "going to exit!"); + return 0; +} + +/* + * One thread loop for each link + * The LOGIC is here + * thrd_link_num is the corresponding link of this thread (int *) + */ + +void *link_loop_and_logic(void *thrd_link_num) +{ + const char *fnct_name = "link_loop_and_logic"; + int ret, ret_available, ret_connected; + int tx_counter; + mb_tx_t *this_mb_tx = NULL; + int this_mb_tx_num; + mb_link_t *this_mb_link = NULL; + int this_mb_link_num; + + if (thrd_link_num == NULL) { + ERR(gbl.init_dbg, "NULL pointer"); + return NULL; + } + this_mb_link_num = *((int *)thrd_link_num); + if (this_mb_link_num < 0 || this_mb_link_num >= gbl.tot_mb_links) { + ERR(gbl.init_dbg, "parameter out of range this_mb_link_num[%d]", this_mb_link_num); + return NULL; + } + this_mb_link = &gbl.mb_links[this_mb_link_num]; + + while (1) { + + for (tx_counter = 0; tx_counter < gbl.tot_mb_tx; tx_counter++) { + + if (gbl.quit_flag != 0) { //tell the threads to quit (SIGTERM o SGIQUIT) (unloadusr mb2hal). + return NULL; + } + + this_mb_tx_num = tx_counter; + this_mb_tx = &gbl.mb_tx[this_mb_tx_num]; + + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] going to TEST availability", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus)); + + //corresponding link and time (update_rate) + if (is_this_tx_ready(this_mb_link_num, this_mb_tx_num, &ret_available) != retOK) { + ERR(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] is_this_tx_ready ERR", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus)); + return NULL; + } + if (ret_available == 0) { + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] NOT available", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus)); + continue; + } + + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] going to TEST connection", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus)); + + //first time connection or reconnection, run time parameters setting + if (get_tx_connection(this_mb_tx_num, &ret_connected) != retOK) { + ERR(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] get_tx_connection ERR", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus)); + return NULL; + } + if (ret_connected == 0) { + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] NOT connected", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus)); + continue; + } + + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] lk_dbg[%d] going to EXECUTE transaction", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus), + this_mb_tx->protocol_debug); + + (**this_mb_tx->cumul_transactions)++; + + switch (this_mb_tx->mb_tx_fnct) { + case mbtx_02_READ_DISCRETE_INPUTS: + ret = fnct_02_read_discrete_inputs(this_mb_tx, this_mb_link); + break; + case mbtx_03_READ_HOLDING_REGISTERS: + ret = fnct_03_read_holding_registers(this_mb_tx, this_mb_link); + break; + case mbtx_04_READ_INPUT_REGISTERS: + ret = fnct_04_read_input_registers(this_mb_tx, this_mb_link); + break; + case mbtx_05_WRITE_SINGLE_COIL: + ret = fnct_05_write_single_coil(this_mb_tx, this_mb_link); + break; + case mbtx_15_WRITE_MULTIPLE_COILS: + ret = fnct_15_write_multiple_coils(this_mb_tx, this_mb_link); + break; + case mbtx_16_WRITE_MULTIPLE_REGISTERS: + ret = fnct_16_write_multiple_registers(this_mb_tx, this_mb_link); + break; + default: + ret = -1; + ERR(this_mb_tx->cfg_debug, "case error with mb_tx_fnct %d [%s] in mb_tx_num[%d]", + this_mb_tx->mb_tx_fnct, this_mb_tx->mb_tx_fnct_name, this_mb_tx_num); + break; + } + + if (gbl.quit_flag != 0) { //tell the threads to quit (SIGTERM o SGIQUIT) (unloadusr mb2hal). + return NULL; + } + + if (ret != retOK && modbus_get_socket(this_mb_link->modbus) < 0) { //link failure + (**this_mb_tx->num_errors)++; + (**this_mb_tx->cumul_errors)++; + **this_mb_tx->modbus_ok = 0; + ERR(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] link failure, going to close link", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus)); + modbus_close(this_mb_link->modbus); + } + else if (ret != retOK) { //transaction failure but link OK + (**this_mb_tx->num_errors)++; + (**this_mb_tx->cumul_errors)++; + **this_mb_tx->modbus_ok = 0; + ERR(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] transaction failure, num_errors[%d]", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus), **this_mb_tx->num_errors); + } + else { //transaction and link OK + OK(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] transaction OK, update_HZ[%0.03f]", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus), + 1.0/(get_time()-this_mb_tx->last_time_ok)); + this_mb_tx->last_time_ok = get_time(); + (**this_mb_tx->num_errors) = 0; + **this_mb_tx->modbus_ok = 1; + } + + //set the next (waiting) time for update rate + this_mb_tx->next_time = get_time() + this_mb_tx->time_increment; + + //wait time for serial lines + if (this_mb_tx->cfg_link_type == linkRTU) { + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] SERIAL_DELAY_MS activated [%d]", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus), + this_mb_tx->cfg_serial_delay_ms); + usleep(this_mb_tx->cfg_serial_delay_ms * 1000); + } + + //wait time to gbl.slowdown activity (debugging) + if (gbl.slowdown > 0) { + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] thread[%d] fd[%d] gbl.slowdown activated [%0.3f]", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_link_num, modbus_get_socket(this_mb_link->modbus), gbl.slowdown); + usleep(gbl.slowdown * 1000 * 1000); + } + } //end for + + } //end while + + return NULL; +} + +/* + * Check if the transaction is available for this link + */ + +retCode is_this_tx_ready(const int this_mb_link_num, const int this_mb_tx_num, int *ret_available) +{ + const char *fnct_name = "is_this_tx_available"; + mb_tx_t *this_mb_tx; + int this_mb_tx_link_num; + + if (this_mb_tx_num < 0 || this_mb_tx_num > gbl.tot_mb_tx) { + ERR(gbl.init_dbg, "parameter out of range this_mb_tx_num[%d]", this_mb_tx_num); + return retERR; + } + this_mb_tx = &gbl.mb_tx[this_mb_tx_num]; + + if (ret_available == NULL) { + ERR(this_mb_tx->cfg_debug, "NULL pointer"); + return retERR; + } + + this_mb_tx_link_num = this_mb_tx->mb_link_num; + if (this_mb_tx_link_num < 0 || this_mb_tx_link_num >= gbl.tot_mb_links) { + ERR(this_mb_tx->cfg_debug, "parameter out of range this_mb_tx_link_num[%d]", this_mb_tx_link_num); + return retERR; + } + + *ret_available = 0; //defaults to not available + + //the tx is not of this link + if (this_mb_link_num != this_mb_tx_link_num) { + return retOK; + } + + //not now + if (get_time() < this_mb_tx->next_time) { + return retOK; + } + + *ret_available = 1; //is available + return retOK; +} + +/* + * First time connection or reconnection + */ + +retCode get_tx_connection(const int this_mb_tx_num, int *ret_connected) +{ + const char *fnct_name = "get_tx_connection"; + int ret; + mb_tx_t *this_mb_tx; + mb_link_t *this_mb_link; + int this_mb_link_num; + struct timeval timeout; + + if (this_mb_tx_num < 0 || this_mb_tx_num > gbl.tot_mb_tx) { + ERR(gbl.init_dbg, "parameter out of range this_mb_tx_num[%d]", this_mb_tx_num); + return retERR; + } + this_mb_tx = &gbl.mb_tx[this_mb_tx_num]; + + if (ret_connected == NULL) { + ERR(this_mb_tx->cfg_debug, "NULL pointer"); + return retERR; + } + + this_mb_link_num = this_mb_tx->mb_link_num; + if (this_mb_link_num < 0 || this_mb_link_num >= gbl.tot_mb_links) { + ERR(this_mb_tx->cfg_debug, "parameter out of range this_mb_link_num[%d]", this_mb_link_num); + return retERR; + } + this_mb_link = &gbl.mb_links[this_mb_link_num]; + + *ret_connected = 0; //defaults to not connected + + if (modbus_get_socket(this_mb_link->modbus) < 0) { + ret = modbus_connect(this_mb_link->modbus); + if (ret != 0 || modbus_get_socket(this_mb_link->modbus) < 0) { + modbus_set_socket(this_mb_link->modbus, -1); //some times ret was < 0 and fd > 0 + ERR(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] cannot connect to link, ret[%d] fd[%d]", + this_mb_tx_num, this_mb_tx->mb_link_num, ret, modbus_get_socket(this_mb_link->modbus)); + return retOK; //not connected + } + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] new connection -> fd[%d]", + this_mb_tx_num, this_mb_tx->mb_link_num, modbus_get_socket(this_mb_link->modbus)); + } + else { + DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] already connected to fd[%d]", + this_mb_tx_num, this_mb_tx->mb_link_num, modbus_get_socket(this_mb_link->modbus)); + } + + //set slave id according to each mb_tx + ret = modbus_set_slave(this_mb_link->modbus, this_mb_tx->mb_tx_slave_id); + if (ret != 0) { + ERR(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] cannot set slave [%d]", + this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id); + return retOK; //not connected + } + + //set the low level mb_link debug according to each mb_tx + modbus_set_debug(this_mb_link->modbus, this_mb_tx->protocol_debug); + + //set response and byte timeout according to each mb_tx + timeout.tv_sec = this_mb_tx->mb_response_timeout_ms / 1000; + timeout.tv_usec = (this_mb_tx->mb_response_timeout_ms % 1000) * 1000; +#if LIBMODBUS_VERSION_CHECK(3, 1, 2) + modbus_set_response_timeout(this_mb_link->modbus, timeout.tv_sec, timeout.tv_usec); +#else + modbus_set_response_timeout(this_mb_link->modbus, &timeout); +#endif + //DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] response timeout [%d] ([%d] [%d])", + // this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_response_timeout_ms, + // (int) timeout.tv_sec, (int) timeout.tv_usec); + + timeout.tv_sec = this_mb_tx->mb_byte_timeout_ms / 1000; + timeout.tv_usec = (this_mb_tx->mb_byte_timeout_ms % 1000) * 1000; +#if LIBMODBUS_VERSION_CHECK(3, 1, 2) + modbus_set_byte_timeout(this_mb_link->modbus, timeout.tv_sec, timeout.tv_usec); +#else + modbus_set_byte_timeout(this_mb_link->modbus, &timeout); +#endif + //DBG(this_mb_tx->cfg_debug, "mb_tx_num[%d] mb_links[%d] byte timeout [%d] ([%d] [%d])", + // this_mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_byte_timeout_ms, + // (int) timeout.tv_sec, (int) timeout.tv_usec); + + *ret_connected = 1; //is connected (fd >= 0) + return retOK; +} + +void set_init_gbl_params() +{ + gbl.hal_mod_name = "mb2hal"; //until readed in config file + gbl.hal_mod_id = -1; + gbl.init_dbg = debugERR; //until readed in config file + gbl.slowdown = 0; //until readed in config file + gbl.mb_tx_fncts[mbtxERR] = ""; + gbl.mb_tx_fncts[mbtx_02_READ_DISCRETE_INPUTS] = "fnct_02_read_discrete_inputs"; + gbl.mb_tx_fncts[mbtx_03_READ_HOLDING_REGISTERS] = "fnct_03_read_holding_registers"; + gbl.mb_tx_fncts[mbtx_04_READ_INPUT_REGISTERS] = "fnct_04_read_input_registers"; + gbl.mb_tx_fncts[mbtx_05_WRITE_SINGLE_COIL] = "fnct_05_write_single_coil"; + gbl.mb_tx_fncts[mbtx_15_WRITE_MULTIPLE_COILS] = "fnct_15_write_multiple_coils"; + gbl.mb_tx_fncts[mbtx_16_WRITE_MULTIPLE_REGISTERS]= "fnct_16_write_multiple_registers"; + + return; +} + +double get_time() +{ + struct timeval time; + gettimeofday(&time, NULL); + return (time.tv_sec + ((double) time.tv_usec / 1000000.0f)); +} + +/* + * Called to unload HAL module + * unloadusr and unload commands + */ + +void quit_signal(int signal) +{ + const char *fnct_name = "quit_signal"; + + gbl.quit_flag = 1; //tell the threads to quit (SIGTERM o SIGQUIT) (unloadusr mb2hal). + DBG(gbl.init_dbg, "signal [%d] received", signal); +} + +void quit_cleanup(void) +{ + const char *fnct_name = "quit_cleanup"; + int counter, ret; + + DBG(gbl.init_dbg, "started"); + + for (counter = 0; counter < gbl.tot_mb_links; counter++) { + if (gbl.mb_links[counter].modbus != NULL) { + modbus_close(gbl.mb_links[counter].modbus); + modbus_free(gbl.mb_links[counter].modbus); + gbl.mb_links[counter].modbus = NULL; + } + } + gbl.tot_mb_links = 0; + + if (gbl.mb_tx != NULL) { + free(gbl.mb_tx); + } + gbl.mb_tx = NULL; + gbl.tot_mb_tx = 0; + + if (gbl.hal_mod_id >= 0) { + ret = hal_exit(gbl.hal_mod_id); + DBG(gbl.init_dbg, "unloading HAL module [%d] ret[%d]", gbl.hal_mod_id, ret); + } + + DBG(gbl.init_dbg, "done OK"); +} diff --git a/mb2halv2/mb2hal.h b/mb2halv2/mb2hal.h new file mode 100755 index 0000000000000000000000000000000000000000..273281685e47374c684f04469034c0d612acf927 --- /dev/null +++ b/mb2halv2/mb2hal.h @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include + +#include "rtapi.h" +#ifdef RTAPI +#include "rtapi_app.h" +#endif +#include "rtapi_string.h" +#include "rtapi_errno.h" +#include "hal.h" +#include "inifile.h" +#include "emcglb.h" + +#include + +#define MB2HAL_MAX_LINKS 32 +#define MB2HAL_MAX_DEVICE_LENGTH 32 +#define MB2HAL_DEFAULT_TCP_PORT 502 +#define MB2HAL_DEFAULT_MB_RESPONSE_TIMEOUT_MS 500 +#define MB2HAL_DEFAULT_MB_BYTE_TIMEOUT_MS 500 +#define MB2HAL_DEFAULT_TCP_PORT 502 +#define MB2HAL_MAX_FNCT02_ELEMENTS 100 +#define MB2HAL_MAX_FNCT03_ELEMENTS 100 +#define MB2HAL_MAX_FNCT04_ELEMENTS 100 +#define MB2HAL_MAX_FNCT05_ELEMENTS 100 +#define MB2HAL_MAX_FNCT15_ELEMENTS 100 +#define MB2HAL_MAX_FNCT16_ELEMENTS 100 +#define MB2HAL_MAX_HAL_MAP_PIN 100 + +#ifdef MODULE_VERBOSE +MODULE_VERBOSE(emc2, "component:mb2halv2:Userspace HAL component to communicate with one or more Modbus devices"); +MODULE_VERBOSE(emc2, "license:LGPL"); +MODULE_LICENSE("LGPL"); +#endif + +typedef enum { + linkUndefined = 1, + linkRTU, + linkTCP + } link_type_t; + +typedef enum { mbtxERR, + mbtx_02_READ_DISCRETE_INPUTS, + mbtx_03_READ_HOLDING_REGISTERS, + mbtx_04_READ_INPUT_REGISTERS, + mbtx_05_WRITE_SINGLE_COIL, + mbtx_15_WRITE_MULTIPLE_COILS, + mbtx_16_WRITE_MULTIPLE_REGISTERS, + mbtxMAX + } mb_tx_fnct_t; //modbus transaction code +typedef enum { debugSILENT, debugERR, debugOK, debugDEBUG, debugMAX + } DEBUG_TYPE; //message levels +typedef enum { retOK, retOKwithWarning, retERR + } retCode; //funtions return codes + +#define ERR(debug, fmt, args...) if(debug >= debugERR) {fprintf(stderr, "%s %s ERR: " fmt "\n", gbl.hal_mod_name, fnct_name, ## args);} +#define OK(debug, fmt, args...) if(debug >= debugOK) {fprintf(stdout, "%s %s OK: " fmt "\n", gbl.hal_mod_name, fnct_name, ## args);} +#define DBG(debug, fmt, args...) if(debug >= debugDEBUG) {fprintf(stdout, "%s %s DEBUG: " fmt "\n", gbl.hal_mod_name, fnct_name, ## args);} + +// Structure used to store enhanced pin mapping configuration + +typedef struct { + char *name; // The name of the HAL pin + int addr; + int width; // Number of successive Modbus registers associated to this pin (useful to make 32bits value from 2 x 16bits registers + hal_type_t type; // HAL_BIT, HAL_FLOAT, HAL_S32, HAL_U32 + hal_float_t scale; + hal_float_t offset; + int bitnumber; +} hal_map_pin_t; + +typedef union { + hal_bit_t _b; + hal_s32_t _s; + hal_u32_t _u; + hal_float_t _f; +} hal_data_u; + +//Modbus transaction structure (mb_tx_t) +//Store each transaction defined in INI config file +//Plus HAL and run time parameters for each transaction +typedef struct { + //cfg_* are link params only for INI config reading purpose + //then we use the parameters of mb_link_t + link_type_t cfg_link_type; //RTU (serial) or TCP + char cfg_link_type_str[10]; //str version of lp_link_type + char cfg_serial_device[MB2HAL_MAX_DEVICE_LENGTH]; //device: example "/dev/ttyS0" + int cfg_serial_baud; //bauds + char cfg_serial_parity[5]; //parity: "even", "odd", "none" + int cfg_serial_data_bit; //data bit + int cfg_serial_stop_bit; //stop bit + int cfg_serial_delay_ms; //delay between tx in serial lines + char cfg_tcp_ip[17]; //tcp address + int cfg_tcp_port; //tcp port number + //mb_* are Modbus transaction protocol related params + int mb_tx_slave_id; //MB device id + mb_tx_fnct_t mb_tx_fnct; //MB function code id + char mb_tx_fnct_name[64]; //str version of mb_tx_fnct + int mb_tx_1st_addr; //MB first register + int mb_tx_nelem; //MB n registers + int mb_response_timeout_ms; //MB response timeout + int mb_byte_timeout_ms; //MB byte timeout + //cfg_* are others INI config params + double cfg_update_rate; //tx update rate + int cfg_debug; //tx debug level (program, may be also protocol) + //Modbus protocol debug + int protocol_debug; //Flag debug Modbus protocol + //internal processing values + int mb_tx_num; //each tx know it's own number + int mb_link_num; //each tx know it's own link + //internal processing values + double time_increment; //wait time between tx + double next_time; //next time for this tx + double last_time_ok; //last OK tx time + //HAL related params + char hal_tx_name[HAL_NAME_LEN + 1]; + hal_float_t **float_value; + hal_s32_t **int_value; + hal_bit_t **bit; + hal_u32_t **num_errors; //num of acummulated errors (0=last tx OK) + hal_s32_t **cumul_errors; //num of errors, never reset + hal_s32_t **cumul_transactions;//num of transactions + hal_bit_t **modbus_ok; //true if modbus communication is ok + int nb_hal_map_pin; + hal_map_pin_t hal_map_pin[MB2HAL_MAX_HAL_MAP_PIN]; + hal_data_u **pin_value; +} mb_tx_t; + +//Modbus link structure (mb_link_t) +//Group common transaction's links in one unique link +//Store each run time Modbus link parameters here +typedef struct { + //lp_* are real link params for real time use + link_type_t lp_link_type; //RTU (serial) or TCP + char lp_type_com_str[10]; //str version of lp_type_com + char lp_serial_device[MB2HAL_MAX_DEVICE_LENGTH]; //device: example "/dev/ttyS0" + int lp_serial_baud; //bauds + char lp_serial_parity; //parity: 'E', 'O', 'N' + //NOTE in mb_tx is "even", "odd", "none" + int lp_serial_data_bit; //data bit + int lp_serial_stop_bit; //stop bit + int lp_serial_delay_ms; //delay between tx in serial lines + char lp_tcp_ip[17]; //tcp address + int lp_tcp_port; //tcp port number + //run time processing values + int mb_link_num; //corresponding number of this link/thread + modbus_t *modbus; + pthread_t thrd; +} mb_link_t; + +//Structure of global data (gbl_t) +//Reduce functions parameters using this common global structure. +typedef struct { + //INI config file + FILE *ini_file_ptr; + char *ini_file_path; + //INI config, common section + int init_dbg; + double slowdown; + //HAL related + int hal_mod_id; + const char *hal_mod_name; + //mb_tx + mb_tx_t *mb_tx; + int tot_mb_tx; + //mb_links + mb_link_t *mb_links; + int tot_mb_links; + //others + const char *mb_tx_fncts[mbtxMAX]; + int quit_flag; +} gbl_t; + +extern gbl_t gbl; + +//mb2hal.c +void *link_loop_and_logic(void *thrd_link_num); +retCode is_this_tx_ready(const int this_mb_link_num, const int this_mb_tx_num, int *ret_available); +retCode get_tx_connection(const int mb_tx_num, int *ret_connected); +void set_init_gbl_params(); +double get_time(); +void quit_signal(int signal); +void quit_cleanup(void); + +//mb2hal_init.c +retCode parse_main_args(int argc, char **argv); +retCode parse_ini_file(); +retCode parse_common_section(); +retCode parse_transaction_section(const int mb_tx_num); +retCode parse_tcp_subsection(const char *section, const int mb_tx_num); +retCode parse_serial_subsection(const char *section, const int mb_tx_num); +retCode check_int_in(int n_args, const int int_value, ...); +retCode check_str_in(int n_args, const char *str_value, ...); +retCode init_mb_links(); +retCode init_mb_tx(); + +//mb2hal_hal.c +retCode create_HAL_pins(); +retCode create_each_mb_tx_hal_pins(mb_tx_t *mb_tx); + +//mb2hal_modbus.c +retCode fnct_15_write_multiple_coils(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link); +retCode fnct_02_read_discrete_inputs(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link); +retCode fnct_04_read_input_registers(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link); +retCode fnct_03_read_holding_registers(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link); +retCode fnct_16_write_multiple_registers(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link); +retCode fnct_05_write_single_coil(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link); diff --git a/mb2halv2/mb2hal_HOWTO.ini b/mb2halv2/mb2hal_HOWTO.ini new file mode 100755 index 0000000000000000000000000000000000000000..35d2b74fc6757114eb4fd5aa7d8dd00fdeffbd48 --- /dev/null +++ b/mb2halv2/mb2hal_HOWTO.ini @@ -0,0 +1,223 @@ +#This .INI file is also the HELP, MANUAL and HOW-TO file for mb2hal. + +#Load the modbus HAL userspace module as the examples below, +#change to match your own HAL_MODULE_NAME and .ini file name +#Using HAL_MODULE_NAME=mb2hal or nothing (default): loadusr -W mb2hal config=config_file.ini +#Using HAL_MODULE_NAME=mymodule: loadusr -Wn mymodule mb2hal config=config_file.ini + +#Common section +[MB2HAL_INIT] + +#OPTIONAL: Debug level of init and INI file parsing. +# 0 = silent. +# 1 = error messages (default). +# 2 = OK confirmation messages. +# 3 = debugging messages. +INIT_DEBUG=3 + +#OPTIONAL: HAL module (component) name. Defaults to "mb2hal". +HAL_MODULE_NAME=mb2hal + +#OPTIONAL: Insert a delay of "FLOAT seconds" between transactions in order +#to not to have a lot of logging and facilitate the debugging. +#Usefull when using DEBUG=3 (NOT INIT_DEBUG=3) +#It affects ALL transactions. +#Use "0.0" for normal activity. +SLOWDOWN=0.0 + +#REQUIRED: The number of total Modbus transactions. There is no maximum. +TOTAL_TRANSACTIONS=9 + +#One transaction section is required per transaction, starting at 00 and counting up sequentially. +#If there is a new link (not transaction), you must provide the REQUIRED parameters 1st time. +#Warning: Any OPTIONAL parameter not specified are copied from the previous transaction. +[TRANSACTION_00] + +#REQUIRED: You must specify either a "serial" or "tcp" link for the first transaction. +#Later transaction will use the previous transaction link if not specified. +LINK_TYPE=tcp + +#if LINK_TYPE=tcp then REQUIRED (only 1st time): The Modbus slave device ip address. +#if LINK_TYPE=serial then IGNORED +TCP_IP=192.168.2.10 + +#if LINK_TYPE=tcp then OPTIONAL. +#if LINK_TYPE=serial then IGNORED +#The Modbus slave device tcp port. Defaults to 502. +TCP_PORT=502 + +#if LINK_TYPE=serial then REQUIRED (only 1st time). +#if LINK_TYPE=tcp then IGNORED +#The serial port. +SERIAL_PORT=/dev/ttyS0 + +#if LINK_TYPE=serial then REQUIRED (only 1st time). +#if LINK_TYPE=tcp then IGNORED +#The baud rate. +SERIAL_BAUD=115200 + +#if LINK_TYPE=serial then REQUIRED (only 1st time). +#if LINK_TYPE=tcp then IGNORED +#Data bits. One of 5,6,7,8. +SERIAL_BITS=8 + +#if LINK_TYPE=serial then REQUIRED (only 1st time). +#if LINK_TYPE=tcp then IGNORED +#Data parity. One of: even, odd, none. +SERIAL_PARITY=none + +#if LINK_TYPE=serial then REQUIRED (only 1st time). +#if LINK_TYPE=tcp then IGNORED +#Stop bits. One of 1, 2. +SERIAL_STOP=2 + +#if LINK_TYPE=serial then OPTIONAL: +#if LINK_TYPE=tcp then IGNORED +#Serial port delay between for this transaction only. +#In ms. Defaults to 0. +SERIAL_DELAY_MS=10 + +#REQUIRED (only 1st time). +#Modbus slave number. +MB_SLAVE_ID=1 + +#REQUIRED: The first element address. +FIRST_ELEMENT=0 + +#REQUIRED: The number of elements. +NELEMENTS=16 + +#REQUIRED: Modbus transaction function code (see www.modbus.org specifications). +# fnct_02_read_discrete_inputs (02 = 0x02) +# fnct_03_read_holding_registers (03 = 0x03) +# fnct_04_read_input_registers (04 = 0x04) +# fnct_15_write_multiple_coils (15 = 0x0F) +# fnct_16_write_multiple_registers (16 = 0x10) + +#fnct_02_read_discrete_inputs: creates boolean output HAL pins. +#fnct_03_read_holding_registers: creates a floating point output HAL pins. +# also creates a u32 output HAL pins. +#fnct_04_read_input_registers: creates a floating point output HAL pins. +# also creates a u32 output HAL pins. +#fnct_15_write_multiple_coils: creates boolean input HAL pins. +#fnct_16_write_multiple_registers: creates a floating point input HAL pins. + +#The pins are named based on component name, transaction number and order number. +#Example: mb2hal.00.01 (transaction=00, second register=01 (00 is the first one)) + +MB_TX_CODE=fnct_03_read_holding_registers + +#OPTIONAL: Response timeout for this transaction. In INTEGER ms. Defaults to 500 ms. +#This is how much to wait for 1st byte before raise an error. +MB_RESPONSE_TIMEOUT_MS=500 + +#OPTIONAL: Byte timeout for this transaction. In INTEGER ms. Defaults to 500 ms. +#This is how much to wait from byte to byte before raise an error. +MB_BYTE_TIMEOUT_MS=500 + +#OPTIONAL: Instead of giving the transaction number, use a name. +#Example: mb2hal.00.01 could become mb2hal.plcin.01 +#The name must not exceed 32 characters. +#NOTE: when using names be careful that you dont end up with two transactions +#usign the same name. +HAL_TX_NAME=remoteIOcfg + +#OPTIONAL: Maximum update rate in HZ. Defaults to 0.0 (0.0 = as soon as available = infinit). +#NOTE: This is a maximum rate and the actual rate may be lower. +#If you want to calculate it in ms use (1000 / required_ms). +#Example: 100 ms = MAX_UPDATE_RATE=10.0, because 1000.0 ms / 100.0 ms = 10.0 Hz +MAX_UPDATE_RATE=0.0 + +#OPTIONAL: Debug level for this transaction only. +#See INIT_DEBUG parameter above. +DEBUG=1 + +#While DEBUGGING transactions note the returned "ret[]" value correspond to: +#/* Modbus protocol exceptions */ +#ILLEGAL_FUNCTION -0x01 the FUNCTION code received in the query is not allowed or invalid. +#ILLEGAL_DATA_ADDRESS -0x02 the DATA ADDRESS received in the query is not an allowable address for the slave or is invalid. +#ILLEGAL_DATA_VALUE -0x03 a VALUE contained in the data query field is not an allowable value or is invalid. +#SLAVE_DEVICE_FAILURE -0x04 SLAVE (or MASTER) device unrecoverable FAILUER while attemping to perform the requested action. +#SERVER_FAILURE -0x04 (see above). +#ACKNOWLEDGE -0x05 This response is returned to PREVENT A TIMEOUT in the master. +# A long duration of time is required to process the request in the slave. +#SLAVE_DEVICE_BUSY -0x06 The slave (or server) is BUSY. Retrasmit the request later. +#SERVER_BUSY -0x06 (see above). +#NEGATIVE_ACKNOWLEDGE -0x07 Unsuccessful programming request using function code 13 or 14. +#MEMORY_PARITY_ERROR -0x08 SLAVE parity error in MEMORY. +#GATEWAY_PROBLEM_PATH -0x0A (-10) Gateway path(s) not available. +#GATEWAY_PROBLEM_TARGET -0x0B (-11) The target device failed to repond (generated by master, not slave). +#/* Program or connection */ +#COMM_TIME_OUT -0x0C (-12) +#PORT_SOCKET_FAILURE -0x0D (-13) +#SELECT_FAILURE -0x0E (-14) +#TOO_MANY_DATAS -0x0F (-15) +#INVALID_CRC -0x10 (-16) +#INVALID_EXCEPTION_CODE -0x11 (-17) + +[TRANSACTION_01] +MB_TX_CODE=fnct_02_read_discrete_inputs +FIRST_ELEMENT=1024 +NELEMENTS=24 +HAL_TX_NAME=remoteIOin +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_02] +MB_TX_CODE=fnct_15_write_multiple_coils +FIRST_ELEMENT=1280 +NELEMENTS=8 +HAL_TX_NAME=remoteIOout +MAX_UPDATE_RATE=0.0 + +[TRANSACTION_03] +LINK_TYPE=serial +SERIAL_PORT=/dev/ttyS0 +SERIAL_BAUD=115200 +SERIAL_BITS=8 +SERIAL_PARITY=none +SERIAL_STOP=2 +SERIAL_DELAY_MS=50 +MB_SLAVE_ID=1 +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1 +NELEMENTS=2 +HAL_TX_NAME=XDrive01 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_04] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=4 +NELEMENTS=3 +HAL_TX_NAME=XDrive02 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_05] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=9 +NELEMENTS=1 +HAL_TX_NAME=XDrive03 +MAX_UPDATE_RATE=0.0 + +[TRANSACTION_06] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1024 +NELEMENTS=1 +HAL_TX_NAME=XDrive04 +MAX_UPDATE_RATE=0.0 + +[TRANSACTION_07] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1030 +NELEMENTS=2 +HAL_TX_NAME=XDrive05 +MAX_UPDATE_RATE=0.0 + +[TRANSACTION_08] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1033 +NELEMENTS=1 +HAL_TX_NAME=XDrive06 +MAX_UPDATE_RATE=0.0 diff --git a/mb2halv2/mb2hal_hal.c b/mb2halv2/mb2hal_hal.c new file mode 100755 index 0000000000000000000000000000000000000000..68a2c3b92c5d2e8936880c0a40d4ec5e5968d44e --- /dev/null +++ b/mb2halv2/mb2hal_hal.c @@ -0,0 +1,224 @@ +#include "mb2hal.h" + +retCode create_HAL_pins() +{ + const char *fnct_name = "create_HAL_pins"; + int tx_counter; + + for (tx_counter = 0; tx_counter < gbl.tot_mb_tx; tx_counter++) { + if (create_each_mb_tx_hal_pins(&gbl.mb_tx[tx_counter]) != retOK) { + ERR(gbl.init_dbg, "failed to initialize hal pins in tx_num[%d] [%d] [%s]", + tx_counter, gbl.mb_tx[tx_counter].mb_tx_fnct, gbl.mb_tx[tx_counter].mb_tx_fnct_name); + return retERR; + } + } + + return retOK; +} + +#define CREATE_PIN(C, T, F)\ +mb_tx->C = (T **)hal_malloc(sizeof(T *));\ +if (mb_tx->C == NULL) {\ + ERR(gbl.init_dbg, "[%d] [%s] NULL hal_malloc num_errors",\ + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name);\ + return retERR;\ +}\ +memset(mb_tx->C, 0, sizeof(T *));\ +snprintf(hal_pin_name, HAL_NAME_LEN, "%s.%s.%s", gbl.hal_mod_name, mb_tx->hal_tx_name, #C);\ +if (0 != F(HAL_OUT, mb_tx->C, gbl.hal_mod_id, "%s", hal_pin_name)) {\ + ERR(gbl.init_dbg, "[%d] [%s] [%s] " #F " pin failed", mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, hal_pin_name);\ + return retERR;\ +}\ +**(mb_tx->num_errors) = 0;\ +DBG(gbl.init_dbg, "mb_tx_num [%d] pin_name [%s]", mb_tx->mb_tx_num, hal_pin_name); + +retCode create_each_mb_tx_hal_pins(mb_tx_t *mb_tx) +{ + const char *fnct_name = "create_each_mb_tx_hal_pins"; + char hal_pin_name[HAL_NAME_LEN + 1]; + int pin_counter; + + if (mb_tx == NULL) { + ERR(gbl.init_dbg, "NULL pointer"); + return retERR; + } + + //num_errors hal pin + mb_tx->num_errors = (hal_u32_t**)hal_malloc(sizeof(hal_u32_t *)); + if (mb_tx->num_errors == NULL) { + ERR(gbl.init_dbg, "[%d] [%s] NULL hal_malloc num_errors", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name); + return retERR; + } + memset(mb_tx->num_errors, 0, sizeof(hal_u32_t *)); + snprintf(hal_pin_name, HAL_NAME_LEN, "%s.%s.num_errors", gbl.hal_mod_name, mb_tx->hal_tx_name); + if (0 != hal_pin_u32_newf(HAL_OUT, mb_tx->num_errors, gbl.hal_mod_id, "%s", hal_pin_name)) { + ERR(gbl.init_dbg, "[%d] [%s] [%s] hal_pin_u32_newf failed", mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, hal_pin_name); + return retERR; + } + **(mb_tx->num_errors) = 0; + DBG(gbl.init_dbg, "mb_tx_num [%d] pin_name [%s]", mb_tx->mb_tx_num, hal_pin_name); + + // modbus_ok hal pin + CREATE_PIN(modbus_ok, hal_bit_t, hal_pin_bit_newf); + CREATE_PIN(cumul_errors, hal_s32_t, hal_pin_s32_newf); + CREATE_PIN(cumul_transactions, hal_s32_t, hal_pin_s32_newf); + + if (!mb_tx->nb_hal_map_pin) { // Old fashioned pin management + switch (mb_tx->mb_tx_fnct) { + + case mbtx_02_READ_DISCRETE_INPUTS: + case mbtx_15_WRITE_MULTIPLE_COILS: + case mbtx_05_WRITE_SINGLE_COIL: + mb_tx->bit = (hal_bit_t **)hal_malloc(sizeof(hal_bit_t *) * mb_tx->mb_tx_nelem); + if (mb_tx->bit == NULL) { + ERR(gbl.init_dbg, "[%d] [%s] NULL hal_malloc [%d] elements", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, mb_tx->mb_tx_nelem); + return retERR; + } + memset(mb_tx->bit, 0, sizeof(hal_bit_t *) * mb_tx->mb_tx_nelem); + break; + + case mbtx_03_READ_HOLDING_REGISTERS: + case mbtx_04_READ_INPUT_REGISTERS: + case mbtx_16_WRITE_MULTIPLE_REGISTERS: + mb_tx->float_value= (hal_float_t **)hal_malloc(sizeof(hal_float_t *) * mb_tx->mb_tx_nelem); + mb_tx->int_value = (hal_s32_t **)hal_malloc(sizeof(hal_s32_t *) * mb_tx->mb_tx_nelem); + //mb_tx->scale = hal_malloc(sizeof(hal_float_t) * mb_tx->mb_tx_nelem); + //mb_tx->offset = hal_malloc(sizeof(hal_float_t) * mb_tx->mb_tx_nelem); + //if (mb_tx->float_value == NULL || mb_tx->int_value == NULL + // || mb_tx->scale == NULL || mb_tx->offset == NULL) { + if (mb_tx->float_value == NULL || mb_tx->int_value == NULL) { + ERR(gbl.init_dbg, "[%d] [%s] NULL hal_malloc [%d] elements", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, mb_tx->mb_tx_nelem); + return retERR; + } + memset(mb_tx->float_value, 0, sizeof(hal_float_t *) * mb_tx->mb_tx_nelem); + memset(mb_tx->int_value, 0, sizeof(hal_s32_t *) * mb_tx->mb_tx_nelem); + //memset((void *) mb_tx->scale, 0, sizeof(hal_float_t) * mb_tx->mb_tx_nelem); + //memset((void *) mb_tx->offset, 0, sizeof(hal_float_t) * mb_tx->mb_tx_nelem); + break; + + default: + ERR(gbl.init_dbg, "[%d] wrong mb_tx_fnct", mb_tx->mb_tx_fnct); + return retERR; + break; + } + + for (pin_counter = 0; pin_counter < mb_tx->mb_tx_nelem; pin_counter++) { + + snprintf(hal_pin_name, HAL_NAME_LEN, "%s.%s.%02d", gbl.hal_mod_name, mb_tx->hal_tx_name, pin_counter); + DBG(gbl.init_dbg, "mb_tx_num [%d] pin_name [%s]", mb_tx->mb_tx_num, hal_pin_name); + + switch (mb_tx->mb_tx_fnct) { + case mbtx_15_WRITE_MULTIPLE_COILS: + case mbtx_05_WRITE_SINGLE_COIL: + if (0 != hal_pin_bit_newf(HAL_IN, mb_tx->bit + pin_counter, gbl.hal_mod_id, + "%s", hal_pin_name)) { + ERR(gbl.init_dbg, "[%d] [%s] [%s] hal_pin_bit_newf failed", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, hal_pin_name); + return retERR; + } + *mb_tx->bit[pin_counter] = 0; + break; + case mbtx_02_READ_DISCRETE_INPUTS: + if (0 != hal_pin_bit_newf(HAL_OUT, mb_tx->bit + pin_counter, gbl.hal_mod_id, + "%s", hal_pin_name)) { + ERR(gbl.init_dbg, "[%d] [%s] [%s] hal_pin_bit_newf failed", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, hal_pin_name); + return retERR; + } + *mb_tx->bit[pin_counter] = 0; + break; + case mbtx_04_READ_INPUT_REGISTERS: + case mbtx_03_READ_HOLDING_REGISTERS: + if (0 != hal_pin_float_newf(HAL_OUT, mb_tx->float_value + pin_counter, gbl.hal_mod_id, + "%s.float", hal_pin_name)) { + ERR(gbl.init_dbg, "[%d] [%s] [%s] hal_pin_float_newf failed", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, hal_pin_name); + return retERR; + } + if (0 != hal_pin_s32_newf(HAL_OUT, mb_tx->int_value + pin_counter, gbl.hal_mod_id, + "%s.int", hal_pin_name)) { + ERR(gbl.init_dbg, "[%d] [%s] [%s] hal_pin_s32_newf failed", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, hal_pin_name); + return retERR; + } + *mb_tx->float_value[pin_counter] = 0; + *mb_tx->int_value[pin_counter] = 0; + break; + case mbtx_16_WRITE_MULTIPLE_REGISTERS: + if (0 != hal_pin_float_newf(HAL_IN, mb_tx->float_value + pin_counter, gbl.hal_mod_id, + "%s", hal_pin_name)) { + ERR(gbl.init_dbg, "[%d] [%s] [%s] hal_pin_float_newf failed", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, hal_pin_name); + return retERR; + } + *mb_tx->float_value[pin_counter] = 0; + break; + default: + ERR(gbl.init_dbg, "[%d]", mb_tx->mb_tx_fnct); + return retERR; + break; + } + } + } + else { // We are using enhanced pin mapping + hal_pin_dir_t dir; + + switch (mb_tx->mb_tx_fnct) { + case mbtx_15_WRITE_MULTIPLE_COILS: + case mbtx_16_WRITE_MULTIPLE_REGISTERS: + case mbtx_05_WRITE_SINGLE_COIL: + dir = HAL_IN; + break; + case mbtx_02_READ_DISCRETE_INPUTS: + case mbtx_04_READ_INPUT_REGISTERS: + case mbtx_03_READ_HOLDING_REGISTERS: + dir = HAL_OUT; + break; + default: + ERR(gbl.init_dbg, "[%d]", mb_tx->mb_tx_fnct); + return retERR; + break; + } + + mb_tx->pin_value = (hal_data_u **)hal_malloc(sizeof(hal_data_u *) * mb_tx->nb_hal_map_pin); + if (mb_tx->pin_value == NULL) { + ERR(gbl.init_dbg, "[%d] [%s] NULL hal_malloc [%d] pins", + mb_tx->mb_tx_fnct, mb_tx->mb_tx_fnct_name, mb_tx->nb_hal_map_pin); + return retERR; + } + memset(mb_tx->pin_value, 0, sizeof(hal_data_u *) * mb_tx->nb_hal_map_pin); + + for (int index=0; indexnb_hal_map_pin; index++) { + hal_map_pin_t *m = &mb_tx->hal_map_pin[index]; + int rc = 1; + switch (m->type) { + case HAL_BIT: + rc = hal_pin_bit_newf(dir, (hal_bit_t **)mb_tx->pin_value + index, gbl.hal_mod_id, + "%s.%s.%s", gbl.hal_mod_name, mb_tx->hal_tx_name, m->name); + break; + case HAL_FLOAT: + rc = hal_pin_float_newf(dir, (hal_float_t **)mb_tx->pin_value + index, gbl.hal_mod_id, + "%s.%s.%s", gbl.hal_mod_name, mb_tx->hal_tx_name, m->name); + break; + case HAL_S32: + rc = hal_pin_s32_newf(dir, (hal_s32_t **)mb_tx->pin_value + index, gbl.hal_mod_id, + "%s.%s.%s", gbl.hal_mod_name, mb_tx->hal_tx_name, m->name); + break; + case HAL_U32: + rc = hal_pin_u32_newf(dir, (hal_u32_t **)mb_tx->pin_value + index, gbl.hal_mod_id, + "%s.%s.%s", gbl.hal_mod_name, mb_tx->hal_tx_name, m->name); + break; + } + + if (0 != rc) { + ERR(gbl.init_dbg, "hal_pin_xxx_newf(type=%d, name=%s.%s.%s) error", + m->type, gbl.hal_mod_name, mb_tx->hal_tx_name, m->name); + return retERR; + } + } + } + return retOK; +} diff --git a/mb2halv2/mb2hal_init.c b/mb2halv2/mb2hal_init.c new file mode 100755 index 0000000000000000000000000000000000000000..6a2d822dca785fa35716c12fd58476b9c5659f93 --- /dev/null +++ b/mb2halv2/mb2hal_init.c @@ -0,0 +1,806 @@ +#include +#include "mb2hal.h" + +#ifdef INIFILE_LEGACY +#include "inifile.hh" + +extern "C" const char *iniFindnum(FILE *fp, const char *tag, const char *section, const int num) +{ + IniFile f(false, fp); + + return(f.Find(tag, section, num)); +} +#endif + +retCode parse_main_args(int argc, char **argv) +{ + const char *fnct_name = "parse_main_args"; + char *arg_filename; + char *tmp; + + if (argc != 2) { + ERR(gbl.init_dbg, "wrong number of args"); + return retERR; + } + + if (strncmp(argv[1], "config=", 7) != 0) { //has config file + ERR(gbl.init_dbg, "config parameter not found"); + return retERR; + } + + arg_filename = argv[1]; + arg_filename += 7; + + if (*arg_filename == '\"') { //file name is quoted + arg_filename++; + tmp = arg_filename; + while (*tmp != '\"' && *tmp != 0) { + tmp++; + } + *tmp = 0; //remove trailing quote + } + gbl.ini_file_path = strdup(arg_filename); + + return retOK; +} + +retCode parse_ini_file() +{ + const char *fnct_name = "parse_ini_file"; + int counter; + + if (gbl.ini_file_ptr == NULL) { + ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); + return retERR; + } + + if (parse_common_section() != retOK) { + ERR(gbl.init_dbg, "parse_common_section failed"); + return retERR; + } + + //default = one link per transaction + //realloc will be used if there are common links + gbl.mb_links = (mb_link_t*)malloc(sizeof(mb_link_t) * gbl.tot_mb_tx); + if (gbl.mb_links == NULL) { + ERR(gbl.init_dbg, "malloc gbl.mb_links failed [%s]", strerror(errno)); + return retERR; + } + memset(gbl.mb_links, 0, sizeof(mb_link_t) * gbl.tot_mb_tx); + + gbl.mb_tx = (mb_tx_t*)malloc(sizeof(mb_tx_t) * gbl.tot_mb_tx); + if (gbl.mb_tx == NULL) { + ERR(gbl.init_dbg, "malloc gbl.mb_tx failed [%s]", strerror(errno)); + return retERR; + } + memset(gbl.mb_tx, 0, sizeof(mb_tx_t) * gbl.tot_mb_tx); + + for (counter = 0; counter < gbl.tot_mb_tx; counter++) { + if (parse_transaction_section(counter) != retOK) { + ERR(gbl.init_dbg, "parse_transaction_section %d failed", counter); + return retERR; + } + OK(gbl.init_dbg, "parse_transaction_section %d OK", counter); + } + + return retOK; +} + +retCode parse_common_section() +{ + const char *fnct_name = "parse_common_section"; + const char *section = "MB2HAL_INIT", *tag; + const char *tmpstr; + + if (gbl.ini_file_ptr == NULL) { + ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); + return retERR; + } + + tag = "INIT_DEBUG"; //optional + iniFindInt(gbl.ini_file_ptr, tag, section, &gbl.init_dbg); + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, gbl.init_dbg); + + tag = "HAL_MODULE_NAME"; //optional + tmpstr = iniFind(gbl.ini_file_ptr, tag, section); + if (tmpstr != NULL) { + gbl.hal_mod_name = strdup(tmpstr); + } + //else already initilizaed by default + DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, gbl.hal_mod_name); + + tag = "SLOWDOWN"; //optional + iniFindDouble(gbl.ini_file_ptr, tag, section, &gbl.slowdown); + DBG(gbl.init_dbg, "[%s] [%s] [%0.3f]", section, tag, gbl.slowdown); + + tag = "TOTAL_TRANSACTIONS"; //required + if (iniFindInt(gbl.ini_file_ptr, tag, section, &gbl.tot_mb_tx) != 0) { + ERR(gbl.init_dbg, "required [%s] [%s] not found", section, tag); + return retERR; + } + if (gbl.tot_mb_tx <= 0) { + ERR(gbl.init_dbg, "[%s] [%s] [%d], must be > 0", section, tag, gbl.tot_mb_tx); + return retERR; + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, gbl.tot_mb_tx); + + return retOK; +} + +/**************************************************************** + * HAL_MAP_PIN config syntax + * HAL_MAP_PIN=,
, s16, [scale, offset] + * HAL_MAP_PIN=,
, u16, [scale, offset] + * HAL_MAP_PIN=,
, f16, [scale, offset] + * HAL_MAP_PIN=,
, s32, [scale, offset] + * HAL_MAP_PIN=,
, u32, [scale, offset] + * HAL_MAP_PIN=,
, f32, [scale, offset] + * HAL_MAP_PIN=,
, bit, [bitnumber] +****************************************************************/ + +retCode parse_hal_map_pin(mb_tx_t *this_mb_tx, const char *config) +{ + const char *fnct_name = "parse_hal_map_pin"; + char *c = strdup(config); + const char delims[] = " ,"; + hal_map_pin_t *m; + + if (!this_mb_tx->nb_hal_map_pin == MB2HAL_MAX_HAL_MAP_PIN) { + ERR(gbl.init_dbg, "out of memory"); + return retERR; + } + + char *name = strtok(c, delims); + char *addr = strtok(NULL, delims); + char *type = strtok(NULL, delims); + char *arg1 = strtok(NULL, delims); + char *arg2 = strtok(NULL, delims); + + if (!name || !addr || !type) { + ERR(gbl.init_dbg, "HAL_MAP_PIN syntax error"); + return retERR; + } + + int index = this_mb_tx->nb_hal_map_pin++; + + m = this_mb_tx->hal_map_pin + index; + m->name = strdup(name); + m->addr = strtol(addr, NULL, 0); + + if (!strcasecmp(type, "bit")) { m->type = HAL_BIT; m->width = 1; } + else if (!strcasecmp(type, "s16")) { m->type = HAL_S32; m->width = 1; } + else if (!strcasecmp(type, "u16")) { m->type = HAL_U32; m->width = 1; } + else if (!strcasecmp(type, "f16")) { m->type = HAL_FLOAT; m->width = 1; } + else if (!strcasecmp(type, "s32")) { m->type = HAL_S32; m->width = 2; } + else if (!strcasecmp(type, "u32")) { m->type = HAL_U32; m->width = 2; } + else if (!strcasecmp(type, "f32")) { m->type = HAL_FLOAT; m->width = 2; } + else { + ERR(gbl.init_dbg, "HAL_MAP_PIN unknown type %s", type); + return retERR; + } + + if (m->type == HAL_BIT) { + m->bitnumber = arg1 ? atoi(arg1) : 0; + } + else { + m->scale = arg1 ? atof(arg1) : 1; + m->offset = arg2 ? atof(arg2) : 0; + } + + free(c); + return retOK; +} + +retCode parse_transaction_section(const int mb_tx_num) +{ + const char *fnct_name = "parse_transaction_section"; + char section[20]; + const char *tag; + const char *tmpstr; + mb_tx_t *this_mb_tx; + + if (gbl.ini_file_ptr == NULL) { + ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); + return retERR; + } + if (mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { + ERR(gbl.init_dbg, "out of range"); + return retERR; + } + + this_mb_tx = &gbl.mb_tx[mb_tx_num]; + + if (gbl.ini_file_ptr == NULL || mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { + ERR(gbl.init_dbg, "parameter error"); + return retERR; + } + + snprintf(section, sizeof(section)-1, "TRANSACTION_%02d", mb_tx_num); + + tag = "LINK_TYPE"; //required 1st time, then optional + tmpstr = iniFind(gbl.ini_file_ptr, tag, section); + if (tmpstr != NULL) { + if (strcasecmp(tmpstr, "tcp") == retOK) { + this_mb_tx->cfg_link_type = linkTCP; + strcpy(this_mb_tx->cfg_link_type_str, tmpstr); + } + else if (strcasecmp(tmpstr, "serial") == retOK) { + this_mb_tx->cfg_link_type = linkRTU; + strcpy(this_mb_tx->cfg_link_type_str, tmpstr); + } + else { + this_mb_tx->cfg_link_type = linkUndefined; + strcpy(this_mb_tx->cfg_link_type_str, ""); + ERR(gbl.init_dbg, "[%s] [%s] [%s] is not valid", section, tag, tmpstr); + return retERR; + } + } + else { + if (mb_tx_num > 0) { //previous value + this_mb_tx->cfg_link_type = gbl.mb_tx[mb_tx_num-1].cfg_link_type; + strcpy(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str); + } + else { //required 1rst time + this_mb_tx->cfg_link_type = linkUndefined; + strcpy(this_mb_tx->cfg_link_type_str, ""); + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%s] [%d]", section, tag, this_mb_tx->cfg_link_type_str, this_mb_tx->cfg_link_type); + + if (this_mb_tx->cfg_link_type == linkTCP) { //tcp + if (parse_tcp_subsection(section, mb_tx_num) != retOK) { + ERR(gbl.init_dbg, "parsing error"); + return retERR; + } + } + else { //serial + if (parse_serial_subsection(section, mb_tx_num) != retOK) { + ERR(gbl.init_dbg, "parsing error"); + return retERR; + } + } + + tag = "MB_SLAVE_ID"; //1st time required + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_tx_slave_id) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->mb_tx_slave_id = gbl.mb_tx[mb_tx_num-1].mb_tx_slave_id; + } + else { + this_mb_tx->mb_tx_slave_id = -1; + ERR(gbl.init_dbg, "required [%s] [%s] not found, and previous LINK_TYPE is useless", section, tag); + return retERR; + } + } + else { //required 1rst time + this_mb_tx->mb_tx_slave_id = -1; + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->mb_tx_slave_id); + + tag = "FIRST_ELEMENT"; //required if HAL_MAP_PIN not used + if (!iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_tx_1st_addr)) { + if (this_mb_tx->mb_tx_1st_addr < 0) { + ERR(gbl.init_dbg, "[%s] [%s] [%d] out of range", section, tag, this_mb_tx->mb_tx_1st_addr); + return retERR; + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->mb_tx_1st_addr); + } + else if (!iniFindnum(gbl.ini_file_ptr, "HAL_MAP_PIN", section, 1)) { + ERR(gbl.init_dbg, "required [%s] [%s or HAL_MAP_PIN] not found", section, tag); + return retERR; + } + + tag = "NELEMENTS"; //required if HAL_MAP_PIN not used + if (!iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_tx_nelem)) { + if (this_mb_tx->mb_tx_nelem < 1) { + ERR(gbl.init_dbg, "[%s] [%s] [%d] out of range", section, tag, this_mb_tx->mb_tx_nelem); + return retERR; + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->mb_tx_nelem); + + + } + else if (!iniFindnum(gbl.ini_file_ptr, "HAL_MAP_PIN", section, 1)) { + ERR(gbl.init_dbg, "required [%s] [%s or HAL_MAP_PIN] not found", section, tag); + return retERR; + } + + tag = "MAX_UPDATE_RATE"; //optional + this_mb_tx->cfg_update_rate = 0; //default: 0=infinit + if (iniFindDouble(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_update_rate) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->cfg_update_rate = gbl.mb_tx[mb_tx_num-1].cfg_update_rate; + } + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%0.3f]", section, tag, this_mb_tx->cfg_update_rate); + + tag = "MB_RESPONSE_TIMEOUT_MS"; //optional + this_mb_tx->mb_response_timeout_ms = MB2HAL_DEFAULT_MB_RESPONSE_TIMEOUT_MS; //default + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_response_timeout_ms) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->mb_response_timeout_ms = gbl.mb_tx[mb_tx_num-1].mb_response_timeout_ms; + } + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->mb_response_timeout_ms); + + tag = "MB_BYTE_TIMEOUT_MS"; //optional + this_mb_tx->mb_byte_timeout_ms = MB2HAL_DEFAULT_MB_BYTE_TIMEOUT_MS; //default + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_byte_timeout_ms) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->mb_byte_timeout_ms = gbl.mb_tx[mb_tx_num-1].mb_byte_timeout_ms; + } + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->mb_byte_timeout_ms); + + tag = "DEBUG"; //optional + this_mb_tx->cfg_debug = debugERR; //default + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_debug) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->cfg_debug = gbl.mb_tx[mb_tx_num-1].cfg_debug; + } + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_debug); + + tag = "MB_TX_CODE"; //required + tmpstr = iniFind(gbl.ini_file_ptr, tag, section); + if (tmpstr != NULL) { + int i; + for (i=0 ; imb_tx_fnct = (mb_tx_fnct_t)i; + strncpy(this_mb_tx->mb_tx_fnct_name, tmpstr, sizeof(this_mb_tx->mb_tx_fnct_name)-1); + break; + } + } + if (this_mb_tx->mb_tx_fnct <= mbtxERR || this_mb_tx->mb_tx_fnct >= mbtxMAX) { + ERR(gbl.init_dbg, "[%s] [%s] [%s] out of range", section, tag, tmpstr); + return retERR; + } + } + else { + ERR(gbl.init_dbg, "required [%s] [%s] not found", section, tag); + return retERR; + } + DBG(gbl.init_dbg, "[%s] [%s] [%s] [%d]", section, tag, this_mb_tx->mb_tx_fnct_name, this_mb_tx->mb_tx_fnct); + + tag = "HAL_TX_NAME"; //optional + tmpstr = iniFind(gbl.ini_file_ptr, tag, section); + if (tmpstr != NULL) { + strncpy(this_mb_tx->hal_tx_name, tmpstr, HAL_NAME_LEN); + } + else { + sprintf(this_mb_tx->hal_tx_name, "%02d", mb_tx_num); + } + DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, this_mb_tx->hal_tx_name); + + // Manage optional HAL_MAP_PIN records + + int num=1; + do { + tmpstr = iniFindnum(gbl.ini_file_ptr, "HAL_MAP_PIN", section, num++); + if (tmpstr) { + retCode rc = parse_hal_map_pin(this_mb_tx, tmpstr); + if (rc != retOK) return rc; + } + + } while (tmpstr); + + // If we are using HAL_MAP_PIN, then FIRST_ELEMENT and NELEMENTS are automatically computed + + if (this_mb_tx->nb_hal_map_pin > 0) { + int min_addr = this_mb_tx->hal_map_pin[0].addr; + int max_addr = min_addr + this_mb_tx->hal_map_pin[0].width -1; + for (int i=1; inb_hal_map_pin; i++) { + min_addr = MIN(min_addr, this_mb_tx->hal_map_pin[i].addr); + max_addr = MAX(max_addr, this_mb_tx->hal_map_pin[i].addr + this_mb_tx->hal_map_pin[i].width -1); + } + this_mb_tx->mb_tx_1st_addr = min_addr; + this_mb_tx->mb_tx_nelem = max_addr - min_addr + 1; + } + + return retOK; +} + +retCode parse_tcp_subsection(const char *section, const int mb_tx_num) +{ + const char *fnct_name="parse_tcp_subsection"; + const char *tag; + const char *tmpstr; + mb_tx_t *this_mb_tx; + + if (gbl.ini_file_ptr == NULL || section == NULL) { + ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); + return retERR; + } + if (mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { + ERR(gbl.init_dbg, "out of range"); + return retERR; + } + + this_mb_tx = &gbl.mb_tx[mb_tx_num]; + + tag = "TCP_IP"; //required 1st time, then optional + tmpstr = iniFind(gbl.ini_file_ptr, tag, section); + if (tmpstr != NULL) { + strncpy(this_mb_tx->cfg_tcp_ip, tmpstr, sizeof(this_mb_tx->cfg_tcp_ip)-1); + } + else { + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + strcpy(this_mb_tx->cfg_tcp_ip, gbl.mb_tx[mb_tx_num-1].cfg_tcp_ip); + } + else { + strcpy(this_mb_tx->cfg_tcp_ip, ""); + ERR(gbl.init_dbg, "required [%s] [%s] not found, and previous LINK_TYPE is useless", section, tag); + return retERR; + } + } + else { //required 1rst time + strcpy(this_mb_tx->cfg_tcp_ip, ""); + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, this_mb_tx->cfg_tcp_ip); + + tag = "TCP_PORT"; //optional + this_mb_tx->cfg_tcp_port = MB2HAL_DEFAULT_TCP_PORT; //default + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_tcp_port) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->cfg_tcp_port = gbl.mb_tx[mb_tx_num-1].cfg_tcp_port; + } + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_tcp_port); + + return retOK; +} + +retCode parse_serial_subsection(const char *section, const int mb_tx_num) +{ + const char *fnct_name="parse_serial_subsection"; + const char *tag; + const char *tmpstr; + mb_tx_t *this_mb_tx; + + if (gbl.ini_file_ptr == NULL || section == NULL) { + ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); + return retERR; + } + if (mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { + ERR(gbl.init_dbg, "out of range"); + return retERR; + } + + this_mb_tx = &gbl.mb_tx[mb_tx_num]; + + tag = "SERIAL_PORT"; //required 1st time + tmpstr = iniFind(gbl.ini_file_ptr, tag, section); + if (tmpstr != NULL) { + strncpy(this_mb_tx->cfg_serial_device, tmpstr, sizeof(this_mb_tx->cfg_serial_device)-1); + } + else { + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + strcpy(this_mb_tx->cfg_serial_device, gbl.mb_tx[mb_tx_num-1].cfg_serial_device); + } + else { + strcpy(this_mb_tx->cfg_serial_device, ""); + ERR(gbl.init_dbg, "required [%s] [%s] not found, and previous LINK_TYPE is useless", section, tag); + return retERR; + } + } + else { //required 1rst time + strcpy(this_mb_tx->cfg_serial_device, ""); + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, this_mb_tx->cfg_serial_device); + + tag = "SERIAL_BAUD"; //1st time required + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_baud) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->cfg_serial_baud = gbl.mb_tx[mb_tx_num-1].cfg_serial_baud; + } + else { + this_mb_tx->cfg_serial_baud = -1; + ERR(gbl.init_dbg, "required [%s] [%s] not found, and previous LINK_TYPE is useless", section, tag); + return retERR; + } + } + else { //required 1rst time + this_mb_tx->cfg_serial_baud = -1; + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_serial_baud); + + tag = "SERIAL_BITS"; //1st time required + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_data_bit) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->cfg_serial_data_bit = gbl.mb_tx[mb_tx_num-1].cfg_serial_data_bit; + } + else { + this_mb_tx->cfg_serial_data_bit = -1; + ERR(gbl.init_dbg, "required [%s] [%s] not found, and previous LINK_TYPE is useless", section, tag); + return retERR; + } + } + else { //required 1rst time + this_mb_tx->cfg_serial_data_bit = -1; + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + if (check_int_in(4, this_mb_tx->cfg_serial_data_bit, 5, 6, 7, 8) != retOK) { + ERR(gbl.init_dbg, "[%s] [%s] [%d] out of range", section, tag, this_mb_tx->cfg_serial_data_bit); + return retERR; + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_serial_data_bit); + + tag = "SERIAL_PARITY"; //required 1st time + tmpstr = iniFind(gbl.ini_file_ptr, tag, section); + if (tmpstr != NULL) { + strncpy(this_mb_tx->cfg_serial_parity, tmpstr, sizeof(this_mb_tx->cfg_serial_parity)-1); + } + else { + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + strcpy(this_mb_tx->cfg_serial_parity, gbl.mb_tx[mb_tx_num-1].cfg_serial_parity); + } + else { + strcpy(this_mb_tx->cfg_serial_parity, ""); + ERR(gbl.init_dbg, "required [%s] [%s] not found, and previous LINK_TYPE is useless", section, tag); + return retERR; + } + } + else { //required 1rst time + strcpy(this_mb_tx->cfg_serial_parity, ""); + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + if (check_str_in(3, this_mb_tx->cfg_serial_parity, "even", "odd", "none") != retOK) { + ERR(gbl.init_dbg, "[%s] [%s] [%s] out of range", section, tag, this_mb_tx->cfg_serial_parity); + return retERR; + } + DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, this_mb_tx->cfg_serial_parity); + + tag = "SERIAL_STOP"; //1st time required + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_stop_bit) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->cfg_serial_stop_bit = gbl.mb_tx[mb_tx_num-1].cfg_serial_stop_bit; + } + else { + this_mb_tx->cfg_serial_stop_bit = -1; + ERR(gbl.init_dbg, "required [%s] [%s] not found, and previous LINK_TYPE is useless", section, tag); + return retERR; + } + } + else { //required 1rst time + this_mb_tx->cfg_serial_stop_bit = -1; + ERR(gbl.init_dbg, "required 1st time [%s] [%s] not found", section, tag); + return retERR; + } + } + if (check_int_in(2, this_mb_tx->cfg_serial_stop_bit, 1, 2) != retOK) { + ERR(gbl.init_dbg, "[%s] [%s] [%d] out of range", section, tag, this_mb_tx->cfg_serial_stop_bit); + return retERR; + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_serial_stop_bit); + + tag = "SERIAL_DELAY_MS"; //optional + this_mb_tx->cfg_serial_delay_ms = 0; //default + if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_delay_ms) != 0) { //not found + if (mb_tx_num > 0) { //previous value? + if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { + this_mb_tx->cfg_serial_delay_ms = gbl.mb_tx[mb_tx_num-1].cfg_serial_delay_ms; + } + } + } + DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_serial_delay_ms); + + return retOK; +} + +retCode check_int_in(int n_args, const int int_value, ...) +{ + va_list ap; + int counter; + + va_start(ap, int_value); + + for (counter = 2; counter < (n_args+2); counter++) { + if (int_value == va_arg(ap, int)) { + va_end(ap); + return retOK; //found + } + } + + va_end(ap); + return retERR; //not found +} + +retCode check_str_in(int n_args, const char *str_value, ...) +{ + va_list ap; + int counter; + + if (str_value == NULL) { + return retERR; //not found + } + + va_start(ap, str_value); + + for (counter = 2; counter < (n_args+2); counter++) { + if (strcasecmp(str_value, va_arg(ap, char *)) == 0) { + va_end(ap); + return retOK; //found + } + } + + va_end(ap); + return retERR; //not found +} + +/* + * init global (unrepeated) modbus links (gbl.mb_links[]) + * (serial or tcp connections) + */ +retCode init_mb_links() +{ + const char *fnct_name="init_mb_links"; + int tx_counter, lk_counter; + int isNewLink; + mb_link_t *this_mb_link; + mb_tx_t *this_mb_tx; + + //group common links transactions + gbl.tot_mb_links = 0; //total and next available unused link + + for (tx_counter = 0; tx_counter < gbl.tot_mb_tx; tx_counter++) { + this_mb_tx = &gbl.mb_tx[tx_counter]; + + isNewLink = 1; //Default to true + if (this_mb_tx->cfg_link_type == linkRTU) { //serial + for (lk_counter = 0; lk_counter < gbl.tot_mb_links; lk_counter++) { + if (strcasecmp(this_mb_tx->cfg_serial_device, gbl.mb_links[lk_counter].lp_serial_device) == 0) { + isNewLink = 0; //repeated link + this_mb_tx->mb_link_num = lk_counter; //each tx know its own link + break; + } + } + } + else { //tcp + for (lk_counter = 0; lk_counter < gbl.tot_mb_links; lk_counter++) { + if (strcasecmp(this_mb_tx->cfg_tcp_ip, gbl.mb_links[lk_counter].lp_tcp_ip) == 0) { + isNewLink = 0; //repeated link + this_mb_tx->mb_link_num = lk_counter; //each tx know its own link + break; + } + } + } + if (isNewLink != 0) { //initialize new link + this_mb_tx->mb_link_num = gbl.mb_links[gbl.tot_mb_links].mb_link_num = gbl.tot_mb_links; //next available unused link + this_mb_link = &gbl.mb_links[gbl.tot_mb_links]; + + this_mb_link->lp_link_type = this_mb_tx->cfg_link_type; + + if (this_mb_link->lp_link_type == linkRTU) { //serial + strncpy(this_mb_link->lp_serial_device, this_mb_tx->cfg_serial_device, MB2HAL_MAX_DEVICE_LENGTH-1); + this_mb_link->lp_serial_baud=this_mb_tx->cfg_serial_baud; + + if (strcasecmp(this_mb_tx->cfg_serial_parity, "even") == 0) { + this_mb_link->lp_serial_parity = 'E'; + } + else if (strcasecmp(this_mb_tx->cfg_serial_parity, "odd") == 0) { + this_mb_link->lp_serial_parity = 'O'; + } + else { //default = parity none + this_mb_link->lp_serial_parity = 'N'; + } + + this_mb_link->lp_serial_data_bit=this_mb_tx->cfg_serial_data_bit; + this_mb_link->lp_serial_stop_bit=this_mb_tx->cfg_serial_stop_bit; + + this_mb_link->modbus = modbus_new_rtu(this_mb_link->lp_serial_device, + this_mb_link->lp_serial_baud, this_mb_link->lp_serial_parity, + this_mb_link->lp_serial_data_bit, this_mb_link->lp_serial_stop_bit); + if (this_mb_link->modbus == NULL) { + ERR(gbl.init_dbg, "modbus_new_rtu failed [%s] [%d] [%c] [%d] [%d]", this_mb_link->lp_serial_device, + this_mb_link->lp_serial_baud, this_mb_link->lp_serial_parity, this_mb_link->lp_serial_data_bit, + this_mb_link->lp_serial_stop_bit); + return retERR; + } + } + else { //tcp + strncpy(this_mb_link->lp_tcp_ip, this_mb_tx->cfg_tcp_ip, sizeof(this_mb_tx->cfg_tcp_ip)-1); + this_mb_link->lp_tcp_port=this_mb_tx->cfg_tcp_port; + + this_mb_link->modbus = modbus_new_tcp(this_mb_link->lp_tcp_ip, this_mb_link->lp_tcp_port); + if (this_mb_link->modbus == NULL) { + ERR(gbl.init_dbg, "modbus_new_tcp failed [%s] [%d]", this_mb_link->lp_tcp_ip, this_mb_link->lp_tcp_port); + return retERR; + } + } + + if (this_mb_link->modbus) + + { + gbl.tot_mb_links++; //set new total and next available unused link + } + } + } + + //DEBUG messagess if needed + for (lk_counter = 0; lk_counter < gbl.tot_mb_links; lk_counter++) { + this_mb_link = &gbl.mb_links[lk_counter]; + + if (this_mb_link->lp_link_type == linkRTU) { //serial + DBG(gbl.init_dbg, "LINK %d (RTU) link_type[%d] device[%s] baud[%d] data[%d] parity[%c] stop[%d] fd[%d]", + lk_counter, this_mb_link->lp_link_type, this_mb_link->lp_serial_device, + this_mb_link->lp_serial_baud, this_mb_link->lp_serial_data_bit, this_mb_link->lp_serial_parity, + this_mb_link->lp_serial_stop_bit, modbus_get_socket(this_mb_link->modbus)); + } + else { //tcp + DBG(gbl.init_dbg, "LINK %d (TCP) link_type[%d] IP[%s] port[%d] fd[%d]", + lk_counter, this_mb_link->lp_link_type, this_mb_link->lp_tcp_ip, + this_mb_link->lp_tcp_port, modbus_get_socket(this_mb_link->modbus)); + } + } + + gbl.mb_links = (mb_link_t*)realloc(gbl.mb_links, sizeof(mb_link_t) * gbl.tot_mb_links); + if (gbl.mb_links == NULL) { + DBG(gbl.init_dbg, "realloc gbl.mb_links failed [%s]", strerror(errno)); + return retERR; + } + + return retOK; +} + +/* + * init more parameters of global modbus transactions (gbl.mb_tx) + */ +retCode init_mb_tx() +{ + const char *fnct_name="init_mb_tx"; + int tx_counter; + mb_tx_t *this_mb_tx; + + for (tx_counter = 0; tx_counter < gbl.tot_mb_tx; tx_counter++) { + this_mb_tx = &gbl.mb_tx[tx_counter]; + + this_mb_tx->mb_tx_num = tx_counter; + this_mb_tx->protocol_debug = (this_mb_tx->cfg_debug >= debugDEBUG)? 1 : 0; + + if (this_mb_tx->cfg_update_rate > 0) { + this_mb_tx->time_increment = 1.0 / this_mb_tx->cfg_update_rate; //wait time between tx + } + this_mb_tx->next_time = 0; //next time for this tx + + DBG(gbl.init_dbg, "MB_TX %d lk_n[%d] tx_n[%d] cfg_dbg[%d] lk_dbg[%d] t_inc[%0.3f] nxt_t[%0.3f]", + tx_counter, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_num, this_mb_tx->cfg_debug, + this_mb_tx->protocol_debug, this_mb_tx->time_increment, this_mb_tx->next_time); + } + + return retOK; +} diff --git a/mb2halv2/mb2hal_modbus.c b/mb2halv2/mb2hal_modbus.c new file mode 100755 index 0000000000000000000000000000000000000000..8d1f383ce0c6b0526316fd63262e66b8c806c1f8 --- /dev/null +++ b/mb2halv2/mb2hal_modbus.c @@ -0,0 +1,328 @@ +#include +#include "mb2hal.h" + +static retCode map_read(mb_tx_t *this_mb_tx, uint16_t *data) +{ + for (int counter = 0; counter < this_mb_tx->nb_hal_map_pin; counter++) { + hal_map_pin_t *m = this_mb_tx->hal_map_pin + counter; + int addr = m->addr - this_mb_tx->mb_tx_1st_addr; + uint32_t val; + + if (this_mb_tx->hal_map_pin[counter].width == 2) { + val = ((uint32_t)data[addr] << 16) | data[addr+1]; + } + else { + if (m->type == HAL_U32) val = data[addr]; + else val = (int16_t)data[addr]; + } + + switch (m->type) { + case HAL_S32: + this_mb_tx->pin_value[counter]->_s = (hal_s32_t)val * m->scale + m->offset; + break; + case HAL_U32: + this_mb_tx->pin_value[counter]->_u = (hal_u32_t)val * m->scale + m->offset; + break; + case HAL_FLOAT: + this_mb_tx->pin_value[counter]->_f = (hal_s32_t)val * m->scale + m->offset; + break; + case HAL_BIT: + this_mb_tx->pin_value[counter]->_b = (val >> m->bitnumber) & 1; + break; + default: + return retERR; + } + } + + return retOK; +} + +static retCode map_write(mb_tx_t *this_mb_tx, uint16_t *data) +{ + for (int counter = 0; counter < this_mb_tx->nb_hal_map_pin; counter++) { + hal_map_pin_t *m = this_mb_tx->hal_map_pin + counter; + int addr = m->addr - this_mb_tx->mb_tx_1st_addr; + uint32_t val; + uint32_t mask; + + switch (m->type) { + case HAL_S32: + val = this_mb_tx->pin_value[counter]->_s * m->scale + m->offset; + break; + case HAL_U32: + val = this_mb_tx->pin_value[counter]->_u * m->scale + m->offset; + break; + case HAL_FLOAT: + val = this_mb_tx->pin_value[counter]->_f * m->scale + m->offset; + break; + case HAL_BIT: + val = this_mb_tx->pin_value[counter]->_b << m->bitnumber; + mask = 0xFFFFFFFF ^ (1 << m->bitnumber); + break; + default: + return retERR; + } + + if (m->width == 2) { + data[addr] = val >> 16; + data[addr+1] = val & 0xFFFF; + } + else { + if (m->type != HAL_BIT) data[addr] = val; + else data[addr] = (data[addr] & mask) | val; + } + } + + return retOK; +} + +retCode fnct_02_read_discrete_inputs(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link) +{ + const char *fnct_name = "fnct_02_read_discrete_inputs"; + int counter, ret; + uint8_t bits[MB2HAL_MAX_FNCT02_ELEMENTS]; + + if (this_mb_tx == NULL || this_mb_link == NULL) { + return retERR; + } + if (this_mb_tx->mb_tx_nelem > MB2HAL_MAX_FNCT02_ELEMENTS) { + return retERR; + } + + DBG(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] fd[%d] 1st_addr[%d] nelem[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, modbus_get_socket(this_mb_link->modbus), + this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem); + + ret = modbus_read_input_bits(this_mb_link->modbus, this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem, bits); + if (ret < 0) { + if (modbus_get_socket(this_mb_link->modbus) < 0) { + modbus_close(this_mb_link->modbus); + } + ERR(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] = ret[%d] fd[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, ret, + modbus_get_socket(this_mb_link->modbus)); + return retERR; + } + + if (!this_mb_tx->nb_hal_map_pin) { + for (counter = 0; counter < this_mb_tx->mb_tx_nelem; counter++) { + *(this_mb_tx->bit[counter]) = bits[counter]; + } + } + else { + for (int counter = 0; counter < this_mb_tx->nb_hal_map_pin; counter++) { + hal_map_pin_t *m = this_mb_tx->hal_map_pin + counter; + int addr = m->addr - this_mb_tx->mb_tx_1st_addr; + this_mb_tx->pin_value[counter]->_b = bits[addr]; + } + } + + return retOK; +} + +retCode fnct_03_read_holding_registers(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link) +{ + const char *fnct_name = "fnct_03_read_holding_registers"; + int counter, ret; + uint16_t data[MB2HAL_MAX_FNCT03_ELEMENTS]; + + if (this_mb_tx == NULL || this_mb_link == NULL) { + return retERR; + } + if (this_mb_tx->mb_tx_nelem > MB2HAL_MAX_FNCT03_ELEMENTS) { + return retERR; + } + + DBG(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] fd[%d] 1st_addr[%d] nelem[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, + modbus_get_socket(this_mb_link->modbus), this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem); + + ret = modbus_read_registers(this_mb_link->modbus, this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem, data); + if (ret < 0) { + if (modbus_get_socket(this_mb_link->modbus) < 0) { + modbus_close(this_mb_link->modbus); + } + ERR(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] = ret[%d] fd[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, ret, + modbus_get_socket(this_mb_link->modbus)); + return retERR; + } + + if (!this_mb_tx->nb_hal_map_pin) { + for (counter = 0; counter < this_mb_tx->mb_tx_nelem; counter++) { + float val = data[counter]; + *(this_mb_tx->float_value[counter]) = val; + *(this_mb_tx->int_value[counter]) = (hal_s32_t) val; + } + } + else { + map_read(this_mb_tx, data); + } + + return retOK; +} + +retCode fnct_04_read_input_registers(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link) +{ + const char *fnct_name = "fnct_04_read_input_registers"; + int counter, ret; + uint16_t data[MB2HAL_MAX_FNCT04_ELEMENTS]; + + if (this_mb_tx == NULL || this_mb_link == NULL) { + return retERR; + } + if (this_mb_tx->mb_tx_nelem > MB2HAL_MAX_FNCT04_ELEMENTS) { + return retERR; + } + + DBG(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] fd[%d] 1st_addr[%d] nelem[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, + modbus_get_socket(this_mb_link->modbus), this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem); + + ret = modbus_read_input_registers(this_mb_link->modbus, this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem, data); + if (ret < 0) { + if (modbus_get_socket(this_mb_link->modbus) < 0) { + modbus_close(this_mb_link->modbus); + } + ERR(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] = ret[%d] fd[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, ret, + modbus_get_socket(this_mb_link->modbus)); + return retERR; + } + + if (!this_mb_tx->nb_hal_map_pin) { + for (counter = 0; counter < this_mb_tx->mb_tx_nelem; counter++) { + float val = data[counter]; + *(this_mb_tx->float_value[counter]) = val; + *(this_mb_tx->int_value[counter]) = (hal_s32_t) val; + } + } + else { + map_read(this_mb_tx, data); + } + + return retOK; +} + +retCode fnct_05_write_single_coil(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link) +{ + const char *fnct_name = "fnct_05_write_single_coil"; + int ret; + uint8_t bit; + + if (this_mb_tx == NULL || this_mb_link == NULL) { + return retERR; + } + if (this_mb_tx->mb_tx_nelem != 1) { + return retERR; + } + + if (!this_mb_tx->nb_hal_map_pin) { + bit = *(this_mb_tx->bit[0]); + } + else { + bit = this_mb_tx->pin_value[0]->_b; + } + + DBG(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] fd[%d] 1st_addr[%d] nelem[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, + modbus_get_socket(this_mb_link->modbus), this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem); + + ret = modbus_write_bit(this_mb_link->modbus, this_mb_tx->mb_tx_1st_addr, bit); + if (ret < 0) { + if (modbus_get_socket(this_mb_link->modbus) < 0) { + modbus_close(this_mb_link->modbus); + } + ERR(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] = ret[%d] fd[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, ret, + modbus_get_socket(this_mb_link->modbus)); + return retERR; + } + + return retOK; +} + +retCode fnct_15_write_multiple_coils(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link) +{ + const char *fnct_name = "fnct_15_write_multiple_coils"; + int counter, ret; + uint8_t bits[MB2HAL_MAX_FNCT15_ELEMENTS]; + + if (this_mb_tx == NULL || this_mb_link == NULL) { + return retERR; + } + if (this_mb_tx->mb_tx_nelem > MB2HAL_MAX_FNCT15_ELEMENTS) { + return retERR; + } + + if (!this_mb_tx->nb_hal_map_pin) { + for (counter = 0; counter < this_mb_tx->mb_tx_nelem; counter++) { + bits[counter] = *(this_mb_tx->bit[counter]); + } + } + else { + for (int counter = 0; counter < this_mb_tx->nb_hal_map_pin; counter++) { + hal_map_pin_t *m = this_mb_tx->hal_map_pin + counter; + int addr = m->addr - this_mb_tx->mb_tx_1st_addr; + bits[addr] = this_mb_tx->pin_value[counter]->_b; + } + } + + DBG(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] fd[%d] 1st_addr[%d] nelem[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, + modbus_get_socket(this_mb_link->modbus), this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem); + + ret = modbus_write_bits(this_mb_link->modbus, this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem, bits); + if (ret < 0) { + if (modbus_get_socket(this_mb_link->modbus) < 0) { + modbus_close(this_mb_link->modbus); + } + ERR(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] = ret[%d] fd[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, ret, + modbus_get_socket(this_mb_link->modbus)); + return retERR; + } + + return retOK; +} + +retCode fnct_16_write_multiple_registers(mb_tx_t *this_mb_tx, mb_link_t *this_mb_link) +{ + const char *fnct_name = "fnct_16_write_multiple_registers"; + int counter, ret; + uint16_t data[MB2HAL_MAX_FNCT16_ELEMENTS] = { 0 }; + + if (this_mb_tx == NULL || this_mb_link == NULL) { + return retERR; + } + if (this_mb_tx->mb_tx_nelem > MB2HAL_MAX_FNCT16_ELEMENTS) { + return retERR; + } + + if (!this_mb_tx->nb_hal_map_pin) { + for (counter = 0; counter < this_mb_tx->mb_tx_nelem; counter++) { + float val = *(this_mb_tx->float_value[counter]); + data[counter] = (int) val; + } + } + else { + map_write(this_mb_tx, data); + } + + DBG(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] fd[%d] 1st_addr[%d] nelem[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, + modbus_get_socket(this_mb_link->modbus), this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem); + + ret = modbus_write_registers(this_mb_link->modbus, this_mb_tx->mb_tx_1st_addr, this_mb_tx->mb_tx_nelem, data); + if (ret < 0) { + if (modbus_get_socket(this_mb_link->modbus) < 0) { + modbus_close(this_mb_link->modbus); + } + ERR(this_mb_tx->cfg_debug, "mb_tx[%d] mb_links[%d] slave[%d] = ret[%d] fd[%d]", + this_mb_tx->mb_tx_num, this_mb_tx->mb_link_num, this_mb_tx->mb_tx_slave_id, ret, + modbus_get_socket(this_mb_link->modbus)); + return retERR; + } + + return retOK; +} diff --git a/mb2halv2/tests/mb2hal_test_01.ini b/mb2halv2/tests/mb2hal_test_01.ini new file mode 100755 index 0000000000000000000000000000000000000000..ca4538f0f07a42ca63ccb9e462d96b5773e3f530 --- /dev/null +++ b/mb2halv2/tests/mb2hal_test_01.ini @@ -0,0 +1,88 @@ +#TEST 01: TCP and RS232. +# - TCP = Remote IO Delta RTU-EN01, 24 inputs, 8 outputs. +# - RS232 = Servo Drive Delta ASDA-B (1021-A 1Kw). + + +[MB2HAL_INIT] +INIT_DEBUG=2 +SLOWDOWN=0.0 +TOTAL_TRANSACTIONS=9 + +[TRANSACTION_00] +LINK_TYPE=tcp +TCP_IP=192.168.2.10 +TCP_PORT=502 +MB_SLAVE_ID=1 +FIRST_ELEMENT=0 +NELEMENTS=16 +MB_TX_CODE=fnct_03_read_holding_registers +HAL_TX_NAME=remoteIOcfg +MAX_UPDATE_RATE=10.0 +DEBUG=1 + +[TRANSACTION_01] +MB_TX_CODE=fnct_02_read_discrete_inputs +FIRST_ELEMENT=1024 +NELEMENTS=24 +HAL_TX_NAME=remoteIOin +MAX_UPDATE_RATE=10.0 +DEBUG=1 + +[TRANSACTION_02] +MB_TX_CODE=fnct_15_write_multiple_coils +FIRST_ELEMENT=1280 +NELEMENTS=8 +HAL_TX_NAME=remoteIOout +MAX_UPDATE_RATE=10.0 + +[TRANSACTION_03] +LINK_TYPE=serial +SERIAL_PORT=/dev/ttyS0 +SERIAL_BAUD=115200 +SERIAL_BITS=8 +SERIAL_PARITY=none +SERIAL_STOP=2 +SERIAL_DELAY_MS=50 +MB_SLAVE_ID=1 +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1 +NELEMENTS=2 +HAL_TX_NAME=XDrive01 +MAX_UPDATE_RATE=10.0 +DEBUG=1 + +[TRANSACTION_04] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=4 +NELEMENTS=3 +HAL_TX_NAME=XDrive02 +MAX_UPDATE_RATE=10.0 +DEBUG=1 + +[TRANSACTION_05] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=9 +NELEMENTS=1 +HAL_TX_NAME=XDrive03 +MAX_UPDATE_RATE=10.0 + +[TRANSACTION_06] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1024 +NELEMENTS=1 +HAL_TX_NAME=XDrive04 +MAX_UPDATE_RATE=10.0 + +[TRANSACTION_07] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1030 +NELEMENTS=2 +HAL_TX_NAME=XDrive05 +MAX_UPDATE_RATE=10.0 + +[TRANSACTION_08] +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1033 +NELEMENTS=1 +HAL_TX_NAME=XDrive06 +MAX_UPDATE_RATE=10.0 diff --git a/mb2halv2/tests/mb2hal_test_02.ini b/mb2halv2/tests/mb2hal_test_02.ini new file mode 100755 index 0000000000000000000000000000000000000000..e807c190210dff3b6f113ee2aa7940596d25643a --- /dev/null +++ b/mb2halv2/tests/mb2hal_test_02.ini @@ -0,0 +1,19 @@ +#TEST 02: TCP speed. +# - TCP = Remote IO Delta RTU-EN01, 24 inputs, 8 outputs. + +[MB2HAL_INIT] +INIT_DEBUG=3 +SLOWDOWN=0.0 +TOTAL_TRANSACTIONS=1 + +[TRANSACTION_00] +LINK_TYPE=tcp +TCP_IP=192.168.2.10 +TCP_PORT=502 +MB_SLAVE_ID=1 +FIRST_ELEMENT=0 +NELEMENTS=16 +MB_TX_CODE=fnct_03_read_holding_registers +HAL_TX_NAME=remoteIOcfg +MAX_UPDATE_RATE=0.0 +DEBUG=2 diff --git a/mb2halv2/tests/mb2hal_test_03.ini b/mb2halv2/tests/mb2hal_test_03.ini new file mode 100755 index 0000000000000000000000000000000000000000..dc5d6a7705d54bd3645860c584d1b4cfda4347b8 --- /dev/null +++ b/mb2halv2/tests/mb2hal_test_03.ini @@ -0,0 +1,23 @@ +#TEST 03: RS232 speed. +# - RS232 = Servo Drive Delta ASDA-B (1021-A 1Kw). + +[MB2HAL_INIT] +INIT_DEBUG=1 +SLOWDOWN=0.0 +TOTAL_TRANSACTIONS=1 + +[TRANSACTION_00] +LINK_TYPE=serial +SERIAL_PORT=/dev/ttyS0 +SERIAL_BAUD=115200 +SERIAL_BITS=8 +SERIAL_PARITY=none +SERIAL_STOP=2 +SERIAL_DELAY_MS=0 +MB_SLAVE_ID=1 +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=1 +NELEMENTS=2 +HAL_TX_NAME=XDrive01 +MAX_UPDATE_RATE=0.0 +DEBUG=2 diff --git a/mb2halv2/tests/mb2hal_test_04.ini b/mb2halv2/tests/mb2hal_test_04.ini new file mode 100755 index 0000000000000000000000000000000000000000..2e58c26019f61a7043f2b901e5ca0109016efa13 --- /dev/null +++ b/mb2halv2/tests/mb2hal_test_04.ini @@ -0,0 +1,59 @@ +#TEST 04: TCP all supported Modbus functions. +# - TCP = MoD_RsSim V8.20 (www.plcsimulator.org) on Windows XP SP3. + +[MB2HAL_INIT] +INIT_DEBUG=3 +SLOWDOWN=0.0 +TOTAL_TRANSACTIONS=5 + +[TRANSACTION_00] +#Manually change values in Mod_RsSim and see result here +LINK_TYPE=tcp +TCP_IP=192.168.2.10 +TCP_PORT=502 +MB_SLAVE_ID=0 +MB_TX_CODE=fnct_02_read_discrete_inputs +FIRST_ELEMENT=1 +NELEMENTS=8 +HAL_TX_NAME=rd_di +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_01] +#Set values in TRANSACTION_04and read the result here +#Also manually change values in Mod_RsSim and see result here +MB_TX_CODE=fnct_03_read_holding_registers +FIRST_ELEMENT=0 +NELEMENTS=8 +HAL_TX_NAME=rd_hold_reg +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_02] +#Manually change values in Mod_RsSim and see result here +MB_TX_CODE=fnct_04_read_input_registers +FIRST_ELEMENT=1 +NELEMENTS=8 +HAL_TX_NAME=rd_in_reg +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_03] +#Manually change values in halrun, example: setp mb2hal.wr_mult_coils.02 1 +#and see the result in ModRsSim +MB_TX_CODE=fnct_15_write_multiple_coils +FIRST_ELEMENT=1 +NELEMENTS=8 +HAL_TX_NAME=wr_mult_coils +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_04] +#Ina halrun manually do: setp mb2hal.wr_mult_reg.02 2 +#then with "show" check: mb2hal.rd_in_reg.01.int = 2 +MB_TX_CODE=fnct_16_write_multiple_registers +FIRST_ELEMENT=0 +NELEMENTS=8 +HAL_TX_NAME=wr_mult_reg +MAX_UPDATE_RATE=0.0 +DEBUG=1 diff --git a/mb2halv2/tests/test05.hal b/mb2halv2/tests/test05.hal new file mode 100755 index 0000000000000000000000000000000000000000..b99c22dceaa490e71481482dc1cdd9848efb2a8f --- /dev/null +++ b/mb2halv2/tests/test05.hal @@ -0,0 +1,12 @@ +loadusr -Wn mb2hal ../mb2halv2 config=test05.ini + +#net loop0 mb2hal.rd_di.toto-00 => mb2hal.wr_mult_coils.toto-00 +#net loop1 mb2hal.rd_di.toto-01 => mb2hal.wr_mult_coils.toto-01 + +#net loop2 mb2hal.rd_hold_reg.pin-f16 => mb2hal.wr_mult_reg.pin-f16 +#net loop3 mb2hal.rd_hold_reg.pin-s16 => mb2hal.wr_mult_reg.pin-s16 +#net loop4 mb2hal.rd_hold_reg.pin-u16 => mb2hal.wr_mult_reg.pin-u16 + +#net loop5 mb2hal.rd_hold_reg.pin-f32 => mb2hal.wr_mult_reg.pin-f32 +#net loop6 mb2hal.rd_hold_reg.pin-s32 => mb2hal.wr_mult_reg.pin-s32 +#net loop7 mb2hal.rd_hold_reg.pin-u32 => mb2hal.wr_mult_reg.pin-u32 \ No newline at end of file diff --git a/mb2halv2/tests/test05.ini b/mb2halv2/tests/test05.ini new file mode 100755 index 0000000000000000000000000000000000000000..6d04f7d91d4762a2706bb71f6201e227a0dc5661 --- /dev/null +++ b/mb2halv2/tests/test05.ini @@ -0,0 +1,112 @@ +# Should be used with test05.py automatic regression testing + +[MB2HAL_INIT] +INIT_DEBUG=3 +SLOWDOWN=0.0 +TOTAL_TRANSACTIONS=11 + +[TRANSACTION_00] +LINK_TYPE=tcp +TCP_IP=127.0.0.1 +TCP_PORT=8502 +MB_SLAVE_ID=1 +MB_TX_CODE=fnct_02_read_discrete_inputs +HAL_TX_NAME=in +HAL_MAP_PIN=pin-0, 0x64, bit +HAL_MAP_PIN=pin-1, 0x65, bit +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_01] +MB_TX_CODE=fnct_15_write_multiple_coils +HAL_TX_NAME=coils +HAL_MAP_PIN=pin-0, 0, bit +HAL_MAP_PIN=pin-1, 1, bit +HAL_MAP_PIN=pin-2, 2, bit +HAL_MAP_PIN=pin-3, 3, bit +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_02] +MB_TX_CODE=fnct_04_read_input_registers +HAL_TX_NAME=analog +HAL_MAP_PIN=pin-0, 0, f16, 0.1, 50 +HAL_MAP_PIN=pin-1, 1, s16, 0.1, 50 +HAL_MAP_PIN=pin-2, 2, u16, 0.1, 50 +HAL_MAP_PIN=pin-4, 4, f32, 0.1, 50 +HAL_MAP_PIN=pin-6, 6, s32, 0.1, 50 +HAL_MAP_PIN=pin-8, 8, u32, 0.1, 50 +HAL_MAP_PIN=pin-9, 10, f32, 0.059 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_03] +MB_TX_CODE=fnct_16_write_multiple_registers +HAL_TX_NAME=hreg +HAL_MAP_PIN=pin-0, 0, f16, 0.1, 50 +HAL_MAP_PIN=pin-1, 1, s16, 0.1, 50 +HAL_MAP_PIN=pin-2, 2, u16, 0.1, 50 +HAL_MAP_PIN=pin-4, 4, f32, 0.1, 50 +HAL_MAP_PIN=pin-6, 6, s32, 0.1, 50 +HAL_MAP_PIN=pin-8, 8, u32, 0.1, 50 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_04] +LINK_TYPE=tcp +TCP_IP=127.0.0.1 +TCP_PORT=8502 +MB_SLAVE_ID=1 +MB_TX_CODE=fnct_02_read_discrete_inputs +FIRST_ELEMENT=116 +NELEMENTS=2 +HAL_TX_NAME=in2 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_05] +MB_TX_CODE=fnct_15_write_multiple_coils +FIRST_ELEMENT=16 +NELEMENTS=2 +HAL_TX_NAME=coils2 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_06] +MB_TX_CODE=fnct_04_read_input_registers +FIRST_ELEMENT=16 +NELEMENTS=2 +HAL_TX_NAME=analog2 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_07] +MB_TX_CODE=fnct_16_write_multiple_registers +FIRST_ELEMENT=16 +NELEMENTS=2 +HAL_TX_NAME=hreg2 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_08] +MB_TX_CODE=fnct_05_write_single_coil +HAL_TX_NAME=single-coil +HAL_MAP_PIN=pin-0, 64, bit +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_09] +MB_TX_CODE=fnct_04_read_input_registers +HAL_TX_NAME=bitfield-in +HAL_MAP_PIN=bit-00, 8, bit, 0 +HAL_MAP_PIN=bit-12, 8, bit, 12 +MAX_UPDATE_RATE=0.0 +DEBUG=1 + +[TRANSACTION_10] +MB_TX_CODE=fnct_16_write_multiple_registers +HAL_TX_NAME=bitfield-out +HAL_MAP_PIN=bit-01, 10, bit, 1 +HAL_MAP_PIN=bit-11, 10, bit, 11 +MAX_UPDATE_RATE=0.0 +DEBUG=1 \ No newline at end of file diff --git a/mb2halv2/tests/test05.py b/mb2halv2/tests/test05.py new file mode 100755 index 0000000000000000000000000000000000000000..909e82c023b36793ca7d3e3abdac14f82b876d8d --- /dev/null +++ b/mb2halv2/tests/test05.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# MB2HAL Validation tool for HAL_MAP_PIN feature + +import sys +import subprocess +import os +import signal +import atexit +import commands +import re +import ctypes + +import time +import logging +import threading + +import modbus_tk +import modbus_tk.defines as cst +import modbus_tk.modbus as modbus +import modbus_tk.modbus_tcp as modbus_tcp + +def read_hal_pin(name): + rc = commands.getoutput("halcmd show pin %s | tail -2" % (name)) + v = re.split('[ \n]+', rc)[4] + if v == "TRUE": return 1 + if v == "FALSE": return 0 + if v == "Name": return None + if (len(v) < 2 or v[1] != 'x'): + return float(v) + else: + return int(v, 0) + +def write_hal_pin(name, value): + os.system("halcmd setp %s %s" % (name, value)) + +def check_hal_pin(name, awaited): + v = read_hal_pin(name) + if v != None: + if v: rc = abs((v - awaited)/v) > 1e-3 + else: rc = abs(v - awaited) > 1.0e-6 + if rc: status = "Error" + else: status = "OK" + else: + status = "Error" + print status, "pin", name, "value=", v, "awaited=", awaited + return rc + +def check_reg(name, v, awaited): + rc = (v != awaited) + if rc: status = "Error" + else: status = "OK" + print status, "pin", name, "value=", v, "awaited=", awaited + return rc + +def map16(v, scale, offset): + v = v - offset + v = v/scale + return (ctypes.c_uint16(int(v)).value, ) + +def map32(v, scale, offset): + v = v - offset + v = v/scale + v = ctypes.c_uint32(int(v)).value + return ( v>>16, v & 0xFFFF ) + +logger = modbus_tk.utils.create_logger(name="console", record_format="%(message)s") + +server = modbus_tcp.TcpServer(port=8502) +server.start() + +slave = server.add_slave(1) +slave.add_block('in', cst.DISCRETE_INPUTS, 100, 32) +slave.add_block('coils', cst.COILS, 0, 32) +slave.add_block('analog', cst.ANALOG_INPUTS, 0, 32) +slave.add_block('hreg', cst.HOLDING_REGISTERS, 0, 32) +slave.add_block('single-coil', cst.COILS, 64, 1) + +slave.set_values('in', 100, ( 1, 0) ) + +if len(sys.argv) > 1: # Server mode + while True: + time.sleep(1) + print "analog ", slave.get_values('analog', 0, 16) + print "hreg ", slave.get_values('hreg', 0, 32) + exit(0) + +halrun = subprocess.Popen(['halrun', '-If', 'test05.hal'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) +time.sleep(2) + +os.system("halcmd show pin") + +err = False + +# Check bitfield mapping + +slave.set_values('analog', 8, 4096) +time.sleep(0.2) +err |= check_hal_pin("mb2hal.bitfield-in.bit-00", 0) +err |= check_hal_pin("mb2hal.bitfield-in.bit-12", 1) + +slave.set_values('analog', 8, 1) +time.sleep(0.2) +err |= check_hal_pin("mb2hal.bitfield-in.bit-00", 1) +err |= check_hal_pin("mb2hal.bitfield-in.bit-12", 0) + +write_hal_pin("mb2hal.bitfield-out.bit-01", 0) +write_hal_pin("mb2hal.bitfield-out.bit-11", 1) +time.sleep(0.2) +err |= check_reg("mb2hal.bitfield-out", slave.get_values('hreg', 10, 1), map16(2048, 1, 0)) + +write_hal_pin("mb2hal.bitfield-out.bit-01", 1) +write_hal_pin("mb2hal.bitfield-out.bit-11", 0) +time.sleep(0.2) +err |= check_reg("mb2hal.bitfield-out", slave.get_values('hreg', 10, 1), map16(2, 1, 0)) + +# Check MODBUS read input registers + +scale = 0.1 +offset = 50 +f16 = -723 +s16 = -723 +u16 = 6500 +slave.set_values('analog', 0, map16(f16, scale, offset) ) +slave.set_values('analog', 1, map16(s16, scale, offset) ) +slave.set_values('analog', 2, map16(u16, scale, offset) ) + +f32 = -1e7 +s32 = int(-1e7) +u32 = int(4e8) +slave.set_values('analog', 4, map32(f32, scale, offset) ) +slave.set_values('analog', 6, map32(s32, scale, offset) ) +slave.set_values('analog', 8, map32(u32, scale, offset) ) +slave.set_values('analog', 10, map32(300, 0.059, 0) ) + +slave.set_values('analog', 16, 4242 ) + +time.sleep(0.2) + +err |= check_hal_pin("mb2hal.analog.pin-0", f16) +err |= check_hal_pin("mb2hal.analog.pin-1", s16) +err |= check_hal_pin("mb2hal.analog.pin-2", u16) +err |= check_hal_pin("mb2hal.analog.pin-4", f32) +err |= check_hal_pin("mb2hal.analog.pin-6", s32) +err |= check_hal_pin("mb2hal.analog.pin-8", u32) +err |= check_hal_pin("mb2hal.analog.pin-9", 300) +err |= check_hal_pin("mb2hal.analog2.00", 4242) + +# Check MODBUS write holding registers + +write_hal_pin("mb2hal.hreg.pin-0", f16) +write_hal_pin("mb2hal.hreg.pin-1", s16) +write_hal_pin("mb2hal.hreg.pin-2", u16) +write_hal_pin("mb2hal.hreg.pin-4", f32) +write_hal_pin("mb2hal.hreg.pin-6", s32) +write_hal_pin("mb2hal.hreg.pin-8", u32) +write_hal_pin("mb2hal.hreg2.00", 1234) +time.sleep(0.2) + +err |= check_reg("mb2hal.hreg.pin-0", slave.get_values('hreg', 0, 1), map16(f16, 1/scale, -offset/scale)) +err |= check_reg("mb2hal.hreg.pin-1", slave.get_values('hreg', 1, 1), map16(s16, 1/scale, -offset/scale)) +err |= check_reg("mb2hal.hreg.pin-2", slave.get_values('hreg', 2, 1), map16(u16, 1/scale, -offset/scale)) +err |= check_reg("mb2hal.hreg.pin-4", slave.get_values('hreg', 4, 2), map32(f32, 1/scale, -offset/scale)) +err |= check_reg("mb2hal.hreg.pin-6", slave.get_values('hreg', 6, 2), map32(s32, 1/scale, -offset/scale)) +err |= check_reg("mb2hal.hreg.pin-8", slave.get_values('hreg', 8, 2), map32(u32, 1/scale, -offset/scale)) +err |= check_reg("mb2hal.hreg2.00", slave.get_values('hreg', 16, 1), ( 1234, )) + +# Check MODBUS read discrete inputs + +slave.set_values('in', 100, ( 0, 1) ) +slave.set_values('in', 116, ( 0, 1) ) +time.sleep(0.2) +err |= check_hal_pin("mb2hal.in.pin-0", 0) +err |= check_hal_pin("mb2hal.in.pin-1", 1) +err |= check_hal_pin("mb2hal.in2.00", 0) +err |= check_hal_pin("mb2hal.in2.01", 1) + +slave.set_values('in', 100, ( 1, 0) ) +slave.set_values('in', 116, ( 1, 0) ) +time.sleep(0.2) +err |= check_hal_pin("mb2hal.in.pin-0", 1) +err |= check_hal_pin("mb2hal.in.pin-1", 0) +err |= check_hal_pin("mb2hal.in2.00", 1) +err |= check_hal_pin("mb2hal.in2.01", 0) + +# Check MODBUS write multiple coils + +write_hal_pin("mb2hal.coils.pin-0", 0) +write_hal_pin("mb2hal.coils.pin-1", 1) +write_hal_pin("mb2hal.coils.pin-2", 0) +write_hal_pin("mb2hal.coils.pin-3", 0) +write_hal_pin("mb2hal.coils2.00", 0) +write_hal_pin("mb2hal.coils2.01", 1) +time.sleep(0.2) +err |= check_reg("mb2hal.coils.pin-0", slave.get_values('coils', 0, 4), ( 0, 1, 0, 0 )) +err |= check_reg("mb2hal.coils.coisl2.00", slave.get_values('coils', 16, 2), ( 0, 1)) + +write_hal_pin("mb2hal.coils.pin-0", 1) +write_hal_pin("mb2hal.coils.pin-1", 0) +write_hal_pin("mb2hal.coils.pin-2", 0) +write_hal_pin("mb2hal.coils.pin-3", 0) +write_hal_pin("mb2hal.coils2.00", 1) +write_hal_pin("mb2hal.coils2.01", 0) +time.sleep(0.2) +err |= check_reg("mb2hal.coils.pin-0", slave.get_values('coils', 0, 4), ( 1, 0, 0, 0 )) +err |= check_reg("mb2hal.coils.coisl2.00", slave.get_values('coils', 16, 2), ( 1, 0)) + +write_hal_pin("mb2hal.coils.pin-0", 0) +write_hal_pin("mb2hal.coils.pin-1", 0) +write_hal_pin("mb2hal.coils.pin-2", 1) +write_hal_pin("mb2hal.coils.pin-3", 0) +time.sleep(0.2) +err |= check_reg("mb2hal.coils.pin-0", slave.get_values('coils', 0, 4), ( 0, 0, 1, 0 )) + +write_hal_pin("mb2hal.coils.pin-0", 0) +write_hal_pin("mb2hal.coils.pin-1", 0) +write_hal_pin("mb2hal.coils.pin-2", 0) +write_hal_pin("mb2hal.coils.pin-3", 1) +time.sleep(0.2) +err |= check_reg("mb2hal.coils.pin-0", slave.get_values('coils', 0, 4), ( 0, 0, 0, 1 )) + +# Check MODBUS write single coil + +write_hal_pin("mb2hal.single-coil.pin-0", 0) +time.sleep(0.2) +err |= check_reg("mb2hal.single-coil.pin-0", slave.get_values('single-coil', 64, 1), ( 0, )) + +write_hal_pin("mb2hal.single-coil.pin-0", 1) +time.sleep(0.2) +err |= check_reg("mb2hal.single-coil.pin-0", slave.get_values('single-coil', 64, 1), ( 1, )) + +# End of test + +os.system("halcmd show pin") +halrun.stdin.write("quit\n") +halrun.stdin.close() +#halrun.send_signal(2) +time.sleep(1) +server.stop() +os.wait() + +if err: + print "TEST FAIL" + exit(1) +else: + print "TEST PASS" + exit(0) +