Skip to content

Commit 0c7386d

Browse files
cpqrobert
authored andcommitted
Add modbus-tcp support
1 parent 1bb8579 commit 0c7386d

File tree

7 files changed

+854
-4
lines changed

7 files changed

+854
-4
lines changed

mongoose.c

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3970,6 +3970,240 @@ void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
39703970
}
39713971
#endif
39723972

3973+
#ifdef MG_ENABLE_LINES
3974+
#line 1 "src/modbus.c"
3975+
#endif
3976+
3977+
3978+
3979+
3980+
3981+
#define MG_MODBUS_MIN_PAYLOAD_LEN 12
3982+
#define MG_MODBUS_HEADER_LEN 7
3983+
#define MG_MODBUS_MAX_DATA_SIZE 250
3984+
3985+
static void mg_modbus_send_mbap(struct mg_connection *c, uint16_t txid,
3986+
uint16_t pdu_len, uint8_t unit_id) {
3987+
uint8_t hdr[7];
3988+
memset(hdr, 0, sizeof(hdr));
3989+
MG_STORE_BE16(&hdr[0], txid & 0xFFFFU);
3990+
MG_STORE_BE16(&hdr[4], (pdu_len + 1) & 0xFFFFU);
3991+
hdr[6] = unit_id;
3992+
mg_send(c, hdr, sizeof(hdr));
3993+
}
3994+
3995+
static void mg_modbus_send_response(struct mg_connection *c,
3996+
struct mg_modbus_req *req,
3997+
uint16_t txid, uint8_t unit_id) {
3998+
uint8_t buf[5];
3999+
uint16_t reg;
4000+
uint16_t i, nbytes;
4001+
4002+
memset(buf, 0, sizeof(buf));
4003+
if (req == NULL) return;
4004+
if (req->error != MG_MODBUS_ERR_NONE) {
4005+
mg_modbus_send_mbap(c, txid, 2, unit_id);
4006+
buf[0] = (uint8_t) (req->func | 0x80);
4007+
buf[1] = req->error;
4008+
mg_send(c, buf, 2);
4009+
return;
4010+
}
4011+
switch (req->func) {
4012+
case MG_MODBUS_FUNC_READ_COILS:
4013+
case MG_MODBUS_FUNC_READ_DISCRETE_INPUTS:
4014+
if (req->u.bits == NULL) return;
4015+
nbytes = (uint16_t) ((req->len + 7) / 8);
4016+
mg_modbus_send_mbap(c, txid, (uint16_t) (nbytes + 2), unit_id);
4017+
buf[0] = req->func;
4018+
buf[1] = (uint8_t) nbytes;
4019+
mg_send(c, buf, 2);
4020+
memset(buf, 0, sizeof(buf));
4021+
for (i = 0; i < req->len; i++) {
4022+
if (req->u.bits[i]) buf[i / 8] |= (uint8_t) (1 << (i % 8));
4023+
if ((i % 8) == 7 || i + 1 == req->len) {
4024+
uint8_t out = buf[i / 8];
4025+
mg_send(c, &out, 1);
4026+
}
4027+
}
4028+
break;
4029+
case MG_MODBUS_FUNC_READ_HOLDING_REGISTERS:
4030+
case MG_MODBUS_FUNC_READ_INPUT_REGISTERS:
4031+
if (req->u.regs == NULL) return;
4032+
nbytes = (uint16_t) (req->len * 2);
4033+
mg_modbus_send_mbap(c, txid, (uint16_t) (nbytes + 2), unit_id);
4034+
buf[0] = req->func;
4035+
buf[1] = (uint8_t) nbytes;
4036+
mg_send(c, buf, 2);
4037+
for (i = 0; i < req->len; i++) {
4038+
MG_STORE_BE16(&reg, req->u.regs[i] & 0xFFFFU);
4039+
mg_send(c, &reg, 2);
4040+
}
4041+
break;
4042+
case MG_MODBUS_FUNC_WRITE_SINGLE_COIL:
4043+
if (req->len != 1 || req->u.bits == NULL) return;
4044+
mg_modbus_send_mbap(c, txid, 5, unit_id);
4045+
buf[0] = req->func;
4046+
MG_STORE_BE16(&buf[1], req->addr & 0xFFFFu);
4047+
MG_STORE_BE16(&buf[3], req->u.bits[0] ? 0xFF00U : 0x0000U);
4048+
mg_send(c, buf, 5);
4049+
break;
4050+
case MG_MODBUS_FUNC_WRITE_SINGLE_REGISTER:
4051+
if (req->len != 1 || req->u.regs == NULL) return;
4052+
mg_modbus_send_mbap(c, txid, 5, unit_id);
4053+
buf[0] = req->func;
4054+
MG_STORE_BE16(&buf[1], req->addr & 0xFFFFU);
4055+
MG_STORE_BE16(&buf[3], req->u.regs[0] & 0xFFFFU);
4056+
mg_send(c, buf, 5);
4057+
break;
4058+
case MG_MODBUS_FUNC_WRITE_MULTIPLE_COILS:
4059+
case MG_MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS:
4060+
mg_modbus_send_mbap(c, txid, 5, unit_id);
4061+
buf[0] = req->func;
4062+
MG_STORE_BE16(&buf[1], req->addr & 0xFFFFU);
4063+
MG_STORE_BE16(&buf[3], req->len & 0xFFFFU);
4064+
mg_send(c, buf, 5);
4065+
break;
4066+
default:
4067+
break;
4068+
}
4069+
}
4070+
4071+
static void handle_pdu(struct mg_connection *c, uint8_t *buf, size_t len) {
4072+
uint16_t max_len, val, txid, addr, quantity;
4073+
uint8_t func, unit_id, byte_count, packed;
4074+
int i;
4075+
size_t pdu_len = len - MG_MODBUS_HEADER_LEN, len_check;
4076+
struct mg_modbus_req mr;
4077+
4078+
if (len < MG_MODBUS_MIN_PAYLOAD_LEN) return;
4079+
memset(&mr, 0, sizeof(mr));
4080+
txid = MG_LOAD_BE16(&buf[0]);
4081+
unit_id = buf[6];
4082+
func = buf[7];
4083+
addr = MG_LOAD_BE16(&buf[8]);
4084+
mr.func = func;
4085+
mr.addr = addr;
4086+
mr.error = MG_MODBUS_ERR_NONE;
4087+
mr.len = 0;
4088+
mr.u.bits = NULL;
4089+
switch (func) {
4090+
case MG_MODBUS_FUNC_READ_COILS:
4091+
case MG_MODBUS_FUNC_READ_DISCRETE_INPUTS:
4092+
if (pdu_len != 5) goto modbus_illegal_value;
4093+
quantity = MG_LOAD_BE16(&buf[10]);
4094+
max_len = 0x07D0; // 2000 bits
4095+
if (quantity == 0 || quantity > max_len) goto modbus_illegal_value;
4096+
mr.len = quantity;
4097+
mr.u.bits = (bool *) mg_calloc((size_t) mr.len, sizeof(bool));
4098+
if (mr.u.bits == NULL) goto modbus_oom;
4099+
break;
4100+
case MG_MODBUS_FUNC_READ_HOLDING_REGISTERS:
4101+
case MG_MODBUS_FUNC_READ_INPUT_REGISTERS:
4102+
if (pdu_len != 5) goto modbus_illegal_value;
4103+
quantity = MG_LOAD_BE16(&buf[10]);
4104+
max_len = 0x007D; // 125 registers
4105+
if (quantity == 0 || quantity > max_len) goto modbus_illegal_value;
4106+
mr.len = quantity;
4107+
mr.u.regs = (uint16_t *) mg_calloc((size_t) mr.len, sizeof(uint16_t));
4108+
if (mr.u.regs == NULL) goto modbus_oom;
4109+
break;
4110+
case MG_MODBUS_FUNC_WRITE_SINGLE_COIL:
4111+
if (pdu_len != 5) goto modbus_illegal_value;
4112+
mr.len = 1;
4113+
mr.u.bits = (bool *) mg_calloc(1, sizeof(bool));
4114+
if (mr.u.bits == NULL) goto modbus_oom;
4115+
val = MG_LOAD_BE16(&buf[10]);
4116+
if (val != 0x0000 && val != 0xFF00) goto modbus_illegal_value;
4117+
mr.u.bits[0] = (val == 0xFF00);
4118+
break;
4119+
case MG_MODBUS_FUNC_WRITE_SINGLE_REGISTER:
4120+
if (pdu_len != 5) goto modbus_illegal_value;
4121+
mr.len = 1;
4122+
mr.u.regs = (uint16_t *) mg_calloc(1, sizeof(uint16_t));
4123+
if (mr.u.regs == NULL) goto modbus_oom;
4124+
mr.u.regs[0] = MG_LOAD_BE16(&buf[10]);
4125+
break;
4126+
case MG_MODBUS_FUNC_WRITE_MULTIPLE_COILS: {
4127+
quantity = MG_LOAD_BE16(&buf[10]);
4128+
if (quantity == 0 || quantity > 0x07B0) goto modbus_illegal_value;
4129+
byte_count = buf[12];
4130+
if (byte_count != (uint8_t) ((quantity + 7) / 8)) goto modbus_illegal_value;
4131+
len_check = (size_t) (6 + byte_count);
4132+
if (len_check != pdu_len) goto modbus_illegal_value;
4133+
mr.len = quantity;
4134+
mr.u.bits = (bool *) mg_calloc((size_t) mr.len, sizeof(bool));
4135+
if (mr.u.bits == NULL) goto modbus_oom;
4136+
for (i = 0; i < mr.len; i++) {
4137+
packed = buf[13 + (i / 8)];
4138+
mr.u.bits[i] = ((packed >> (i % 8)) & 1) != 0;
4139+
}
4140+
break;
4141+
}
4142+
case MG_MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS: {
4143+
quantity = MG_LOAD_BE16(&buf[10]);
4144+
if (quantity == 0 || quantity > 0x007B) goto modbus_illegal_value;
4145+
byte_count = buf[12];
4146+
if (byte_count != (uint8_t) (quantity * 2)) goto modbus_illegal_value;
4147+
len_check = (size_t) (6 + quantity * 2);
4148+
if (len_check != pdu_len) goto modbus_illegal_value;
4149+
mr.len = quantity;
4150+
mr.u.regs = (uint16_t *) mg_calloc((size_t) mr.len, sizeof(uint16_t));
4151+
if (mr.u.regs == NULL) goto modbus_oom;
4152+
for (i = 0; i < mr.len; i++) {
4153+
mr.u.regs[i] = MG_LOAD_BE16(&buf[13 + i * 2]);
4154+
}
4155+
break;
4156+
}
4157+
default:
4158+
MG_ERROR(("Unsupported modbus function"));
4159+
mr.error = MG_MODBUS_ERR_ILLEGAL_FUNCTION;
4160+
mg_modbus_send_response(c, &mr, txid, unit_id);
4161+
return;
4162+
}
4163+
mg_call(c, MG_EV_MODBUS_REQ, &mr);
4164+
mg_modbus_send_response(c, &mr, txid, unit_id);
4165+
if (mr.u.bits != NULL) mg_free(mr.u.bits);
4166+
return;
4167+
4168+
modbus_illegal_value:
4169+
MG_ERROR(("Invalid data"));
4170+
mr.error = MG_MODBUS_ERR_ILLEGAL_VALUE;
4171+
goto modbus_exit_err;
4172+
modbus_oom:
4173+
MG_ERROR(("OOM"));
4174+
mr.error = MG_MODBUS_ERR_DEVICE_FAILURE;
4175+
4176+
modbus_exit_err:
4177+
mg_modbus_send_response(c, &mr, txid, unit_id);
4178+
if (mr.u.bits != NULL) mg_free(mr.u.bits);
4179+
}
4180+
4181+
static void modbus_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
4182+
if (ev == MG_EV_READ) {
4183+
while (c->recv.len >= MG_MODBUS_MIN_PAYLOAD_LEN) {
4184+
uint16_t len = MG_LOAD_BE16(&c->recv.buf[4]); // PDU length
4185+
uint16_t proto_id = MG_LOAD_BE16(&c->recv.buf[2]);
4186+
if (len < 6 || proto_id != 0) {
4187+
mg_error(c, "invalid pdu");
4188+
break;
4189+
} else if (c->recv.len < len + 6U) {
4190+
break; // Partial frame, buffer more
4191+
} else {
4192+
handle_pdu(c, c->recv.buf, len + 6); // Parse PDU and call user
4193+
mg_iobuf_del(&c->recv, 0, len + 6); // Delete received PDU
4194+
}
4195+
}
4196+
}
4197+
(void) ev_data;
4198+
}
4199+
4200+
struct mg_connection *mg_modbus_listen(struct mg_mgr *mgr, const char *url,
4201+
mg_event_handler_t fn, void *fn_data) {
4202+
struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
4203+
if (c != NULL) c->pfn = modbus_ev_handler;
4204+
return c;
4205+
}
4206+
39734207
#ifdef MG_ENABLE_LINES
39744208
#line 1 "src/mqtt.c"
39754209
#endif

