#include "modbusino.h" #include #include #include #if !defined(MBSN_BIG_ENDIAN) && !defined(MBSN_LITTLE_ENDIAN) #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || \ defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) #define MBSN_BIG_ENDIAN #elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \ defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) #define MBSN_LITTLE_ENDIAN #else #error "Failed to automatically detect system endianness. Please define either MBSN_BIG_ENDIAN or MBSN_LITTLE_ENDIAN" "in modbusino_platform.h" #endif #endif #ifdef MBSN_BIG_ENDIAN #define HTONS(x) (x) #define NTOHS(x) (x) #else #define HTONS(x) (((x) >> 8) | ((x) << 8)) #define NTOHS(x) (((x) >> 8) | ((x) << 8)) #endif #define CLOCKS_PER_MS ((uint64_t) (CLOCKS_PER_SEC / 1000)); typedef struct req_state { uint8_t client_id; uint8_t fc; uint16_t transaction_id; bool broadcast; bool ignored; uint16_t crc; } req_state; uint64_t clock_ms() { return (uint64_t) clock() / CLOCKS_PER_MS; } int mbsn_create(mbsn_t* mbsn, mbsn_transport_conf transport_conf) { memset(mbsn, 0, sizeof(mbsn_t)); mbsn->byte_timeout_ms = -1; mbsn->response_timeout_ms = -1; mbsn->transport = transport_conf.transport; if (mbsn->transport != MBSN_TRANSPORT_RTU && mbsn->transport != MBSN_TRANSPORT_TCP) return MBSN_ERROR_INVALID_ARGUMENT; if (!transport_conf.read_byte || !transport_conf.write_byte) return MBSN_ERROR_INVALID_ARGUMENT; mbsn->transport_read_byte = transport_conf.read_byte; mbsn->transport_write_byte = transport_conf.write_byte; return MBSN_ERROR_NONE; } mbsn_error mbsn_client_create(mbsn_t* mbsn, mbsn_transport_conf transport_conf) { return mbsn_create(mbsn, transport_conf); } mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address, mbsn_transport_conf transport_conf, mbsn_callbacks callbacks) { if (address == 0) return MBSN_ERROR_INVALID_ARGUMENT; mbsn_error ret = mbsn_create(mbsn, transport_conf); mbsn->address_rtu = address; mbsn->callbacks = callbacks; return ret; } void mbsn_set_byte_timeout(mbsn_t* mbsn, int64_t timeout_ms) { mbsn->byte_timeout_ms = timeout_ms; } void mbsn_set_response_timeout(mbsn_t* mbsn, int64_t timeout_ms) { mbsn->response_timeout_ms = timeout_ms; } void mbsn_client_set_server_address_rtu(mbsn_t* mbsn, uint8_t address) { mbsn->server_dest_address_rtu = address; } uint16_t crc_add_n(uint16_t crc, const uint8_t* data, unsigned int length) { for (int i = 0; i < length; i++) { crc ^= (uint16_t) data[i]; for (int j = 8; j != 0; j--) { if ((crc & 0x0001) != 0) { crc >>= 1; crc ^= 0xA001; } else crc >>= 1; } } return crc; } uint16_t crc_add_1(uint16_t crc, const uint8_t b) { return crc_add_n(crc, &b, 1); } uint16_t crc_add_2(uint16_t crc, const uint8_t w) { return crc_add_n(crc, &w, 2); } /* mbsn_error read(uint8_t* buf, uint64_t len, uint64_t byte_timeout_ms, mbsn_error (*transport_read_byte)(uint8_t*)) { uint64_t now = clock_ms(); int r = 0; while (r != len) { int ret = transport_read_byte(buf + r); if (byte_timeout_ms > 0 && clock_ms() - now >= byte_timeout_ms) return MBSN_ERROR_TIMEOUT; if (ret == 0) continue; else if (ret != 1) return MBSN_ERROR_TRANSPORT; r++; } return 0; } */ mbsn_error recv_n(mbsn_t* mbsn, uint8_t* buf, uint32_t count, uint16_t* crc) { //uint64_t start = clock_ms(); int r = 0; while (r != count) { int ret = mbsn->transport_read_byte(buf + r); /* if (mbsn->byte_timeout_ms > 0 && clock_ms() - start >= mbsn->byte_timeout_ms) return MBSN_ERROR_TIMEOUT; */ if (ret == 0) continue; else if (ret != 1) return MBSN_ERROR_TRANSPORT; *crc = crc_add_1(*crc, buf[r]); r++; //start = clock_ms(); } return MBSN_ERROR_NONE; } mbsn_error recv_1(mbsn_t* mbsn, uint8_t* b, uint16_t* crc) { return recv_n(mbsn, b, 1, crc); } mbsn_error recv_2(mbsn_t* mbsn, uint16_t* w, uint16_t* crc) { mbsn_error err = recv_n(mbsn, (uint8_t*) w, 2, crc); if (err != MBSN_ERROR_NONE) return err; *w = NTOHS(*w); return MBSN_ERROR_NONE; } /* void put_send_1(mbsn_t* mbsn, uint8_t w) { mbsn->send_buf[mbsn->send_idx] = w; mbsn->send_idx++; assert(mbsn->send_idx <= SEND_BUF_SIZE); } void put_send_2(mbsn_t* mbsn, uint16_t w) { mbsn->send_buf[mbsn->send_idx] = HTONS(w); mbsn->send_idx += 2; assert(mbsn->send_idx <= SEND_BUF_SIZE); } void put_send_n(mbsn_t* mbsn, uint8_t* buf, uint32_t n) { assert(mbsn->send_idx + n <= SEND_BUF_SIZE); memcpy(mbsn->send_buf + mbsn->send_idx, buf, n); mbsn->send_idx += n; } mbsn_error send(mbsn_t* mbsn) { const uint32_t target = mbsn->send_idx; //uint64_t start = clock_ms(); while (mbsn->send_idx != 0) { mbsn->send_idx--; mbsn_error err = mbsn->transport_write_byte(mbsn->send_buf[target - mbsn->send_idx - 1]); if (mbsn->byte_timeout_ms > 0 && clock_ms() - start >= mbsn->byte_timeout_ms) return MBSN_ERROR_TIMEOUT; if (err == 0) continue; else if (err != 1) return MBSN_ERROR_TRANSPORT; //start = clock_ms(); } return MBSN_ERROR_NONE; } */ mbsn_error send_n(mbsn_t* mbsn, uint8_t* buf, uint32_t count, uint16_t* crc) { int w = 0; while (w != count) { int ret = mbsn->transport_write_byte(buf[w]); if (ret == 0) continue; else if (ret != 1) return MBSN_ERROR_TRANSPORT; w++; } if (crc) *crc = crc_add_n(*crc, buf, count); return MBSN_ERROR_NONE; } mbsn_error send_1(mbsn_t* mbsn, uint8_t b, uint16_t* crc) { return send_n(mbsn, &b, 1, crc); } mbsn_error send_2(mbsn_t* mbsn, uint16_t w, uint16_t* crc) { w = HTONS(w); return send_n(mbsn, (uint8_t*) &w, 2, crc); } mbsn_error send_mbap(mbsn_t* mbsn, req_state* s, uint16_t data_length) { mbsn_error err = send_2(mbsn, s->transaction_id, NULL); if (err != MBSN_ERROR_NONE) return err; const uint16_t protocol_id = 0; err = send_2(mbsn, protocol_id, NULL); if (err != MBSN_ERROR_NONE) return err; err = send_2(mbsn, 1 + 1 + data_length, NULL); if (err != MBSN_ERROR_NONE) return err; err = send_1(mbsn, s->client_id, NULL); if (err != MBSN_ERROR_NONE) return err; return MBSN_ERROR_NONE; } mbsn_error send_req_header(mbsn_t* mbsn, req_state* s, uint8_t data_length, uint16_t* crc) { if (mbsn->transport == MBSN_TRANSPORT_RTU) { mbsn_error err = send_1(mbsn, s->client_id, crc); if (err != MBSN_ERROR_NONE) return err; err = send_1(mbsn, s->fc, crc); if (err != MBSN_ERROR_NONE) return err; } else if (mbsn->transport == MBSN_TRANSPORT_TCP) { return send_mbap(mbsn, s, data_length); } return MBSN_ERROR_NONE; } mbsn_error send_req_footer(mbsn_t* mbsn, uint16_t crc) { if (mbsn->transport == MBSN_TRANSPORT_RTU) return send_2(mbsn, crc, NULL); return MBSN_ERROR_NONE; } mbsn_error handle_exception(mbsn_t* mbsn, req_state* s, uint8_t exception) { uint16_t crc = 0xFFFF; mbsn_error err = send_req_header(mbsn, s, 1, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_1(mbsn, s->fc + 0x80, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_1(mbsn, exception, &crc); if (err != MBSN_ERROR_NONE) return err; return send_req_footer(mbsn, crc); } mbsn_error handle_read_discrete(mbsn_t* mbsn, req_state* s, mbsn_error (*callback)(uint16_t, uint16_t, mbsn_bitfield)) { uint16_t addr; mbsn_error err = recv_2(mbsn, &addr, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t quantity; err = recv_2(mbsn, &quantity, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t recv_crc; err = recv_2(mbsn, &recv_crc, NULL); if (err != MBSN_ERROR_NONE) return err; if (recv_crc != s->crc) return MBSN_ERROR_TRANSPORT; if (quantity < 1 || quantity > 2000) return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if ((uint32_t) addr + (uint32_t) quantity > 65535) return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; if (!s->ignored) { if (callback) { mbsn_bitfield bf; err = callback(addr, quantity, bf); if (err != MBSN_ERROR_NONE) { if (err < 0) return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; else return err; } if (!s->broadcast) { uint8_t discrete_bytes = (quantity / 8) + 1; uint16_t crc = 0xFFFF; err = send_req_header(mbsn, s, discrete_bytes, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_1(mbsn, discrete_bytes, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_n(mbsn, bf, discrete_bytes, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_req_footer(mbsn, crc); if (err != MBSN_ERROR_NONE) return err; } } else { return MBSN_EXCEPTION_ILLEGAL_FUNCTION; } } return MBSN_ERROR_NONE; } mbsn_error handle_read_registers(mbsn_t* mbsn, req_state* s, mbsn_error (*callback)(uint16_t, uint16_t, uint16_t*)) { uint16_t addr; mbsn_error err = recv_2(mbsn, &addr, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t quantity; err = recv_2(mbsn, &quantity, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t recv_crc; err = recv_2(mbsn, &recv_crc, NULL); if (err != MBSN_ERROR_NONE) return err; if (recv_crc != s->crc) return MBSN_ERROR_TRANSPORT; if (quantity < 1 || quantity > 125) return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if ((uint32_t) addr + (uint32_t) quantity > 65535) return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; if (!s->ignored) { if (callback) { uint16_t regs[125]; err = callback(addr, quantity, regs); if (err != MBSN_ERROR_NONE) { if (err < 0) return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; else return err; } for (int i = 0; i < quantity; i++) regs[i] = HTONS(regs[i]); if (!s->broadcast) { uint8_t regs_bytes = quantity * 2; uint16_t crc = 0xFFFF; err = send_req_header(mbsn, s, regs_bytes, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_1(mbsn, regs_bytes, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_n(mbsn, (uint8_t*) regs, regs_bytes, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_req_footer(mbsn, crc); if (err != MBSN_ERROR_NONE) return err; } } else { return MBSN_EXCEPTION_ILLEGAL_FUNCTION; } } return MBSN_ERROR_NONE; } mbsn_error handle_read_coils(mbsn_t* mbsn, req_state* s) { return handle_read_discrete(mbsn, s, mbsn->callbacks.read_coils); } mbsn_error handle_read_discrete_inputs(mbsn_t* mbsn, req_state* s) { return handle_read_discrete(mbsn, s, mbsn->callbacks.read_discrete_inputs); } mbsn_error handle_read_holding_registers(mbsn_t* mbsn, req_state* s) { return handle_read_registers(mbsn, s, mbsn->callbacks.read_holding_registers); } mbsn_error handle_read_input_registers(mbsn_t* mbsn, req_state* s) { return handle_read_registers(mbsn, s, mbsn->callbacks.read_input_registers); } mbsn_error handle_write_single_coil(mbsn_t* mbsn, req_state* s) { uint16_t addr; mbsn_error err = recv_2(mbsn, &addr, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t value; err = recv_2(mbsn, &value, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t recv_crc; err = recv_2(mbsn, &recv_crc, NULL); if (err != MBSN_ERROR_NONE) return err; if (recv_crc != s->crc) return MBSN_ERROR_TRANSPORT; if (value != 0 && value != 0xFF00) return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if (!s->ignored) { if (mbsn->callbacks.write_single_coil) { err = mbsn->callbacks.write_single_coil(addr, value == 0 ? false : true); if (err != MBSN_ERROR_NONE) { if (err < 0) return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; else return err; } if (!s->broadcast) { uint16_t crc = 0xFFFF; err = send_req_header(mbsn, s, 2, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_2(mbsn, addr, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_req_footer(mbsn, crc); if (err != MBSN_ERROR_NONE) return err; } } else { return MBSN_EXCEPTION_ILLEGAL_FUNCTION; } } return MBSN_ERROR_NONE; } mbsn_error handle_write_single_register(mbsn_t* mbsn, req_state* s) { uint16_t addr; mbsn_error err = recv_2(mbsn, &addr, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t value; err = recv_2(mbsn, &value, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t recv_crc; err = recv_2(mbsn, &recv_crc, NULL); if (err != MBSN_ERROR_NONE) return err; if (recv_crc != s->crc) return MBSN_ERROR_TRANSPORT; if (!s->ignored) { if (mbsn->callbacks.write_single_register) { err = mbsn->callbacks.write_single_register(addr, value); if (err != MBSN_ERROR_NONE) { if (err < 0) return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; else return err; } if (!s->broadcast) { uint16_t crc = 0xFFFF; err = send_req_header(mbsn, s, 1, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_2(mbsn, value, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_req_footer(mbsn, crc); if (err != MBSN_ERROR_NONE) return err; } } else { return MBSN_EXCEPTION_ILLEGAL_FUNCTION; } } return MBSN_ERROR_NONE; } mbsn_error handle_write_multiple_coils(mbsn_t* mbsn, req_state* s) { uint16_t addr; mbsn_error err = recv_2(mbsn, &addr, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t quantity; err = recv_2(mbsn, &quantity, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint8_t coils_bytes; err = recv_1(mbsn, &coils_bytes, &s->crc); if (err != MBSN_ERROR_NONE) return err; mbsn_bitfield coils; err = recv_n(mbsn, coils, coils_bytes, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t recv_crc; err = recv_2(mbsn, &recv_crc, NULL); if (err != MBSN_ERROR_NONE) return err; if (recv_crc != s->crc) return MBSN_ERROR_TRANSPORT; if (quantity < 1 || quantity > 0x07B0) // 0x07B0 == 1968 return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if (coils_bytes == 0) return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if ((quantity / 8) + 1 != coils_bytes) return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if (!s->ignored) { if (mbsn->callbacks.write_multiple_coils) { err = mbsn->callbacks.write_multiple_coils(addr, quantity, coils); if (err != MBSN_ERROR_NONE) { if (err < 0) return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; else return err; } if (!s->broadcast) { uint16_t crc = 0xFFFF; err = send_req_header(mbsn, s, 4, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_2(mbsn, addr, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_2(mbsn, quantity, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_req_footer(mbsn, crc); if (err != MBSN_ERROR_NONE) return err; } } else { return MBSN_EXCEPTION_ILLEGAL_FUNCTION; } } return MBSN_ERROR_NONE; } mbsn_error handle_write_multiple_registers(mbsn_t* mbsn, req_state* s) { uint16_t addr; mbsn_error err = recv_2(mbsn, &addr, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t quantity; err = recv_2(mbsn, &quantity, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint8_t registers_bytes; err = recv_1(mbsn, ®isters_bytes, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t registers[0x007B]; err = recv_n(mbsn, (uint8_t*) registers, registers_bytes, &s->crc); if (err != MBSN_ERROR_NONE) return err; uint16_t recv_crc; err = recv_2(mbsn, &recv_crc, NULL); if (err != MBSN_ERROR_NONE) return err; if (recv_crc != s->crc) return MBSN_ERROR_TRANSPORT; if (quantity < 1 || quantity > 0x007B) // 0x007B == 123 return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if (registers_bytes == 0) return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; if (registers_bytes != quantity * 2) return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; for (int i = 0; i < quantity; i++) registers[i] = NTOHS(registers[i]); if (!s->ignored) { if (mbsn->callbacks.write_multiple_registers) { err = mbsn->callbacks.write_multiple_registers(addr, quantity, registers); if (err != MBSN_ERROR_NONE) { if (err < 0) return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; else return err; } if (!s->broadcast) { uint16_t crc = 0xFFFF; err = send_req_header(mbsn, s, 4, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_2(mbsn, addr, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_2(mbsn, quantity, &crc); if (err != MBSN_ERROR_NONE) return err; err = send_req_footer(mbsn, crc); if (err != MBSN_ERROR_NONE) return err; } } else { return MBSN_EXCEPTION_ILLEGAL_FUNCTION; } } return MBSN_ERROR_NONE; } int mbsn_server_receive(mbsn_t* mbsn) { req_state s = {0}; s.crc = 0xFFFF; if (mbsn->transport == MBSN_TRANSPORT_RTU) { uint8_t id; mbsn_error err = recv_1(mbsn, &id, &s.crc); if (err != 0) return err; // Check if request is for us if (id == 0) s.broadcast = true; else if (id != mbsn->address_rtu) s.ignored = true; else s.ignored = false; err = recv_1(mbsn, &s.fc, &s.crc); if (err != MBSN_ERROR_NONE) return err; } else if (mbsn->transport == MBSN_TRANSPORT_TCP) { mbsn_error err = recv_2(mbsn, &s.transaction_id, NULL); if (err != MBSN_ERROR_NONE) return err; uint16_t protocol_id = 0xFFFF; err = recv_2(mbsn, &protocol_id, NULL); if (err != MBSN_ERROR_NONE) return err; uint16_t length = 0xFFFF; err = recv_2(mbsn, &length, NULL); if (err != MBSN_ERROR_NONE) return err; err = recv_1(mbsn, &s.client_id, NULL); if (err != MBSN_ERROR_NONE) return err; err = recv_1(mbsn, &s.fc, NULL); if (err != MBSN_ERROR_NONE) return err; if (protocol_id != 0) return MBSN_ERROR_TRANSPORT; // TODO maybe we should actually check the length of the request against this value if (length == 0xFFFF) return MBSN_ERROR_TRANSPORT; } mbsn_error err; switch (s.fc) { case 1: err = handle_read_coils(mbsn, &s); break; case 2: err = handle_read_discrete_inputs(mbsn, &s); break; case 3: err = handle_read_holding_registers(mbsn, &s); break; case 4: err = handle_read_input_registers(mbsn, &s); break; case 5: err = handle_write_single_coil(mbsn, &s); break; case 6: err = handle_write_single_register(mbsn, &s); break; case 15: err = handle_write_multiple_coils(mbsn, &s); break; case 16: err = handle_write_multiple_registers(mbsn, &s); break; default: err = MBSN_EXCEPTION_ILLEGAL_FUNCTION; } if (err != MBSN_ERROR_NONE) { if (!s.broadcast && !s.ignored && mbsn_error_is_exception(err)) { err = handle_exception(mbsn, &s, err); if (err != MBSN_ERROR_NONE) return err; } else return err; } return MBSN_ERROR_NONE; }