plc_sim.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. /* PLC simulator: connects to the PLC as an S7comm client and every
  2. * <interval_ms> writes the next state pattern into the app's read area,
  3. * cycling through 6 states:
  4. * 0: all bits 0 -> default layout
  5. * 1: byte0 = 0b00000011 (M0.0|M0.1) -> layout 0
  6. * 2: byte0 = 0b00001100 (M0.2|M0.3) -> layout 1
  7. * 3: byte0 = 0b00110000 (M0.4|M0.5) -> layout 2
  8. * 4: byte0 = 0b00001111 (two groups) -> layout 3
  9. * 5: byte0 = 0b00111111 (three groups) -> layout 4
  10. *
  11. * Usage: plc_sim [-c channels.conf] [-t interval_ms] [-f first_state]
  12. */
  13. #include <arpa/inet.h>
  14. #include <errno.h>
  15. #include <fcntl.h>
  16. #include <poll.h>
  17. #include <signal.h>
  18. #include <stdarg.h>
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <sys/socket.h>
  23. #include <netinet/in.h>
  24. #include <netinet/tcp.h>
  25. #include <unistd.h>
  26. #define S7_PDU_REQ 0x01
  27. #define S7_PDU_ACKD 0x03
  28. #define COTP_CR 0xE0
  29. #define COTP_CC 0xD0
  30. #define COTP_DT 0xF0
  31. #define TS_BYTE 0x02
  32. #define SOCK_TIMEOUT_MS 3000
  33. #define MAX_CFG_STR 256
  34. #define MAX_BLOCK 64
  35. static volatile int g_running = 1;
  36. static uint16_t g_seq = 0;
  37. static char g_err[256];
  38. typedef struct {
  39. char ip[MAX_CFG_STR];
  40. int port;
  41. int area; /* 0x81 I, 0x82 Q, 0x83 M, 0x84 DB */
  42. int db;
  43. int start;
  44. int len;
  45. int tsap_local;
  46. int tsap_remote;
  47. } SimCfg;
  48. static const char *area_names[] = { "?", "I", "Q", "M", "DB" };
  49. #define MAX_RULES 8
  50. #define MAX_RULE_BITS 16
  51. /* One bit position (byte.bit relative to read_start) taken from [plc_rules]. */
  52. typedef struct {
  53. int byte;
  54. int bit;
  55. } RuleBit;
  56. typedef struct {
  57. int layout; /* rule id -> layout index */
  58. RuleBit bits[MAX_RULE_BITS];
  59. int nbits;
  60. int has_bits; /* non-zero if the rule carries explicit bits */
  61. } PlcRuleCfg;
  62. static PlcRuleCfg g_rules[MAX_RULES];
  63. static int g_num_rules = 0;
  64. /* Build the write block (cfg.len bytes) for a given rule state.
  65. * state 0 -> all bits cleared (default layout).
  66. * n>=1 -> set the bits listed in g_rules[state-1] (fall back to byte0). */
  67. static void state_to_block(uint8_t *data, int len, int state, int nrules) {
  68. memset(data, 0, (size_t)len * sizeof(data[0]));
  69. if (state <= 0) return;
  70. int idx = state - 1;
  71. if (idx >= nrules || idx < 0) return;
  72. PlcRuleCfg *R = &g_rules[idx];
  73. if (!R->has_bits) {
  74. /* no [plc_rules] for this state: fall back to byte0 low bits */
  75. if (len > 0) data[0] = (uint8_t)(0x03u << (2 * (idx % 3)));
  76. return;
  77. }
  78. for (int i = 0; i < R->nbits; i++) {
  79. int by = R->bits[i].byte;
  80. int bi = R->bits[i].bit;
  81. if (by < 0 || by >= len || bi < 0 || bi > 7) continue;
  82. data[by] |= (uint8_t)(1u << bi);
  83. }
  84. }
  85. /* Render the set bits of a rule state as `prefix byte.bit` / `db.byte.bit`,
  86. * matching the config grammar, for display only. */
  87. static int format_state_addr(char *buf, size_t buflen,
  88. const SimCfg *cfg, const uint8_t *data, int len) {
  89. const char *prefix = "?";
  90. switch (cfg->area) {
  91. case 0x81: prefix = "I"; break;
  92. case 0x82: prefix = "Q"; break;
  93. case 0x83: prefix = "M"; break;
  94. case 0x84: prefix = "DB"; break;
  95. }
  96. int first = 1;
  97. size_t o = 0;
  98. for (int i = 0; i < len * 8 && o < buflen; i++) {
  99. int byte = i / 8;
  100. int bit = i % 8;
  101. if (!(data[byte] & (1u << bit))) continue;
  102. int pos = cfg->start + byte;
  103. int n;
  104. if (cfg->area == 0x84)
  105. n = snprintf(buf + o, buflen - o, "%s%d.%d.%d",
  106. first ? "" : ",", cfg->db, pos, bit);
  107. else
  108. n = snprintf(buf + o, buflen - o, "%s%s%d.%d",
  109. first ? "" : ",", prefix, pos, bit);
  110. if (n < 0 || (size_t)n >= buflen - o) break;
  111. o += (size_t)n;
  112. first = 0;
  113. }
  114. if (o == 0 && buflen > 0) { buf[0] = '\0'; }
  115. return (int)o;
  116. }
  117. static void sig_handler(int sig) {
  118. (void)sig;
  119. g_running = 0;
  120. }
  121. static void set_err(const char *fmt, ...) {
  122. va_list ap;
  123. va_start(ap, fmt);
  124. vsnprintf(g_err, sizeof(g_err), fmt, ap);
  125. va_end(ap);
  126. }
  127. static int recv_exact(int fd, void *buf, size_t len) {
  128. size_t got = 0;
  129. while (got < len) {
  130. ssize_t r = recv(fd, (uint8_t *)buf + got, len - got, 0);
  131. if (r <= 0) {
  132. if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
  133. set_err("recv timeout");
  134. else
  135. set_err("recv failed: %s", strerror(errno));
  136. return -1;
  137. }
  138. got += (size_t)r;
  139. }
  140. return 0;
  141. }
  142. static int send_all(int fd, const void *buf, size_t len) {
  143. size_t sent = 0;
  144. while (sent < len) {
  145. ssize_t r = send(fd, (const uint8_t *)buf + sent, len - sent, MSG_NOSIGNAL);
  146. if (r <= 0) {
  147. if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
  148. set_err("send timeout");
  149. else
  150. set_err("send failed: %s", strerror(errno));
  151. return -1;
  152. }
  153. sent += (size_t)r;
  154. }
  155. return 0;
  156. }
  157. static int send_s7(int fd, const uint8_t *s7, int s7len) {
  158. uint8_t frame[4 + 3 + 1024];
  159. if (3 + s7len > (int)sizeof(frame) - 4) {
  160. set_err("s7 frame too large");
  161. return -1;
  162. }
  163. int total = 4 + 3 + s7len;
  164. frame[0] = 3; frame[1] = 0;
  165. frame[2] = (uint8_t)(total >> 8);
  166. frame[3] = (uint8_t)(total & 0xFF);
  167. frame[4] = 2; /* COTP DT header length */
  168. frame[5] = COTP_DT;
  169. frame[6] = 0x80; /* EOT + sequence 0 */
  170. memcpy(frame + 7, s7, s7len);
  171. return send_all(fd, frame, total);
  172. }
  173. static int recv_s7(int fd, uint8_t *out, int maxout, int *out_len) {
  174. uint8_t hdr[4];
  175. if (recv_exact(fd, hdr, 4) < 0) return -1;
  176. if (hdr[0] != 3) { set_err("bad TPKT version %d", hdr[0]); return -1; }
  177. int total = ((int)hdr[2] << 8) | hdr[3];
  178. if (total < 7 || total > 4 + 1024) {
  179. set_err("bad TPKT length %d", total);
  180. return -1;
  181. }
  182. uint8_t payload[1024];
  183. if (recv_exact(fd, payload, total - 4) < 0) return -1;
  184. if (payload[0] != 2 || payload[1] != COTP_DT) {
  185. set_err("expected COTP DT, got %02x %02x", payload[0], payload[1]);
  186. return -1;
  187. }
  188. int s7len = total - 4 - 3;
  189. if (s7len < 0 || s7len > maxout) {
  190. set_err("bad s7 length %d", s7len);
  191. return -1;
  192. }
  193. memcpy(out, payload + 3, s7len);
  194. *out_len = s7len;
  195. return 0;
  196. }
  197. static void put_u16(uint8_t *p, uint16_t v) {
  198. p[0] = (uint8_t)(v >> 8);
  199. p[1] = (uint8_t)(v & 0xFF);
  200. }
  201. /* ---- COTP Connection Request ---- */
  202. static int cotp_connect(int fd, const SimCfg *cfg) {
  203. uint8_t pdu[22];
  204. int n = 0;
  205. pdu[n++] = 0x11;
  206. pdu[n++] = COTP_CR;
  207. pdu[n++] = 0x00; pdu[n++] = 0x00;
  208. pdu[n++] = 0x00; pdu[n++] = 0x01;
  209. pdu[n++] = 0x00;
  210. pdu[n++] = 0xC1; pdu[n++] = 0x02;
  211. pdu[n++] = (uint8_t)((cfg->tsap_local >> 8) & 0xFF);
  212. pdu[n++] = (uint8_t)(cfg->tsap_local & 0xFF);
  213. pdu[n++] = 0xC2; pdu[n++] = 0x02;
  214. pdu[n++] = (uint8_t)((cfg->tsap_remote >> 8) & 0xFF);
  215. pdu[n++] = (uint8_t)(cfg->tsap_remote & 0xFF);
  216. pdu[n++] = 0xC0; pdu[n++] = 0x01; pdu[n++] = 0x0A;
  217. uint8_t frame[4 + n];
  218. int total = 4 + n;
  219. frame[0] = 3; frame[1] = 0;
  220. frame[2] = (uint8_t)(total >> 8);
  221. frame[3] = (uint8_t)(total & 0xFF);
  222. memcpy(frame + 4, pdu, n);
  223. if (send_all(fd, frame, total) < 0) return -1;
  224. uint8_t resp[64];
  225. if (recv_exact(fd, resp, 4) < 0) return -1;
  226. if (resp[0] != 3) { set_err("CC bad TPKT version"); return -1; }
  227. int rtot = ((int)resp[2] << 8) | resp[3];
  228. if (rtot < 7) { set_err("CC too short"); return -1; }
  229. if (recv_exact(fd, resp + 4, rtot - 4) < 0) return -1;
  230. if (resp[5] != COTP_CC) { set_err("expected COTP CC"); return -1; }
  231. return 0;
  232. }
  233. /* ---- S7 Setup Communication ---- */
  234. static int s7_setup(int fd) {
  235. uint8_t req[18];
  236. req[0] = 0x32; req[1] = S7_PDU_REQ;
  237. req[2] = 0x00; req[3] = 0x00;
  238. put_u16(req + 4, ++g_seq);
  239. put_u16(req + 6, 0x0008);
  240. put_u16(req + 8, 0x0000);
  241. req[10] = 0xF0; req[11] = 0x00;
  242. put_u16(req + 12, 0x0001);
  243. put_u16(req + 14, 0x0001);
  244. put_u16(req + 16, 0x01E0);
  245. if (send_s7(fd, req, sizeof(req)) < 0) return -1;
  246. uint8_t resp[1024];
  247. int rlen;
  248. if (recv_s7(fd, resp, sizeof(resp), &rlen) < 0) return -1;
  249. return 0;
  250. }
  251. /* ---- S7 Write Var ---- */
  252. static int s7_write(int fd, const SimCfg *cfg, const uint8_t *data, int len) {
  253. uint8_t req[10 + 14 + 4 + MAX_BLOCK];
  254. int o = 0;
  255. req[o++] = 0x32; req[o++] = S7_PDU_REQ;
  256. req[o++] = 0x00; req[o++] = 0x00;
  257. put_u16(req + o, ++g_seq); o += 2; /* PDU ref */
  258. put_u16(req + o, 0x000E); o += 2; /* param len = 14 */
  259. put_u16(req + o, 0x0004 + len); o += 2; /* data len */
  260. req[o++] = 0x05; req[o++] = 0x01; /* WRITE VAR, 1 item */
  261. req[o++] = 0x12; req[o++] = 0x0A; /* spec, addr spec len */
  262. req[o++] = 0x10; /* syntax S7-ANY */
  263. req[o++] = TS_BYTE; /* transport size BYTE */
  264. put_u16(req + o, (uint16_t)len); o += 2;
  265. put_u16(req + o, (uint16_t)cfg->db); o += 2;
  266. req[o++] = (uint8_t)cfg->area;
  267. uint32_t addr = (uint32_t)cfg->start * 8u;
  268. req[o++] = (uint8_t)(addr >> 16);
  269. req[o++] = (uint8_t)(addr >> 8);
  270. req[o++] = (uint8_t)(addr & 0xFF);
  271. req[o++] = 0x00; /* return code (ignored) */
  272. req[o++] = TS_BYTE;
  273. put_u16(req + o, (uint16_t)(len * 8)); o += 2;
  274. memcpy(req + o, data, len); o += len;
  275. if (send_s7(fd, req, o) < 0) return -1;
  276. uint8_t resp[1024];
  277. int rlen;
  278. if (recv_s7(fd, resp, sizeof(resp), &rlen) < 0) return -1;
  279. if (rlen < 14 || resp[0] != 0x32 || resp[1] != S7_PDU_ACKD) {
  280. set_err("bad write response header");
  281. return -1;
  282. }
  283. if (resp[10] != 0 || resp[11] != 0) {
  284. set_err("s7 error class %02x code %02x", resp[10], resp[11]);
  285. return -1;
  286. }
  287. /* param: 0x05 0x01 0xFF (0xFF = item success) */
  288. if (rlen > 14 && resp[14] != 0xFF) {
  289. set_err("write item rc=0x%02x", resp[14]);
  290. return -1;
  291. }
  292. return 0;
  293. }
  294. static int tcp_connect_handshake(const SimCfg *cfg) {
  295. int fd = socket(AF_INET, SOCK_STREAM, 0);
  296. if (fd < 0) { set_err("socket: %s", strerror(errno)); return -1; }
  297. struct sockaddr_in sa;
  298. memset(&sa, 0, sizeof(sa));
  299. sa.sin_family = AF_INET;
  300. sa.sin_port = htons((uint16_t)cfg->port);
  301. if (inet_pton(AF_INET, cfg->ip, &sa.sin_addr) != 1) {
  302. set_err("bad ip %s", cfg->ip);
  303. close(fd);
  304. return -1;
  305. }
  306. int flags = fcntl(fd, F_GETFL, 0);
  307. fcntl(fd, F_SETFL, flags | O_NONBLOCK);
  308. int rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
  309. if (rc < 0 && errno != EINPROGRESS) {
  310. set_err("tcp connect: %s", strerror(errno));
  311. close(fd);
  312. return -1;
  313. }
  314. if (rc < 0) {
  315. struct pollfd pfd = { .fd = fd, .events = POLLOUT };
  316. int pr = poll(&pfd, 1, SOCK_TIMEOUT_MS);
  317. if (pr <= 0) {
  318. set_err(pr == 0 ? "tcp connect timeout" : "tcp connect: %s",
  319. strerror(errno));
  320. close(fd);
  321. return -1;
  322. }
  323. int soerr = 0;
  324. socklen_t sl = sizeof(soerr);
  325. if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerr, &sl) < 0 || soerr != 0) {
  326. set_err("tcp connect refused: %s", strerror(soerr));
  327. close(fd);
  328. return -1;
  329. }
  330. }
  331. fcntl(fd, F_SETFL, flags);
  332. struct timeval tv;
  333. tv.tv_sec = SOCK_TIMEOUT_MS / 1000;
  334. tv.tv_usec = (SOCK_TIMEOUT_MS % 1000) * 1000;
  335. setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
  336. setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
  337. int one = 1;
  338. setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
  339. if (cotp_connect(fd, cfg) < 0) { close(fd); return -1; }
  340. if (s7_setup(fd) < 0) {
  341. /* Setup is non-fatal. */
  342. }
  343. return fd;
  344. }
  345. /* Read ip/port/area/db/read_start/read_len/tsap from the [plc] section and the
  346. * layout-switch bit rules from the [plc_rules] section of a channels.conf-style
  347. * file. Rules use `byte.bit` (relative to read_start), e.g. `6.6,6.7`.
  348. * Unknown keys are ignored. */
  349. static int parse_config(const char *path, SimCfg *cfg) {
  350. FILE *fp = fopen(path, "r");
  351. if (!fp) { set_err("cannot open %s", path); return -1; }
  352. int section = 0;
  353. int rules_seen = 0;
  354. char line[512];
  355. while (fgets(line, sizeof(line), fp)) {
  356. char *s = line;
  357. while (*s == ' ' || *s == '\t') s++;
  358. if (*s == '\n' || *s == '\r' || *s == '#' || *s == '\0') continue;
  359. if (s[0] == '[') {
  360. if (strncmp(s, "[plc]", 5) == 0) section = 5;
  361. else if (strncmp(s, "[plc_rules]", 11) == 0) section = 7;
  362. else section = 0;
  363. continue;
  364. }
  365. if (section == 5) {
  366. char key[64], val[256];
  367. if (sscanf(s, "%63s = %255[^\n\r]", key, val) < 2) continue;
  368. char *c = strchr(val, ';');
  369. if (!c) c = strchr(val, '#');
  370. if (c) *c = '\0';
  371. int vlen = (int)strlen(val);
  372. while (vlen > 0 && (val[vlen-1] == ' ' || val[vlen-1] == '\t'))
  373. val[--vlen] = '\0';
  374. if (strcmp(key, "ip") == 0) {
  375. strncpy(cfg->ip, val, sizeof(cfg->ip) - 1);
  376. } else if (strcmp(key, "port") == 0) {
  377. cfg->port = atoi(val);
  378. } else if (strcmp(key, "area") == 0) {
  379. if (strcasecmp(val, "i") == 0) cfg->area = 0x81;
  380. else if (strcasecmp(val, "q") == 0) cfg->area = 0x82;
  381. else if (strcasecmp(val, "m") == 0) cfg->area = 0x83;
  382. else if (strcasecmp(val, "db") == 0) cfg->area = 0x84;
  383. } else if (strcmp(key, "db") == 0) {
  384. cfg->db = atoi(val);
  385. } else if (strcmp(key, "read_start") == 0) {
  386. cfg->start = atoi(val);
  387. } else if (strcmp(key, "read_len") == 0) {
  388. cfg->len = atoi(val);
  389. } else if (strcmp(key, "tsap_local") == 0) {
  390. cfg->tsap_local = (int)strtol(val, NULL, 0);
  391. } else if (strcmp(key, "tsap_remote") == 0) {
  392. cfg->tsap_remote = (int)strtol(val, NULL, 0);
  393. }
  394. } else if (section == 7) {
  395. /* e.g. `0 : 6.6,6.7 ; 8.4,8.5` (byte.bit, ';' = another group) */
  396. int layout;
  397. char *colon = strchr(s, ':');
  398. if (!colon) continue;
  399. if (sscanf(s, "%d", &layout) != 1) continue;
  400. if (layout < 0 || layout >= MAX_RULES) continue;
  401. PlcRuleCfg *R = &g_rules[layout];
  402. memset(R, 0, sizeof(*R));
  403. R->layout = layout;
  404. char *tok = colon + 1;
  405. while (tok && *tok && R->nbits < MAX_RULE_BITS) {
  406. while (*tok == ' ' || *tok == '\t' || *tok == ',' || *tok == ';')
  407. tok++;
  408. if (*tok == ':' || *tok == '\0' ||
  409. *tok == '\n' || *tok == '\r' || *tok == '#') break;
  410. int by, bi = 0;
  411. if (sscanf(tok, "%d.%d", &by, &bi) == 2) {
  412. /* ok */
  413. } else if (sscanf(tok, "%d", &by) == 1) {
  414. bi = 0;
  415. } else {
  416. break;
  417. }
  418. R->bits[R->nbits].byte = by;
  419. R->bits[R->nbits].bit = bi;
  420. R->nbits++;
  421. R->has_bits = 1;
  422. while (*tok && *tok != ',' && *tok != ';' && *tok != ':')
  423. tok++;
  424. }
  425. if (R->nbits > 0 && layout + 1 > g_num_rules)
  426. g_num_rules = layout + 1;
  427. rules_seen = 1;
  428. }
  429. }
  430. fclose(fp);
  431. if (cfg->len <= 0 || cfg->len > MAX_BLOCK) cfg->len = 1;
  432. if (!rules_seen) {
  433. /* no [plc_rules]: fall back to a fixed 6-state cycle on byte0 */
  434. g_num_rules = 5;
  435. }
  436. return 0;
  437. }
  438. static void usage(const char *prog) {
  439. fprintf(stderr,
  440. "Usage: %s [-c channels.conf] [-t interval_ms] [-f first_state]\n"
  441. " -c config file with [plc] and [plc_rules] sections (default: channels.conf)\n"
  442. " -t interval between state switches in ms (default: 5000)\n"
  443. " -f first state index 0..N (default: 0)\n",
  444. prog);
  445. }
  446. int main(int argc, char **argv) {
  447. SimCfg cfg;
  448. memset(&cfg, 0, sizeof(cfg));
  449. strncpy(cfg.ip, "192.168.1.90", sizeof(cfg.ip) - 1);
  450. cfg.port = 102;
  451. cfg.area = 0x81; /* I area, matches channels.conf */
  452. cfg.db = 1;
  453. cfg.start = 0;
  454. cfg.len = 1;
  455. cfg.tsap_local = 0x0100;
  456. cfg.tsap_remote = 0x0102;
  457. const char *config_path = "channels.conf";
  458. int interval_ms = 5000;
  459. int state = 0;
  460. int opt;
  461. while ((opt = getopt(argc, argv, "c:t:f:h")) != -1) {
  462. switch (opt) {
  463. case 'c': config_path = optarg; break;
  464. case 't': interval_ms = atoi(optarg); break;
  465. case 'f': state = atoi(optarg); break;
  466. case 'h': default: usage(argv[0]); return 1;
  467. }
  468. }
  469. if (interval_ms <= 0) interval_ms = 5000;
  470. if (state < 0) state = 0;
  471. if (parse_config(config_path, &cfg) < 0)
  472. fprintf(stderr, "WARN: %s\n", g_err);
  473. if (state >= g_num_rules + 1) state = 0;
  474. signal(SIGINT, sig_handler);
  475. signal(SIGTERM, sig_handler);
  476. printf("PLC simulator: %s:%d area=%s db=%d start=%d len=%d "
  477. "interval=%dms rules=%d\n",
  478. cfg.ip, cfg.port,
  479. cfg.area >= 0x81 && cfg.area <= 0x84 ? area_names[cfg.area - 0x80] : "?",
  480. cfg.db, cfg.start, cfg.len, interval_ms, g_num_rules);
  481. int fd = -1;
  482. while (g_running) {
  483. if (fd < 0) {
  484. fd = tcp_connect_handshake(&cfg);
  485. if (fd < 0) {
  486. printf("connect failed (%s), retrying in 2s...\n", g_err);
  487. for (int i = 0; i < 40 && g_running; i++) usleep(50000);
  488. continue;
  489. }
  490. printf("connected to %s:%d\n", cfg.ip, cfg.port);
  491. }
  492. uint8_t data[MAX_BLOCK];
  493. char write_desc[256];
  494. state_to_block(data, cfg.len, state, g_num_rules);
  495. format_state_addr(write_desc, sizeof(write_desc), &cfg, data, cfg.len);
  496. if (s7_write(fd, &cfg, data, cfg.len) < 0) {
  497. printf("write failed (%s), reconnecting...\n", g_err);
  498. close(fd);
  499. fd = -1;
  500. continue;
  501. }
  502. printf("state %d: %s (byte0=0x%02X)\n", state, write_desc, data[0]);
  503. state = (state + 1) % (g_num_rules + 1);
  504. for (int i = 0; i < interval_ms / 50 && g_running; i++)
  505. usleep(50000);
  506. }
  507. if (fd >= 0) close(fd);
  508. printf("done\n");
  509. return 0;
  510. }