mongoose.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,7 @@ enum {
16601660
MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data
16611661
MG_EV_MDNS_REQ, // mDNS request struct mg_mdns_req *
16621662
MG_EV_MDNS_RESP, // mDNS response struct mg_mdns_resp *
1663+
MG_EV_MODBUS_REQ, // ModBus request struct mg_modbus_cmd *
16631664
MG_EV_USER // Starting ID for user events
16641665
};
16651666

@@ -3024,6 +3025,40 @@ bool mg_mdns_query(struct mg_connection *, const char *, unsigned int);
30243025

30253026

30263027

3028+
// Functions
3029+
#define MG_MODBUS_FUNC_READ_COILS 1
3030+
#define MG_MODBUS_FUNC_READ_DISCRETE_INPUTS 2
3031+
#define MG_MODBUS_FUNC_READ_HOLDING_REGISTERS 3
3032+
#define MG_MODBUS_FUNC_READ_INPUT_REGISTERS 4
3033+
#define MG_MODBUS_FUNC_WRITE_SINGLE_COIL 5
3034+
#define MG_MODBUS_FUNC_WRITE_SINGLE_REGISTER 6
3035+
#define MG_MODBUS_FUNC_WRITE_MULTIPLE_COILS 15
3036+
#define MG_MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS 16
3037+
3038+
// Error codes
3039+
#define MG_MODBUS_ERR_NONE 0
3040+
#define MG_MODBUS_ERR_ILLEGAL_FUNCTION 1
3041+
#define MG_MODBUS_ERR_ILLEGAL_ADDRESS 2
3042+
#define MG_MODBUS_ERR_ILLEGAL_VALUE 3
3043+
#define MG_MODBUS_ERR_DEVICE_FAILURE 4
3044+
3045+
struct mg_modbus_req {
3046+
uint8_t func; // Function code, one of MG_MODBUS_FUNC_*
3047+
uint8_t error; // Error code user handler can set
3048+
uint16_t addr; // Address
3049+
union {
3050+
bool *bits;
3051+
uint16_t *regs;
3052+
} u;
3053+
uint16_t len; // Number of registers or bits
3054+
};
3055+
3056+
struct mg_connection *mg_modbus_listen(struct mg_mgr *mgr, const char *url,
3057+
mg_event_handler_t fn, void *fn_data);
3058+
3059+
3060+
3061+
30273062

30283063
#ifndef MG_JSON_MAX_DEPTH
30293064
#define MG_JSON_MAX_DEPTH 30

src/event.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ enum {
2929
MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data
3030
MG_EV_MDNS_REQ, // mDNS request struct mg_mdns_req *
3131
MG_EV_MDNS_RESP, // mDNS response struct mg_mdns_resp *
32+
MG_EV_MODBUS_REQ, // ModBus request struct mg_modbus_cmd *
3233
MG_EV_USER // Starting ID for user events
3334
};

0 commit comments

Comments
 (0)