From 6c31ec9a527d256e8bd439242f1ddf925f53d722 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:50:07 +0800 Subject: [PATCH 01/12] Refactor bilingual README for clarity and replace Mermaid diagrams with Excalidraw-based PNG images (#29) * docs: rewrite bilingual README with mermaid diagrams Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/457dc57e-fd6d-4087-a604-3a7a4dd68b7b Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: replace mermaid diagrams with static PNG images Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: replace mermaid with png diagrams and add excalidraw sources Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: align client diagram alt text with sequence content Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: normalize client diagram naming and alt text Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: unify client diagram alt text naming Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: improve diagram alt text accessibility in both READMEs Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- .doc/csm-tcp-router-client-console.excalidraw | 14 +++ .doc/csm-tcp-router-client-console.png | Bin 0 -> 9476 bytes .doc/csm-tcp-router-command-sets.excalidraw | 17 ++++ .doc/csm-tcp-router-command-sets.png | Bin 0 -> 117195 bytes .doc/csm-tcp-router-framework.excalidraw | 17 ++++ ...image.png => csm-tcp-router-framework.png} | Bin README(zh-cn).md | 95 ++++++++---------- README.md | 93 +++++++---------- 8 files changed, 127 insertions(+), 109 deletions(-) create mode 100644 .doc/csm-tcp-router-client-console.excalidraw create mode 100644 .doc/csm-tcp-router-client-console.png create mode 100644 .doc/csm-tcp-router-command-sets.excalidraw create mode 100644 .doc/csm-tcp-router-command-sets.png create mode 100644 .doc/csm-tcp-router-framework.excalidraw rename .doc/{image.png => csm-tcp-router-framework.png} (100%) diff --git a/.doc/csm-tcp-router-client-console.excalidraw b/.doc/csm-tcp-router-client-console.excalidraw new file mode 100644 index 0000000..91524ac --- /dev/null +++ b/.doc/csm-tcp-router-client-console.excalidraw @@ -0,0 +1,14 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + {"id":"u","type":"text","x":80,"y":60,"width":52,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":21,"version":1,"versionNonce":21,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"User","fontSize":20,"fontFamily":1,"textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"User","lineHeight":1.2}, + {"id":"c","type":"text","x":290,"y":60,"width":150,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":22,"version":1,"versionNonce":22,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"Client Console","fontSize":20,"fontFamily":1,"textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"Client Console","lineHeight":1.2}, + {"id":"r","type":"text","x":570,"y":60,"width":150,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":23,"version":1,"versionNonce":23,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"TCP Router","fontSize":20,"fontFamily":1,"textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"TCP Router","lineHeight":1.2}, + {"id":"s","type":"text","x":830,"y":60,"width":104,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":24,"version":1,"versionNonce":24,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"Server Log","fontSize":20,"fontFamily":1,"textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"Server Log","lineHeight":1.2}, + {"id":"note","type":"text","x":140,"y":180,"width":760,"height":72,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":25,"version":1,"versionNonce":25,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"Connect -> welcome(info) -> send cmd -> resp/async-resp -> Bye -> goodbye(info)","fontSize":24,"fontFamily":1,"textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"Connect -> welcome(info) -> send cmd -> resp/async-resp -> Bye -> goodbye(info)","lineHeight":1.2} + ], + "appState": {"gridSize": null, "viewBackgroundColor": "#ffffff"}, + "files": {} +} diff --git a/.doc/csm-tcp-router-client-console.png b/.doc/csm-tcp-router-client-console.png new file mode 100644 index 0000000000000000000000000000000000000000..e1998f3f018b63e3e8c9ea3d3c74ef597a889843 GIT binary patch literal 9476 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 P5Omqn-lEq0#&7=tilp-r literal 0 HcmV?d00001 diff --git a/.doc/csm-tcp-router-command-sets.excalidraw b/.doc/csm-tcp-router-command-sets.excalidraw new file mode 100644 index 0000000..4d00445 --- /dev/null +++ b/.doc/csm-tcp-router-command-sets.excalidraw @@ -0,0 +1,17 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + {"id":"r0","type":"rectangle","x":420,"y":40,"width":240,"height":70,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"#fff7ed","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":11,"version":1,"versionNonce":11,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false}, + {"id":"t0","type":"text","x":470,"y":64,"width":140,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":12,"version":1,"versionNonce":12,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"Command Sets","fontSize":20,"fontFamily":1,"textAlign":"center","verticalAlign":"middle","containerId":null,"originalText":"Command Sets","lineHeight":1.2}, + {"id":"r1","type":"rectangle","x":80,"y":200,"width":280,"height":82,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"#eff6ff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":13,"version":1,"versionNonce":13,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false}, + {"id":"t1","type":"text","x":110,"y":230,"width":220,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":14,"version":1,"versionNonce":14,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"1) CSM Message APIs","fontSize":20,"fontFamily":1,"textAlign":"center","verticalAlign":"middle","containerId":null,"originalText":"1) CSM Message APIs","lineHeight":1.2}, + {"id":"r2","type":"rectangle","x":430,"y":200,"width":280,"height":82,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"#f0fdfa","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":15,"version":1,"versionNonce":15,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false}, + {"id":"t2","type":"text","x":444,"y":230,"width":252,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":16,"version":1,"versionNonce":16,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"2) Router Management APIs","fontSize":20,"fontFamily":1,"textAlign":"center","verticalAlign":"middle","containerId":null,"originalText":"2) Router Management APIs","lineHeight":1.2}, + {"id":"r3","type":"rectangle","x":780,"y":200,"width":280,"height":82,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"#faf5ff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":17,"version":1,"versionNonce":17,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false}, + {"id":"t3","type":"text","x":820,"y":230,"width":200,"height":24,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"hachure","strokeWidth":1,"strokeStyle":"solid","roughness":0,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":18,"version":1,"versionNonce":18,"isDeleted":false,"boundElements":[],"updated":1,"link":null,"locked":false,"text":"3) Client Built-ins","fontSize":20,"fontFamily":1,"textAlign":"center","verticalAlign":"middle","containerId":null,"originalText":"3) Client Built-ins","lineHeight":1.2} + ], + "appState": {"gridSize": null, "viewBackgroundColor": "#ffffff"}, + "files": {} +} diff --git a/.doc/csm-tcp-router-command-sets.png b/.doc/csm-tcp-router-command-sets.png new file mode 100644 index 0000000000000000000000000000000000000000..4ecf947d176fb53e04dfddd18e7cd28aaddac856 GIT binary patch literal 117195 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 Date: Tue, 21 Apr 2026 23:09:44 +0800 Subject: [PATCH 02/12] Convert `.doc/CSM-TCP-Router.drawio` to Excalidraw source and synced PNG export (#30) * docs: add excalidraw and png converted from drawio Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/8747d072-0055-4877-a383-891476e8e333 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: fix application spelling in converted diagram Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/8747d072-0055-4877-a383-891476e8e333 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- .doc/CSM-TCP-Router.excalidraw | 1481 ++++++++++++++++++++++++++++++++ .doc/CSM-TCP-Router.png | Bin 0 -> 110059 bytes 2 files changed, 1481 insertions(+) create mode 100644 .doc/CSM-TCP-Router.excalidraw create mode 100644 .doc/CSM-TCP-Router.png diff --git a/.doc/CSM-TCP-Router.excalidraw b/.doc/CSM-TCP-Router.excalidraw new file mode 100644 index 0000000..fb64d1a --- /dev/null +++ b/.doc/CSM-TCP-Router.excalidraw @@ -0,0 +1,1481 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "06097f4ce4a944f5b840", + "type": "rectangle", + "x": -40.0, + "y": 1060.0, + "width": 710.0, + "height": 260.0, + "angle": 0, + "strokeColor": "#666666", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1516676960, + "version": 1, + "versionNonce": 894759244, + "isDeleted": false, + "boundElements": [ + { + "id": "40d09f28b6f5449b8416", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "40d09f28b6f5449b8416", + "type": "text", + "x": 196.68, + "y": 1175.0, + "width": 236.64, + "height": 30.0, + "angle": 0, + "strokeColor": "#67AB9F", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1359914060, + "version": 1, + "versionNonce": 267946355, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Server Application", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "06097f4ce4a944f5b840", + "originalText": "Server Application", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "f2931a99db14441d92c2", + "type": "rectangle", + "x": 250.0, + "y": 70.0, + "width": 570.0, + "height": 630.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 334935149, + "version": 1, + "versionNonce": 912941967, + "isDeleted": false, + "boundElements": [ + { + "id": "d39d2f1397d84cf29c94", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "d39d2f1397d84cf29c94", + "type": "text", + "x": 367.96000000000004, + "y": 365.0, + "width": 334.08, + "height": 40.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1166131094, + "version": 1, + "versionNonce": 1488794127, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Server Application", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "f2931a99db14441d92c2", + "originalText": "Server Application", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "3a12c9cd83ac49d4af82", + "type": "rectangle", + "x": 320.0, + "y": 120.0, + "width": 450.0, + "height": 340.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 405658968, + "version": 1, + "versionNonce": 1007193791, + "isDeleted": false, + "boundElements": [ + { + "id": "96fc065142d741bfab28", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "96fc065142d741bfab28", + "type": "text", + "x": 411.89, + "y": 273.125, + "width": 266.21999999999997, + "height": 33.75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1687535625, + "version": 1, + "versionNonce": 1573432143, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "CSM Module System", + "fontSize": 27, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3a12c9cd83ac49d4af82", + "originalText": "CSM Module System", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "58eb2e1dfd49455083d7", + "type": "rectangle", + "x": 370.0, + "y": 190.0, + "width": 170.0, + "height": 110.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 828310685, + "version": 1, + "versionNonce": 1098432173, + "isDeleted": false, + "boundElements": [ + { + "id": "b4cb0e46d8754a6ba28b", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "b4cb0e46d8754a6ba28b", + "type": "text", + "x": 374.0, + "y": 232.5, + "width": 162.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 540651128, + "version": 1, + "versionNonce": 643699448, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "AI (CSM Module1)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "58eb2e1dfd49455083d7", + "originalText": "AI (CSM Module1)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "8a5c90e4c9214aff98d8", + "type": "rectangle", + "x": 370.0, + "y": 310.0, + "width": 170.0, + "height": 110.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 2061525606, + "version": 1, + "versionNonce": 1612758244, + "isDeleted": false, + "boundElements": [ + { + "id": "15dba5756d714bc5be93", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "15dba5756d714bc5be93", + "type": "text", + "x": 374.0, + "y": 352.5, + "width": 162.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1094784021, + "version": 1, + "versionNonce": 1455312304, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "DIO1 (CSM Module2)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "8a5c90e4c9214aff98d8", + "originalText": "DIO1 (CSM Module2)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "378abd886fa942b5ae6f", + "type": "rectangle", + "x": 550.0, + "y": 190.0, + "width": 170.0, + "height": 110.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1968915559, + "version": 1, + "versionNonce": 137789224, + "isDeleted": false, + "boundElements": [ + { + "id": "867f1a5210ac42c7a82d", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "867f1a5210ac42c7a82d", + "type": "text", + "x": 554.0, + "y": 232.5, + "width": 162.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1862328426, + "version": 1, + "versionNonce": 1835601418, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "DIO1 (CSM Module3)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "378abd886fa942b5ae6f", + "originalText": "DIO1 (CSM Module3)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "adc34df6058e4435acf1", + "type": "rectangle", + "x": 550.0, + "y": 310.0, + "width": 170.0, + "height": 110.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 76596677, + "version": 1, + "versionNonce": 1494446171, + "isDeleted": false, + "boundElements": [ + { + "id": "109e949f39d34b6cb355", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "109e949f39d34b6cb355", + "type": "text", + "x": 554.0, + "y": 352.5, + "width": 162.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1205658623, + "version": 1, + "versionNonce": 1551557532, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Measure (CSM Module4)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "adc34df6058e4435acf1", + "originalText": "Measure (CSM Module4)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "72a83ed504ed4d0da987", + "type": "rectangle", + "x": 320.0, + "y": 560.0, + "width": 450.0, + "height": 100.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 513845840, + "version": 1, + "versionNonce": 1692515392, + "isDeleted": false, + "boundElements": [ + { + "id": "3b93d90e9ba740ec8d2c", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "3b93d90e9ba740ec8d2c", + "type": "text", + "x": 324.0, + "y": 590.0, + "width": 442.0, + "height": 40.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 133198406, + "version": 1, + "versionNonce": 2087443845, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "CSM TCP Router (based on JKI TCP Server)", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "72a83ed504ed4d0da987", + "originalText": "CSM TCP Router (based on JKI TCP Server)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "66d9bb7d39ed49bab977", + "type": "rectangle", + "x": -330.0, + "y": 80.0, + "width": 470.0, + "height": 620.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1857846310, + "version": 1, + "versionNonce": 2135205116, + "isDeleted": false, + "boundElements": [ + { + "id": "d61e4a50c7424aafb49c", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "d61e4a50c7424aafb49c", + "type": "text", + "x": -262.03999999999996, + "y": 370.0, + "width": 334.08, + "height": 40.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 531398026, + "version": 1, + "versionNonce": 1296356900, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Client Application", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "66d9bb7d39ed49bab977", + "originalText": "Client Application", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d4b5bc67b5e84b86aec2", + "type": "rectangle", + "x": -300.0, + "y": 300.0, + "width": 340.0, + "height": 160.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 451019402, + "version": 1, + "versionNonce": 2020205762, + "isDeleted": false, + "boundElements": [ + { + "id": "e14f5e9cd4674be1bda4", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "e14f5e9cd4674be1bda4", + "type": "text", + "x": -296.0, + "y": 317.5, + "width": 332.0, + "height": 125.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1029267775, + "version": 1, + "versionNonce": 738718317, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Server Build-in Command\n\nCSM TCP Router 定义的指令,用于管理和显示帮助- List: 列出所有的 CSM 模块\n- List API: 列出CSM 模块的参数\n- Help: 显示CSM模块的帮助(VI Description)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "d4b5bc67b5e84b86aec2", + "originalText": "Server Build-in Command\n\nCSM TCP Router 定义的指令,用于管理和显示帮助- List: 列出所有的 CSM 模块\n- List API: 列出CSM 模块的参数\n- Help: 显示CSM模块的帮助(VI Description)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c44e3d5dc3a5490cb039", + "type": "rectangle", + "x": -300.0, + "y": 490.0, + "width": 340.0, + "height": 160.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 286961250, + "version": 1, + "versionNonce": 1108591278, + "isDeleted": false, + "boundElements": [ + { + "id": "ec5f57e035cf45908018", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "ec5f57e035cf45908018", + "type": "text", + "x": -296.0, + "y": 532.5, + "width": 332.0, + "height": 75.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1260928053, + "version": 1, + "versionNonce": 1047655250, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Server  CSM Command\n由 CSM 模块定义的指令\nCSM 的所有消息,都被转发到 CSM Modules 中执行,因此支持的消息种类,由Server 中的CSM Module System 中的CSM模块决定", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c44e3d5dc3a5490cb039", + "originalText": "Server  CSM Command\n由 CSM 模块定义的指令\nCSM 的所有消息,都被转发到 CSM Modules 中执行,因此支持的消息种类,由Server 中的CSM Module System 中的CSM模块决定", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "68d09aa25b504907b22d", + "type": "rectangle", + "x": -300.0, + "y": 110.0, + "width": 340.0, + "height": 160.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1280667824, + "version": 1, + "versionNonce": 645069776, + "isDeleted": false, + "boundElements": [ + { + "id": "0916692baae84d6a8cb2", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "0916692baae84d6a8cb2", + "type": "text", + "x": -296.0, + "y": 127.5, + "width": 332.0, + "height": 125.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1861740026, + "version": 1, + "versionNonce": 1550368398, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Client-Def Command\nClient 定义的本地指令,和server无关\n\n- Switch: 切换发送的CSM模块,节省键入CSM模块名称\n- Script: 导入执行 scipt 文本", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "68d09aa25b504907b22d", + "originalText": "Client-Def Command\nClient 定义的本地指令,和server无关\n\n- Switch: 切换发送的CSM模块,节省键入CSM模块名称\n- Script: 导入执行 scipt 文本", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c3d7e032eee9479d92fe", + "type": "rectangle", + "x": -10.0, + "y": 1090.0, + "width": 460.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#82b366", + "backgroundColor": "#d5e8d4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 616346550, + "version": 1, + "versionNonce": 1272916373, + "isDeleted": false, + "boundElements": [ + { + "id": "ba1ba4b5800841ef99ba", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "ba1ba4b5800841ef99ba", + "type": "text", + "x": 80.80000000000001, + "y": 1105.0, + "width": 278.4, + "height": 30.0, + "angle": 0, + "strokeColor": "#67AB9F", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1360334683, + "version": 1, + "versionNonce": 721767282, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "TCP Layer( Reusable)", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c3d7e032eee9479d92fe", + "originalText": "TCP Layer( Reusable)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "f1fcce1f99dc4c1f8847", + "type": "rectangle", + "x": -10.0, + "y": 1160.0, + "width": 460.0, + "height": 140.0, + "angle": 0, + "strokeColor": "#b85450", + "backgroundColor": "#f8cecc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 527125112, + "version": 1, + "versionNonce": 1980260540, + "isDeleted": false, + "boundElements": [ + { + "id": "5bab6bdeb62b40228319", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "5bab6bdeb62b40228319", + "type": "text", + "x": -6.0, + "y": 1217.5, + "width": 452.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#67AB9F", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 894462135, + "version": 1, + "versionNonce": 378597871, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "Code Based CSM Framework(Based on the Requirements)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "f1fcce1f99dc4c1f8847", + "originalText": "Code Based CSM Framework(Based on the Requirements)", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "5c296355ff174e0195e2", + "type": "rectangle", + "x": 50.0, + "y": 790.0, + "width": 100.0, + "height": 100.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#D2D3D3", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 425614159, + "version": 1, + "versionNonce": 501325967, + "isDeleted": false, + "boundElements": [ + { + "id": "b543c6d67fdf4c61a3f0", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "b543c6d67fdf4c61a3f0", + "type": "text", + "x": 54.0, + "y": 827.5, + "width": 92.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#67AB9F", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 91453709, + "version": 1, + "versionNonce": 884719392, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "TCP Client", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "5c296355ff174e0195e2", + "originalText": "TCP Client", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b56d185ccff9414282e7", + "type": "rectangle", + "x": 170.0, + "y": 790.0, + "width": 100.0, + "height": 100.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#D2D3D3", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1346551058, + "version": 1, + "versionNonce": 1266930958, + "isDeleted": false, + "boundElements": [ + { + "id": "e1c67e3c14464e33954a", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "e1c67e3c14464e33954a", + "type": "text", + "x": 174.0, + "y": 827.5, + "width": 92.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#67AB9F", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 757773874, + "version": 1, + "versionNonce": 492781324, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "TCP Client", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "b56d185ccff9414282e7", + "originalText": "TCP Client", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c8513775c6fe4da58f55", + "type": "rectangle", + "x": 290.0, + "y": 790.0, + "width": 100.0, + "height": 100.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#D2D3D3", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 757589191, + "version": 1, + "versionNonce": 1529136688, + "isDeleted": false, + "boundElements": [ + { + "id": "709beda0e58b4b5ab6cd", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "709beda0e58b4b5ab6cd", + "type": "text", + "x": 294.0, + "y": 827.5, + "width": 92.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#67AB9F", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1211271590, + "version": 1, + "versionNonce": 534009896, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "TCP Client", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c8513775c6fe4da58f55", + "originalText": "TCP Client", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "09a22cc2431a4ca7b1e0", + "type": "rectangle", + "x": 370.0, + "y": 910.0, + "width": 430.0, + "height": 120.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1821414404, + "version": 1, + "versionNonce": 1294207142, + "isDeleted": false, + "boundElements": [ + { + "id": "627048f5d60843d0a878", + "type": "text" + } + ], + "updated": 0, + "link": null, + "locked": false + }, + { + "id": "627048f5d60843d0a878", + "type": "text", + "x": 374.0, + "y": 920.0, + "width": 422.0, + "height": 100.0, + "angle": 0, + "strokeColor": "#67AB9F", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 215362962, + "version": 1, + "versionNonce": 1839110588, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "text": "TCP Command\n- All CSM command from your code is supported\n- System command provided by TCP Layer is supported\nlist/help/list api ...", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "09a22cc2431a4ca7b1e0", + "originalText": "TCP Command\n- All CSM command from your code is supported\n- System command provided by TCP Layer is supported\nlist/help/list api ...", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "4343ca43bb1b46aaab89", + "type": "arrow", + "x": 545.0, + "y": 610.0, + "width": 0.0, + "height": -320.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1789827435, + "version": 1, + "versionNonce": 480108401, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + -320.0 + ] + ], + "lastCommittedPoint": [ + 0.0, + -320.0 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "c967d534ee594bd8b6f7", + "type": "arrow", + "x": 140.0, + "y": 599.0, + "width": 395.0, + "height": -214.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1930751864, + "version": 1, + "versionNonce": 709055981, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 395.0, + -214.0 + ] + ], + "lastCommittedPoint": [ + 395.0, + -214.0 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "c9b3b417bdbf45d89ce0", + "type": "arrow", + "x": 100.0, + "y": 840.0, + "width": 120.0, + "height": 280.0, + "angle": 0, + "strokeColor": "#97D077", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1801585414, + "version": 1, + "versionNonce": 373435437, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 120.0, + 280.0 + ] + ], + "lastCommittedPoint": [ + 120.0, + 280.0 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "5f55e1df753d4fb2a6b9", + "type": "arrow", + "x": 220.0, + "y": 840.0, + "width": 0.0, + "height": 280.0, + "angle": 0, + "strokeColor": "#97D077", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1574452212, + "version": 1, + "versionNonce": 751253604, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 280.0 + ] + ], + "lastCommittedPoint": [ + 0.0, + 280.0 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "7a7d7908178144bb97dd", + "type": "arrow", + "x": 340.0, + "y": 840.0, + "width": -120.0, + "height": 280.0, + "angle": 0, + "strokeColor": "#97D077", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1366313377, + "version": 1, + "versionNonce": 633510248, + "isDeleted": false, + "boundElements": null, + "updated": 0, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -120.0, + 280.0 + ] + ], + "lastCommittedPoint": [ + -120.0, + 280.0 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/.doc/CSM-TCP-Router.png b/.doc/CSM-TCP-Router.png new file mode 100644 index 0000000000000000000000000000000000000000..37d8fab79a25bcf9768354c274bf6c214fcf6889 GIT binary patch literal 110059 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%0D*pI{CJUg=ItDHEwpO>g`Uq+T>O{;AJ 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 C4`Imw literal 0 HcmV?d00001 From aaf5ce4cbd08a8d5e633f50a844f141f242087d7 Mon Sep 17 00:00:00 2001 From: NEVSTOP <8196752+nevstop@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:28:46 +0800 Subject: [PATCH 03/12] 17 python sdk (#32) * feat: add Python pip-publishable SDK for CSM-TCP-Router (#31) * feat: add Python pip-publishable SDK for CSM-TCP-Router Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/4a1ee665-7464-4bd0-8898-0725daef43d5 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * fix: add least-privilege permissions to CI workflow jobs Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/4a1ee665-7464-4bd0-8898-0725daef43d5 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * feat: add asyncio client, Chinese README, and TestPyPI CI stage (v0.2.0) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/93afe5c4-c917-4b9b-a347-189efb3bf4db Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * fix: address all PR review comments (shared _errors, socket leak, locks, disconnect sentinels, isawaitable, changelog) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/b0917f8e-c50c-4a63-ae30-a1c245a6d3d7 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Add bilingual VI API reference docs for CSM-TCP-Router (Server + Client) (#35) * Initial plan * docs: add bilingual VI API documentation for CSM-TCP-Router Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/cacb955d-6c38-4fc7-b30d-9381fbbd06e1 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: move VI API docs under src and align reference format Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/117da425-df26-4ecc-a8c4-ab0e98412e50 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: restore compatibility notes in status API sections Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/117da425-df26-4ecc-a8c4-ab0e98412e50 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .github/workflows/Python_SDK.yml | 157 ++++++ SDK/python-package/CHANGELOG.md | 84 +++ SDK/python-package/LICENSE | 21 + SDK/python-package/README.md | 311 +++++++++++ SDK/python-package/README.zh-cn.md | 308 +++++++++++ SDK/python-package/examples/async_usage.py | 97 ++++ SDK/python-package/examples/basic_usage.py | 100 ++++ .../examples/subscribe_status.py | 86 +++ SDK/python-package/pyproject.toml | 85 +++ .../src/csm_tcp_router/__init__.py | 58 ++ .../src/csm_tcp_router/_errors.py | 26 + .../src/csm_tcp_router/_protocol.py | 91 +++ .../src/csm_tcp_router/_transport.py | 176 ++++++ .../src/csm_tcp_router/async_client.py | 523 ++++++++++++++++++ .../src/csm_tcp_router/client.py | 456 +++++++++++++++ .../src/csm_tcp_router/exceptions.py | 45 ++ .../src/csm_tcp_router/models.py | 151 +++++ SDK/python-package/tests/__init__.py | 1 + SDK/python-package/tests/conftest.py | 211 +++++++ SDK/python-package/tests/test_async_client.py | 455 +++++++++++++++ SDK/python-package/tests/test_client.py | 302 ++++++++++ SDK/python-package/tests/test_integration.py | 220 ++++++++ SDK/python-package/tests/test_protocol.py | 154 ++++++ .../VI Description(en-us) - CSM-TCP-Router.md | 95 ++++ .../VI Description(zh-cn) - CSM-TCP-Router.md | 95 ++++ 25 files changed, 4308 insertions(+) create mode 100644 .github/workflows/Python_SDK.yml create mode 100644 SDK/python-package/CHANGELOG.md create mode 100644 SDK/python-package/LICENSE create mode 100644 SDK/python-package/README.md create mode 100644 SDK/python-package/README.zh-cn.md create mode 100644 SDK/python-package/examples/async_usage.py create mode 100644 SDK/python-package/examples/basic_usage.py create mode 100644 SDK/python-package/examples/subscribe_status.py create mode 100644 SDK/python-package/pyproject.toml create mode 100644 SDK/python-package/src/csm_tcp_router/__init__.py create mode 100644 SDK/python-package/src/csm_tcp_router/_errors.py create mode 100644 SDK/python-package/src/csm_tcp_router/_protocol.py create mode 100644 SDK/python-package/src/csm_tcp_router/_transport.py create mode 100644 SDK/python-package/src/csm_tcp_router/async_client.py create mode 100644 SDK/python-package/src/csm_tcp_router/client.py create mode 100644 SDK/python-package/src/csm_tcp_router/exceptions.py create mode 100644 SDK/python-package/src/csm_tcp_router/models.py create mode 100644 SDK/python-package/tests/__init__.py create mode 100644 SDK/python-package/tests/conftest.py create mode 100644 SDK/python-package/tests/test_async_client.py create mode 100644 SDK/python-package/tests/test_client.py create mode 100644 SDK/python-package/tests/test_integration.py create mode 100644 SDK/python-package/tests/test_protocol.py create mode 100644 src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(en-us)/VI Description(en-us) - CSM-TCP-Router.md create mode 100644 src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(zh-cn)/VI Description(zh-cn) - CSM-TCP-Router.md diff --git a/.github/workflows/Python_SDK.yml b/.github/workflows/Python_SDK.yml new file mode 100644 index 0000000..5711af5 --- /dev/null +++ b/.github/workflows/Python_SDK.yml @@ -0,0 +1,157 @@ +name: Python SDK + +on: + push: + paths: + - 'SDK/python-package/**' + tags: + - 'python-sdk-v*' + pull_request: + paths: + - 'SDK/python-package/**' + workflow_dispatch: + +defaults: + run: + working-directory: SDK/python-package + +jobs: + # ------------------------------------------------------------------------- + # Lint + # ------------------------------------------------------------------------- + lint: + name: Lint (ruff) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install ruff + run: pip install ruff + + - name: Run ruff + run: ruff check src/ tests/ examples/ + + # ------------------------------------------------------------------------- + # Test matrix + # ------------------------------------------------------------------------- + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + needs: lint + permissions: + contents: read + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package and test dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install pytest pytest-cov pytest-asyncio + + - name: Run tests + run: pytest --cov=csm_tcp_router --cov-report=term-missing + + # ------------------------------------------------------------------------- + # Build (wheel + sdist) + # ------------------------------------------------------------------------- + build: + name: Build distribution + runs-on: ubuntu-latest + needs: test + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build tools + run: pip install build + + - name: Build wheel and sdist + run: python -m build + + - name: Verify wheel is importable + run: | + pip install dist/*.whl + python -c "import csm_tcp_router; print('Version:', csm_tcp_router.__version__)" + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: python-sdk-dist + path: SDK/python-package/dist/ + + # ------------------------------------------------------------------------- + # Publish to TestPyPI (on tag push only – gates production publish) + # ------------------------------------------------------------------------- + publish-testpypi: + name: Publish to TestPyPI + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/python-sdk-v') + environment: + name: testpypi + url: https://test.pypi.org/project/csm-tcp-router-client/ + + permissions: + id-token: write # required for OIDC trusted publishing + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: python-sdk-dist + path: dist/ + + - name: Publish to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + # ------------------------------------------------------------------------- + # Publish to PyPI (on tag push only – after TestPyPI succeeds) + # ------------------------------------------------------------------------- + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: publish-testpypi + if: startsWith(github.ref, 'refs/tags/python-sdk-v') + environment: + name: pypi + url: https://pypi.org/project/csm-tcp-router-client/ + + permissions: + id-token: write # required for trusted publishing (OIDC) + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: python-sdk-dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/SDK/python-package/CHANGELOG.md b/SDK/python-package/CHANGELOG.md new file mode 100644 index 0000000..a69cd48 --- /dev/null +++ b/SDK/python-package/CHANGELOG.md @@ -0,0 +1,84 @@ +# Changelog + +All notable changes to `csm-tcp-router-client` are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +--- + +## [0.2.0] – 2026-04-22 + +### Added + +- `AsyncTcpRouterClient` class: full asyncio API mirroring every method of + `TcpRouterClient`, using `asyncio.StreamReader`/`StreamWriter` and + `asyncio.Queue` for non-blocking I/O. +- Async context-manager support: `async with AsyncTcpRouterClient() as client:`. +- Both sync and `async def` callbacks supported for `subscribe_status()` and + `register_async_callback()` on the async client. +- `AsyncTcpRouterClient` exported from the top-level `csm_tcp_router` package. +- `examples/async_usage.py` – asyncio quickstart demonstrating all features. +- Test suite extended with `tests/test_async_client.py` (48 tests: unit + + integration via `MockServer`); test runner now uses `asyncio_mode = "auto"`. +- `pytest-asyncio` added to CI test dependencies. +- Chinese documentation: `README.zh-cn.md` (full translation of `README.md`). +- `README.md` updated with asyncio quickstart, async API reference table, link + to Chinese docs, and `async_usage.py` in the examples list. +- CI: added `publish-testpypi` job that publishes to TestPyPI *before* + `publish` (production PyPI); production publish now depends on TestPyPI + success; both use OIDC trusted publishing. +- `Framework :: AsyncIO` classifier added to package metadata. + +### Changed + +- Package version bumped to `0.2.0`. +- `asyncio_mode = "auto"` added to `pyproject.toml` pytest options; all async + tests run automatically without explicit `@pytest.mark.asyncio` decorators. + +--- + +## [0.1.0] – 2026-04-22 + +### Added + +- Initial release of the `csm-tcp-router-client` Python SDK. +- `TcpRouterClient` class with full thread-safe implementation of the + CSM-TCP-Router protocol v0. +- Connection lifecycle: `connect()`, `disconnect()`, `wait_for_server()`, + `connected` property, context-manager support. +- Synchronous command: `send_and_wait()`. +- Asynchronous command: `post()` with `CMD_RESP` handshake. +- No-reply async command: `post_no_reply()` with `CMD_RESP` handshake. +- Round-trip ping: `ping()`. +- Router management helpers: `list_modules()`, `list_api()`, `list_states()`, + `help()`. +- Status / interrupt subscriptions: `subscribe_status()`, + `unsubscribe_status()`, `register_async_callback()`, + `unregister_async_callback()`. +- Polling queues: `status_queue`, `async_response_queue`. +- Typed exception hierarchy: `TcpRouterError`, `ConnectionError`, + `TimeoutError`, `ProtocolError`, `ServerError` (with `.code` and `.message`). +- Public data models: `PacketType`, `Packet`, `CommandResponse`, + `AsyncResponse`, `StatusNotification`. +- Internal protocol v0 codec (`_protocol.py`) with `encode_packet()`, + `decode_header()`, `parse_packet()`; unknown packet types mapped to + `INFO` for forward compatibility. +- Internal TCP transport layer (`_transport.py`) with background daemon + receive thread, `memoryview`-based zero-copy reads, and clean shutdown. +- Comprehensive test suite: unit tests for protocol codec, unit tests for + client dispatch logic (mock transport), and integration tests against a + `MockServer` fixture. +- Examples: `basic_usage.py`, `subscribe_status.py`. +- `pyproject.toml` with `hatchling` build backend; ready for `pip install` + and upload to PyPI. +- GitHub Actions workflow `Python_SDK.yml`: lint (ruff), test (pytest) on + Python 3.8–3.12, build, and optional publish to PyPI on tag. + +[Unreleased]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/compare/python-sdk-v0.2.0...HEAD +[0.2.0]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/compare/python-sdk-v0.1.0...python-sdk-v0.2.0 +[0.1.0]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/releases/tag/python-sdk-v0.1.0 diff --git a/SDK/python-package/LICENSE b/SDK/python-package/LICENSE new file mode 100644 index 0000000..78e1cd2 --- /dev/null +++ b/SDK/python-package/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 NEVSTOP-LAB + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SDK/python-package/README.md b/SDK/python-package/README.md new file mode 100644 index 0000000..1460803 --- /dev/null +++ b/SDK/python-package/README.md @@ -0,0 +1,311 @@ +# csm-tcp-router-client + +[![PyPI](https://img.shields.io/pypi/v/csm-tcp-router-client)](https://pypi.org/project/csm-tcp-router-client/) +[![Python](https://img.shields.io/pypi/pyversions/csm-tcp-router-client)](https://pypi.org/project/csm-tcp-router-client/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/Python_SDK.yml/badge.svg)](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/Python_SDK.yml) + +Python client SDK for the [CSM-TCP-Router](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App) LabVIEW server. + +CSM-TCP-Router exposes a LabVIEW [Communicable State Machine (CSM)](https://github.com/NEVSTOP-LAB/Communicable-State-Machine) application over TCP so that any TCP client—including Python scripts, test harnesses, or CI pipelines—can send commands and receive responses without touching the LabVIEW code. + +> 📖 [中文文档 README.zh-cn.md](README.zh-cn.md) + +--- + +## Installation + +```bash +pip install csm-tcp-router-client +``` + +Requires Python 3.8 or later. No third-party dependencies—only the Python standard library. + +--- + +## Quickstart + +### Synchronous client + +```python +from csm_tcp_router import TcpRouterClient + +with TcpRouterClient() as client: + client.connect("localhost", 30007) + + # List all loaded CSM modules + print(client.list_modules()) + + # Send a synchronous command and wait for the response + resp = client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + + # Ping the server + ok, elapsed_s = client.ping() + print(f"Ping: {ok}, latency={elapsed_s*1000:.1f} ms") +``` + +### Asyncio client + +```python +import asyncio +from csm_tcp_router import AsyncTcpRouterClient + +async def main(): + async with AsyncTcpRouterClient() as client: + await client.connect("localhost", 30007) + print(await client.list_modules()) + resp = await client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + +asyncio.run(main()) +``` + +--- + +## Features + +- **Synchronous commands** (`-@`) – `send_and_wait()` blocks until the server returns the response. +- **Asynchronous commands** (`->`) – `post()` waits for the `cmd-resp` handshake; the eventual response is delivered via callback or queue. +- **No-reply commands** (`->|`) – `post_no_reply()` waits for the `cmd-resp` handshake; no further response expected. +- **Status subscriptions** – `subscribe_status()` / `unsubscribe_status()` with optional callback or polling queue. +- **Router management helpers** – `list_modules()`, `list_api()`, `list_states()`, `help()`. +- **Connection utilities** – `wait_for_server()` for polling during app startup. +- **Thread-safe sync client** – `TcpRouterClient`: all methods may be called from multiple threads concurrently. +- **Asyncio client** – `AsyncTcpRouterClient`: full `async def` API with both sync and async callbacks supported. +- **Zero dependencies** – pure Python standard library. +- **Context manager** support (`with TcpRouterClient()` / `async with AsyncTcpRouterClient()`). + +--- + +## Protocol + +The SDK implements the CSM-TCP-Router **protocol v0**. + +``` +| Data Length (4B) | Version (1B) | TYPE (1B) | FLAG1 (1B) | FLAG2 (1B) | Text Data | +╰────────────────────────── Header (8B) ─────────────────────────────╯ +``` + +| TYPE byte | Name | Direction | Description | +|-----------|---------------|----------------|------------------------------------------------| +| `0x00` | `INFO` | Server → Client| Welcome / goodbye informational message | +| `0x01` | `ERROR` | Server → Client| CSM error: `[Error: ] ` | +| `0x02` | `CMD` | Client → Server| Command string | +| `0x03` | `CMD_RESP` | Server → Client| Handshake ACK for async / subscribe commands | +| `0x04` | `RESP` | Server → Client| Synchronous response payload | +| `0x05` | `ASYNC_RESP` | Server → Client| Async response: ` <- ` | +| `0x06` | `STATUS` | Server → Client| Status broadcast: ` >> <- ` | +| `0x07` | `INTERRUPT` | Server → Client| Interrupt broadcast (same format as STATUS) | + +### Communication flows + +**Synchronous (`-@`)** + +``` +Client ─── CMD ──────────────────► Server +Client ◄── RESP (or ERROR) ─────── Server +``` + +**Asynchronous (`->`)** + +``` +Client ─── CMD ──────────────────► Server +Client ◄── CMD_RESP (or ERROR) ─── Server ← handshake +Client ◄── ASYNC_RESP ──────────── Server ← later, async result +``` + +**No-reply (`->|`)** + +``` +Client ─── CMD ──────────────────► Server +Client ◄── CMD_RESP (or ERROR) ─── Server ← handshake; no further reply +``` + +**Subscribe / unsubscribe** + +``` +Client ─── CMD () ─────► Server +Client ◄── CMD_RESP (or ERROR) ─── Server + … (whenever the CSM module emits the status) … +Client ◄── STATUS ──────────────── Server +Client ─── CMD () ───► Server +Client ◄── CMD_RESP ─────────────── Server +``` + +--- + +## API Reference + +### `TcpRouterClient` (sync) + +#### Connection + +| Method | Description | +|---|---| +| `connect(host, port, timeout=5.0)` | Connect to the server; raises `ConnectionError` on failure. | +| `disconnect()` | Close the connection; safe to call even when not connected. | +| `connected` | `True` when the transport is connected. | +| `wait_for_server(host, port, timeout=30, retry_interval=0.5)` | Poll until the server is reachable; returns `True`/`False`. | + +#### Commands + +| Method | Description | +|---|---| +| `send_and_wait(command, timeout=5.0) → CommandResponse` | Synchronous command (`-@`); blocks until `RESP` arrives. | +| `post(command, timeout=5.0)` | Async command (`->`); waits for `CMD_RESP` handshake. | +| `post_no_reply(command, timeout=5.0)` | No-reply command (`->|`); waits for `CMD_RESP` handshake. | +| `ping(timeout=2.0) → (bool, float)` | Round-trip latency check. | + +#### Router management helpers + +| Method | Description | +|---|---| +| `list_modules(timeout=5.0) → str` | `List` command result. | +| `list_api(module, timeout=5.0) → str` | `List API ` result. | +| `list_states(module, timeout=5.0) → str` | `List State ` result. | +| `help(module, timeout=5.0) → str` | `Help ` result. | + +#### Subscriptions + +| Method | Description | +|---|---| +| `subscribe_status(status_name, module_name, callback=None, timeout=5.0)` | Subscribe; optional callback invoked per notification. | +| `unsubscribe_status(status_name, module_name, timeout=5.0)` | Unsubscribe. | +| `register_async_callback(original_command, callback)` | Register a callback for `ASYNC_RESP` packets. | +| `unregister_async_callback(original_command)` | Remove an async callback. | + +#### Queues (polling alternative to callbacks) + +| Attribute | Type | Description | +|---|---|---| +| `status_queue` | `Queue[StatusNotification]` | Receive status/interrupt broadcasts by polling. | +| `async_response_queue` | `Queue[AsyncResponse]` | Receive async responses by polling. | + +--- + +### `AsyncTcpRouterClient` (asyncio) + +All methods are `async def` coroutines; use `await` to call them. + +#### Connection + +| Method | Description | +|---|---| +| `await connect(host, port, timeout=5.0)` | Open a TCP connection; raises `ConnectionError` on failure. | +| `await disconnect()` | Close the connection; safe to call when not connected. | +| `connected` | `True` when the writer is open. | +| `await wait_for_server(host, port, timeout=30, retry_interval=0.5)` | Poll until the server is reachable. | + +#### Commands + +| Method | Description | +|---|---| +| `await send_and_wait(command, timeout=5.0) → CommandResponse` | Synchronous command (`-@`). | +| `await post(command, timeout=5.0)` | Async command (`->`). | +| `await post_no_reply(command, timeout=5.0)` | No-reply command (`->|`). | +| `await ping(timeout=2.0) → (bool, float)` | Round-trip latency check. | + +#### Router management helpers + +Same as sync client but all methods are `async def`. + +#### Subscriptions + +| Method | Description | +|---|---| +| `await subscribe_status(status_name, module_name, callback=None, timeout=5.0)` | Subscribe; callback may be sync or `async def`. | +| `await unsubscribe_status(status_name, module_name, timeout=5.0)` | Unsubscribe. | +| `register_async_callback(original_command, callback)` | Register callback for `ASYNC_RESP`; may be sync or `async def`. | +| `unregister_async_callback(original_command)` | Remove callback. | + +#### Queues + +| Attribute | Type | Description | +|---|---|---| +| `status_queue` | `asyncio.Queue[StatusNotification]` | Available after `connect()`; poll with `await queue.get()`. | +| `async_response_queue` | `asyncio.Queue[AsyncResponse]` | Available after `connect()`. | + +--- + +### Data models + +#### `CommandResponse` +- `.raw: bytes` – raw server payload +- `.text: str` – UTF-8 decoded text + +#### `AsyncResponse` +- `.raw: bytes`, `.text: str` +- `.original_command: str` – the command echoed by the server + +#### `StatusNotification` +- `.raw: bytes` +- `.packet_type: PacketType` – `STATUS` or `INTERRUPT` +- `.status_name: str` – e.g. `"Status"` +- `.data: str` – the broadcasted value +- `.module_name: str` – the sending CSM module + +### Exceptions + +| Exception | Raised when | +|---|---| +| `TcpRouterError` | Base class for all SDK exceptions | +| `ConnectionError` | TCP connection fails or is lost | +| `TimeoutError` | No response within the timeout window | +| `ProtocolError` | Invalid or unexpected wire frame | +| `ServerError` | Server returns an `ERROR` packet; `.code` and `.message` attributes available | + +--- + +## Examples + +See the [`examples/`](examples/) directory: + +- [`basic_usage.py`](examples/basic_usage.py) – sync client: connect, ping, list modules, send commands. +- [`subscribe_status.py`](examples/subscribe_status.py) – sync client: real-time status subscription with callback. +- [`async_usage.py`](examples/async_usage.py) – asyncio client: all features using `async def` / `await`. + +--- + +## Migration from the script SDK + +The previous single-file SDK (`SDK/PythonClientAPI/tcp_router_client.py`) is +still available but is not pip-installable and uses a different packet-type +numbering (aligned with protocol v1-draft rather than the published v0 spec). + +| Old method | New method | Notes | +|---|---|---| +| `connect()` | `connect()` | Returns `None`; raises `ConnectionError` instead of returning `False` | +| `disconnect()` | `disconnect()` | Unchanged | +| `send_message_and_wait_for_reply(msg)` | `send_and_wait(cmd)` | Returns `CommandResponse`; raises on error | +| `post_message(msg)` | `post(cmd)` | Waits for `CMD_RESP` handshake | +| `post_no_rep_message(msg)` | `post_no_reply(cmd)` | Waits for `CMD_RESP` handshake | +| `ping()` | `ping()` | Same signature | +| `register_status_change(s, m, cb)` | `subscribe_status(s, m, callback=cb)` | Raises on error instead of returning `False` | +| `unregister_status_change(s, m)` | `unsubscribe_status(s, m)` | Raises on error | +| `wait_for_server(h, p, t)` | `wait_for_server(h, p, timeout=t)` | Keyword arg | +| `obtain()` / `release()` | Use context manager `with TcpRouterClient() as c:` | – | + +--- + +## Development + +```bash +# Install dev dependencies +pip install -e ".[dev]" +# or +pip install hatchling pytest pytest-asyncio ruff + +# Run tests (sync + async) +pytest + +# Lint +ruff check src/ tests/ +``` + +--- + +## License + +[MIT](LICENSE) — © NEVSTOP-LAB + diff --git a/SDK/python-package/README.zh-cn.md b/SDK/python-package/README.zh-cn.md new file mode 100644 index 0000000..49f3ef5 --- /dev/null +++ b/SDK/python-package/README.zh-cn.md @@ -0,0 +1,308 @@ +# csm-tcp-router-client + +[![PyPI](https://img.shields.io/pypi/v/csm-tcp-router-client)](https://pypi.org/project/csm-tcp-router-client/) +[![Python](https://img.shields.io/pypi/pyversions/csm-tcp-router-client)](https://pypi.org/project/csm-tcp-router-client/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/Python_SDK.yml/badge.svg)](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/Python_SDK.yml) + +[CSM-TCP-Router](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App) LabVIEW 服务器的 Python 客户端 SDK。 + +CSM-TCP-Router 将 LabVIEW [可通信状态机(CSM)](https://github.com/NEVSTOP-LAB/Communicable-State-Machine) 应用通过 TCP 对外暴露,使任意 TCP 客户端(Python 脚本、测试框架、CI 流水线等)无需修改 LabVIEW 代码即可发送指令并接收响应。 + +> 📖 [English README](README.md) + +--- + +## 安装 + +```bash +pip install csm-tcp-router-client +``` + +要求 Python 3.8 或更高版本,无第三方依赖——仅依赖 Python 标准库。 + +--- + +## 快速入门 + +### 同步客户端 + +```python +from csm_tcp_router import TcpRouterClient + +with TcpRouterClient() as client: + client.connect("localhost", 30007) + + # 获取已加载的 CSM 模块列表 + print(client.list_modules()) + + # 发送同步指令并等待响应 + resp = client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + + # Ping 服务器 + ok, elapsed_s = client.ping() + print(f"Ping: {ok}, 延迟={elapsed_s*1000:.1f} ms") +``` + +### 异步客户端(asyncio) + +```python +import asyncio +from csm_tcp_router import AsyncTcpRouterClient + +async def main(): + async with AsyncTcpRouterClient() as client: + await client.connect("localhost", 30007) + print(await client.list_modules()) + resp = await client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + +asyncio.run(main()) +``` + +--- + +## 功能特性 + +- **同步指令**(`-@`)——`send_and_wait()` 阻塞直到服务器返回响应。 +- **异步指令**(`->`)——`post()` 等待 `cmd-resp` 握手包;最终响应通过回调或队列传递。 +- **无响应指令**(`->|`)——`post_no_reply()` 等待 `cmd-resp` 握手包;不再有后续响应。 +- **状态订阅**——`subscribe_status()` / `unsubscribe_status()`,支持可选回调或轮询队列。 +- **路由器管理助手**——`list_modules()`、`list_api()`、`list_states()`、`help()`。 +- **连接工具**——`wait_for_server()` 在应用启动期间轮询等待服务器就绪。 +- **线程安全的同步客户端**——`TcpRouterClient`:所有方法均可从多个线程并发调用。 +- **异步客户端**——`AsyncTcpRouterClient`:完整的 `async def` API,支持同步和异步回调。 +- **零第三方依赖**——纯 Python 标准库实现。 +- **上下文管理器**支持(`with TcpRouterClient()` / `async with AsyncTcpRouterClient()`)。 + +--- + +## 通信协议 + +本 SDK 实现了 CSM-TCP-Router **v0 协议**。 + +``` +| 数据长度 (4B) | 版本 (1B) | TYPE (1B) | FLAG1 (1B) | FLAG2 (1B) | 文本数据 | +╰────────────────────────── 头部 (8B) ────────────────────────────────╯ +``` + +| TYPE 字节 | 名称 | 方向 | 描述 | +|-----------|---------------|----------------|---------------------------------------------------| +| `0x00` | `INFO` | 服务器 → 客户端 | 欢迎 / 再见等信息报文 | +| `0x01` | `ERROR` | 服务器 → 客户端 | CSM 错误:`[Error: ] ` | +| `0x02` | `CMD` | 客户端 → 服务器 | 指令字符串 | +| `0x03` | `CMD_RESP` | 服务器 → 客户端 | 异步 / 订阅指令的握手确认包 | +| `0x04` | `RESP` | 服务器 → 客户端 | 同步响应负载 | +| `0x05` | `ASYNC_RESP` | 服务器 → 客户端 | 异步响应:`<数据> <- <原始指令>` | +| `0x06` | `STATUS` | 服务器 → 客户端 | 状态广播:`<名称> >> <数据> <- <模块>` | +| `0x07` | `INTERRUPT` | 服务器 → 客户端 | 中断广播(格式与 STATUS 相同) | + +### 通信流程 + +**同步(`-@`)** + +``` +客户端 ─── CMD ──────────────────► 服务器 +客户端 ◄── RESP(或 ERROR)─────── 服务器 +``` + +**异步(`->`)** + +``` +客户端 ─── CMD ──────────────────► 服务器 +客户端 ◄── CMD_RESP(或 ERROR)─── 服务器 ← 握手 +客户端 ◄── ASYNC_RESP ──────────── 服务器 ← 稍后,异步结果 +``` + +**无响应(`->|`)** + +``` +客户端 ─── CMD ──────────────────► 服务器 +客户端 ◄── CMD_RESP(或 ERROR)─── 服务器 ← 握手;无后续响应 +``` + +**订阅 / 取消订阅** + +``` +客户端 ─── CMD () ─────► 服务器 +客户端 ◄── CMD_RESP(或 ERROR)─── 服务器 + …(CSM 模块每次发出状态时)… +客户端 ◄── STATUS ──────────────── 服务器 +客户端 ─── CMD () ───► 服务器 +客户端 ◄── CMD_RESP ─────────────── 服务器 +``` + +--- + +## API 参考 + +### `TcpRouterClient`(同步) + +#### 连接管理 + +| 方法 | 描述 | +|---|---| +| `connect(host, port, timeout=5.0)` | 连接服务器;失败时抛出 `ConnectionError`。 | +| `disconnect()` | 关闭连接;即使未连接也可安全调用。 | +| `connected` | 已连接时为 `True`。 | +| `wait_for_server(host, port, timeout=30, retry_interval=0.5)` | 轮询直到服务器可达;返回 `True`/`False`。 | + +#### 指令方法 + +| 方法 | 描述 | +|---|---| +| `send_and_wait(command, timeout=5.0) → CommandResponse` | 同步指令(`-@`);阻塞直到 `RESP` 到达。 | +| `post(command, timeout=5.0)` | 异步指令(`->`);等待 `CMD_RESP` 握手。 | +| `post_no_reply(command, timeout=5.0)` | 无响应指令(`->|`);等待 `CMD_RESP` 握手。 | +| `ping(timeout=2.0) → (bool, float)` | 往返延迟检测。 | + +#### 路由器管理助手 + +| 方法 | 描述 | +|---|---| +| `list_modules(timeout=5.0) → str` | 执行 `List` 指令,返回模块列表。 | +| `list_api(module, timeout=5.0) → str` | 执行 `List API ` 指令。 | +| `list_states(module, timeout=5.0) → str` | 执行 `List State ` 指令。 | +| `help(module, timeout=5.0) → str` | 执行 `Help ` 指令。 | + +#### 订阅管理 + +| 方法 | 描述 | +|---|---| +| `subscribe_status(status_name, module_name, callback=None, timeout=5.0)` | 订阅;可选回调,每次收到通知时调用。 | +| `unsubscribe_status(status_name, module_name, timeout=5.0)` | 取消订阅。 | +| `register_async_callback(original_command, callback)` | 注册 `ASYNC_RESP` 回调。 | +| `unregister_async_callback(original_command)` | 移除异步响应回调。 | + +#### 轮询队列(回调的替代方案) + +| 属性 | 类型 | 描述 | +|---|---|---| +| `status_queue` | `Queue[StatusNotification]` | 通过轮询接收状态/中断广播。 | +| `async_response_queue` | `Queue[AsyncResponse]` | 通过轮询接收异步响应。 | + +--- + +### `AsyncTcpRouterClient`(asyncio) + +所有方法均为 `async def` 协程,需使用 `await` 调用。 + +#### 连接管理 + +| 方法 | 描述 | +|---|---| +| `await connect(host, port, timeout=5.0)` | 建立 TCP 连接;失败时抛出 `ConnectionError`。 | +| `await disconnect()` | 关闭连接;未连接时可安全调用。 | +| `connected` | 写入端开启时为 `True`。 | +| `await wait_for_server(host, port, timeout=30, retry_interval=0.5)` | 轮询直到服务器可达。 | + +#### 指令方法 + +| 方法 | 描述 | +|---|---| +| `await send_and_wait(command, timeout=5.0) → CommandResponse` | 同步指令(`-@`)。 | +| `await post(command, timeout=5.0)` | 异步指令(`->`)。 | +| `await post_no_reply(command, timeout=5.0)` | 无响应指令(`->|`)。 | +| `await ping(timeout=2.0) → (bool, float)` | 往返延迟检测。 | + +#### 路由器管理助手 + +与同步客户端相同,但所有方法均为 `async def`。 + +#### 订阅管理 + +| 方法 | 描述 | +|---|---| +| `await subscribe_status(status_name, module_name, callback=None, timeout=5.0)` | 订阅;回调可以是普通函数或 `async def` 协程。 | +| `await unsubscribe_status(status_name, module_name, timeout=5.0)` | 取消订阅。 | +| `register_async_callback(original_command, callback)` | 注册 `ASYNC_RESP` 回调;可以是普通函数或 `async def`。 | +| `unregister_async_callback(original_command)` | 移除回调。 | + +#### 轮询队列 + +| 属性 | 类型 | 描述 | +|---|---|---| +| `status_queue` | `asyncio.Queue[StatusNotification]` | `connect()` 后可用;使用 `await queue.get()` 轮询。 | +| `async_response_queue` | `asyncio.Queue[AsyncResponse]` | `connect()` 后可用。 | + +--- + +### 数据模型 + +#### `CommandResponse` +- `.raw: bytes` – 原始服务器负载 +- `.text: str` – UTF-8 解码后的文本 + +#### `AsyncResponse` +- `.raw: bytes`, `.text: str` +- `.original_command: str` – 服务器回显的原始指令 + +#### `StatusNotification` +- `.raw: bytes` +- `.packet_type: PacketType` – `STATUS` 或 `INTERRUPT` +- `.status_name: str` – 例如 `"Status"` +- `.data: str` – 广播的值 +- `.module_name: str` – 发送该状态的 CSM 模块名称 + +### 异常 + +| 异常 | 触发场景 | +|---|---| +| `TcpRouterError` | 所有 SDK 异常的基类 | +| `ConnectionError` | TCP 连接失败或断开 | +| `TimeoutError` | 在超时时间内未收到响应 | +| `ProtocolError` | 无效或意外的数据帧 | +| `ServerError` | 服务器返回 `ERROR` 包;可通过 `.code` 和 `.message` 属性获取错误详情 | + +--- + +## 示例 + +详见 [`examples/`](examples/) 目录: + +- [`basic_usage.py`](examples/basic_usage.py) – 同步客户端:连接、Ping、列出模块、发送指令。 +- [`subscribe_status.py`](examples/subscribe_status.py) – 同步客户端:通过回调实时接收状态订阅。 +- [`async_usage.py`](examples/async_usage.py) – 异步客户端:使用 `async def` / `await` 实现所有功能。 + +--- + +## 从旧版脚本 SDK 迁移 + +原有的单文件 SDK(`SDK/PythonClientAPI/tcp_router_client.py`)仍然可用,但无法通过 pip 安装,且其数据包类型编号采用的是 v1 草稿协议,而非已发布的 v0 规范。 + +| 旧方法 | 新方法 | 备注 | +|---|---|---| +| `connect()` | `connect()` | 返回 `None`;失败时抛出 `ConnectionError` 而非返回 `False` | +| `disconnect()` | `disconnect()` | 无变化 | +| `send_message_and_wait_for_reply(msg)` | `send_and_wait(cmd)` | 返回 `CommandResponse`;出错时抛出异常 | +| `post_message(msg)` | `post(cmd)` | 等待 `CMD_RESP` 握手 | +| `post_no_rep_message(msg)` | `post_no_reply(cmd)` | 等待 `CMD_RESP` 握手 | +| `ping()` | `ping()` | 签名不变 | +| `register_status_change(s, m, cb)` | `subscribe_status(s, m, callback=cb)` | 失败时抛出异常而非返回 `False` | +| `unregister_status_change(s, m)` | `unsubscribe_status(s, m)` | 失败时抛出异常 | +| `wait_for_server(h, p, t)` | `wait_for_server(h, p, timeout=t)` | 改为关键字参数 | +| `obtain()` / `release()` | 使用上下文管理器 `with TcpRouterClient() as c:` | — | + +--- + +## 开发 + +```bash +# 安装开发依赖 +pip install -e ".[dev]" +# 或 +pip install hatchling pytest pytest-asyncio ruff + +# 运行测试(同步 + 异步) +pytest + +# 代码检查 +ruff check src/ tests/ +``` + +--- + +## 许可证 + +[MIT](LICENSE) — © NEVSTOP-LAB diff --git a/SDK/python-package/examples/async_usage.py b/SDK/python-package/examples/async_usage.py new file mode 100644 index 0000000..226023c --- /dev/null +++ b/SDK/python-package/examples/async_usage.py @@ -0,0 +1,97 @@ +"""Async quickstart example for csm-tcp-router-client. + +Run against a live CSM-TCP-Router server:: + + pip install csm-tcp-router-client + python examples/async_usage.py +""" + +import asyncio + +from csm_tcp_router import AsyncTcpRouterClient +from csm_tcp_router.models import StatusNotification + + +async def on_status(notif: StatusNotification) -> None: + """Async callback – invoked each time the subscribed status changes.""" + print(f"[async callback] {notif.module_name}/{notif.status_name} = {notif.data!r}") + + +async def main() -> None: + # --------------------------------------------------------------------------- + # Basic connection + # --------------------------------------------------------------------------- + async with AsyncTcpRouterClient() as client: + # Optional: wait until the server is available (e.g. during app startup) + print("Waiting for server …", end=" ", flush=True) + ok = await client.wait_for_server("localhost", 30007, timeout=15.0) + if not ok: + print("timed out") + return + print("ready") + + await client.connect("localhost", 30007) + print(f"Connected: {client.connected}") + + # --------------------------------------------------------------------------- + # Router management helpers + # --------------------------------------------------------------------------- + modules = await client.list_modules() + print(f"\nLoaded modules:\n{modules}") + + # Ping / latency check + ok, elapsed_s = await client.ping() + print(f"\nPing: {ok}, latency = {elapsed_s * 1000:.1f} ms") + + # --------------------------------------------------------------------------- + # Synchronous command (client blocks until RESP arrives) + # --------------------------------------------------------------------------- + resp = await client.send_and_wait("API: Read -@ DAQmx", timeout=5.0) + print(f"\nsend_and_wait → {resp.text!r}") + + # --------------------------------------------------------------------------- + # Asynchronous command (await the cmd-resp handshake only) + # --------------------------------------------------------------------------- + await client.post("API: Start Sampling -> DAQmx", timeout=5.0) + print("post → handshake received (async result delivered via queue)") + + # Collect the eventual async response from the queue + if client.async_response_queue is not None: + try: + ar = await asyncio.wait_for(client.async_response_queue.get(), timeout=5.0) + print(f"async_response_queue → {ar.text!r}") + except asyncio.TimeoutError: + print("async_response_queue → no result yet (server may not have replied)") + + # --------------------------------------------------------------------------- + # No-reply command + # --------------------------------------------------------------------------- + await client.post_no_reply("API: Reset ->| DAQmx", timeout=5.0) + print("post_no_reply → handshake received") + + # --------------------------------------------------------------------------- + # Status subscription with an async callback + # --------------------------------------------------------------------------- + await client.subscribe_status("Status", "DAQmx", callback=on_status, timeout=5.0) + print("\nSubscribed to Status@DAQmx — waiting 3 s for notifications …") + await asyncio.sleep(3.0) + + # Also drain any notifications that arrived via the polling queue + if client.status_queue is not None: + count = 0 + while not client.status_queue.empty(): + notif = client.status_queue.get_nowait() + print( + f" [queue poll] {notif.module_name}/{notif.status_name} = {notif.data!r}" + ) + count += 1 + print(f" {count} notification(s) retrieved from queue") + + await client.unsubscribe_status("Status", "DAQmx", timeout=5.0) + print("Unsubscribed") + + print("\nDisconnected.") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/SDK/python-package/examples/basic_usage.py b/SDK/python-package/examples/basic_usage.py new file mode 100644 index 0000000..3e07d5a --- /dev/null +++ b/SDK/python-package/examples/basic_usage.py @@ -0,0 +1,100 @@ +"""Basic usage example for csm-tcp-router-client. + +Prerequisites +------------- +A running CSM-TCP-Router server (LabVIEW app). The reference app defaults +to port 30007. Start it from ``CSM-TCP-Router(Server).vi``. + +Install the SDK:: + + pip install csm-tcp-router-client + +Run this example:: + + python basic_usage.py +""" + + +from csm_tcp_router import TcpRouterClient +from csm_tcp_router.exceptions import ConnectionError + +HOST = "localhost" +PORT = 30007 + + +def main() -> None: + # ----------------------------------------------------------------------- + # 1. Wait until the server is ready (optional – useful during app startup) + # ----------------------------------------------------------------------- + print("Waiting for server …", end=" ", flush=True) + client = TcpRouterClient() + ok = client.wait_for_server(HOST, PORT, timeout=30, retry_interval=0.5) + if not ok: + print("TIMEOUT – server did not start within 30 s.") + return + print("ready.") + + # ----------------------------------------------------------------------- + # 2. Connect (use as a context manager so disconnect is always called) + # ----------------------------------------------------------------------- + with TcpRouterClient() as client: + try: + client.connect(HOST, PORT) + except ConnectionError as exc: + print(f"Connection failed: {exc}") + return + + print(f"Connected to {HOST}:{PORT}") + + # ------------------------------------------------------------------- + # 3. Ping – verify round-trip latency + # ------------------------------------------------------------------- + ok, ms = client.ping() + if ok: + print(f"Ping OK latency={ms * 1000:.1f} ms") + else: + print("Ping failed.") + + # ------------------------------------------------------------------- + # 4. List CSM modules loaded on the server + # ------------------------------------------------------------------- + modules = client.list_modules() + print(f"\nLoaded modules:\n{modules}") + + # ------------------------------------------------------------------- + # 5. List the API for the first module (if any) + # ------------------------------------------------------------------- + first_module = modules.strip().splitlines()[0] if modules.strip() else None + if first_module: + api_text = client.list_api(first_module) + print(f"\nAPI for '{first_module}':\n{api_text}") + + # ------------------------------------------------------------------- + # 6. Send a synchronous command (replace with a real API of yours) + # ------------------------------------------------------------------- + # resp = client.send_and_wait("API: Read -@ DAQmx") + # print(f"\nSync response: {resp.text}") + + # ------------------------------------------------------------------- + # 7. Send an asynchronous command (server returns cmd-resp handshake) + # ------------------------------------------------------------------- + # client.post("API: Start Sampling -> DAQmx") + # print("Async command sent – waiting for async-resp …") + # time.sleep(1) + # if not client.async_response_queue.empty(): + # ar = client.async_response_queue.get_nowait() + # print(f"Async-resp: {ar.text}") + + # ------------------------------------------------------------------- + # 8. Send a no-reply command + # ------------------------------------------------------------------- + # client.post_no_reply("API: Reset ->| DAQmx") + # print("No-reply command sent.") + + print("\nDone.") + + print("Disconnected.") + + +if __name__ == "__main__": + main() diff --git a/SDK/python-package/examples/subscribe_status.py b/SDK/python-package/examples/subscribe_status.py new file mode 100644 index 0000000..0442467 --- /dev/null +++ b/SDK/python-package/examples/subscribe_status.py @@ -0,0 +1,86 @@ +"""Status subscription example for csm-tcp-router-client. + +Prerequisites +------------- +A running CSM-TCP-Router server that has a CSM module publishing a status. +The reference app (``CSM-TCP-Router(Server).vi``) exposes an ``AI`` module +that continuously broadcasts a ``Status`` status. + +Install the SDK:: + + pip install csm-tcp-router-client + +Run this example:: + + python subscribe_status.py +""" + +import signal +import threading +import time + +from csm_tcp_router import StatusNotification, TcpRouterClient +from csm_tcp_router.exceptions import ConnectionError, ServerError + +HOST = "localhost" +PORT = 30007 + +# Module and status name to subscribe to (adjust to match your server) +MODULE_NAME = "AI" +STATUS_NAME = "Status" + +# Global stop flag +_stop = threading.Event() + + +def on_status(notification: StatusNotification) -> None: + """Callback invoked on every status broadcast from the server.""" + print( + f"[{time.strftime('%H:%M:%S')}] " + f"{notification.status_name} @ {notification.module_name} " + f"→ {notification.data}" + ) + + +def main() -> None: + # Allow Ctrl-C to exit cleanly + signal.signal(signal.SIGINT, lambda *_: _stop.set()) + + with TcpRouterClient() as client: + try: + client.connect(HOST, PORT) + except ConnectionError as exc: + print(f"Connection failed: {exc}") + return + + print(f"Connected to {HOST}:{PORT}") + + # Subscribe to status broadcasts + try: + client.subscribe_status(STATUS_NAME, MODULE_NAME, callback=on_status) + print( + f"Subscribed to '{STATUS_NAME}' from module '{MODULE_NAME}'. " + "Press Ctrl-C to exit.\n" + ) + except ServerError as exc: + print(f"Subscription failed: {exc}") + return + + # Keep running until Ctrl-C + while not _stop.is_set(): + # You can also poll client.status_queue here if you prefer + # notification = client.status_queue.get(timeout=1.0) + time.sleep(0.1) + + # Unsubscribe cleanly before disconnecting + try: + client.unsubscribe_status(STATUS_NAME, MODULE_NAME) + print("\nUnsubscribed.") + except Exception: + pass + + print("Disconnected.") + + +if __name__ == "__main__": + main() diff --git a/SDK/python-package/pyproject.toml b/SDK/python-package/pyproject.toml new file mode 100644 index 0000000..3b77791 --- /dev/null +++ b/SDK/python-package/pyproject.toml @@ -0,0 +1,85 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "csm-tcp-router-client" +version = "0.2.0" +description = "Python client SDK for the CSM-TCP-Router LabVIEW server" +readme = "README.md" +license = { text = "MIT" } +requires-python = ">=3.8" +authors = [{ name = "NEVSTOP-LAB" }] +keywords = [ + "csm", + "labview", + "tcp", + "router", + "client", + "sdk", + "daq", + "communicable-state-machine", + "asyncio", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Framework :: AsyncIO", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Networking", +] + +[project.urls] +Homepage = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App" +Repository = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App" +Issues = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/issues" +Documentation = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/blob/main/SDK/python-package/README.md" +Changelog = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/blob/main/SDK/python-package/CHANGELOG.md" + +[tool.hatch.build.targets.wheel] +packages = ["src/csm_tcp_router"] + +# --------------------------------------------------------------------------- +# Testing +# --------------------------------------------------------------------------- +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-v --tb=short" +asyncio_mode = "auto" + +# --------------------------------------------------------------------------- +# Linting (ruff) +# --------------------------------------------------------------------------- +[tool.ruff] +target-version = "py38" +line-length = 100 + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "UP", "B", "C4", "PIE", "SIM", "RUF"] +ignore = [ + "E501", # line length handled by formatter + "B008", # do not perform function calls in argument defaults + "UP006", # use `type` instead of `Type` — Python 3.8 compat + "UP007", # use `X | Y` — Python 3.9 compat + "UP035", # deprecated typing imports — Python 3.8 compat + "UP045", # use `X | None` — Python 3.10 compat + "SIM105", # contextlib.suppress — prefer explicit try/except for clarity + "RUF001", # ambiguous unicode in strings — intentional em-dash usage + "RUF002", # ambiguous unicode in docstrings — intentional em-dash usage + "RUF003", # ambiguous unicode in comments — intentional em-dash usage + "RUF022", # __all__ not sorted — grouped by category intentionally +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["S101"] # allow assert in tests +"examples/*" = ["T201"] # allow print in examples diff --git a/SDK/python-package/src/csm_tcp_router/__init__.py b/SDK/python-package/src/csm_tcp_router/__init__.py new file mode 100644 index 0000000..1a8d875 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/__init__.py @@ -0,0 +1,58 @@ +"""csm-tcp-router-client – Python client SDK for the CSM-TCP-Router server. + +Sync usage:: + + from csm_tcp_router import TcpRouterClient + + with TcpRouterClient() as client: + client.connect("localhost", 30007) + print(client.list_modules()) + +Async usage:: + + import asyncio + from csm_tcp_router import AsyncTcpRouterClient + + async def main(): + async with AsyncTcpRouterClient() as client: + await client.connect("localhost", 30007) + print(await client.list_modules()) + + asyncio.run(main()) +""" + +from .async_client import AsyncTcpRouterClient +from .client import TcpRouterClient +from .exceptions import ( + ConnectionError, + ProtocolError, + ServerError, + TcpRouterError, + TimeoutError, +) +from .models import ( + AsyncResponse, + CommandResponse, + PacketType, + StatusNotification, +) + +__version__ = "0.2.0" + +__all__ = [ + "TcpRouterClient", + "AsyncTcpRouterClient", + # Exceptions + "TcpRouterError", + "ConnectionError", + "TimeoutError", + "ProtocolError", + "ServerError", + # Models + "PacketType", + "CommandResponse", + "AsyncResponse", + "StatusNotification", + # Version + "__version__", +] diff --git a/SDK/python-package/src/csm_tcp_router/_errors.py b/SDK/python-package/src/csm_tcp_router/_errors.py new file mode 100644 index 0000000..41c6017 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/_errors.py @@ -0,0 +1,26 @@ +"""Shared server-error parsing helper. + +Internal module – nothing is re-exported from the package. +""" + +from __future__ import annotations + +from .exceptions import ServerError +from .models import Packet + +__all__: list = [] # internal; nothing re-exported + + +def _parse_server_error(packet: Packet) -> ServerError: + """Extract code and message from a CSM Error format ``[Error: ] ``.""" + text = packet.data.decode("utf-8", errors="replace").strip() + code = "" + msg = text + if text.startswith("[Error:"): + try: + end_idx = text.index("]") + code = text[7:end_idx].strip() + msg = text[end_idx + 1:].strip() + except ValueError: + pass + return ServerError(msg, code) diff --git a/SDK/python-package/src/csm_tcp_router/_protocol.py b/SDK/python-package/src/csm_tcp_router/_protocol.py new file mode 100644 index 0000000..4834ff0 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/_protocol.py @@ -0,0 +1,91 @@ +"""Internal protocol v0 codec. + +Wire format (8-byte header, big-endian):: + + | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | + ╰────────────────────────── Header (8B) ──────────────────────────╯ + +followed by exactly ``Data Length`` bytes of payload. + +This module is internal; nothing is re-exported from the package. +""" + +from __future__ import annotations + +import struct +from typing import Tuple + +from .exceptions import ProtocolError +from .models import Packet, PacketType + +__all__: list = [] # internal; nothing re-exported + +# Header layout: big-endian uint32 data_len + 4 x uint8 (version, type, flag1, flag2) +_HEADER_FORMAT = "!IBBBB" + +#: Number of bytes in the fixed packet header. +HEADER_SIZE: int = struct.calcsize(_HEADER_FORMAT) # == 8 + +#: Protocol version byte sent in every outgoing packet. +PROTOCOL_VERSION: int = 0x01 + + +def encode_packet( + data: bytes, + packet_type: PacketType, + flag1: int = 0, + flag2: int = 0, +) -> bytes: + """Encode *data* into a complete wire-format packet (header + body). + + :param data: Raw payload bytes. + :param packet_type: :class:`~csm_tcp_router.models.PacketType` for the header. + :param flag1: FLAG1 byte (currently unused; defaults to 0). + :param flag2: FLAG2 byte (currently unused; defaults to 0). + :returns: Concatenated header + payload bytes ready for ``sendall()``. + """ + header = struct.pack( + _HEADER_FORMAT, + len(data), + PROTOCOL_VERSION, + packet_type.value, + flag1, + flag2, + ) + return header + data + + +def decode_header(header_bytes: bytes) -> Tuple[int, int, int, int, int]: + """Decode an 8-byte header into its constituent fields. + + :returns: ``(data_len, version, type_byte, flag1, flag2)`` + :raises ProtocolError: if *header_bytes* is not exactly :data:`HEADER_SIZE` bytes. + """ + if len(header_bytes) != HEADER_SIZE: + raise ProtocolError( + f"Expected {HEADER_SIZE}-byte header, got {len(header_bytes)} bytes." + ) + return struct.unpack(_HEADER_FORMAT, header_bytes) # type: ignore[return-value] + + +def parse_packet(header_bytes: bytes, body: bytes) -> Packet: + """Build a :class:`~csm_tcp_router.models.Packet` from raw header + body. + + Unknown packet type bytes are mapped to :attr:`PacketType.INFO` for + forward compatibility (the server may introduce new types in future + protocol revisions). + + :raises ProtocolError: on header size mismatch or body length mismatch. + """ + data_len, version, type_byte, flag1, flag2 = decode_header(header_bytes) + if len(body) != data_len: + raise ProtocolError( + f"Payload length mismatch: header says {data_len} bytes, " + f"got {len(body)} bytes." + ) + try: + ptype = PacketType(type_byte) + except ValueError: + # Forward-compatible: treat unknown type as INFO + ptype = PacketType.INFO + return Packet(type=ptype, data=body, version=version, flag1=flag1, flag2=flag2) diff --git a/SDK/python-package/src/csm_tcp_router/_transport.py b/SDK/python-package/src/csm_tcp_router/_transport.py new file mode 100644 index 0000000..2de9938 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/_transport.py @@ -0,0 +1,176 @@ +"""Internal TCP transport layer with a background receive thread. + +This module is internal; nothing is re-exported from the package. +""" + +from __future__ import annotations + +import socket +import struct +import threading +from typing import Callable, Optional + +from ._protocol import HEADER_SIZE, parse_packet +from .exceptions import ConnectionError as RouterConnectionError +from .exceptions import ProtocolError +from .models import Packet + +__all__: list = [] # internal; nothing re-exported + + +class Transport: + """Thread-safe, blocking TCP transport. + + A background daemon thread continuously reads packets from the socket and + dispatches them via *on_packet*. Callers are responsible for keeping + callbacks fast and non-blocking, as they run in the receive thread. + + Lifecycle:: + + t = Transport(on_packet=..., on_disconnect=...) + t.connect("localhost", 30007) + t.send_raw(wire_bytes) + t.disconnect() + """ + + def __init__( + self, + on_packet: Callable[[Packet], None], + on_disconnect: Callable[[], None], + ) -> None: + self._sock: Optional[socket.socket] = None + self._send_lock = threading.Lock() + self._stop_event = threading.Event() + self._recv_thread: Optional[threading.Thread] = None + self._on_packet = on_packet + self._on_disconnect = on_disconnect + + # ------------------------------------------------------------------ + # Public interface + # ------------------------------------------------------------------ + + @property + def connected(self) -> bool: + """``True`` while the socket is open and the stop event has not fired.""" + return self._sock is not None and not self._stop_event.is_set() + + def connect(self, host: str, port: int, timeout: float = 5.0) -> None: + """Open a TCP connection and start the receive thread. + + :param host: Target hostname or IP address. + :param port: Target TCP port. + :param timeout: Connect timeout in seconds. + :raises ConnectionError: if already connected or if the OS refuses. + """ + if self.connected: + raise RouterConnectionError( + "Already connected; call disconnect() first." + ) + sock: Optional[socket.socket] = None + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((host, port)) + sock.settimeout(None) # switch to blocking for the recv loop + except OSError as exc: + if sock is not None: + try: + sock.close() + except OSError: + pass + raise RouterConnectionError( + f"Cannot connect to {host}:{port}: {exc}" + ) from exc + + self._sock = sock + self._stop_event.clear() + self._recv_thread = threading.Thread( + target=self._recv_loop, + daemon=True, + name="csm-tcp-router-recv", + ) + self._recv_thread.start() + + def disconnect(self, join_timeout: float = 2.0) -> None: + """Close the connection and stop the receive thread. + + Safe to call even if not connected. + """ + self._stop_event.set() + if self._sock is not None: + try: + self._sock.shutdown(socket.SHUT_RDWR) + except OSError: + pass + try: + self._sock.close() + except OSError: + pass + self._sock = None + if self._recv_thread is not None and self._recv_thread.is_alive(): + self._recv_thread.join(timeout=join_timeout) + + def send_raw(self, data: bytes) -> None: + """Send *data* atomically. Thread-safe. + + :raises ConnectionError: if not connected or if the send fails. + """ + if not self.connected: + raise RouterConnectionError("Not connected.") + with self._send_lock: + try: + self._sock.sendall(data) # type: ignore[union-attr] + except OSError as exc: + self._stop_event.set() + raise RouterConnectionError(f"Send failed: {exc}") from exc + + # ------------------------------------------------------------------ + # Private helpers + # ------------------------------------------------------------------ + + def _recv_all(self, size: int) -> bytes: + """Read exactly *size* bytes; returns empty bytes on clean EOF or disconnect.""" + buf = bytearray(size) + view = memoryview(buf) + received = 0 + while received < size: + sock = self._sock # capture locally to avoid TOCTOU race with disconnect() + if sock is None: + return b"" + try: + n = sock.recv_into(view[received:], size - received) + except OSError: + return b"" + if n == 0: + return b"" + received += n + return bytes(buf) + + def _recv_loop(self) -> None: + """Background thread: read packets and dispatch via callback.""" + try: + while not self._stop_event.is_set(): + header = self._recv_all(HEADER_SIZE) + if not header: + break + + # Extract data_len from the first 4 bytes without full decode + (data_len,) = struct.unpack("!I", header[:4]) + body = self._recv_all(data_len) + if len(body) != data_len: + break + + try: + packet = parse_packet(header, body) + except ProtocolError: + # Corrupted frame – skip it and keep the loop alive + continue + + self._on_packet(packet) + + except OSError: + pass + finally: + if not self._stop_event.is_set(): + self._stop_event.set() + self._on_disconnect() diff --git a/SDK/python-package/src/csm_tcp_router/async_client.py b/SDK/python-package/src/csm_tcp_router/async_client.py new file mode 100644 index 0000000..de64c00 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/async_client.py @@ -0,0 +1,523 @@ +"""Asyncio-based CSM-TCP-Router client.""" + +from __future__ import annotations + +import asyncio +import inspect +import struct +import time +from typing import Any, Callable, Coroutine, Dict, Optional, Tuple, Union + +from ._errors import _parse_server_error +from ._protocol import HEADER_SIZE, encode_packet, parse_packet +from .exceptions import ConnectionError as RouterConnectionError +from .exceptions import ProtocolError, ServerError +from .exceptions import TimeoutError as RouterTimeoutError +from .models import ( + AsyncResponse, + CommandResponse, + Packet, + PacketType, + StatusNotification, +) + +__all__ = ["AsyncTcpRouterClient"] + +# --------------------------------------------------------------------------- +# Callback type aliases – both plain callables and async coroutines are accepted +# --------------------------------------------------------------------------- + +_SyncStatusCb = Callable[[StatusNotification], None] +_AsyncStatusCb = Callable[[StatusNotification], "Coroutine[Any, Any, None]"] +StatusCallback = Union[_SyncStatusCb, _AsyncStatusCb] + +_SyncAsyncRespCb = Callable[[AsyncResponse], None] +_AsyncAsyncRespCb = Callable[[AsyncResponse], "Coroutine[Any, Any, None]"] +AsyncRespCallback = Union[_SyncAsyncRespCb, _AsyncAsyncRespCb] + +_SubKey = Tuple[str, str] + + +class AsyncTcpRouterClient: + """Asyncio client for a CSM-TCP-Router server. + + Provides the same interface as :class:`~csm_tcp_router.TcpRouterClient` but + as ``async def`` coroutines, suitable for use inside an asyncio event loop. + + **Quickstart**:: + + import asyncio + from csm_tcp_router import AsyncTcpRouterClient + + async def main(): + async with AsyncTcpRouterClient() as client: + await client.connect("localhost", 30007) + print(await client.list_modules()) + resp = await client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + + asyncio.run(main()) + + **Protocol flows** are identical to :class:`~csm_tcp_router.TcpRouterClient`. + + **Callbacks** passed to :meth:`subscribe_status` and + :meth:`register_async_callback` may be either a plain callable *or* an + ``async def`` coroutine — both are supported. + + **Polling queues** (:attr:`async_response_queue`, :attr:`status_queue`) are + created when :meth:`connect` is called and are bound to the running event + loop. Access them only after :meth:`connect` has been awaited. + """ + + def __init__(self) -> None: + self._reader: Optional[asyncio.StreamReader] = None + self._writer: Optional[asyncio.StreamWriter] = None + self._recv_task: Optional[asyncio.Task[None]] = None + + # Asyncio objects created lazily in connect() to bind to the running loop + self._resp_queue: Optional[asyncio.Queue[object]] = None + self._cmd_resp_queue: Optional[asyncio.Queue[object]] = None + self._send_lock: Optional[asyncio.Lock] = None + # Serialisation locks – at most one in-flight RESP / CMD_RESP waiter + self._resp_lock: Optional[asyncio.Lock] = None + self._cmd_resp_lock: Optional[asyncio.Lock] = None + + #: Polling queue for :class:`~csm_tcp_router.models.AsyncResponse` objects + #: received from the server. Available after :meth:`connect` is called. + self.async_response_queue: Optional[asyncio.Queue[AsyncResponse]] = None + + #: Polling queue for :class:`~csm_tcp_router.models.StatusNotification` + #: objects received from the server. Available after :meth:`connect`. + self.status_queue: Optional[asyncio.Queue[StatusNotification]] = None + + # Callback registries – plain dicts (asyncio is single-threaded) + self._status_callbacks: Dict[_SubKey, Optional[StatusCallback]] = {} + self._async_callbacks: Dict[str, AsyncRespCallback] = {} + + # ------------------------------------------------------------------ + # Connection management + # ------------------------------------------------------------------ + + def _init_async_objects(self) -> None: + """(Re)create asyncio objects bound to the current running loop.""" + self._resp_queue = asyncio.Queue() + self._cmd_resp_queue = asyncio.Queue() + self._send_lock = asyncio.Lock() + self._resp_lock = asyncio.Lock() + self._cmd_resp_lock = asyncio.Lock() + self.async_response_queue = asyncio.Queue() + self.status_queue = asyncio.Queue() + + @property + def connected(self) -> bool: + """``True`` while the writer is open and not being closed.""" + return self._writer is not None and not self._writer.is_closing() + + async def connect(self, host: str, port: int, timeout: float = 5.0) -> None: + """Open a TCP connection and start the background receive task. + + :param host: Server hostname or IP address. + :param port: Server TCP port (the reference app defaults to 30007). + :param timeout: Connection timeout in seconds. + :raises ConnectionError: if already connected or the OS refuses. + """ + if self.connected: + raise RouterConnectionError( + "Already connected; call disconnect() first." + ) + self._init_async_objects() + try: + self._reader, self._writer = await asyncio.wait_for( + asyncio.open_connection(host, port), timeout=timeout + ) + except asyncio.TimeoutError: + raise RouterConnectionError( + f"Connection to {host}:{port} timed out after {timeout:.1f}s." + ) from None + except OSError as exc: + raise RouterConnectionError( + f"Cannot connect to {host}:{port}: {exc}" + ) from exc + self._recv_task = asyncio.ensure_future(self._recv_loop()) + + async def disconnect(self) -> None: + """Close the connection and stop the background receive task. + + Safe to call even if not currently connected. Any coroutines currently + blocked inside :meth:`send_and_wait`, :meth:`post`, or similar methods + will receive a :exc:`~csm_tcp_router.exceptions.ConnectionError` + immediately rather than waiting for their timeout to expire. + """ + # Wake blocked waiters *before* cancelling the recv task. + sentinel = RouterConnectionError("Disconnected from server.") + if self._resp_queue is not None: + self._resp_queue.put_nowait(sentinel) + if self._cmd_resp_queue is not None: + self._cmd_resp_queue.put_nowait(sentinel) + # Cancel the recv task first; its finally block notifies pending waiters + if self._recv_task is not None and not self._recv_task.done(): + self._recv_task.cancel() + try: + await self._recv_task + except (asyncio.CancelledError, Exception): + pass + self._recv_task = None + + if self._writer is not None: + try: + self._writer.close() + await self._writer.wait_closed() + except OSError: + pass + self._writer = None + self._reader = None + + async def wait_for_server( + self, + host: str, + port: int, + timeout: float = 30.0, + retry_interval: float = 0.5, + ) -> bool: + """Poll until *host*:*port* accepts a connection or *timeout* elapses. + + :param host: Server hostname or IP address. + :param port: Server TCP port. + :param timeout: Maximum time to wait in seconds. + :param retry_interval: Pause between retries in seconds. + :returns: ``True`` when the server is reachable; ``False`` on timeout. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + _, writer = await asyncio.wait_for( + asyncio.open_connection(host, port), timeout=1.0 + ) + writer.close() + try: + await writer.wait_closed() + except OSError: + pass + return True + except (OSError, asyncio.TimeoutError): + pass + await asyncio.sleep(retry_interval) + return False + + # ------------------------------------------------------------------ + # Core command methods + # ------------------------------------------------------------------ + + async def send_and_wait( + self, command: str, timeout: float = 5.0 + ) -> CommandResponse: + """Send a **synchronous** command and await the response. + + Use the CSM synchronous suffix ``-@`` in *command*:: + + resp = await client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + + :param command: CSM command string. + :param timeout: Seconds to wait for the ``resp`` packet. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no response arrives within *timeout*. + :raises ServerError: if the server returns an error packet. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + assert self._resp_lock is not None + async with self._resp_lock: + await self._send_raw(wire) + return await self._wait_for_resp(timeout) + + async def post(self, command: str, timeout: float = 5.0) -> None: + """Send an **asynchronous** command and await the ``cmd-resp`` handshake. + + Use the CSM async suffix ``->`` in *command*:: + + await client.post("API: Start Sampling -> DAQmx") + + :param command: CSM command string including the ``->`` suffix. + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the command. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + + async def post_no_reply(self, command: str, timeout: float = 5.0) -> None: + """Send an **async no-reply** command and await the ``cmd-resp`` handshake. + + Use the CSM no-reply suffix ``->|`` in *command*:: + + await client.post_no_reply("API: Reset ->| DAQmx") + + :param command: CSM command string including the ``->|`` suffix. + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the command. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + + async def ping(self, timeout: float = 2.0) -> Tuple[bool, float]: + """Send a ``Ping`` command and measure round-trip latency. + + :returns: ``(True, elapsed_seconds)`` on success, + ``(False, 0.0)`` on failure. + """ + try: + t0 = time.monotonic() + await self.send_and_wait("Ping", timeout=timeout) + return True, time.monotonic() - t0 + except (RouterConnectionError, RouterTimeoutError, ServerError): + return False, 0.0 + + # ------------------------------------------------------------------ + # Router management helpers + # ------------------------------------------------------------------ + + async def list_modules(self, timeout: float = 5.0) -> str: + """Return the server's loaded CSM module list as plain text.""" + return (await self.send_and_wait("List", timeout=timeout)).text + + async def list_api(self, module: str, timeout: float = 5.0) -> str: + """Return the API list for *module* as plain text.""" + return (await self.send_and_wait(f"List API {module}", timeout=timeout)).text + + async def list_states(self, module: str, timeout: float = 5.0) -> str: + """Return the CSM state list for *module* as plain text.""" + return (await self.send_and_wait(f"List State {module}", timeout=timeout)).text + + async def help(self, module: str, timeout: float = 5.0) -> str: + """Return the help text for *module* as plain text.""" + return (await self.send_and_wait(f"Help {module}", timeout=timeout)).text + + # ------------------------------------------------------------------ + # Status / interrupt subscriptions + # ------------------------------------------------------------------ + + async def subscribe_status( + self, + status_name: str, + module_name: str, + callback: Optional[StatusCallback] = None, + timeout: float = 5.0, + ) -> None: + """Subscribe to a CSM module's status broadcast. + + Sends ``"@ ->"`` and awaits the + ``cmd-resp`` handshake. Once subscribed, + :class:`~csm_tcp_router.models.StatusNotification` objects will be: + + * delivered to *callback* (if provided – sync or async both accepted), and + * added to :attr:`status_queue`. + + :param status_name: Name of the status (e.g. ``"Status"``). + :param module_name: Name of the CSM module (e.g. ``"AI"``). + :param callback: Optional callable or coroutine invoked per notification. + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the subscription. + """ + # Register the callback before sending to eliminate the race where a + # STATUS packet could arrive before the callback is stored. + self._status_callbacks[(status_name, module_name)] = callback + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + try: + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + except Exception: + self._status_callbacks.pop((status_name, module_name), None) + raise + + async def unsubscribe_status( + self, + status_name: str, + module_name: str, + timeout: float = 5.0, + ) -> None: + """Cancel a status subscription. + + :param status_name: Name of the subscribed status. + :param module_name: Name of the CSM module. + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the request. + """ + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + self._status_callbacks.pop((status_name, module_name), None) + + def register_async_callback( + self, + original_command: str, + callback: AsyncRespCallback, + ) -> None: + """Register a callback for ``async-resp`` packets. + + The callback is matched by *original_command* (the command text + echoed in the ``async-resp`` payload after the `` <- `` separator). + + Callbacks may be either a plain callable or an ``async def`` coroutine. + + :param original_command: The command text echoed in the ``async-resp``. + :param callback: Callable or coroutine receiving an + :class:`~csm_tcp_router.models.AsyncResponse`. + """ + self._async_callbacks[original_command] = callback + + def unregister_async_callback(self, original_command: str) -> None: + """Remove a previously registered async-response callback.""" + self._async_callbacks.pop(original_command, None) + + # ------------------------------------------------------------------ + # Async context-manager support + # ------------------------------------------------------------------ + + async def __aenter__(self) -> AsyncTcpRouterClient: + return self + + async def __aexit__(self, *_args: object) -> None: + await self.disconnect() + + # ------------------------------------------------------------------ + # Internal: send + # ------------------------------------------------------------------ + + async def _send_raw(self, data: bytes) -> None: + if not self.connected: + raise RouterConnectionError("Not connected.") + assert self._writer is not None + assert self._send_lock is not None + async with self._send_lock: + self._writer.write(data) + await self._writer.drain() + + # ------------------------------------------------------------------ + # Internal: receive loop (background task) + # ------------------------------------------------------------------ + + async def _recv_loop(self) -> None: + """Background task: read frames and dispatch them.""" + assert self._reader is not None + try: + while True: + header = await self._reader.readexactly(HEADER_SIZE) + (data_len,) = struct.unpack("!I", header[:4]) + body = ( + await self._reader.readexactly(data_len) if data_len else b"" + ) + try: + packet = parse_packet(header, body) + except ProtocolError: + continue # skip corrupted frame; keep connection alive + await self._dispatch_packet(packet) + except (asyncio.IncompleteReadError, asyncio.CancelledError, OSError): + pass + finally: + self._notify_disconnect() + + async def _dispatch_packet(self, packet: Packet) -> None: + """Route a received packet to the correct queue and/or callback.""" + assert self._resp_queue is not None + assert self._cmd_resp_queue is not None + assert self.async_response_queue is not None + assert self.status_queue is not None + + ptype = packet.type + + if ptype == PacketType.RESP: + self._resp_queue.put_nowait(packet) + + elif ptype == PacketType.CMD_RESP: + self._cmd_resp_queue.put_nowait(packet) + + elif ptype == PacketType.ASYNC_RESP: + resp = AsyncResponse.from_packet(packet) + self.async_response_queue.put_nowait(resp) + cb = self._async_callbacks.get(resp.original_command) + if cb is not None: + try: + result = cb(resp) # type: ignore[arg-type] + if inspect.isawaitable(result): + await result + except Exception: + pass + + elif ptype in (PacketType.STATUS, PacketType.INTERRUPT): + notif = StatusNotification.from_packet(packet) + self.status_queue.put_nowait(notif) + cb = self._status_callbacks.get( # type: ignore[assignment] + (notif.status_name, notif.module_name) + ) + if cb is not None: + try: + result = cb(notif) # type: ignore[arg-type] + if inspect.isawaitable(result): + await result + except Exception: + pass + + elif ptype == PacketType.ERROR: + err = _parse_server_error(packet) + self._resp_queue.put_nowait(err) + self._cmd_resp_queue.put_nowait(err) + + # PacketType.INFO is silently discarded (welcome / goodbye messages) + + def _notify_disconnect(self) -> None: + """Put sentinels in waiter queues when the connection is lost.""" + if self._resp_queue is None: + return + sentinel = RouterConnectionError("Connection lost unexpectedly.") + self._resp_queue.put_nowait(sentinel) + self._cmd_resp_queue.put_nowait(sentinel) + + # ------------------------------------------------------------------ + # Internal: synchronised waiters + # ------------------------------------------------------------------ + + async def _wait_for_resp(self, timeout: float) -> CommandResponse: + assert self._resp_queue is not None + try: + item = await asyncio.wait_for(self._resp_queue.get(), timeout=timeout) + except asyncio.TimeoutError: + raise RouterTimeoutError( + f"No response received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + assert isinstance(item, Packet) + return CommandResponse(raw=item.data) + + async def _wait_for_cmd_resp(self, timeout: float) -> None: + assert self._cmd_resp_queue is not None + try: + item = await asyncio.wait_for( + self._cmd_resp_queue.get(), timeout=timeout + ) + except asyncio.TimeoutError: + raise RouterTimeoutError( + f"No cmd-resp received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + # CMD_RESP payload is a handshake acknowledgment; discard it diff --git a/SDK/python-package/src/csm_tcp_router/client.py b/SDK/python-package/src/csm_tcp_router/client.py new file mode 100644 index 0000000..2f870d0 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/client.py @@ -0,0 +1,456 @@ +"""High-level CSM-TCP-Router client.""" + +from __future__ import annotations + +import queue +import threading +import time +from typing import Callable, Dict, Optional, Tuple + +from ._errors import _parse_server_error +from ._protocol import encode_packet +from ._transport import Transport +from .exceptions import ConnectionError as RouterConnectionError +from .exceptions import ServerError +from .exceptions import TimeoutError as RouterTimeoutError +from .models import ( + AsyncResponse, + CommandResponse, + Packet, + PacketType, + StatusNotification, +) + +__all__ = ["TcpRouterClient"] + +# Type aliases +_SubKey = Tuple[str, str] +StatusCallback = Callable[[StatusNotification], None] +AsyncCallback = Callable[[AsyncResponse], None] + +# Items held in the internal queues are either Packet or Exception instances. +_QueueItem = object + + +class TcpRouterClient: + """Python client for a CSM-TCP-Router server. + + This class mirrors the LabVIEW ClientAPI VIs and speaks the + CSM-TCP-Router protocol v0. It is thread-safe in that its internal + state is protected by locks; however, the protocol allows at most one + in-flight *synchronous* command at a time and at most one in-flight + *async* command / subscription at a time. Concurrent callers are + serialised by ``_resp_lock`` and ``_cmd_resp_lock`` respectively. + + **Quickstart**:: + + from csm_tcp_router import TcpRouterClient + + with TcpRouterClient() as client: + client.connect("localhost", 30007) + print(client.list_modules()) + + **Protocol flows**: + + - *Synchronous* command (``-@``): :meth:`send_and_wait` – sends a ``CMD`` + packet and blocks until a ``RESP`` (or ``ERROR``) is received. + - *Asynchronous* command (``->``): :meth:`post` – sends a ``CMD`` packet + and blocks until the ``CMD_RESP`` handshake is received; the eventual + ``ASYNC_RESP`` is delivered asynchronously. + - *No-reply async* command (``->|``): :meth:`post_no_reply` – same as + :meth:`post` but no ``ASYNC_RESP`` will ever arrive. + - *Subscribe / unsubscribe*: :meth:`subscribe_status` / + :meth:`unsubscribe_status` – sends a ```` / ```` + command and waits for the ``CMD_RESP`` handshake. + + **Received-packet routing** (on the background receive thread): + + - ``RESP`` (0x04) – unblocks the caller of :meth:`send_and_wait`. + - ``CMD_RESP`` (0x03) – unblocks callers of :meth:`post`, + :meth:`post_no_reply`, :meth:`subscribe_status`, and + :meth:`unsubscribe_status`. + - ``ASYNC_RESP`` (0x05) – added to :attr:`async_response_queue` and + dispatched to any matching :meth:`register_async_callback`. + - ``STATUS`` / ``INTERRUPT`` (0x06 / 0x07) – added to + :attr:`status_queue` and dispatched to any matching + :meth:`subscribe_status` callback. + - ``ERROR`` (0x01) – unblocks any pending synchronous waiter with a + :exc:`~csm_tcp_router.exceptions.ServerError`. + - ``INFO`` (0x00) – silently discarded (welcome / goodbye messages). + """ + + def __init__(self) -> None: + self._transport = Transport( + on_packet=self._on_packet, + on_disconnect=self._on_disconnect, + ) + + # One-item-deep queues for synchronised waits. + # Items are either Packet or Exception instances. + self._resp_queue: queue.Queue[_QueueItem] = queue.Queue() + self._cmd_resp_queue: queue.Queue[_QueueItem] = queue.Queue() + + #: Polling queue for :class:`~csm_tcp_router.models.AsyncResponse` + #: objects received from the server. + self.async_response_queue: queue.Queue[AsyncResponse] = queue.Queue() + + #: Polling queue for :class:`~csm_tcp_router.models.StatusNotification` + #: objects received from the server. + self.status_queue: queue.Queue[StatusNotification] = queue.Queue() + + # Callback registries (protected by _lock) + self._status_callbacks: Dict[_SubKey, Optional[StatusCallback]] = {} + self._async_callbacks: Dict[str, AsyncCallback] = {} + self._lock = threading.Lock() + + # Serialisation locks – at most one in-flight RESP / CMD_RESP waiter + # at a time. This prevents concurrent callers from consuming each + # other's response packets. + self._resp_lock = threading.Lock() + self._cmd_resp_lock = threading.Lock() + + # ------------------------------------------------------------------ + # Connection management + # ------------------------------------------------------------------ + + def connect(self, host: str, port: int, timeout: float = 5.0) -> None: + """Connect to a CSM-TCP-Router server. + + :param host: Server hostname or IP address. + :param port: Server TCP port (the reference app defaults to 30007). + :param timeout: Connect timeout in seconds. + :raises ConnectionError: if the connection cannot be established. + """ + self._transport.connect(host, port, timeout=timeout) + + def disconnect(self) -> None: + """Disconnect from the server and release all resources. + + Safe to call even if not currently connected. Any threads currently + blocked inside :meth:`send_and_wait`, :meth:`post`, or similar methods + will receive a :exc:`~csm_tcp_router.exceptions.ConnectionError` + immediately rather than waiting for their timeout to expire. + """ + # Wake blocked waiters *before* tearing down the transport. + sentinel = RouterConnectionError("Disconnected from server.") + self._resp_queue.put(sentinel) + self._cmd_resp_queue.put(sentinel) + self._transport.disconnect() + + @property + def connected(self) -> bool: + """``True`` if the underlying transport is currently connected.""" + return self._transport.connected + + def wait_for_server( + self, + host: str, + port: int, + timeout: float = 30.0, + retry_interval: float = 0.5, + ) -> bool: + """Poll until *host*:*port* accepts a connection or *timeout* elapses. + + :param host: Server hostname or IP address. + :param port: Server TCP port. + :param timeout: Maximum time to wait in seconds. + :param retry_interval: Pause between retries in seconds. + :returns: ``True`` when the server is reachable; ``False`` on timeout. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + probe = Transport( + on_packet=lambda _p: None, + on_disconnect=lambda: None, + ) + try: + probe.connect(host, port, timeout=1.0) + probe.disconnect() + return True + except RouterConnectionError: + pass + time.sleep(retry_interval) + return False + + # ------------------------------------------------------------------ + # Core command methods + # ------------------------------------------------------------------ + + def send_and_wait(self, command: str, timeout: float = 5.0) -> CommandResponse: + """Send a **synchronous** command and block until the response arrives. + + Use the CSM synchronous message suffix ``-@`` in *command*:: + + resp = client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + + The built-in router management commands (``List``, ``Ping``, …) are + also synchronous and do not require a suffix. + + :param command: CSM command string. + :param timeout: Seconds to wait for the ``resp`` packet. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no response arrives within *timeout*. + :raises ServerError: if the server returns an error packet. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + with self._resp_lock: + self._transport.send_raw(wire) + return self._wait_for_resp(timeout) + + def post(self, command: str, timeout: float = 5.0) -> None: + """Send an **asynchronous** command and wait for the ``cmd-resp`` handshake. + + Use the CSM async message suffix ``->`` in *command*:: + + client.post("API: Start Sampling -> DAQmx") + + The eventual ``async-resp`` payload will be delivered to any callback + registered with :meth:`register_async_callback` and added to + :attr:`async_response_queue`. + + :param command: CSM command string including the ``->`` suffix. + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the command. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + + def post_no_reply(self, command: str, timeout: float = 5.0) -> None: + """Send an **async no-reply** command and wait for the ``cmd-resp`` handshake. + + Use the CSM no-reply suffix ``->|`` in *command*:: + + client.post_no_reply("API: Reset ->| DAQmx") + + After the handshake the server will not send any further response. + + :param command: CSM command string including the ``->|`` suffix. + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the command. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + + def ping(self, timeout: float = 2.0) -> Tuple[bool, float]: + """Send a ``Ping`` command and measure round-trip latency. + + :param timeout: Seconds to wait for the reply. + :returns: ``(True, elapsed_seconds)`` on success, + ``(False, 0.0)`` on failure or error. + """ + try: + t0 = time.monotonic() + self.send_and_wait("Ping", timeout=timeout) + return True, time.monotonic() - t0 + except (RouterConnectionError, RouterTimeoutError, ServerError): + return False, 0.0 + + # ------------------------------------------------------------------ + # Router management helpers + # ------------------------------------------------------------------ + + def list_modules(self, timeout: float = 5.0) -> str: + """Return the server's loaded CSM module list as plain text. + + Equivalent to the ``List`` router management command. + """ + return self.send_and_wait("List", timeout=timeout).text + + def list_api(self, module: str, timeout: float = 5.0) -> str: + """Return the API list for *module* as plain text.""" + return self.send_and_wait(f"List API {module}", timeout=timeout).text + + def list_states(self, module: str, timeout: float = 5.0) -> str: + """Return the CSM state list for *module* as plain text.""" + return self.send_and_wait(f"List State {module}", timeout=timeout).text + + def help(self, module: str, timeout: float = 5.0) -> str: + """Return the help text for *module* as plain text.""" + return self.send_and_wait(f"Help {module}", timeout=timeout).text + + # ------------------------------------------------------------------ + # Status / interrupt subscriptions + # ------------------------------------------------------------------ + + def subscribe_status( + self, + status_name: str, + module_name: str, + callback: Optional[StatusCallback] = None, + timeout: float = 5.0, + ) -> None: + """Subscribe to a CSM module's status broadcast. + + Sends ``"@ ->"`` and waits for + the ``cmd-resp`` handshake. Once subscribed, + :class:`~csm_tcp_router.models.StatusNotification` objects will be: + + * delivered to *callback* (if provided), and + * added to :attr:`status_queue`. + + :param status_name: Name of the status to subscribe to (e.g. ``"Status"``). + :param module_name: Name of the CSM module (e.g. ``"AI"``). + :param callback: Optional callable invoked on each notification. + Must be fast and non-blocking (runs in the recv thread). + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the subscription. + """ + # Register the callback *before* sending to eliminate the race where + # a STATUS packet could arrive before the callback is stored. + with self._lock: + self._status_callbacks[(status_name, module_name)] = callback + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + try: + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + except Exception: + with self._lock: + self._status_callbacks.pop((status_name, module_name), None) + raise + + def unsubscribe_status( + self, + status_name: str, + module_name: str, + timeout: float = 5.0, + ) -> None: + """Cancel a status subscription. + + :param status_name: Name of the subscribed status. + :param module_name: Name of the CSM module. + :param timeout: Seconds to wait for the ``cmd-resp`` handshake. + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the request. + """ + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + with self._lock: + self._status_callbacks.pop((status_name, module_name), None) + + def register_async_callback( + self, + original_command: str, + callback: AsyncCallback, + ) -> None: + """Register a callback for ``async-resp`` packets. + + The callback is matched by *original_command* (the command text + echoed in the ``async-resp`` payload after the `` <- `` separator). + + :param original_command: The command text that will appear in the + ``async-resp`` echo + (e.g. ``"API: Read -> DAQmx"``). + :param callback: Callable receiving an + :class:`~csm_tcp_router.models.AsyncResponse`. + """ + with self._lock: + self._async_callbacks[original_command] = callback + + def unregister_async_callback(self, original_command: str) -> None: + """Remove a previously registered async callback.""" + with self._lock: + self._async_callbacks.pop(original_command, None) + + # ------------------------------------------------------------------ + # Context-manager support + # ------------------------------------------------------------------ + + def __enter__(self) -> TcpRouterClient: + return self + + def __exit__(self, *_args: object) -> None: + self.disconnect() + + # ------------------------------------------------------------------ + # Internal: packet dispatch (runs in the receive thread) + # ------------------------------------------------------------------ + + def _on_packet(self, packet: Packet) -> None: + ptype = packet.type + if ptype == PacketType.RESP: + self._resp_queue.put(packet) + + elif ptype == PacketType.CMD_RESP: + self._cmd_resp_queue.put(packet) + + elif ptype == PacketType.ASYNC_RESP: + resp = AsyncResponse.from_packet(packet) + self.async_response_queue.put(resp) + with self._lock: + cb = self._async_callbacks.get(resp.original_command) + if cb is not None: + try: + cb(resp) + except Exception: + pass + + elif ptype in (PacketType.STATUS, PacketType.INTERRUPT): + notif = StatusNotification.from_packet(packet) + self.status_queue.put(notif) + with self._lock: + cb = self._status_callbacks.get( # type: ignore[assignment] + (notif.status_name, notif.module_name) + ) + if cb is not None: + try: + cb(notif) # type: ignore[call-arg] + except Exception: + pass + + elif ptype == PacketType.ERROR: + err = _parse_server_error(packet) + # Unblock any pending synchronous waiter + self._resp_queue.put(err) + self._cmd_resp_queue.put(err) + + # PacketType.INFO is silently discarded (welcome / goodbye messages) + + def _on_disconnect(self) -> None: + """Called from the receive thread when the connection drops unexpectedly.""" + sentinel = RouterConnectionError("Connection lost unexpectedly.") + self._resp_queue.put(sentinel) + self._cmd_resp_queue.put(sentinel) + + # ------------------------------------------------------------------ + # Internal: synchronised waiters + # ------------------------------------------------------------------ + + def _wait_for_resp(self, timeout: float) -> CommandResponse: + try: + item = self._resp_queue.get(timeout=timeout) + except queue.Empty: + raise RouterTimeoutError( + f"No response received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + assert isinstance(item, Packet) + return CommandResponse(raw=item.data) + + def _wait_for_cmd_resp(self, timeout: float) -> None: + try: + item = self._cmd_resp_queue.get(timeout=timeout) + except queue.Empty: + raise RouterTimeoutError( + f"No cmd-resp received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + # CMD_RESP payload is a handshake acknowledgment; discard it diff --git a/SDK/python-package/src/csm_tcp_router/exceptions.py b/SDK/python-package/src/csm_tcp_router/exceptions.py new file mode 100644 index 0000000..e79ad48 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/exceptions.py @@ -0,0 +1,45 @@ +"""Exception hierarchy for csm-tcp-router-client.""" + +__all__ = [ + "ConnectionError", + "ProtocolError", + "ServerError", + "TcpRouterError", + "TimeoutError", +] + + +class TcpRouterError(Exception): + """Base exception for all CSM-TCP-Router client errors.""" + + +class ConnectionError(TcpRouterError): + """Raised when a connection cannot be established or is lost.""" + + +class TimeoutError(TcpRouterError): + """Raised when a synchronous operation exceeds its timeout.""" + + +class ProtocolError(TcpRouterError): + """Raised when an invalid or unexpected protocol frame is received.""" + + +class ServerError(TcpRouterError): + """Raised when the server returns an error packet. + + Attributes: + message: Human-readable error text from the server. + code: Optional error code extracted from the CSM Error format + ``[Error: ] ``. + """ + + def __init__(self, message: str, code: str = "") -> None: + super().__init__(message) + self.message = message + self.code = code + + def __str__(self) -> str: + if self.code: + return f"[Error: {self.code}] {self.message}" + return self.message diff --git a/SDK/python-package/src/csm_tcp_router/models.py b/SDK/python-package/src/csm_tcp_router/models.py new file mode 100644 index 0000000..70ec0e2 --- /dev/null +++ b/SDK/python-package/src/csm_tcp_router/models.py @@ -0,0 +1,151 @@ +"""Public data models and enumerations for the CSM-TCP-Router protocol.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import IntEnum + +__all__ = [ + "AsyncResponse", + "CommandResponse", + "Packet", + "PacketType", + "StatusNotification", +] + + +class PacketType(IntEnum): + """Packet type constants as defined in the CSM-TCP-Router protocol v0. + + Wire values + ----------- + ``INFO`` 0x00 – informational messages (welcome / goodbye) + ``ERROR`` 0x01 – error messages from the server + ``CMD`` 0x02 – command sent by the client + ``CMD_RESP`` 0x03 – server handshake for async / no-reply / subscribe + ``RESP`` 0x04 – synchronous response payload + ``ASYNC_RESP`` 0x05 – asynchronous response payload + ``STATUS`` 0x06 – status broadcast from a subscribed CSM module + ``INTERRUPT`` 0x07 – interrupt broadcast from a subscribed CSM module + """ + + INFO = 0x00 + ERROR = 0x01 + CMD = 0x02 + CMD_RESP = 0x03 + RESP = 0x04 + ASYNC_RESP = 0x05 + STATUS = 0x06 + INTERRUPT = 0x07 + + +@dataclass(frozen=True) +class Packet: + """A decoded packet received from the server (internal representation).""" + + type: PacketType + data: bytes + version: int = 1 + flag1: int = 0 + flag2: int = 0 + + +@dataclass(frozen=True) +class CommandResponse: + """The result of a synchronous command (:meth:`~csm_tcp_router.TcpRouterClient.send_and_wait`).""" + + raw: bytes + + @property + def text(self) -> str: + """Decoded UTF-8 text of the response payload.""" + return self.raw.decode("utf-8", errors="replace") + + def __repr__(self) -> str: + return f"CommandResponse({self.text!r})" + + +@dataclass(frozen=True) +class AsyncResponse: + """An asynchronous response payload delivered via an ``async-resp`` packet. + + Attributes: + raw: Raw response bytes (the part *before* the `` <- `` separator). + original_command: The original command text echoed back by the server + (the part *after* the `` <- `` separator). + """ + + raw: bytes + original_command: str = "" + + @property + def text(self) -> str: + """Decoded UTF-8 text of the response payload.""" + return self.raw.decode("utf-8", errors="replace") + + @classmethod + def from_packet(cls, packet: Packet) -> AsyncResponse: + """Parse an ``ASYNC_RESP`` packet. + + Server format: ``" <- "``. + """ + text = packet.data.decode("utf-8", errors="replace") + parts = text.split(" <- ", 1) + if len(parts) == 2: + return cls(raw=parts[0].encode("utf-8"), original_command=parts[1]) + return cls(raw=packet.data) + + def __repr__(self) -> str: + return f"AsyncResponse({self.text!r}, cmd={self.original_command!r})" + + +@dataclass(frozen=True) +class StatusNotification: + """A status broadcast delivered via a ``status`` or ``interrupt`` packet. + + Attributes: + raw: Full raw payload bytes. + packet_type: Either :attr:`PacketType.STATUS` or + :attr:`PacketType.INTERRUPT`. + status_name: The name of the broadcasted status (left of ``>>``). + data: The status payload (between ``>>`` and ``<-``). + module_name: The sending CSM module name (right of ``<-``). + """ + + raw: bytes + packet_type: PacketType = PacketType.STATUS + status_name: str = "" + data: str = "" + module_name: str = "" + + @classmethod + def from_packet(cls, packet: Packet) -> StatusNotification: + """Parse a ``STATUS`` or ``INTERRUPT`` packet. + + Server format: ``" >> <- "``. + """ + text = packet.data.decode("utf-8", errors="replace") + module = "" + left = text + if " <- " in text: + left, module = text.rsplit(" <- ", 1) + module = module.strip() + status_name = "" + data = left.strip() + if " >> " in left: + status_name, data = left.split(" >> ", 1) + status_name = status_name.strip() + data = data.strip() + return cls( + raw=packet.data, + packet_type=packet.type, + status_name=status_name, + data=data, + module_name=module, + ) + + def __repr__(self) -> str: + return ( + f"StatusNotification(status={self.status_name!r}, " + f"data={self.data!r}, module={self.module_name!r})" + ) diff --git a/SDK/python-package/tests/__init__.py b/SDK/python-package/tests/__init__.py new file mode 100644 index 0000000..9cece0a --- /dev/null +++ b/SDK/python-package/tests/__init__.py @@ -0,0 +1 @@ +# tests package marker diff --git a/SDK/python-package/tests/conftest.py b/SDK/python-package/tests/conftest.py new file mode 100644 index 0000000..8055db0 --- /dev/null +++ b/SDK/python-package/tests/conftest.py @@ -0,0 +1,211 @@ +"""Shared pytest fixtures – mock TCP server for integration tests.""" + +from __future__ import annotations + +import queue +import socket +import struct +import threading +from typing import Dict, Optional, Tuple + +import pytest + +from csm_tcp_router._protocol import HEADER_SIZE, encode_packet +from csm_tcp_router.models import PacketType + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- + +def _recv_all(sock: socket.socket, size: int) -> bytes: + """Read exactly *size* bytes from *sock*; returns ``b""`` on EOF.""" + buf = bytearray(size) + view = memoryview(buf) + received = 0 + while received < size: + try: + n = sock.recv_into(view[received:], size - received) + except OSError: + return b"" + if n == 0: + return b"" + received += n + return bytes(buf) + + +# --------------------------------------------------------------------------- +# MockServer +# --------------------------------------------------------------------------- + +class MockServer: + """Minimal TCP server that emulates a CSM-TCP-Router for testing. + + Usage:: + + server = MockServer() + server.start() + # ... connect a TcpRouterClient to server.port ... + server.stop() + + Custom responses can be registered before the client sends commands:: + + server.set_response("My Command", "My Reply") + server.set_error_response("Bad Command", "[Error: 42] bad command") + """ + + def __init__(self) -> None: + self._server_sock: Optional[socket.socket] = None + self._thread: Optional[threading.Thread] = None + self._stop = threading.Event() + self.host: str = "127.0.0.1" + self.port: int = 0 + + #: All raw command strings received from the client, in order. + self.received_commands: queue.Queue[str] = queue.Queue() + + # custom response map: command text -> (PacketType, bytes) + self._responses: Dict[str, Tuple[PacketType, bytes]] = {} + + # Connected client sockets (for push operations like STATUS) + self._client_sockets: list = [] + self._clients_lock = threading.Lock() + + def start(self) -> None: + self._server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._server_sock.bind((self.host, 0)) + self.port = self._server_sock.getsockname()[1] + self._server_sock.listen(5) + self._stop.clear() + self._thread = threading.Thread( + target=self._accept_loop, daemon=True, name="mock-server" + ) + self._thread.start() + + def stop(self) -> None: + self._stop.set() + if self._server_sock: + try: + self._server_sock.close() + except OSError: + pass + if self._thread: + self._thread.join(timeout=2) + + def set_response(self, cmd_text: str, resp_text: str) -> None: + """Register a custom ``RESP`` reply for *cmd_text*.""" + self._responses[cmd_text] = (PacketType.RESP, resp_text.encode("utf-8")) + + def set_error_response(self, cmd_text: str, error_text: str) -> None: + """Register an ``ERROR`` reply for *cmd_text*.""" + self._responses[cmd_text] = (PacketType.ERROR, error_text.encode("utf-8")) + + def push_status(self, payload: str) -> None: + """Push a ``STATUS`` packet to all currently connected clients.""" + wire = encode_packet(payload.encode("utf-8"), PacketType.STATUS) + with self._clients_lock: + for conn in list(self._client_sockets): + try: + conn.sendall(wire) + except OSError: + pass + + def get_received(self, timeout: float = 1.0) -> Optional[str]: + """Return the next received command string, or ``None`` on timeout.""" + try: + return self.received_commands.get(timeout=timeout) + except queue.Empty: + return None + + # ------------------------------------------------------------------ + # Internal + # ------------------------------------------------------------------ + + def _accept_loop(self) -> None: + assert self._server_sock is not None + self._server_sock.settimeout(0.5) + while not self._stop.is_set(): + try: + conn, _ = self._server_sock.accept() + except (OSError, socket.timeout): + continue + with self._clients_lock: + self._client_sockets.append(conn) + t = threading.Thread( + target=self._handle_client, args=(conn,), daemon=True + ) + t.start() + + def _handle_client(self, conn: socket.socket) -> None: + # Send welcome info packet on connect + conn.sendall(encode_packet(b"Welcome to mock server", PacketType.INFO)) + conn.settimeout(1.0) + try: + while not self._stop.is_set(): + header = _recv_all(conn, HEADER_SIZE) + if not header: + break + try: + (data_len,) = struct.unpack("!I", header[:4]) + body = _recv_all(conn, data_len) + except (OSError, struct.error): + break + if len(body) != data_len: + break + + type_byte = header[5] # offset 5 == TYPE byte + if type_byte == PacketType.CMD.value: + cmd_text = body.decode("utf-8", errors="replace").strip() + self.received_commands.put(cmd_text) + self._handle_command(conn, cmd_text) + except OSError: + pass + finally: + with self._clients_lock: + try: + self._client_sockets.remove(conn) + except ValueError: + pass + try: + conn.close() + except OSError: + pass + + def _handle_command(self, conn: socket.socket, cmd: str) -> None: + """Respond to a received command.""" + if cmd in self._responses: + ptype, data = self._responses[cmd] + conn.sendall(encode_packet(data, ptype)) + return + + # Built-in defaults + if cmd == "Ping": + conn.sendall(encode_packet(b"Pong", PacketType.RESP)) + elif cmd == "List": + conn.sendall(encode_packet(b"AI\nDIO\nSystem", PacketType.RESP)) + elif cmd.startswith("List API "): + module = cmd[len("List API "):].strip() + payload = f"API: Start -> {module}\nAPI: Stop -> {module}" + conn.sendall(encode_packet(payload.encode(), PacketType.RESP)) + elif cmd.startswith("List State "): + module = cmd[len("List State "):].strip() + payload = f"Idle <- {module}\nRunning <- {module}" + conn.sendall(encode_packet(payload.encode(), PacketType.RESP)) + elif "->" in cmd or "->" in cmd: + conn.sendall(encode_packet(b"", PacketType.CMD_RESP)) + else: + # Generic async handshake for any other command + conn.sendall(encode_packet(b"", PacketType.CMD_RESP)) + + +# --------------------------------------------------------------------------- +# Pytest fixture +# --------------------------------------------------------------------------- + +@pytest.fixture +def mock_server(): + """Provide a running :class:`MockServer`; stop it after the test.""" + server = MockServer() + server.start() + yield server + server.stop() diff --git a/SDK/python-package/tests/test_async_client.py b/SDK/python-package/tests/test_async_client.py new file mode 100644 index 0000000..7047ec2 --- /dev/null +++ b/SDK/python-package/tests/test_async_client.py @@ -0,0 +1,455 @@ +"""Tests for AsyncTcpRouterClient – unit tests and integration tests.""" + +from __future__ import annotations + +import asyncio +import time +from typing import List + +import pytest + +from csm_tcp_router import AsyncTcpRouterClient +from csm_tcp_router.async_client import _parse_server_error +from csm_tcp_router.exceptions import ( + ConnectionError as RouterConnectionError, +) +from csm_tcp_router.exceptions import ( + ServerError, +) +from csm_tcp_router.exceptions import ( + TimeoutError as RouterTimeoutError, +) +from csm_tcp_router.models import AsyncResponse, Packet, PacketType, StatusNotification + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_packet(ptype: PacketType, text: str = "") -> Packet: + return Packet(type=ptype, data=text.encode("utf-8")) + + +def _client_with_queues() -> AsyncTcpRouterClient: + """Return a client with asyncio objects pre-initialised (no TCP connection).""" + client = AsyncTcpRouterClient() + client._init_async_objects() + return client + + +# --------------------------------------------------------------------------- +# Unit tests: _parse_server_error (sync – no asyncio needed) +# --------------------------------------------------------------------------- + + +def test_parse_error_plain_text(): + pkt = _make_packet(PacketType.ERROR, "something went wrong") + err = _parse_server_error(pkt) + assert err.message == "something went wrong" + assert err.code == "" + + +def test_parse_error_csm_format(): + pkt = _make_packet(PacketType.ERROR, "[Error: 42] module not found") + err = _parse_server_error(pkt) + assert err.code == "42" + assert err.message == "module not found" + + +def test_parse_error_malformed_bracket(): + pkt = _make_packet(PacketType.ERROR, "[Error: missing close") + err = _parse_server_error(pkt) + assert err.message == "[Error: missing close" + + +# --------------------------------------------------------------------------- +# Unit tests: packet dispatch +# --------------------------------------------------------------------------- + + +async def test_dispatch_resp(): + client = _client_with_queues() + pkt = _make_packet(PacketType.RESP, "ok") + await client._dispatch_packet(pkt) + item = client._resp_queue.get_nowait() + assert isinstance(item, Packet) + assert item.data == b"ok" + + +async def test_dispatch_cmd_resp(): + client = _client_with_queues() + await client._dispatch_packet(_make_packet(PacketType.CMD_RESP)) + assert not client._cmd_resp_queue.empty() + + +async def test_dispatch_error_unblocks_both_queues(): + client = _client_with_queues() + await client._dispatch_packet(_make_packet(PacketType.ERROR, "[Error: 7] bad")) + r = client._resp_queue.get_nowait() + c = client._cmd_resp_queue.get_nowait() + assert isinstance(r, ServerError) and r.code == "7" + assert isinstance(c, ServerError) + + +async def test_dispatch_async_resp_to_queue(): + client = _client_with_queues() + await client._dispatch_packet( + _make_packet(PacketType.ASYNC_RESP, "result <- API: Start -> DIO") + ) + ar = client.async_response_queue.get_nowait() + assert ar.text == "result" + assert ar.original_command == "API: Start -> DIO" + + +async def test_dispatch_async_resp_sync_callback(): + client = _client_with_queues() + received: List[AsyncResponse] = [] + client.register_async_callback("API: Start -> DIO", received.append) + await client._dispatch_packet( + _make_packet(PacketType.ASYNC_RESP, "result <- API: Start -> DIO") + ) + assert len(received) == 1 and received[0].text == "result" + + +async def test_dispatch_async_resp_async_callback(): + client = _client_with_queues() + received: List[AsyncResponse] = [] + + async def async_cb(ar: AsyncResponse) -> None: + received.append(ar) + + client.register_async_callback("cmd", async_cb) + await client._dispatch_packet(_make_packet(PacketType.ASYNC_RESP, "val <- cmd")) + assert len(received) == 1 + + +async def test_dispatch_status_to_queue(): + client = _client_with_queues() + await client._dispatch_packet(_make_packet(PacketType.STATUS, "Status >> 42 <- AI")) + notif = client.status_queue.get_nowait() + assert notif.status_name == "Status" + assert notif.data == "42" + assert notif.module_name == "AI" + + +async def test_dispatch_status_sync_callback(): + client = _client_with_queues() + received: List[StatusNotification] = [] + client._status_callbacks[("Status", "AI")] = received.append + await client._dispatch_packet(_make_packet(PacketType.STATUS, "Status >> v1 <- AI")) + assert len(received) == 1 and received[0].data == "v1" + + +async def test_dispatch_status_async_callback(): + client = _client_with_queues() + received: List[StatusNotification] = [] + + async def async_cb(notif: StatusNotification) -> None: + received.append(notif) + + client._status_callbacks[("Temp", "Sensor")] = async_cb + await client._dispatch_packet(_make_packet(PacketType.STATUS, "Temp >> 25.5 <- Sensor")) + assert len(received) == 1 and received[0].data == "25.5" + + +async def test_dispatch_interrupt_to_queue(): + client = _client_with_queues() + await client._dispatch_packet( + _make_packet(PacketType.INTERRUPT, "Stop >> 1 <- AI") + ) + notif = client.status_queue.get_nowait() + assert notif.packet_type == PacketType.INTERRUPT + + +async def test_dispatch_info_silently_discarded(): + client = _client_with_queues() + await client._dispatch_packet(_make_packet(PacketType.INFO, "Welcome")) + assert client._resp_queue.empty() + assert client._cmd_resp_queue.empty() + + +async def test_notify_disconnect_puts_sentinels(): + client = _client_with_queues() + client._notify_disconnect() + r = client._resp_queue.get_nowait() + c = client._cmd_resp_queue.get_nowait() + assert isinstance(r, RouterConnectionError) + assert isinstance(c, RouterConnectionError) + + +# --------------------------------------------------------------------------- +# Unit tests: callback management +# --------------------------------------------------------------------------- + + +def test_unregister_async_callback_noop_if_missing(): + client = AsyncTcpRouterClient() + client.unregister_async_callback("nonexistent") # must not raise + + +# --------------------------------------------------------------------------- +# Unit tests: timeout waiters +# --------------------------------------------------------------------------- + + +async def test_wait_for_resp_timeout(): + client = _client_with_queues() + with pytest.raises(RouterTimeoutError, match=r"0\.1s"): + await client._wait_for_resp(timeout=0.1) + + +async def test_wait_for_cmd_resp_timeout(): + client = _client_with_queues() + with pytest.raises(RouterTimeoutError, match=r"0\.1s"): + await client._wait_for_cmd_resp(timeout=0.1) + + +async def test_wait_for_resp_raises_server_error(): + client = _client_with_queues() + client._resp_queue.put_nowait(ServerError("boom", "5")) + with pytest.raises(ServerError, match="boom"): + await client._wait_for_resp(timeout=1.0) + + +async def test_wait_for_resp_raises_connection_error(): + client = _client_with_queues() + client._resp_queue.put_nowait(RouterConnectionError("lost")) + with pytest.raises(RouterConnectionError): + await client._wait_for_resp(timeout=1.0) + + +# --------------------------------------------------------------------------- +# Integration tests (real TCP via MockServer fixture) +# --------------------------------------------------------------------------- + + +class TestConnection: + async def test_connect_and_disconnect(self, mock_server): + client = AsyncTcpRouterClient() + await client.connect(mock_server.host, mock_server.port) + assert client.connected + await client.disconnect() + assert not client.connected + + async def test_async_context_manager(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + assert client.connected + assert not client.connected + + async def test_connect_already_connected_raises(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + with pytest.raises(RouterConnectionError, match="Already connected"): + await client.connect(mock_server.host, mock_server.port) + + async def test_connect_bad_port_raises(self): + client = AsyncTcpRouterClient() + with pytest.raises(RouterConnectionError): + await client.connect("127.0.0.1", 1, timeout=0.5) + + async def test_wait_for_server_success(self, mock_server): + client = AsyncTcpRouterClient() + ok = await client.wait_for_server( + mock_server.host, mock_server.port, timeout=5.0, retry_interval=0.1 + ) + assert ok is True + + async def test_wait_for_server_timeout(self): + client = AsyncTcpRouterClient() + ok = await client.wait_for_server("127.0.0.1", 1, timeout=0.3, retry_interval=0.1) + assert ok is False + + +class TestPing: + async def test_ping_returns_true_and_elapsed(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + ok, elapsed = await client.ping(timeout=2.0) + assert ok is True + assert elapsed > 0 + + +class TestSendAndWait: + async def test_list_modules(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + text = await client.list_modules(timeout=2.0) + assert "AI" in text and "DIO" in text + + async def test_list_api(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + text = await client.list_api("DAQmx", timeout=2.0) + assert "DAQmx" in text + + async def test_list_states(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + text = await client.list_states("AI", timeout=2.0) + assert "AI" in text + + async def test_custom_command(self, mock_server): + mock_server.set_response("My Cmd", "My Reply") + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + resp = await client.send_and_wait("My Cmd", timeout=2.0) + assert resp.text == "My Reply" + assert resp.raw == b"My Reply" + + async def test_server_error_raises(self, mock_server): + mock_server.set_error_response("Bad Cmd", "[Error: 9] nope") + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + with pytest.raises(ServerError) as exc_info: + await client.send_and_wait("Bad Cmd", timeout=2.0) + assert exc_info.value.code == "9" + assert exc_info.value.message == "nope" + + async def test_timeout_when_server_sends_only_cmd_resp(self, mock_server): + """send_and_wait should time out when server sends CMD_RESP instead of RESP.""" + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + with pytest.raises(RouterTimeoutError): + # Default mock sends CMD_RESP for unknown commands, never RESP + await client.send_and_wait("Unknown Async", timeout=0.3) + + async def test_concurrent_commands(self, mock_server): + """Two sequential send_and_wait calls on the same client both succeed.""" + mock_server.set_response("Cmd1", "R1") + mock_server.set_response("Cmd2", "R2") + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + r1 = await client.send_and_wait("Cmd1", timeout=2.0) + r2 = await client.send_and_wait("Cmd2", timeout=2.0) + assert r1.text == "R1" + assert r2.text == "R2" + + +class TestPost: + async def test_post_command(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + await client.post("API: Start -> DIO", timeout=2.0) + + async def test_post_no_reply(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + await client.post_no_reply("API: Reset ->| DIO", timeout=2.0) + + async def test_post_error_raises(self, mock_server): + mock_server.set_error_response("API: Start -> DIO", "module missing") + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + with pytest.raises(ServerError): + await client.post("API: Start -> DIO", timeout=2.0) + + +class TestSubscriptions: + async def test_subscribe_receives_via_queue(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + await client.subscribe_status("Status", "AI", timeout=2.0) + mock_server.push_status("Status >> 99 <- AI") + assert client.status_queue is not None + notif = await asyncio.wait_for(client.status_queue.get(), timeout=2.0) + assert notif.status_name == "Status" + assert notif.data == "99" + assert notif.module_name == "AI" + + async def test_subscribe_sync_callback(self, mock_server): + received: List[StatusNotification] = [] + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + await client.subscribe_status( + "Status", "AI", callback=received.append, timeout=2.0 + ) + mock_server.push_status("Status >> hello <- AI") + await asyncio.sleep(0.3) + assert len(received) == 1 and received[0].data == "hello" + + async def test_subscribe_async_callback(self, mock_server): + received: List[StatusNotification] = [] + + async def async_cb(notif: StatusNotification) -> None: + received.append(notif) + + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + await client.subscribe_status("Status", "AI", callback=async_cb, timeout=2.0) + mock_server.push_status("Status >> world <- AI") + await asyncio.sleep(0.3) + assert len(received) == 1 and received[0].data == "world" + + async def test_unsubscribe_stops_callback(self, mock_server): + received: List[StatusNotification] = [] + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + await client.subscribe_status( + "Status", "AI", callback=received.append, timeout=2.0 + ) + await client.unsubscribe_status("Status", "AI", timeout=2.0) + mock_server.push_status("Status >> ignored <- AI") + await asyncio.sleep(0.2) + assert len(received) == 0 + + async def test_multiple_notifications_in_order(self, mock_server): + received: List[StatusNotification] = [] + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + await client.subscribe_status( + "Temp", "Sensor", callback=received.append, timeout=2.0 + ) + for i in range(5): + mock_server.push_status(f"Temp >> {i} <- Sensor") + await asyncio.sleep(0.5) + assert len(received) == 5 + assert [n.data for n in received] == ["0", "1", "2", "3", "4"] + + async def test_subscribe_error_rolls_back_callback(self, mock_server): + mock_server.set_error_response( + "Status@AI ->", "[Error: 1] denied" + ) + received: List[StatusNotification] = [] + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + with pytest.raises(ServerError): + await client.subscribe_status( + "Status", "AI", callback=received.append, timeout=2.0 + ) + # Callback must be removed on failure + assert ("Status", "AI") not in client._status_callbacks + + +class TestConnectedProperty: + async def test_not_connected_before_connect(self): + client = AsyncTcpRouterClient() + assert not client.connected + + async def test_connected_after_connect(self, mock_server): + client = AsyncTcpRouterClient() + await client.connect(mock_server.host, mock_server.port) + assert client.connected + await client.disconnect() + + async def test_not_connected_after_disconnect(self, mock_server): + client = AsyncTcpRouterClient() + await client.connect(mock_server.host, mock_server.port) + await client.disconnect() + assert not client.connected + + async def test_send_when_not_connected_raises(self): + client = AsyncTcpRouterClient() + client._init_async_objects() + with pytest.raises(RouterConnectionError, match="Not connected"): + await client.send_and_wait("Ping", timeout=0.1) + + +class TestTimingAndPerformance: + async def test_elapsed_time_is_positive(self, mock_server): + async with AsyncTcpRouterClient() as client: + await client.connect(mock_server.host, mock_server.port) + t0 = time.monotonic() + await client.send_and_wait("Ping", timeout=2.0) + elapsed = time.monotonic() - t0 + assert elapsed >= 0 diff --git a/SDK/python-package/tests/test_client.py b/SDK/python-package/tests/test_client.py new file mode 100644 index 0000000..4f824df --- /dev/null +++ b/SDK/python-package/tests/test_client.py @@ -0,0 +1,302 @@ +"""Unit tests for TcpRouterClient using a mock transport.""" + +from __future__ import annotations + +import threading +import time +from typing import List +from unittest.mock import MagicMock, patch + +import pytest + +from csm_tcp_router.client import TcpRouterClient, _parse_server_error +from csm_tcp_router.exceptions import ( + ConnectionError as RouterConnectionError, +) +from csm_tcp_router.exceptions import ( + ServerError, +) +from csm_tcp_router.exceptions import ( + TimeoutError as RouterTimeoutError, +) +from csm_tcp_router.models import AsyncResponse, Packet, PacketType, StatusNotification + +# --------------------------------------------------------------------------- +# Helpers: inject packets directly into the client's dispatch method +# --------------------------------------------------------------------------- + +def make_packet(ptype: PacketType, text: str = "") -> Packet: + return Packet(type=ptype, data=text.encode("utf-8")) + + +def inject(client: TcpRouterClient, packet: Packet) -> None: + """Simulate the receive thread delivering a packet.""" + client._on_packet(packet) + + +# --------------------------------------------------------------------------- +# _parse_server_error +# --------------------------------------------------------------------------- + +class TestParseServerError: + def test_plain_message(self): + pkt = make_packet(PacketType.ERROR, "something went wrong") + err = _parse_server_error(pkt) + assert err.message == "something went wrong" + assert err.code == "" + + def test_csm_format(self): + pkt = make_packet(PacketType.ERROR, "[Error: 42] module not found") + err = _parse_server_error(pkt) + assert err.code == "42" + assert err.message == "module not found" + assert str(err) == "[Error: 42] module not found" + + def test_csm_format_no_message(self): + pkt = make_packet(PacketType.ERROR, "[Error: 0]") + err = _parse_server_error(pkt) + assert err.code == "0" + assert err.message == "" + + def test_malformed_bracket_no_crash(self): + pkt = make_packet(PacketType.ERROR, "[Error: no closing bracket") + err = _parse_server_error(pkt) + assert err.code == "" + assert "no closing bracket" in err.message + + +# --------------------------------------------------------------------------- +# TcpRouterClient internal dispatch +# --------------------------------------------------------------------------- + +class TestPacketDispatch: + def _client_no_transport(self) -> TcpRouterClient: + """Return a client with a mocked (never-connecting) transport.""" + client = TcpRouterClient() + client._transport = MagicMock() + client._transport.connected = True + client._transport.send_raw = MagicMock() + return client + + def test_resp_unblocks_wait_for_resp(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.RESP, "ok") + threading.Timer(0.05, inject, args=(client, pkt)).start() + resp = client._wait_for_resp(timeout=1.0) + assert resp.text == "ok" + + def test_cmd_resp_unblocks_wait_for_cmd_resp(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.CMD_RESP) + threading.Timer(0.05, inject, args=(client, pkt)).start() + client._wait_for_cmd_resp(timeout=1.0) # should not raise + + def test_error_unblocks_resp_waiter_with_exception(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.ERROR, "[Error: 1] bad") + threading.Timer(0.05, inject, args=(client, pkt)).start() + with pytest.raises(ServerError): + client._wait_for_resp(timeout=1.0) + + def test_error_unblocks_cmd_resp_waiter_with_exception(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.ERROR, "no module") + threading.Timer(0.05, inject, args=(client, pkt)).start() + with pytest.raises(ServerError): + client._wait_for_cmd_resp(timeout=1.0) + + def test_async_resp_added_to_queue(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.ASYNC_RESP, "result <- API: Start -> DIO") + inject(client, pkt) + ar = client.async_response_queue.get(timeout=0.5) + assert ar.text == "result" + assert ar.original_command == "API: Start -> DIO" + + def test_async_resp_calls_callback(self): + client = self._client_no_transport() + received: List[AsyncResponse] = [] + client.register_async_callback("API: Start -> DIO", received.append) + pkt = make_packet(PacketType.ASYNC_RESP, "result <- API: Start -> DIO") + inject(client, pkt) + time.sleep(0.05) + assert len(received) == 1 + assert received[0].text == "result" + + def test_status_added_to_queue(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.STATUS, "Status >> value42 <- AI") + inject(client, pkt) + notif = client.status_queue.get(timeout=0.5) + assert notif.status_name == "Status" + assert notif.data == "value42" + assert notif.module_name == "AI" + + def test_status_calls_registered_callback(self): + client = self._client_no_transport() + received: List[StatusNotification] = [] + + with patch.object(client._transport, "send_raw"), patch.object(client, "_wait_for_cmd_resp"): + client._status_callbacks[("Status", "AI")] = received.append + + pkt = make_packet(PacketType.STATUS, "Status >> v1 <- AI") + inject(client, pkt) + time.sleep(0.05) + assert len(received) == 1 + assert received[0].data == "v1" + + def test_interrupt_added_to_status_queue(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.INTERRUPT, "Alarm >> fire <- Safety") + inject(client, pkt) + notif = client.status_queue.get(timeout=0.5) + assert notif.packet_type == PacketType.INTERRUPT + assert notif.status_name == "Alarm" + + def test_info_packet_silently_discarded(self): + client = self._client_no_transport() + pkt = make_packet(PacketType.INFO, "Welcome to the server") + inject(client, pkt) + assert client._resp_queue.empty() + assert client._cmd_resp_queue.empty() + + def test_on_disconnect_unblocks_waiters(self): + client = self._client_no_transport() + threading.Timer(0.05, client._on_disconnect).start() + with pytest.raises(RouterConnectionError): + client._wait_for_resp(timeout=1.0) + + +# --------------------------------------------------------------------------- +# Timeout behaviour +# --------------------------------------------------------------------------- + +class TestTimeouts: + def _client(self) -> TcpRouterClient: + client = TcpRouterClient() + client._transport = MagicMock() + client._transport.connected = True + client._transport.send_raw = MagicMock() + return client + + def test_wait_for_resp_timeout(self): + client = self._client() + with pytest.raises(RouterTimeoutError, match=r"0\.1s"): + client._wait_for_resp(timeout=0.1) + + def test_wait_for_cmd_resp_timeout(self): + client = self._client() + with pytest.raises(RouterTimeoutError, match=r"0\.1s"): + client._wait_for_cmd_resp(timeout=0.1) + + +# --------------------------------------------------------------------------- +# ping convenience method +# --------------------------------------------------------------------------- + +class TestPing: + def test_ping_success_returns_true_and_elapsed(self): + client = TcpRouterClient() + client._transport = MagicMock() + client._transport.connected = True + client._transport.send_raw = MagicMock() + + threading.Timer( + 0.02, + inject, + args=(client, make_packet(PacketType.RESP, "Pong")), + ).start() + ok, elapsed = client.ping(timeout=1.0) + assert ok is True + assert elapsed > 0 + + def test_ping_failure_returns_false(self): + client = TcpRouterClient() + client._transport = MagicMock() + client._transport.connected = True + client._transport.send_raw = MagicMock() + # No packet injected → times out + ok, elapsed = client.ping(timeout=0.05) + assert ok is False + assert elapsed == 0.0 + + +# --------------------------------------------------------------------------- +# Context manager +# --------------------------------------------------------------------------- + +def test_context_manager_calls_disconnect(): + client = TcpRouterClient() + client._transport = MagicMock() + client._transport.connected = False + + with client: + pass + + client._transport.disconnect.assert_called_once() + + +# --------------------------------------------------------------------------- +# subscribe_status / unsubscribe_status +# --------------------------------------------------------------------------- + +class TestSubscriptions: + def _client_with_mock_handshake(self) -> TcpRouterClient: + client = TcpRouterClient() + client._transport = MagicMock() + client._transport.connected = True + client._transport.send_raw = MagicMock() + # Patch _wait_for_cmd_resp to succeed immediately + client._wait_for_cmd_resp = MagicMock() + return client + + def test_subscribe_stores_callback(self): + client = self._client_with_mock_handshake() + cb = MagicMock() + client.subscribe_status("Status", "AI", callback=cb) + assert client._status_callbacks[("Status", "AI")] is cb + + def test_subscribe_sends_register_command(self): + client = self._client_with_mock_handshake() + client.subscribe_status("Status", "AI") + wire = client._transport.send_raw.call_args[0][0] + assert b"Status@AI ->" in wire + + def test_unsubscribe_removes_callback(self): + client = self._client_with_mock_handshake() + client._status_callbacks[("Status", "AI")] = MagicMock() + client.unsubscribe_status("Status", "AI") + assert ("Status", "AI") not in client._status_callbacks + + def test_unsubscribe_sends_unregister_command(self): + client = self._client_with_mock_handshake() + client.unsubscribe_status("Status", "AI") + wire = client._transport.send_raw.call_args[0][0] + assert b"Status@AI ->" in wire + + def test_subscribe_cleans_up_callback_on_error(self): + client = TcpRouterClient() + client._transport = MagicMock() + client._transport.connected = True + client._transport.send_raw = MagicMock() + # Make _wait_for_cmd_resp raise + client._wait_for_cmd_resp = MagicMock(side_effect=RouterTimeoutError("t/o")) + + cb = MagicMock() + with pytest.raises(RouterTimeoutError): + client.subscribe_status("Status", "AI", callback=cb) + assert ("Status", "AI") not in client._status_callbacks + + +# --------------------------------------------------------------------------- +# register_async_callback / unregister_async_callback +# --------------------------------------------------------------------------- + +class TestAsyncCallbacks: + def test_register_and_unregister(self): + client = TcpRouterClient() + cb = MagicMock() + client.register_async_callback("cmd", cb) + assert client._async_callbacks["cmd"] is cb + client.unregister_async_callback("cmd") + assert "cmd" not in client._async_callbacks diff --git a/SDK/python-package/tests/test_integration.py b/SDK/python-package/tests/test_integration.py new file mode 100644 index 0000000..563dfd1 --- /dev/null +++ b/SDK/python-package/tests/test_integration.py @@ -0,0 +1,220 @@ +"""Integration tests: real TcpRouterClient talking to MockServer over localhost TCP.""" + +from __future__ import annotations + +import time +from typing import List + +import pytest + +from csm_tcp_router import TcpRouterClient +from csm_tcp_router.exceptions import ServerError +from csm_tcp_router.exceptions import TimeoutError as RouterTimeoutError +from csm_tcp_router.models import StatusNotification + +# All tests in this module use the `mock_server` fixture from conftest.py. + + +# --------------------------------------------------------------------------- +# Connection lifecycle +# --------------------------------------------------------------------------- + +class TestConnection: + def test_connect_and_disconnect(self, mock_server): + client = TcpRouterClient() + client.connect(mock_server.host, mock_server.port) + assert client.connected + client.disconnect() + assert not client.connected + + def test_context_manager(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + assert client.connected + assert not client.connected + + def test_connect_bad_port_raises(self): + client = TcpRouterClient() + from csm_tcp_router.exceptions import ConnectionError as RouterConnectionError + with pytest.raises(RouterConnectionError): + client.connect("127.0.0.1", 1, timeout=0.5) + + def test_wait_for_server_success(self, mock_server): + client = TcpRouterClient() + ok = client.wait_for_server( + mock_server.host, mock_server.port, timeout=5.0, retry_interval=0.1 + ) + assert ok is True + + def test_wait_for_server_timeout(self): + client = TcpRouterClient() + ok = client.wait_for_server("127.0.0.1", 1, timeout=0.3, retry_interval=0.1) + assert ok is False + + +# --------------------------------------------------------------------------- +# Ping +# --------------------------------------------------------------------------- + +class TestPing: + def test_ping_returns_true_and_elapsed(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + ok, elapsed = client.ping(timeout=2.0) + assert ok is True + assert elapsed > 0 + + +# --------------------------------------------------------------------------- +# Synchronous command (send_and_wait) +# --------------------------------------------------------------------------- + +class TestSendAndWait: + def test_list_modules(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + resp = client.send_and_wait("List", timeout=2.0) + assert "AI" in resp.text + assert "DIO" in resp.text + + def test_list_api(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + text = client.list_api("DAQmx", timeout=2.0) + assert "DAQmx" in text + + def test_list_states(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + text = client.list_states("DAQmx", timeout=2.0) + assert "Idle" in text or "Running" in text + + def test_custom_command_response(self, mock_server): + mock_server.set_response("My Command", "My Reply") + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + resp = client.send_and_wait("My Command", timeout=2.0) + assert resp.text == "My Reply" + + def test_server_error_raises(self, mock_server): + mock_server.set_error_response("Bad Command", "[Error: 7] not found") + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + with pytest.raises(ServerError) as exc_info: + client.send_and_wait("Bad Command", timeout=2.0) + assert exc_info.value.code == "7" + + def test_list_modules_helper(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + text = client.list_modules(timeout=2.0) + assert "AI" in text + + def test_command_received_by_server(self, mock_server): + mock_server.set_response("API: Probe -@ Sensor", "ok") + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + client.send_and_wait("API: Probe -@ Sensor", timeout=2.0) + received = mock_server.get_received(timeout=0.5) + assert received == "API: Probe -@ Sensor" + + +# --------------------------------------------------------------------------- +# Async command (post) +# --------------------------------------------------------------------------- + +class TestPost: + def test_post_command_sends_and_receives_handshake(self, mock_server): + # MockServer sends CMD_RESP for unknown commands by default + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + client.post("API: Start -> DIO", timeout=2.0) # should not raise + + def test_post_no_reply_sends_and_receives_handshake(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + client.post_no_reply("API: Reset ->| DIO", timeout=2.0) + + def test_post_error_raises(self, mock_server): + mock_server.set_error_response("API: Start -> DIO", "module missing") + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + with pytest.raises(ServerError): + client.post("API: Start -> DIO", timeout=2.0) + + +# --------------------------------------------------------------------------- +# Status subscriptions +# --------------------------------------------------------------------------- + +class TestStatusSubscriptions: + def test_subscribe_receives_status_via_queue(self, mock_server): + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + client.subscribe_status("Status", "AI", timeout=2.0) + + # Server pushes a STATUS packet + mock_server.push_status("Status >> 42.5 <- AI") + + notif = client.status_queue.get(timeout=2.0) + assert notif.status_name == "Status" + assert notif.data == "42.5" + assert notif.module_name == "AI" + + def test_subscribe_callback_is_invoked(self, mock_server): + received: List[StatusNotification] = [] + + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + client.subscribe_status("Status", "AI", callback=received.append, timeout=2.0) + mock_server.push_status("Status >> hello <- AI") + time.sleep(0.3) + + assert len(received) == 1 + assert received[0].data == "hello" + + def test_unsubscribe_removes_callback(self, mock_server): + received: List[StatusNotification] = [] + + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + client.subscribe_status("Status", "AI", callback=received.append, timeout=2.0) + client.unsubscribe_status("Status", "AI", timeout=2.0) + mock_server.push_status("Status >> ignored <- AI") + time.sleep(0.3) + + # Callback was removed so it should not have been called + assert len(received) == 0 + + def test_multiple_status_notifications(self, mock_server): + received: List[StatusNotification] = [] + + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + client.subscribe_status("Temp", "Sensor", callback=received.append, timeout=2.0) + for i in range(5): + mock_server.push_status(f"Temp >> {i} <- Sensor") + time.sleep(0.5) + + assert len(received) == 5 + values = [n.data for n in received] + assert values == ["0", "1", "2", "3", "4"] + + +# --------------------------------------------------------------------------- +# Timeout on disconnect +# --------------------------------------------------------------------------- + +class TestDisconnectBehaviour: + def test_wait_raises_timeout_when_no_resp(self, mock_server): + """send_and_wait raises TimeoutError when the server sends CMD_RESP instead of RESP. + + The mock server's default handler for unknown commands sends a CMD_RESP + handshake, which goes to the cmd_resp queue. send_and_wait waits on + the resp queue, so it must time out. + """ + # "Unknown Sync Command" has no registered response → server sends CMD_RESP + with TcpRouterClient() as client: + client.connect(mock_server.host, mock_server.port) + with pytest.raises(RouterTimeoutError): + client.send_and_wait("Unknown Sync Command", timeout=0.3) diff --git a/SDK/python-package/tests/test_protocol.py b/SDK/python-package/tests/test_protocol.py new file mode 100644 index 0000000..bb5c12e --- /dev/null +++ b/SDK/python-package/tests/test_protocol.py @@ -0,0 +1,154 @@ +"""Unit tests for the protocol v0 codec (_protocol.py).""" + +from __future__ import annotations + +import struct + +import pytest + +from csm_tcp_router._protocol import ( + HEADER_SIZE, + PROTOCOL_VERSION, + decode_header, + encode_packet, + parse_packet, +) +from csm_tcp_router.exceptions import ProtocolError +from csm_tcp_router.models import PacketType + +# --------------------------------------------------------------------------- +# encode_packet +# --------------------------------------------------------------------------- + +class TestEncodePacket: + def test_returns_header_plus_body(self): + data = b"hello" + wire = encode_packet(data, PacketType.CMD) + assert len(wire) == HEADER_SIZE + len(data) + + def test_header_format(self): + data = b"hello" + wire = encode_packet(data, PacketType.CMD) + data_len, version, type_byte, flag1, flag2 = struct.unpack( + "!IBBBB", wire[:HEADER_SIZE] + ) + assert data_len == len(data) + assert version == PROTOCOL_VERSION + assert type_byte == PacketType.CMD.value + assert flag1 == 0 + assert flag2 == 0 + + def test_body_appended_verbatim(self): + data = b"test payload" + wire = encode_packet(data, PacketType.RESP) + assert wire[HEADER_SIZE:] == data + + def test_empty_body(self): + wire = encode_packet(b"", PacketType.CMD_RESP) + assert len(wire) == HEADER_SIZE + (data_len,) = struct.unpack("!I", wire[:4]) + assert data_len == 0 + + def test_custom_flags(self): + wire = encode_packet(b"x", PacketType.INFO, flag1=0xAB, flag2=0xCD) + _, _, _, flag1, flag2 = struct.unpack("!IBBBB", wire[:HEADER_SIZE]) + assert flag1 == 0xAB + assert flag2 == 0xCD + + def test_all_packet_types_encode(self): + for ptype in PacketType: + wire = encode_packet(b"data", ptype) + assert wire[5] == ptype.value # TYPE byte at offset 5 + + def test_utf8_command_string(self): + cmd = "API: Start Sampling -@ DAQmx" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + assert wire[HEADER_SIZE:] == cmd.encode("utf-8") + + def test_large_payload_length_field(self): + data = b"x" * 1024 + wire = encode_packet(data, PacketType.RESP) + (data_len,) = struct.unpack("!I", wire[:4]) + assert data_len == 1024 + + +# --------------------------------------------------------------------------- +# decode_header +# --------------------------------------------------------------------------- + +class TestDecodeHeader: + def test_round_trip(self): + wire = encode_packet(b"body", PacketType.ASYNC_RESP, flag1=1, flag2=2) + data_len, version, type_byte, flag1, flag2 = decode_header( + wire[:HEADER_SIZE] + ) + assert data_len == 4 + assert version == PROTOCOL_VERSION + assert type_byte == PacketType.ASYNC_RESP.value + assert flag1 == 1 + assert flag2 == 2 + + def test_wrong_length_raises(self): + with pytest.raises(ProtocolError, match="header"): + decode_header(b"\x00" * 7) + + def test_zero_length_raises(self): + with pytest.raises(ProtocolError): + decode_header(b"") + + +# --------------------------------------------------------------------------- +# parse_packet +# --------------------------------------------------------------------------- + +class TestParsePacket: + def _make_wire(self, data: bytes, ptype: PacketType) -> tuple: + wire = encode_packet(data, ptype) + return wire[:HEADER_SIZE], wire[HEADER_SIZE:] + + def test_basic_round_trip(self): + header, body = self._make_wire(b"hello", PacketType.RESP) + pkt = parse_packet(header, body) + assert pkt.type == PacketType.RESP + assert pkt.data == b"hello" + assert pkt.version == PROTOCOL_VERSION + + def test_all_known_types(self): + for ptype in PacketType: + header, body = self._make_wire(b"data", ptype) + pkt = parse_packet(header, body) + assert pkt.type == ptype + + def test_unknown_type_mapped_to_info(self): + # Manually craft a packet with an unknown type byte (0xFF) + raw_header = struct.pack("!IBBBB", 4, PROTOCOL_VERSION, 0xFF, 0, 0) + pkt = parse_packet(raw_header, b"data") + assert pkt.type == PacketType.INFO + + def test_body_length_mismatch_raises(self): + header, _ = self._make_wire(b"hello", PacketType.CMD) + with pytest.raises(ProtocolError, match="mismatch"): + parse_packet(header, b"hi") # shorter body + + def test_empty_body(self): + header, body = self._make_wire(b"", PacketType.CMD_RESP) + pkt = parse_packet(header, body) + assert pkt.data == b"" + + def test_flags_preserved(self): + wire = encode_packet(b"x", PacketType.STATUS, flag1=3, flag2=7) + pkt = parse_packet(wire[:HEADER_SIZE], wire[HEADER_SIZE:]) + assert pkt.flag1 == 3 + assert pkt.flag2 == 7 + + def test_header_too_short_raises(self): + with pytest.raises(ProtocolError): + parse_packet(b"\x00" * 4, b"") + + +# --------------------------------------------------------------------------- +# HEADER_SIZE constant +# --------------------------------------------------------------------------- + +def test_header_size_is_eight(): + assert HEADER_SIZE == 8 diff --git a/src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(en-us)/VI Description(en-us) - CSM-TCP-Router.md b/src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(en-us)/VI Description(en-us) - CSM-TCP-Router.md new file mode 100644 index 0000000..8f7ae0a --- /dev/null +++ b/src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(en-us)/VI Description(en-us) - CSM-TCP-Router.md @@ -0,0 +1,95 @@ +# CSM-TCP-Router + +## API + +> [!NOTE] +> CSM-TCP-Router API Scope +> +> CSM-TCP-Router APIs are split into two parts: +> - Server VIs: Start and host the TCP router service for CSM modules. +> - Client VIs: Connect to the router and send/receive commands. +> +> The API list below is inferred from current project structure, VI naming, and shipped examples. + +## Server VIs + +### CSM-TCP-Router.vi +Core router VI in `src/_addons/TCP-Router/`. + +Starts the CSM TCP router communication layer, handles packet routing, and serves Router built-in management commands. + +### CSM-TCP-Router(Server).vi +Server startup VI in `src/Server/`. + +Standard runnable entry VI used by the example project to host CSM modules through CSM-TCP-Router. + +### Router Built-in Commands +Commands provided by the router service side: + +- `List`: List all available CSM modules. +- `List API`: List exposed APIs of a specified module. +- `List State`: List CSM states of a specified module. +- `Help`: Return module help text from VI Description. +- `Refresh lvcsm`: Refresh cached lvcsm data. + +## Client VIs + +Client API VIs are under `src/_addons/TCP-Router/ClientAPI/`. + +### Obtain.vi +Create and connect a client session to the TCP router server. + +### Release.vi +Release a client session and related resources. + +### Send Message and Wait for Reply.vi +Send a synchronous command and wait for the final response. + +### Post Message.vi +Post an asynchronous command (non-blocking for final response). + +### Post No-Rep Message.vi +Post an asynchronous command that does not require final response. + +### Ping.vi +Check server reachability and return communication elapsed time. + +### Wait for Server.vi +Wait until the server is reachable or timeout is reached. + +### Register Status Change.vi +Register a status-change subscription callback. + +### Unregister Status Change.vi +Unregister a status-change subscription callback. + +> [!NOTE] +> `Register Status Change.vi` / `Unregister Status Change.vi` are kept for compatibility. +> For new integrations, prefer the `... for Client.vi` variants. + +### Register Status for Client.vi +Register status subscription for a specified client context. + +### Unregister Status for Client.vi +Unregister status subscription for a specified client context. + +### Status Queue.vi +Receive status updates through queue-based API. + +### ASync-Response Queue.vi +Receive asynchronous command responses through queue-based API. + +### ASync-Response User Event.vi +Receive asynchronous command responses through User Event API. + +### Register Broadcast.vi +Register broadcast-message subscription. + +### Unregister Broadcast.vi +Unregister broadcast-message subscription. + +### Register Broadcast for Client.vi +Register broadcast-message subscription for a specified client context. + +### Unregister Broadcast for Client.vi +Unregister broadcast-message subscription for a specified client context. diff --git a/src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(zh-cn)/VI Description(zh-cn) - CSM-TCP-Router.md b/src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(zh-cn)/VI Description(zh-cn) - CSM-TCP-Router.md new file mode 100644 index 0000000..c3f13f1 --- /dev/null +++ b/src/help/NEVSTOP/Communicable State Machine(CSM)/VI Description/VI Description(zh-cn)/VI Description(zh-cn) - CSM-TCP-Router.md @@ -0,0 +1,95 @@ +# CSM-TCP-Router + +## API + +> [!NOTE] +> CSM-TCP-Router API 范围 +> +> CSM-TCP-Router 的 API 分为两部分: +> - Server 侧 VI:启动并承载 CSM 模块的 TCP Router 服务。 +> - Client 侧 VI:连接 Router 并发送/接收指令。 +> +> 下述 API 清单依据当前项目结构、VI 命名和示例工程推断整理。 + +## Server 侧 VI + +### CSM-TCP-Router.vi +位于 `src/_addons/TCP-Router/` 的核心 Router VI。 + +用于启动 CSM TCP Router 通讯层,处理数据包路由,并提供 Router 内建管理指令。 + +### CSM-TCP-Router(Server).vi +位于 `src/Server/` 的服务端启动 VI。 + +作为示例工程的标准入口 VI,用于通过 CSM-TCP-Router 对外承载 CSM 模块。 + +### Router 内建指令 +由 Router 服务端提供的内建指令: + +- `List`:列出所有可用 CSM 模块。 +- `List API`:列出指定模块暴露的 API。 +- `List State`:列出指定模块可用的 CSM 状态。 +- `Help`:返回模块 VI Description 中的帮助文本。 +- `Refresh lvcsm`:刷新 lvcsm 缓存数据。 + +## Client 侧 VI + +Client API 位于 `src/_addons/TCP-Router/ClientAPI/`。 + +### Obtain.vi +创建并连接到 TCP Router 服务端的客户端会话。 + +### Release.vi +释放客户端会话及相关资源。 + +### Send Message and Wait for Reply.vi +发送同步指令并等待最终响应。 + +### Post Message.vi +发送异步指令(调用不阻塞等待最终响应)。 + +### Post No-Rep Message.vi +发送无需最终响应的异步指令。 + +### Ping.vi +检查服务端可达性并返回通讯耗时。 + +### Wait for Server.vi +等待服务端可连接,直到成功或超时。 + +### Register Status Change.vi +注册状态变更订阅回调。 + +### Unregister Status Change.vi +取消状态变更订阅回调。 + +> [!NOTE] +> `Register Status Change.vi` / `Unregister Status Change.vi` 主要用于兼容旧用法。 +> 新的集成建议优先使用 `... for Client.vi` 版本接口。 + +### Register Status for Client.vi +面向指定客户端上下文注册状态订阅。 + +### Unregister Status for Client.vi +面向指定客户端上下文取消状态订阅。 + +### Status Queue.vi +通过队列方式获取状态更新。 + +### ASync-Response Queue.vi +通过队列方式获取异步指令响应。 + +### ASync-Response User Event.vi +通过 User Event 方式获取异步指令响应。 + +### Register Broadcast.vi +注册广播消息订阅。 + +### Unregister Broadcast.vi +取消广播消息订阅。 + +### Register Broadcast for Client.vi +面向指定客户端上下文注册广播订阅。 + +### Unregister Broadcast for Client.vi +面向指定客户端上下文取消广播订阅。 From dbb3ec598fe1b6fff81d1405e700f298846bba29 Mon Sep 17 00:00:00 2001 From: nevstop Date: Mon, 27 Apr 2026 18:52:51 +0800 Subject: [PATCH 04/12] update PythonClientAPI/* --- .doc/CSM-TCP-Router.excalidraw | 854 ++++++++++-------- .doc/Protocol.v0.(zh-cn).md | 1 + .doc/{ => obsolete}/Protocol.v1.(zh-cn).md | 0 CSM-TCP-Router.lvproj | 4 + SDK/PythonClientAPI/README.md | 166 ---- SDK/PythonClientAPI/example_usage.py | 103 --- SDK/PythonClientAPI/tcp_router_client.py | 259 ------ SDK/{python-package => python}/CHANGELOG.md | 0 SDK/{python-package => python}/LICENSE | 0 SDK/{python-package => python}/README.md | 0 .../README.zh-cn.md | 0 .../examples/async_usage.py | 0 .../examples/basic_usage.py | 0 .../examples/subscribe_status.py | 0 SDK/{python-package => python}/pyproject.toml | 0 .../src/csm_tcp_router/__init__.py | 0 .../src/csm_tcp_router/_errors.py | 0 .../src/csm_tcp_router/_protocol.py | 0 .../src/csm_tcp_router/_transport.py | 0 .../src/csm_tcp_router/async_client.py | 0 .../src/csm_tcp_router/client.py | 0 .../src/csm_tcp_router/exceptions.py | 0 .../src/csm_tcp_router/models.py | 0 .../tests/__init__.py | 0 .../tests/conftest.py | 0 .../tests/test_async_client.py | 0 .../tests/test_client.py | 0 .../tests/test_integration.py | 0 .../tests/test_protocol.py | 0 29 files changed, 462 insertions(+), 925 deletions(-) rename .doc/{ => obsolete}/Protocol.v1.(zh-cn).md (100%) delete mode 100644 SDK/PythonClientAPI/README.md delete mode 100644 SDK/PythonClientAPI/example_usage.py delete mode 100644 SDK/PythonClientAPI/tcp_router_client.py rename SDK/{python-package => python}/CHANGELOG.md (100%) rename SDK/{python-package => python}/LICENSE (100%) rename SDK/{python-package => python}/README.md (100%) rename SDK/{python-package => python}/README.zh-cn.md (100%) rename SDK/{python-package => python}/examples/async_usage.py (100%) rename SDK/{python-package => python}/examples/basic_usage.py (100%) rename SDK/{python-package => python}/examples/subscribe_status.py (100%) rename SDK/{python-package => python}/pyproject.toml (100%) rename SDK/{python-package => python}/src/csm_tcp_router/__init__.py (100%) rename SDK/{python-package => python}/src/csm_tcp_router/_errors.py (100%) rename SDK/{python-package => python}/src/csm_tcp_router/_protocol.py (100%) rename SDK/{python-package => python}/src/csm_tcp_router/_transport.py (100%) rename SDK/{python-package => python}/src/csm_tcp_router/async_client.py (100%) rename SDK/{python-package => python}/src/csm_tcp_router/client.py (100%) rename SDK/{python-package => python}/src/csm_tcp_router/exceptions.py (100%) rename SDK/{python-package => python}/src/csm_tcp_router/models.py (100%) rename SDK/{python-package => python}/tests/__init__.py (100%) rename SDK/{python-package => python}/tests/conftest.py (100%) rename SDK/{python-package => python}/tests/test_async_client.py (100%) rename SDK/{python-package => python}/tests/test_client.py (100%) rename SDK/{python-package => python}/tests/test_integration.py (100%) rename SDK/{python-package => python}/tests/test_protocol.py (100%) diff --git a/.doc/CSM-TCP-Router.excalidraw b/.doc/CSM-TCP-Router.excalidraw index fb64d1a..b9b5455 100644 --- a/.doc/CSM-TCP-Router.excalidraw +++ b/.doc/CSM-TCP-Router.excalidraw @@ -1,15 +1,15 @@ { "type": "excalidraw", "version": 2, - "source": "https://excalidraw.com", + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", "elements": [ { "id": "06097f4ce4a944f5b840", "type": "rectangle", - "x": -40.0, - "y": 1060.0, - "width": 710.0, - "height": 260.0, + "x": -377.1428571428571, + "y": 1010.8571951729912, + "width": 710, + "height": 260, "angle": 0, "strokeColor": "#666666", "backgroundColor": "#f5f5f5", @@ -24,8 +24,8 @@ "type": 3 }, "seed": 1516676960, - "version": 1, - "versionNonce": 894759244, + "version": 69, + "versionNonce": 1599584990, "isDeleted": false, "boundElements": [ { @@ -33,17 +33,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287137300, "link": null, - "locked": false + "locked": false, + "index": "a0" }, { "id": "40d09f28b6f5449b8416", "type": "text", - "x": 196.68, - "y": 1175.0, + "x": -140.4628571428571, + "y": 1125.8571951729912, "width": 236.64, - "height": 30.0, + "height": 30, "angle": 0, "strokeColor": "#67AB9F", "backgroundColor": "transparent", @@ -56,11 +57,11 @@ "frameId": null, "roundness": null, "seed": 1359914060, - "version": 1, - "versionNonce": 267946355, + "version": 69, + "versionNonce": 1083748126, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, "text": "Server Application", @@ -71,15 +72,16 @@ "containerId": "06097f4ce4a944f5b840", "originalText": "Server Application", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "a1" }, { "id": "f2931a99db14441d92c2", "type": "rectangle", - "x": 250.0, - "y": 70.0, - "width": 570.0, - "height": 630.0, + "x": 201.42848423549094, + "y": -51.142839704241055, + "width": 570, + "height": 630, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -94,8 +96,8 @@ "type": 3 }, "seed": 334935149, - "version": 1, - "versionNonce": 912941967, + "version": 35, + "versionNonce": 479119682, "isDeleted": false, "boundElements": [ { @@ -103,17 +105,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "a2" }, { "id": "d39d2f1397d84cf29c94", "type": "text", - "x": 367.96000000000004, - "y": 365.0, + "x": 319.388484235491, + "y": 243.85716029575894, "width": 334.08, - "height": 40.0, + "height": 40, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -126,11 +129,11 @@ "frameId": null, "roundness": null, "seed": 1166131094, - "version": 1, - "versionNonce": 1488794127, + "version": 35, + "versionNonce": 1613254914, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, "text": "Server Application", @@ -141,15 +144,16 @@ "containerId": "f2931a99db14441d92c2", "originalText": "Server Application", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "a3" }, { "id": "3a12c9cd83ac49d4af82", "type": "rectangle", - "x": 320.0, - "y": 120.0, - "width": 450.0, - "height": 340.0, + "x": 271.42848423549094, + "y": -1.1428397042410552, + "width": 450, + "height": 340, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -164,24 +168,29 @@ "type": 3 }, "seed": 405658968, - "version": 1, - "versionNonce": 1007193791, + "version": 53, + "versionNonce": 1320870082, "isDeleted": false, "boundElements": [ { "id": "96fc065142d741bfab28", "type": "text" + }, + { + "id": "4343ca43bb1b46aaab89", + "type": "arrow" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "a4" }, { "id": "96fc065142d741bfab28", "type": "text", - "x": 411.89, - "y": 273.125, + "x": 363.3184842354909, + "y": 3.857160295758945, "width": 266.21999999999997, "height": 33.75, "angle": 0, @@ -196,30 +205,31 @@ "frameId": null, "roundness": null, "seed": 1687535625, - "version": 1, - "versionNonce": 1573432143, + "version": 54, + "versionNonce": 870676610, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, "text": "CSM Module System", "fontSize": 27, "fontFamily": 1, "textAlign": "center", - "verticalAlign": "middle", + "verticalAlign": "top", "containerId": "3a12c9cd83ac49d4af82", "originalText": "CSM Module System", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "a5" }, { "id": "58eb2e1dfd49455083d7", "type": "rectangle", - "x": 370.0, - "y": 190.0, - "width": 170.0, - "height": 110.0, + "x": 288.2284720284597, + "y": 68.85716029575894, + "width": 203.20001220703125, + "height": 110, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -234,8 +244,8 @@ "type": 3 }, "seed": 828310685, - "version": 1, - "versionNonce": 1098432173, + "version": 70, + "versionNonce": 287227906, "isDeleted": false, "boundElements": [ { @@ -243,17 +253,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "a6" }, { "id": "b4cb0e46d8754a6ba28b", "type": "text", - "x": 374.0, - "y": 232.5, - "width": 162.0, - "height": 25.0, + "x": 304.7285406930105, + "y": 111.35716029575894, + "width": 170.1998748779297, + "height": 25, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -266,11 +277,11 @@ "frameId": null, "roundness": null, "seed": 540651128, - "version": 1, - "versionNonce": 643699448, + "version": 72, + "versionNonce": 353071042, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, "text": "AI (CSM Module1)", @@ -281,15 +292,16 @@ "containerId": "58eb2e1dfd49455083d7", "originalText": "AI (CSM Module1)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "a7" }, { "id": "8a5c90e4c9214aff98d8", "type": "rectangle", - "x": 370.0, - "y": 310.0, - "width": 170.0, - "height": 110.0, + "x": 288.2284720284597, + "y": 188.85716029575894, + "width": 203.20001220703125, + "height": 110, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -304,26 +316,31 @@ "type": 3 }, "seed": 2061525606, - "version": 1, - "versionNonce": 1612758244, + "version": 71, + "versionNonce": 525084546, "isDeleted": false, "boundElements": [ { "id": "15dba5756d714bc5be93", "type": "text" + }, + { + "id": "c967d534ee594bd8b6f7", + "type": "arrow" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "a8" }, { "id": "15dba5756d714bc5be93", "type": "text", - "x": 374.0, - "y": 352.5, - "width": 162.0, - "height": 25.0, + "x": 337.27851322719016, + "y": 218.85716029575894, + "width": 105.09992980957031, + "height": 50, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -336,14 +353,14 @@ "frameId": null, "roundness": null, "seed": 1094784021, - "version": 1, - "versionNonce": 1455312304, + "version": 71, + "versionNonce": 1478485826, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, - "text": "DIO1 (CSM Module2)", + "text": "DIO1 (CSM\nModule2)", "fontSize": 20, "fontFamily": 1, "textAlign": "center", @@ -351,15 +368,16 @@ "containerId": "8a5c90e4c9214aff98d8", "originalText": "DIO1 (CSM Module2)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "a9" }, { "id": "378abd886fa942b5ae6f", "type": "rectangle", - "x": 550.0, - "y": 190.0, - "width": 170.0, - "height": 110.0, + "x": 501.42848423549094, + "y": 68.85716029575894, + "width": 200, + "height": 110, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -374,8 +392,8 @@ "type": 3 }, "seed": 1968915559, - "version": 1, - "versionNonce": 137789224, + "version": 52, + "versionNonce": 320634562, "isDeleted": false, "boundElements": [ { @@ -383,17 +401,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "aA" }, { "id": "867f1a5210ac42c7a82d", "type": "text", - "x": 554.0, - "y": 232.5, - "width": 162.0, - "height": 25.0, + "x": 548.8785193307058, + "y": 98.85716029575894, + "width": 105.09992980957031, + "height": 50, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -406,14 +425,14 @@ "frameId": null, "roundness": null, "seed": 1862328426, - "version": 1, - "versionNonce": 1835601418, + "version": 53, + "versionNonce": 1986221698, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, - "text": "DIO1 (CSM Module3)", + "text": "DIO1 (CSM\nModule3)", "fontSize": 20, "fontFamily": 1, "textAlign": "center", @@ -421,15 +440,16 @@ "containerId": "378abd886fa942b5ae6f", "originalText": "DIO1 (CSM Module3)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aB" }, { "id": "adc34df6058e4435acf1", "type": "rectangle", - "x": 550.0, - "y": 310.0, - "width": 170.0, - "height": 110.0, + "x": 501.42848423549094, + "y": 188.85716029575894, + "width": 200, + "height": 110, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -444,8 +464,8 @@ "type": 3 }, "seed": 76596677, - "version": 1, - "versionNonce": 1494446171, + "version": 52, + "versionNonce": 1115240002, "isDeleted": false, "boundElements": [ { @@ -453,17 +473,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "aC" }, { "id": "109e949f39d34b6cb355", "type": "text", - "x": 554.0, - "y": 352.5, - "width": 162.0, - "height": 25.0, + "x": 531.4185431344167, + "y": 218.85716029575894, + "width": 140.01988220214844, + "height": 50, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -476,14 +497,14 @@ "frameId": null, "roundness": null, "seed": 1205658623, - "version": 1, - "versionNonce": 1551557532, + "version": 53, + "versionNonce": 406658562, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, - "text": "Measure (CSM Module4)", + "text": "Measure (CSM\nModule4)", "fontSize": 20, "fontFamily": 1, "textAlign": "center", @@ -491,15 +512,16 @@ "containerId": "adc34df6058e4435acf1", "originalText": "Measure (CSM Module4)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aD" }, { "id": "72a83ed504ed4d0da987", "type": "rectangle", - "x": 320.0, - "y": 560.0, - "width": 450.0, - "height": 100.0, + "x": 262.62843540736594, + "y": 438.85716029575894, + "width": 458.800048828125, + "height": 100, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -514,26 +536,31 @@ "type": 3 }, "seed": 513845840, - "version": 1, - "versionNonce": 1692515392, + "version": 87, + "versionNonce": 615386562, "isDeleted": false, "boundElements": [ { "id": "3b93d90e9ba740ec8d2c", "type": "text" + }, + { + "id": "4343ca43bb1b46aaab89", + "type": "arrow" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "aE" }, { "id": "3b93d90e9ba740ec8d2c", "type": "text", - "x": 324.0, - "y": 590.0, - "width": 442.0, - "height": 40.0, + "x": 272.20451572963157, + "y": 448.85716029575894, + "width": 439.64788818359375, + "height": 80, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -546,14 +573,14 @@ "frameId": null, "roundness": null, "seed": 133198406, - "version": 1, - "versionNonce": 2087443845, + "version": 89, + "versionNonce": 485164418, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, - "text": "CSM TCP Router (based on JKI TCP Server)", + "text": "CSM TCP Router (based on\nJKI TCP Server)", "fontSize": 32, "fontFamily": 1, "textAlign": "center", @@ -561,15 +588,16 @@ "containerId": "72a83ed504ed4d0da987", "originalText": "CSM TCP Router (based on JKI TCP Server)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aF" }, { "id": "66d9bb7d39ed49bab977", "type": "rectangle", - "x": -330.0, - "y": 80.0, - "width": 470.0, - "height": 620.0, + "x": -556.1715218680247, + "y": -41.142839704241055, + "width": 647.6000061035156, + "height": 620, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -584,26 +612,31 @@ "type": 3 }, "seed": 1857846310, - "version": 1, - "versionNonce": 2135205116, + "version": 78, + "versionNonce": 904203522, "isDeleted": false, "boundElements": [ { "id": "d61e4a50c7424aafb49c", "type": "text" + }, + { + "id": "c967d534ee594bd8b6f7", + "type": "arrow" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "aG" }, { "id": "d61e4a50c7424aafb49c", "type": "text", - "x": -262.03999999999996, - "y": 370.0, - "width": 334.08, - "height": 40.0, + "x": -365.21946498325906, + "y": 248.85716029575894, + "width": 265.6958923339844, + "height": 40, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -616,11 +649,11 @@ "frameId": null, "roundness": null, "seed": 531398026, - "version": 1, - "versionNonce": 1296356900, + "version": 78, + "versionNonce": 1166023874, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, "text": "Client Application", @@ -631,15 +664,16 @@ "containerId": "66d9bb7d39ed49bab977", "originalText": "Client Application", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aH" }, { "id": "d4b5bc67b5e84b86aec2", "type": "rectangle", - "x": -300.0, - "y": 300.0, - "width": 340.0, - "height": 160.0, + "x": -510.97150966099343, + "y": 161.12381837229248, + "width": 564.7999877929688, + "height": 190.39999050564236, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -652,8 +686,8 @@ "frameId": null, "roundness": null, "seed": 451019402, - "version": 1, - "versionNonce": 2020205762, + "version": 149, + "versionNonce": 1259164738, "isDeleted": false, "boundElements": [ { @@ -661,17 +695,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "aI" }, { "id": "e14f5e9cd4674be1bda4", "type": "text", - "x": -296.0, - "y": 317.5, - "width": 332.0, - "height": 125.0, + "x": -505.97150966099343, + "y": 181.32381362511364, + "width": 479.3798828125, + "height": 150, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -684,30 +719,31 @@ "frameId": null, "roundness": null, "seed": 1029267775, - "version": 1, - "versionNonce": 738718317, + "version": 166, + "versionNonce": 1956477954, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, - "text": "Server Build-in Command\n\nCSM TCP Router 定义的指令,用于管理和显示帮助- List: 列出所有的 CSM 模块\n- List API: 列出CSM 模块的参数\n- Help: 显示CSM模块的帮助(VI Description)", + "text": "Server Build-in Command\n\nCSM TCP Router 定义的指令,用于管理和显示帮助\n- List: 列出所有的 CSM 模块\n- List API: 列出CSM 模块的参数\n- Help: 显示CSM模块的帮助(VI Description)", "fontSize": 20, "fontFamily": 1, - "textAlign": "center", + "textAlign": "left", "verticalAlign": "middle", "containerId": "d4b5bc67b5e84b86aec2", - "originalText": "Server Build-in Command\n\nCSM TCP Router 定义的指令,用于管理和显示帮助- List: 列出所有的 CSM 模块\n- List API: 列出CSM 模块的参数\n- Help: 显示CSM模块的帮助(VI Description)", + "originalText": "Server Build-in Command\n\nCSM TCP Router 定义的指令,用于管理和显示帮助\n- List: 列出所有的 CSM 模块\n- List API: 列出CSM 模块的参数\n- Help: 显示CSM模块的帮助(VI Description)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aJ" }, { "id": "c44e3d5dc3a5490cb039", "type": "rectangle", - "x": -300.0, - "y": 490.0, - "width": 340.0, - "height": 160.0, + "x": -511.7714974539622, + "y": 369.39047644882606, + "width": 564.7999877929688, + "height": 145.06665943287038, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -720,8 +756,8 @@ "frameId": null, "roundness": null, "seed": 286961250, - "version": 1, - "versionNonce": 1108591278, + "version": 185, + "versionNonce": 290630594, "isDeleted": false, "boundElements": [ { @@ -729,17 +765,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "aK" }, { "id": "ec5f57e035cf45908018", "type": "text", - "x": -296.0, - "y": 532.5, - "width": 332.0, - "height": 75.0, + "x": -506.7714974539622, + "y": 379.4238061652612, + "width": 536.919921875, + "height": 125, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -752,30 +789,31 @@ "frameId": null, "roundness": null, "seed": 1260928053, - "version": 1, - "versionNonce": 1047655250, + "version": 207, + "versionNonce": 122413954, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, - "text": "Server  CSM Command\n由 CSM 模块定义的指令\nCSM 的所有消息,都被转发到 CSM Modules 中执行,因此支持的消息种类,由Server 中的CSM Module System 中的CSM模块决定", + "text": "Server  CSM Command\n由 CSM 模块定义的指令\nCSM 的所有消息,都被转发到 CSM Modules 中执行,因此\n支持的消息种类,由Server 中的CSM Module System 中的\nCSM模块决定", "fontSize": 20, "fontFamily": 1, - "textAlign": "center", + "textAlign": "left", "verticalAlign": "middle", "containerId": "c44e3d5dc3a5490cb039", "originalText": "Server  CSM Command\n由 CSM 模块定义的指令\nCSM 的所有消息,都被转发到 CSM Modules 中执行,因此支持的消息种类,由Server 中的CSM Module System 中的CSM模块决定", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aL" }, { "id": "68d09aa25b504907b22d", "type": "rectangle", - "x": -300.0, - "y": 110.0, - "width": 340.0, - "height": 160.0, + "x": -510.97150966099343, + "y": -11.142839704241055, + "width": 564.7999877929688, + "height": 167.7333249692564, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -788,8 +826,8 @@ "frameId": null, "roundness": null, "seed": 1280667824, - "version": 1, - "versionNonce": 645069776, + "version": 149, + "versionNonce": 549987138, "isDeleted": false, "boundElements": [ { @@ -797,17 +835,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287133715, "link": null, - "locked": false + "locked": false, + "index": "aM" }, { "id": "0916692baae84d6a8cb2", "type": "text", - "x": -296.0, - "y": 127.5, - "width": 332.0, - "height": 125.0, + "x": -505.97150966099343, + "y": 10.223822780387138, + "width": 493.7198486328125, + "height": 125, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -820,30 +859,31 @@ "frameId": null, "roundness": null, "seed": 1861740026, - "version": 1, - "versionNonce": 1550368398, + "version": 162, + "versionNonce": 136735490, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133715, "link": null, "locked": false, "text": "Client-Def Command\nClient 定义的本地指令,和server无关\n\n- Switch: 切换发送的CSM模块,节省键入CSM模块名称\n- Script: 导入执行 scipt 文本", "fontSize": 20, "fontFamily": 1, - "textAlign": "center", + "textAlign": "left", "verticalAlign": "middle", "containerId": "68d09aa25b504907b22d", "originalText": "Client-Def Command\nClient 定义的本地指令,和server无关\n\n- Switch: 切换发送的CSM模块,节省键入CSM模块名称\n- Script: 导入执行 scipt 文本", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aN" }, { "id": "c3d7e032eee9479d92fe", "type": "rectangle", - "x": -10.0, - "y": 1090.0, - "width": 460.0, - "height": 60.0, + "x": -347.1428571428571, + "y": 1040.8571951729912, + "width": 647.199951171875, + "height": 60, "angle": 0, "strokeColor": "#82b366", "backgroundColor": "#d5e8d4", @@ -858,8 +898,8 @@ "type": 3 }, "seed": 616346550, - "version": 1, - "versionNonce": 1272916373, + "version": 89, + "versionNonce": 1848712030, "isDeleted": false, "boundElements": [ { @@ -867,17 +907,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287137300, "link": null, - "locked": false + "locked": false, + "index": "aO" }, { "id": "ba1ba4b5800841ef99ba", "type": "text", - "x": 80.80000000000001, - "y": 1105.0, - "width": 278.4, - "height": 30.0, + "x": -154.28281075613836, + "y": 1055.8571951729912, + "width": 261.4798583984375, + "height": 30, "angle": 0, "strokeColor": "#67AB9F", "backgroundColor": "transparent", @@ -890,11 +931,11 @@ "frameId": null, "roundness": null, "seed": 1360334683, - "version": 1, - "versionNonce": 721767282, + "version": 90, + "versionNonce": 1584529310, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, "text": "TCP Layer( Reusable)", @@ -905,15 +946,16 @@ "containerId": "c3d7e032eee9479d92fe", "originalText": "TCP Layer( Reusable)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aP" }, { "id": "f1fcce1f99dc4c1f8847", "type": "rectangle", - "x": -10.0, - "y": 1160.0, - "width": 460.0, - "height": 140.0, + "x": -347.1428571428571, + "y": 1110.8571951729912, + "width": 647.199951171875, + "height": 140, "angle": 0, "strokeColor": "#b85450", "backgroundColor": "#f8cecc", @@ -926,8 +968,8 @@ "frameId": null, "roundness": null, "seed": 527125112, - "version": 1, - "versionNonce": 1980260540, + "version": 85, + "versionNonce": 361520094, "isDeleted": false, "boundElements": [ { @@ -935,17 +977,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287137300, "link": null, - "locked": false + "locked": false, + "index": "aQ" }, { "id": "5bab6bdeb62b40228319", "type": "text", - "x": -6.0, - "y": 1217.5, - "width": 452.0, - "height": 25.0, + "x": -309.78268868582586, + "y": 1168.3571951729912, + "width": 572.4796142578125, + "height": 25, "angle": 0, "strokeColor": "#67AB9F", "backgroundColor": "transparent", @@ -958,11 +1001,11 @@ "frameId": null, "roundness": null, "seed": 894462135, - "version": 1, - "versionNonce": 378597871, + "version": 87, + "versionNonce": 1771122718, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, "text": "Code Based CSM Framework(Based on the Requirements)", @@ -973,15 +1016,16 @@ "containerId": "f1fcce1f99dc4c1f8847", "originalText": "Code Based CSM Framework(Based on the Requirements)", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aR" }, { "id": "5c296355ff174e0195e2", "type": "rectangle", - "x": 50.0, - "y": 790.0, - "width": 100.0, - "height": 100.0, + "x": -272.85714285714266, + "y": 636.857125418527, + "width": 100, + "height": 100, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#D2D3D3", @@ -994,8 +1038,8 @@ "frameId": null, "roundness": null, "seed": 425614159, - "version": 1, - "versionNonce": 501325967, + "version": 71, + "versionNonce": 1181842526, "isDeleted": false, "boundElements": [ { @@ -1003,17 +1047,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287137300, "link": null, - "locked": false + "locked": false, + "index": "aS" }, { "id": "b543c6d67fdf4c61a3f0", "type": "text", - "x": 54.0, - "y": 827.5, - "width": 92.0, - "height": 25.0, + "x": -268.85714285714266, + "y": 661.857125418527, + "width": 92, + "height": 50, "angle": 0, "strokeColor": "#67AB9F", "backgroundColor": "transparent", @@ -1026,14 +1071,14 @@ "frameId": null, "roundness": null, "seed": 91453709, - "version": 1, - "versionNonce": 884719392, + "version": 73, + "versionNonce": 1912488094, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, - "text": "TCP Client", + "text": "TCP\nClient", "fontSize": 20, "fontFamily": 1, "textAlign": "center", @@ -1041,15 +1086,16 @@ "containerId": "5c296355ff174e0195e2", "originalText": "TCP Client", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aT" }, { "id": "b56d185ccff9414282e7", "type": "rectangle", - "x": 170.0, - "y": 790.0, - "width": 100.0, - "height": 100.0, + "x": -152.85714285714266, + "y": 636.857125418527, + "width": 100, + "height": 100, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#D2D3D3", @@ -1062,8 +1108,8 @@ "frameId": null, "roundness": null, "seed": 1346551058, - "version": 1, - "versionNonce": 1266930958, + "version": 71, + "versionNonce": 1340636382, "isDeleted": false, "boundElements": [ { @@ -1071,17 +1117,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287137300, "link": null, - "locked": false + "locked": false, + "index": "aU" }, { "id": "e1c67e3c14464e33954a", "type": "text", - "x": 174.0, - "y": 827.5, - "width": 92.0, - "height": 25.0, + "x": -148.85714285714266, + "y": 661.857125418527, + "width": 92, + "height": 50, "angle": 0, "strokeColor": "#67AB9F", "backgroundColor": "transparent", @@ -1094,14 +1141,14 @@ "frameId": null, "roundness": null, "seed": 757773874, - "version": 1, - "versionNonce": 492781324, + "version": 73, + "versionNonce": 633418014, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, - "text": "TCP Client", + "text": "TCP\nClient", "fontSize": 20, "fontFamily": 1, "textAlign": "center", @@ -1109,15 +1156,16 @@ "containerId": "b56d185ccff9414282e7", "originalText": "TCP Client", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aV" }, { "id": "c8513775c6fe4da58f55", "type": "rectangle", - "x": 290.0, - "y": 790.0, - "width": 100.0, - "height": 100.0, + "x": -32.85714285714266, + "y": 636.857125418527, + "width": 100, + "height": 100, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#D2D3D3", @@ -1130,8 +1178,8 @@ "frameId": null, "roundness": null, "seed": 757589191, - "version": 1, - "versionNonce": 1529136688, + "version": 71, + "versionNonce": 1593400670, "isDeleted": false, "boundElements": [ { @@ -1139,17 +1187,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287137300, "link": null, - "locked": false + "locked": false, + "index": "aW" }, { "id": "709beda0e58b4b5ab6cd", "type": "text", - "x": 294.0, - "y": 827.5, - "width": 92.0, - "height": 25.0, + "x": -28.857142857142662, + "y": 661.857125418527, + "width": 92, + "height": 50, "angle": 0, "strokeColor": "#67AB9F", "backgroundColor": "transparent", @@ -1162,14 +1211,14 @@ "frameId": null, "roundness": null, "seed": 1211271590, - "version": 1, - "versionNonce": 534009896, + "version": 73, + "versionNonce": 1356988830, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, - "text": "TCP Client", + "text": "TCP\nClient", "fontSize": 20, "fontFamily": 1, "textAlign": "center", @@ -1177,15 +1226,16 @@ "containerId": "c8513775c6fe4da58f55", "originalText": "TCP Client", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aX" }, { "id": "09a22cc2431a4ca7b1e0", "type": "rectangle", - "x": 370.0, - "y": 910.0, - "width": 430.0, - "height": 120.0, + "x": 33.42878069196445, + "y": 839.1429792131696, + "width": 651.1429268973216, + "height": 120, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "#ffffff", @@ -1198,8 +1248,8 @@ "frameId": null, "roundness": null, "seed": 1821414404, - "version": 1, - "versionNonce": 1294207142, + "version": 187, + "versionNonce": 2125209054, "isDeleted": false, "boundElements": [ { @@ -1207,17 +1257,18 @@ "type": "text" } ], - "updated": 0, + "updated": 1777287137300, "link": null, - "locked": false + "locked": false, + "index": "aY" }, { "id": "627048f5d60843d0a878", "type": "text", - "x": 374.0, - "y": 920.0, - "width": 422.0, - "height": 100.0, + "x": 88.73049926757835, + "y": 849.1429792131696, + "width": 540.5394897460938, + "height": 100, "angle": 0, "strokeColor": "#67AB9F", "backgroundColor": "transparent", @@ -1230,11 +1281,11 @@ "frameId": null, "roundness": null, "seed": 215362962, - "version": 1, - "versionNonce": 1839110588, + "version": 147, + "versionNonce": 1567142430, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, "text": "TCP Command\n- All CSM command from your code is supported\n- System command provided by TCP Layer is supported\nlist/help/list api ...", @@ -1245,15 +1296,16 @@ "containerId": "09a22cc2431a4ca7b1e0", "originalText": "TCP Command\n- All CSM command from your code is supported\n- System command provided by TCP Layer is supported\nlist/help/list api ...", "lineHeight": 1.25, - "autoResize": false + "autoResize": false, + "index": "aZ" }, { "id": "4343ca43bb1b46aaab89", "type": "arrow", - "x": 545.0, - "y": 610.0, - "width": 0.0, - "height": -320.0, + "x": 494.02845982142844, + "y": 427.2571236746652, + "width": 0.00006103515625, + "height": 85.5999755859375, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -1266,11 +1318,11 @@ "frameId": null, "roundness": null, "seed": 1789827435, - "version": 1, - "versionNonce": 480108401, + "version": 197, + "versionNonce": 63192862, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133813, "link": null, "locked": false, "points": [ @@ -1279,26 +1331,32 @@ 0 ], [ - 0.0, - -320.0 + 0.00006103515625, + -85.5999755859375 ] ], - "lastCommittedPoint": [ - 0.0, - -320.0 - ], - "startBinding": null, - "endBinding": null, + "lastCommittedPoint": null, + "startBinding": { + "elementId": "72a83ed504ed4d0da987", + "focus": 0.008718202065400868, + "gap": 11.60003662109375 + }, + "endBinding": { + "elementId": "3a12c9cd83ac49d4af82", + "focus": 0.010665950554903025, + "gap": 2.79998779296875 + }, "startArrowhead": null, - "endArrowhead": "arrow" + "endArrowhead": "arrow", + "index": "aa" }, { "id": "c967d534ee594bd8b6f7", "type": "arrow", - "x": 140.0, - "y": 599.0, - "width": 395.0, - "height": -214.0, + "x": 91.42848423549094, + "y": 477.85716029575894, + "width": 176.19998168945312, + "height": 218.79998779296875, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -1311,11 +1369,11 @@ "frameId": null, "roundness": null, "seed": 1930751864, - "version": 1, - "versionNonce": 709055981, + "version": 148, + "versionNonce": 1475973022, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287133814, "link": null, "locked": false, "points": [ @@ -1324,26 +1382,32 @@ 0 ], [ - 395.0, - -214.0 + 176.19998168945312, + -218.79998779296875 ] ], - "lastCommittedPoint": [ - 395.0, - -214.0 - ], - "startBinding": null, - "endBinding": null, + "lastCommittedPoint": null, + "startBinding": { + "elementId": "66d9bb7d39ed49bab977", + "focus": 0.8581630760314127, + "gap": 1 + }, + "endBinding": { + "elementId": "8a5c90e4c9214aff98d8", + "focus": 0.7537063280415125, + "gap": 20.600006103515625 + }, "startArrowhead": null, - "endArrowhead": "arrow" + "endArrowhead": "arrow", + "index": "ab" }, { "id": "c9b3b417bdbf45d89ce0", "type": "arrow", - "x": 100.0, - "y": 840.0, - "width": 120.0, - "height": 280.0, + "x": -222.85714285714266, + "y": 750.8571951729912, + "width": 120, + "height": 280, "angle": 0, "strokeColor": "#97D077", "backgroundColor": "transparent", @@ -1356,11 +1420,11 @@ "frameId": null, "roundness": null, "seed": 1801585414, - "version": 1, - "versionNonce": 373435437, + "version": 120, + "versionNonce": 403226206, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, "points": [ @@ -1369,26 +1433,24 @@ 0 ], [ - 120.0, - 280.0 + 120, + 280 ] ], - "lastCommittedPoint": [ - 120.0, - 280.0 - ], + "lastCommittedPoint": null, "startBinding": null, "endBinding": null, "startArrowhead": null, - "endArrowhead": "arrow" + "endArrowhead": "arrow", + "index": "ac" }, { "id": "5f55e1df753d4fb2a6b9", "type": "arrow", - "x": 220.0, - "y": 840.0, - "width": 0.0, - "height": 280.0, + "x": -102.85714285714266, + "y": 750.8571951729912, + "width": 0, + "height": 280, "angle": 0, "strokeColor": "#97D077", "backgroundColor": "transparent", @@ -1401,11 +1463,11 @@ "frameId": null, "roundness": null, "seed": 1574452212, - "version": 1, - "versionNonce": 751253604, + "version": 51, + "versionNonce": 1398620830, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, "points": [ @@ -1414,26 +1476,24 @@ 0 ], [ - 0.0, - 280.0 + 0, + 280 ] ], - "lastCommittedPoint": [ - 0.0, - 280.0 - ], + "lastCommittedPoint": null, "startBinding": null, "endBinding": null, "startArrowhead": null, - "endArrowhead": "arrow" + "endArrowhead": "arrow", + "index": "ad" }, { "id": "7a7d7908178144bb97dd", "type": "arrow", - "x": 340.0, - "y": 840.0, - "width": -120.0, - "height": 280.0, + "x": 17.142857142857338, + "y": 750.8571951729912, + "width": 120, + "height": 280, "angle": 0, "strokeColor": "#97D077", "backgroundColor": "transparent", @@ -1446,11 +1506,11 @@ "frameId": null, "roundness": null, "seed": 1366313377, - "version": 1, - "versionNonce": 633510248, + "version": 51, + "versionNonce": 1914126046, "isDeleted": false, - "boundElements": null, - "updated": 0, + "boundElements": [], + "updated": 1777287137300, "link": null, "locked": false, "points": [ @@ -1459,22 +1519,22 @@ 0 ], [ - -120.0, - 280.0 + -120, + 280 ] ], - "lastCommittedPoint": [ - -120.0, - 280.0 - ], + "lastCommittedPoint": null, "startBinding": null, "endBinding": null, "startArrowhead": null, - "endArrowhead": "arrow" + "endArrowhead": "arrow", + "index": "ae" } ], "appState": { - "gridSize": null, + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, "viewBackgroundColor": "#ffffff" }, "files": {} diff --git a/.doc/Protocol.v0.(zh-cn).md b/.doc/Protocol.v0.(zh-cn).md index e4f5321..67ba818 100644 --- a/.doc/Protocol.v0.(zh-cn).md +++ b/.doc/Protocol.v0.(zh-cn).md @@ -200,6 +200,7 @@ sequenceDiagram > [!NOTE] > `status` 和 `interrupt` 两种订阅广播类型均受支持: +> > - `status`(`0x06`):普通广播,订阅模块的常规状态变化 > - `interrupt`(`0x07`):中断广播,订阅模块触发的中断事件 > diff --git a/.doc/Protocol.v1.(zh-cn).md b/.doc/obsolete/Protocol.v1.(zh-cn).md similarity index 100% rename from .doc/Protocol.v1.(zh-cn).md rename to .doc/obsolete/Protocol.v1.(zh-cn).md diff --git a/CSM-TCP-Router.lvproj b/CSM-TCP-Router.lvproj index 123e3ed..92feb53 100644 --- a/CSM-TCP-Router.lvproj +++ b/CSM-TCP-Router.lvproj @@ -89,6 +89,7 @@ + @@ -105,6 +106,9 @@ + + + diff --git a/SDK/PythonClientAPI/README.md b/SDK/PythonClientAPI/README.md deleted file mode 100644 index 78a0e18..0000000 --- a/SDK/PythonClientAPI/README.md +++ /dev/null @@ -1,166 +0,0 @@ -# CSM-TCP-Router Python Client API - -这是一个Python版本的CSM-TCP-Router客户端API,实现了与LabVIEW版本相同的功能,可以连接到CSM-TCP-Router服务器,发送命令并接收响应。 - -## 功能特性 - -- 与CSM-TCP-Router服务器建立TCP连接 -- 发送同步命令并等待回复 -- 发送异步命令 -- 发送无返回异步命令 -- Ping服务器 -- 订阅状态变化通知 -- 等待服务器可用 -- 完整的错误处理和线程安全设计 - -## 文件结构 - -- `tcp_router_client.py`: 主要的客户端API类实现 -- `example_usage.py`: 使用示例代码 -- `README.md`: 使用说明文档 - -## 使用方法 - -### 基本连接 - -```python -from tcp_router_client import TcpRouterClient - -# 创建客户端实例 -client = TcpRouterClient() - -# 连接到服务器 -if client.connect("localhost", 9999): - print("连接成功") - # 执行操作... - - # 断开连接 - client.disconnect() -else: - print("连接失败") -``` - -### 发送同步命令 - -```python -# 发送命令并等待回复 -response = client.send_message_and_wait_for_reply("List") -print(f"回复: {response}") -``` - -### 发送异步命令 - -```python -# 发送异步命令 -async_cmd = "API: Read Channels -> AI" -client.post_message(async_cmd) - -# 发送无返回异步命令 -no_rep_cmd = "API: Refresh ->| System" -client.post_no_rep_message(no_rep_cmd) -``` - -### 订阅状态变化 - -```python -# 定义状态变化回调函数 -def status_callback(status_data): - print(f"收到状态更新: {status_data}") - -# 注册状态变化通知 -client.register_status_change("Status", "AI", status_callback) - -# 取消订阅 -client.unregister_status_change("Status", "AI") -``` - -### 等待服务器可用 - -```python -# 等待服务器可用,最多等待30秒 -success = client.wait_for_server("localhost", 9999, timeout=30) -if success: - print("服务器已可用") - client.connect("localhost", 9999) -``` - -### Ping服务器 - -```python -# Ping服务器,检查连接状态 -success, elapsed = client.ping() -if success: - print(f"Ping成功,延迟: {elapsed*1000:.2f}ms") -``` - -## 通讯协议 - -Python客户端实现了与CSM-TCP-Router相同的通讯协议,数据包格式如下: - -``` -| 数据长度(4B) | 版本(1B) | TYPE(1B) | FLAG1(1B) | FLAG2(1B) | 文本数据 | -╰─────────────────────────── 包头 ──────────────────────────╯╰──── 数据长度字范围 ────╯ -``` - -支持的数据包类型: -- 信息数据包(info) - `0x00` -- 错误数据包(error) - `0x01` -- 指令数据包(cmd) - `0x02` -- 同步响应数据包(resp) - `0x03` -- 异步响应数据包(async-resp) - `0x04` -- 订阅返回数据包(status) - `0x05` - -## 支持的指令集 - -### 1. CSM 消息指令集 -由原有基于CSM开发的代码定义,支持: -- 同步消息 (-@) -- 异步消息 (->) -- 无返回异步消息 (->|) - -### 2. CSM-TCP-Router 指令集 -- `List` - 列出所有的CSM模块 -- `List API`: 列出指定模块的所有API -- `List State`: 列出指定模块的所有CSM状态 -- `Help` - 显示模块的帮助文件 -- `Refresh lvcsm`: 刷新缓存文件 -- `Ping` - 测试服务器连接 - -## 注意事项 - -1. 确保在使用完客户端后调用`disconnect()`或`release()`方法释放资源 -2. 回调函数将在接收线程中执行,避免在回调函数中执行长时间阻塞操作 -3. 当网络连接异常断开时,客户端会自动将`connected`标志设为False -4. 对于频繁发送消息的场景,建议使用连接池或重用同一个客户端实例 - -## 示例程序 - -请参考`example_usage.py`文件,其中包含了详细的使用示例。 - -## 依赖项 - -本客户端API仅使用Python标准库,无需安装额外依赖: -- `socket`: 用于TCP通信 -- `struct`: 用于解析数据包 -- `threading`: 用于多线程处理 -- `queue`: 用于线程间通信 -- `json`: 用于数据序列化(预留) -- `time`: 用于超时和延时 -- `enum`: 用于定义数据包类型枚举 - -## 与LabVIEW版本对比 - -此Python版本实现了LabVIEW版本ClientAPI的所有核心功能: -- `Obtain.vi` -> `__init__()` 和 `obtain()` -- `Release.vi` -> `release()` -- `Send Message and Wait for Reply.vi` -> `send_message_and_wait_for_reply()` -- `Post Message.vi` -> `post_message()` -- `Post No-Rep Message.vi` -> `post_no_rep_message()` -- `Ping.vi` -> `ping()` -- `Register Status Change.vi` -> `register_status_change()` -- `Unregister Status Change.vi` -> `unregister_status_change()` -- `Wait for Server.vi` -> `wait_for_server()` - -## 版本历史 - -- v1.0.0: 初始版本,实现基本功能 \ No newline at end of file diff --git a/SDK/PythonClientAPI/example_usage.py b/SDK/PythonClientAPI/example_usage.py deleted file mode 100644 index 5569d7c..0000000 --- a/SDK/PythonClientAPI/example_usage.py +++ /dev/null @@ -1,103 +0,0 @@ -import time -from tcp_router_client import TcpRouterClient - -"""CSM-TCP-Router Python客户端API使用示例""" - -def main(): - # 创建客户端实例 - client = TcpRouterClient() - - print("CSM-TCP-Router Python客户端API示例") - print("================================") - - # 示例1: 连接到服务器 - print("\n示例1: 连接到服务器") - if client.connect("localhost", 30007): - print("✅ 成功连接到服务器") - else: - print("❌ 连接服务器失败,请确保服务器已启动") - return - - # 示例2: Ping服务器 - print("\n示例2: Ping服务器") - success, elapsed = client.ping(timeout=2) - if success: - print(f"✅ Ping成功,延迟: {elapsed*1000:.2f}ms") - else: - print("❌ Ping失败") - - # 示例3: 发送同步命令并等待回复 - print("\n示例3: 发送同步命令并等待回复") - # 列出所有CSM模块 - response = client.send_message_and_wait_for_reply("List") - print(f"命令: List") - print(f"回复: {response}") - - # 列出特定模块的API - # 注意:这里假设存在名为"AI"的模块,如果不存在,您需要修改为实际存在的模块名 - module_name = "AI" - response = client.send_message_and_wait_for_reply(f"List API {module_name}") - print(f"\n命令: List API {module_name}") - print(f"回复: {response}") - - # 示例4: 发送异步命令 - print("\n示例4: 发送异步命令") - # 注意:这里的命令需要根据实际的CSM模块进行调整 - async_cmd = "API: Read Channels -> AI" - success = client.post_message(async_cmd) - print(f"命令: {async_cmd}") - print(f"发送结果: {'✅ 成功' if success else '❌ 失败'}") - - # 示例5: 发送无返回异步命令 - print("\n示例5: 发送无返回异步命令") - # 注意:这里的命令需要根据实际的CSM模块进行调整 - no_rep_cmd = "API: Refresh ->| System" - success = client.post_no_rep_message(no_rep_cmd) - print(f"命令: {no_rep_cmd}") - print(f"发送结果: {'✅ 成功' if success else '❌ 失败'}") - - # 示例6: 订阅状态变化 - print("\n示例6: 订阅状态变化") - # 状态变化回调函数 - def status_callback(status_data): - print(f"📢 收到状态更新: {status_data}") - - # 注册状态变化通知 - # 注意:这里假设存在名为"AI"的模块和"Status"状态,如果不存在,您需要修改为实际存在的模块名和状态名 - success = client.register_status_change("Status", "AI", status_callback) - print(f"订阅 'Status@AI' 结果: {'✅ 成功' if success else '❌ 失败'}") - - # 保持连接一段时间,等待状态更新 - print("\n等待5秒,观察状态更新...") - time.sleep(5) - - # 取消订阅 - success = client.unregister_status_change("Status", "AI") - print(f"取消订阅 'Status@AI' 结果: {'✅ 成功' if success else '❌ 失败'}") - - # 示例7: 等待服务器可用 - print("\n示例7: 等待服务器可用(演示用,当前已连接)") - # 断开当前连接 - client.disconnect() - print("已断开连接") - - # 等待服务器可用 - print("等待服务器可用,最多等待10秒...") - # 注意:如果服务器未运行,这个调用将会超时 - success = client.wait_for_server("localhost", 9999, timeout=10) - print(f"服务器可用检查结果: {'✅ 服务器可用' if success else '❌ 服务器不可用'}") - - # 重新连接(如果服务器可用) - if success: - client.connect("localhost", 9999) - print("✅ 已重新连接到服务器") - - # 示例8: 释放资源 - print("\n示例8: 释放资源") - client.release() - print("✅ 客户端资源已释放") - - print("\n示例执行完毕") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/SDK/PythonClientAPI/tcp_router_client.py b/SDK/PythonClientAPI/tcp_router_client.py deleted file mode 100644 index 0780e4a..0000000 --- a/SDK/PythonClientAPI/tcp_router_client.py +++ /dev/null @@ -1,259 +0,0 @@ -import socket -import struct -import threading -import queue -import json -import time -from enum import Enum - -class PacketType(Enum): - INFO = 0x00 - ERROR = 0x01 - CMD = 0x02 - RESP = 0x03 - ASYNC_RESP = 0x04 - STATUS = 0x05 - -class TcpRouterClient: - def __init__(self): - self.socket = None - self.connected = False - self.host = "" - self.port = 0 - self.recv_thread = None - self.stop_event = threading.Event() - self.response_queue = queue.Queue() - self.async_response_callbacks = {} - self.status_callbacks = {} - self.async_response_queue = queue.Queue() - self.status_queue = queue.Queue() - self.lock = threading.Lock() - - def connect(self, host, port, timeout=5): - """连接到CSM-TCP-Router服务器""" - try: - self.host = host - self.port = port - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.settimeout(timeout) - self.socket.connect((host, port)) - self.connected = True - self.stop_event.clear() - self.recv_thread = threading.Thread(target=self._receive_thread) - self.recv_thread.daemon = True - self.recv_thread.start() - return True - except Exception as e: - print(f"连接失败: {e}") - self.connected = False - return False - - def disconnect(self): - """断开与服务器的连接""" - if self.connected: - self.stop_event.set() - try: - if self.socket: - self.socket.close() - except: - pass - self.connected = False - if self.recv_thread: - self.recv_thread.join(timeout=2) - - def send_message(self, message, packet_type, flag1=0, flag2=0): - """发送消息到服务器""" - if not self.connected: - print("未连接到服务器") - return False - - try: - # 确保消息为字节类型 - if isinstance(message, str): - message_bytes = message.encode() # 使用系统默认编码 - else: - message_bytes = message - - # 计算数据长度 - data_len = len(message_bytes) - # 构建数据包 - header = struct.pack('!IBBBB', data_len, 0x01, packet_type.value, flag1, flag2) - # 发送数据包 - with self.lock: - self.socket.sendall(header) - self.socket.sendall(message_bytes) - return True - except Exception as e: - print(f"发送消息失败: {e}") - self.connected = False - return False - - def send_message_and_wait_for_reply(self, message, timeout=5): - """发送消息并等待回复""" - if not self.send_message(message, PacketType.CMD): - return None - - try: - response = self.response_queue.get(timeout=timeout) - return response - except queue.Empty: - print("等待回复超时") - return None - - def post_message(self, message): - """发送异步消息""" - return self.send_message(message, PacketType.CMD) - - def post_no_rep_message(self, message): - """发送无返回异步消息""" - return self.send_message(message, PacketType.CMD) - - def ping(self, timeout=2): - """Ping服务器""" - start_time = time.time() - response = self.send_message_and_wait_for_reply("Ping", timeout=timeout) - if response: - elapsed = time.time() - start_time - return True, elapsed - return False, 0 - - def register_status_change(self, status_name, module_name, callback=None): - """注册状态变化通知""" - cmd = f"{status_name}@{module_name} ->" - success = self.send_message(cmd, PacketType.CMD) - if success and callback: - with self.lock: - self.status_callbacks[(status_name, module_name)] = callback - return success - - def unregister_status_change(self, status_name, module_name): - """取消注册状态变化通知""" - cmd = f"{status_name}@{module_name} ->" - success = self.send_message(cmd, PacketType.CMD) - if success: - with self.lock: - key = (status_name, module_name) - if key in self.status_callbacks: - del self.status_callbacks[key] - return success - - def wait_for_server(self, host, port, timeout=30): - """等待服务器可用""" - start_time = time.time() - while time.time() - start_time < timeout: - if self.connect(host, port, timeout=1): - self.disconnect() - return True - time.sleep(0.5) - return False - - def _receive_thread(self): - """接收线程,处理来自服务器的消息""" - while not self.stop_event.is_set(): - try: - # 接收包头 - header = self._receive_all(8) # 4+1+1+1+1=8字节 - if not header: - break - - # 解析包头 - data_len, version, packet_type, flag1, flag2 = struct.unpack('!IBBBB', header) - - # 接收数据(保持字节类型) - data = self._receive_all(data_len) - - # 处理不同类型的数据包 - if packet_type == PacketType.RESP.value: - self.response_queue.put(data) - elif packet_type == PacketType.ASYNC_RESP.value: - self._handle_async_response(data) - elif packet_type == PacketType.STATUS.value: - self._handle_status(data) - elif packet_type == PacketType.INFO.value: - print(f"[INFO] {data}") - elif packet_type == PacketType.ERROR.value: - print(f"[ERROR] {data}") - - except Exception as e: - if not self.stop_event.is_set(): - print(f"接收数据错误: {e}") - break - - # 线程结束,标记断开连接 - self.connected = False - - def _receive_all(self, size): - """接收指定大小的数据""" - data = b'' - while len(data) < size: - packet = self.socket.recv(size - len(data)) - if not packet: - return b'' - data += packet - return data - - def _handle_async_response(self, data): - """处理异步响应""" - self.async_response_queue.put(data) - # 这里可以根据需要调用注册的回调函数 - # 例如,可以解析data中的信息,找到对应的回调函数并调用 - - def _handle_status(self, data): - """处理状态更新""" - self.status_queue.put(data) - # 解析状态数据并调用相应的回调函数 - # 简化处理,实际应用中可能需要更复杂的解析逻辑 - parts = data.split(' >> ', 1) - if len(parts) == 2: - status_info, _ = parts - status_parts = status_info.split(' <- ', 1) - if len(status_parts) == 2: - status_name, module_name = status_parts - with self.lock: - callback = self.status_callbacks.get((status_name, module_name)) - if callback: - callback(data) - - def obtain(self): - """获取客户端实例(模拟LabVIEW的Obtain.vi)""" - # 在Python中,这个方法可以简单返回自身实例 - return self - - def release(self): - """释放客户端资源(模拟LabVIEW的Release.vi)""" - self.disconnect() - -# 示例用法 -if __name__ == "__main__": - client = TcpRouterClient() - - # 连接服务器 - if client.connect("localhost", 30007): - print("连接成功") - - # 发送Ping命令 - success, elapsed = client.ping() - if success: - print(f"Ping成功,延迟: {elapsed*1000:.2f}ms") - - # 发送命令并等待回复 - response = client.send_message_and_wait_for_reply("List") - print(f"List命令回复: {response}") - - # 订阅状态变化 - def status_callback(data): - print(f"收到状态更新: {data}") - - client.register_status_change("Status", "AI", status_callback) - - # 保持连接一段时间 - time.sleep(5) - - # 取消订阅 - client.unregister_status_change("Status", "AI") - - # 断开连接 - client.disconnect() - print("已断开连接") - else: - print("连接失败") \ No newline at end of file diff --git a/SDK/python-package/CHANGELOG.md b/SDK/python/CHANGELOG.md similarity index 100% rename from SDK/python-package/CHANGELOG.md rename to SDK/python/CHANGELOG.md diff --git a/SDK/python-package/LICENSE b/SDK/python/LICENSE similarity index 100% rename from SDK/python-package/LICENSE rename to SDK/python/LICENSE diff --git a/SDK/python-package/README.md b/SDK/python/README.md similarity index 100% rename from SDK/python-package/README.md rename to SDK/python/README.md diff --git a/SDK/python-package/README.zh-cn.md b/SDK/python/README.zh-cn.md similarity index 100% rename from SDK/python-package/README.zh-cn.md rename to SDK/python/README.zh-cn.md diff --git a/SDK/python-package/examples/async_usage.py b/SDK/python/examples/async_usage.py similarity index 100% rename from SDK/python-package/examples/async_usage.py rename to SDK/python/examples/async_usage.py diff --git a/SDK/python-package/examples/basic_usage.py b/SDK/python/examples/basic_usage.py similarity index 100% rename from SDK/python-package/examples/basic_usage.py rename to SDK/python/examples/basic_usage.py diff --git a/SDK/python-package/examples/subscribe_status.py b/SDK/python/examples/subscribe_status.py similarity index 100% rename from SDK/python-package/examples/subscribe_status.py rename to SDK/python/examples/subscribe_status.py diff --git a/SDK/python-package/pyproject.toml b/SDK/python/pyproject.toml similarity index 100% rename from SDK/python-package/pyproject.toml rename to SDK/python/pyproject.toml diff --git a/SDK/python-package/src/csm_tcp_router/__init__.py b/SDK/python/src/csm_tcp_router/__init__.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/__init__.py rename to SDK/python/src/csm_tcp_router/__init__.py diff --git a/SDK/python-package/src/csm_tcp_router/_errors.py b/SDK/python/src/csm_tcp_router/_errors.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/_errors.py rename to SDK/python/src/csm_tcp_router/_errors.py diff --git a/SDK/python-package/src/csm_tcp_router/_protocol.py b/SDK/python/src/csm_tcp_router/_protocol.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/_protocol.py rename to SDK/python/src/csm_tcp_router/_protocol.py diff --git a/SDK/python-package/src/csm_tcp_router/_transport.py b/SDK/python/src/csm_tcp_router/_transport.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/_transport.py rename to SDK/python/src/csm_tcp_router/_transport.py diff --git a/SDK/python-package/src/csm_tcp_router/async_client.py b/SDK/python/src/csm_tcp_router/async_client.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/async_client.py rename to SDK/python/src/csm_tcp_router/async_client.py diff --git a/SDK/python-package/src/csm_tcp_router/client.py b/SDK/python/src/csm_tcp_router/client.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/client.py rename to SDK/python/src/csm_tcp_router/client.py diff --git a/SDK/python-package/src/csm_tcp_router/exceptions.py b/SDK/python/src/csm_tcp_router/exceptions.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/exceptions.py rename to SDK/python/src/csm_tcp_router/exceptions.py diff --git a/SDK/python-package/src/csm_tcp_router/models.py b/SDK/python/src/csm_tcp_router/models.py similarity index 100% rename from SDK/python-package/src/csm_tcp_router/models.py rename to SDK/python/src/csm_tcp_router/models.py diff --git a/SDK/python-package/tests/__init__.py b/SDK/python/tests/__init__.py similarity index 100% rename from SDK/python-package/tests/__init__.py rename to SDK/python/tests/__init__.py diff --git a/SDK/python-package/tests/conftest.py b/SDK/python/tests/conftest.py similarity index 100% rename from SDK/python-package/tests/conftest.py rename to SDK/python/tests/conftest.py diff --git a/SDK/python-package/tests/test_async_client.py b/SDK/python/tests/test_async_client.py similarity index 100% rename from SDK/python-package/tests/test_async_client.py rename to SDK/python/tests/test_async_client.py diff --git a/SDK/python-package/tests/test_client.py b/SDK/python/tests/test_client.py similarity index 100% rename from SDK/python-package/tests/test_client.py rename to SDK/python/tests/test_client.py diff --git a/SDK/python-package/tests/test_integration.py b/SDK/python/tests/test_integration.py similarity index 100% rename from SDK/python-package/tests/test_integration.py rename to SDK/python/tests/test_integration.py diff --git a/SDK/python-package/tests/test_protocol.py b/SDK/python/tests/test_protocol.py similarity index 100% rename from SDK/python-package/tests/test_protocol.py rename to SDK/python/tests/test_protocol.py From 279071b6f9ea3ff911f029df1773064012911816 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:44:14 +0800 Subject: [PATCH 05/12] Consolidate Python SDK into single-file `csm_tcp_router_client` module and release as v0.3.0 (#37) * Consolidate Python SDK into single-file csm_tcp_router_client module Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/ffc74aa4-b55d-45b4-b066-749d1db8c176 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Bump SDK to 0.3.0 and fix workflow paths so publish jobs trigger Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/f2eec384-ae45-4817-bcb3-1f990db4954b Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- .github/workflows/Python_SDK.yml | 12 +- SDK/python/CHANGELOG.md | 28 +- SDK/python/README.md | 4 +- SDK/python/README.zh-cn.md | 4 +- SDK/python/examples/async_usage.py | 3 +- SDK/python/examples/basic_usage.py | 3 +- SDK/python/examples/subscribe_status.py | 3 +- SDK/python/pyproject.toml | 9 +- SDK/python/src/csm_tcp_router/__init__.py | 58 - SDK/python/src/csm_tcp_router/_errors.py | 26 - SDK/python/src/csm_tcp_router/_protocol.py | 91 -- SDK/python/src/csm_tcp_router/_transport.py | 176 --- SDK/python/src/csm_tcp_router/async_client.py | 523 ------- SDK/python/src/csm_tcp_router/client.py | 456 ------ SDK/python/src/csm_tcp_router/exceptions.py | 45 - SDK/python/src/csm_tcp_router/models.py | 151 -- SDK/python/src/csm_tcp_router_client.py | 1316 +++++++++++++++++ SDK/python/tests/conftest.py | 3 +- SDK/python/tests/test_async_client.py | 19 +- SDK/python/tests/test_client.py | 18 +- SDK/python/tests/test_integration.py | 8 +- SDK/python/tests/test_protocol.py | 6 +- 22 files changed, 1386 insertions(+), 1576 deletions(-) delete mode 100644 SDK/python/src/csm_tcp_router/__init__.py delete mode 100644 SDK/python/src/csm_tcp_router/_errors.py delete mode 100644 SDK/python/src/csm_tcp_router/_protocol.py delete mode 100644 SDK/python/src/csm_tcp_router/_transport.py delete mode 100644 SDK/python/src/csm_tcp_router/async_client.py delete mode 100644 SDK/python/src/csm_tcp_router/client.py delete mode 100644 SDK/python/src/csm_tcp_router/exceptions.py delete mode 100644 SDK/python/src/csm_tcp_router/models.py create mode 100644 SDK/python/src/csm_tcp_router_client.py diff --git a/.github/workflows/Python_SDK.yml b/.github/workflows/Python_SDK.yml index 5711af5..d12bbda 100644 --- a/.github/workflows/Python_SDK.yml +++ b/.github/workflows/Python_SDK.yml @@ -3,17 +3,17 @@ name: Python SDK on: push: paths: - - 'SDK/python-package/**' + - 'SDK/python/**' tags: - 'python-sdk-v*' pull_request: paths: - - 'SDK/python-package/**' + - 'SDK/python/**' workflow_dispatch: defaults: run: - working-directory: SDK/python-package + working-directory: SDK/python jobs: # ------------------------------------------------------------------------- @@ -67,7 +67,7 @@ jobs: pip install pytest pytest-cov pytest-asyncio - name: Run tests - run: pytest --cov=csm_tcp_router --cov-report=term-missing + run: pytest --cov=csm_tcp_router_client --cov-report=term-missing # ------------------------------------------------------------------------- # Build (wheel + sdist) @@ -96,13 +96,13 @@ jobs: - name: Verify wheel is importable run: | pip install dist/*.whl - python -c "import csm_tcp_router; print('Version:', csm_tcp_router.__version__)" + python -c "import csm_tcp_router_client; print('Version:', csm_tcp_router_client.__version__)" - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: python-sdk-dist - path: SDK/python-package/dist/ + path: SDK/python/dist/ # ------------------------------------------------------------------------- # Publish to TestPyPI (on tag push only – gates production publish) diff --git a/SDK/python/CHANGELOG.md b/SDK/python/CHANGELOG.md index a69cd48..2183933 100644 --- a/SDK/python/CHANGELOG.md +++ b/SDK/python/CHANGELOG.md @@ -11,6 +11,31 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm --- +## [0.3.0] – 2026-04-27 + +### Changed + +- Consolidated the entire client SDK into a single importable module + `csm_tcp_router_client` (file: `src/csm_tcp_router_client.py`). The old + `csm_tcp_router` package directory and its sub-modules (`client`, + `async_client`, `models`, `exceptions`, `_protocol`, `_transport`, + `_errors`) have been removed. All public symbols (`TcpRouterClient`, + `AsyncTcpRouterClient`, exceptions, models, protocol helpers) are now + re-exported directly from the top-level `csm_tcp_router_client` module. +- **Breaking**: replace `from csm_tcp_router import …` with + `from csm_tcp_router_client import …`. Sub-module imports such as + `from csm_tcp_router.exceptions import …` or + `from csm_tcp_router.models import …` should also be replaced with + `from csm_tcp_router_client import …`. +- CI workflow `Python_SDK.yml` now triggers on changes under `SDK/python/**` + (previously the stale `SDK/python-package/**` path filter prevented the + publish jobs from firing); `working-directory` and the artifact upload + path were updated to match. +- `pyproject.toml` `Documentation` and `Changelog` URLs updated to point at + `SDK/python/` instead of the old `SDK/python-package/` path. + +--- + ## [0.2.0] – 2026-04-22 ### Added @@ -79,6 +104,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - GitHub Actions workflow `Python_SDK.yml`: lint (ruff), test (pytest) on Python 3.8–3.12, build, and optional publish to PyPI on tag. -[Unreleased]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/compare/python-sdk-v0.2.0...HEAD +[Unreleased]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/compare/python-sdk-v0.3.0...HEAD +[0.3.0]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/compare/python-sdk-v0.2.0...python-sdk-v0.3.0 [0.2.0]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/compare/python-sdk-v0.1.0...python-sdk-v0.2.0 [0.1.0]: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/releases/tag/python-sdk-v0.1.0 diff --git a/SDK/python/README.md b/SDK/python/README.md index 1460803..16d3351 100644 --- a/SDK/python/README.md +++ b/SDK/python/README.md @@ -28,7 +28,7 @@ Requires Python 3.8 or later. No third-party dependencies—only the Python sta ### Synchronous client ```python -from csm_tcp_router import TcpRouterClient +from csm_tcp_router_client import TcpRouterClient with TcpRouterClient() as client: client.connect("localhost", 30007) @@ -49,7 +49,7 @@ with TcpRouterClient() as client: ```python import asyncio -from csm_tcp_router import AsyncTcpRouterClient +from csm_tcp_router_client import AsyncTcpRouterClient async def main(): async with AsyncTcpRouterClient() as client: diff --git a/SDK/python/README.zh-cn.md b/SDK/python/README.zh-cn.md index 49f3ef5..4e19ae1 100644 --- a/SDK/python/README.zh-cn.md +++ b/SDK/python/README.zh-cn.md @@ -28,7 +28,7 @@ pip install csm-tcp-router-client ### 同步客户端 ```python -from csm_tcp_router import TcpRouterClient +from csm_tcp_router_client import TcpRouterClient with TcpRouterClient() as client: client.connect("localhost", 30007) @@ -49,7 +49,7 @@ with TcpRouterClient() as client: ```python import asyncio -from csm_tcp_router import AsyncTcpRouterClient +from csm_tcp_router_client import AsyncTcpRouterClient async def main(): async with AsyncTcpRouterClient() as client: diff --git a/SDK/python/examples/async_usage.py b/SDK/python/examples/async_usage.py index 226023c..55a6e88 100644 --- a/SDK/python/examples/async_usage.py +++ b/SDK/python/examples/async_usage.py @@ -8,8 +8,7 @@ import asyncio -from csm_tcp_router import AsyncTcpRouterClient -from csm_tcp_router.models import StatusNotification +from csm_tcp_router_client import AsyncTcpRouterClient, StatusNotification async def on_status(notif: StatusNotification) -> None: diff --git a/SDK/python/examples/basic_usage.py b/SDK/python/examples/basic_usage.py index 3e07d5a..e53652e 100644 --- a/SDK/python/examples/basic_usage.py +++ b/SDK/python/examples/basic_usage.py @@ -15,8 +15,7 @@ """ -from csm_tcp_router import TcpRouterClient -from csm_tcp_router.exceptions import ConnectionError +from csm_tcp_router_client import ConnectionError, TcpRouterClient HOST = "localhost" PORT = 30007 diff --git a/SDK/python/examples/subscribe_status.py b/SDK/python/examples/subscribe_status.py index 0442467..165df6b 100644 --- a/SDK/python/examples/subscribe_status.py +++ b/SDK/python/examples/subscribe_status.py @@ -19,8 +19,7 @@ import threading import time -from csm_tcp_router import StatusNotification, TcpRouterClient -from csm_tcp_router.exceptions import ConnectionError, ServerError +from csm_tcp_router_client import ConnectionError, ServerError, StatusNotification, TcpRouterClient HOST = "localhost" PORT = 30007 diff --git a/SDK/python/pyproject.toml b/SDK/python/pyproject.toml index 3b77791..90ec5bb 100644 --- a/SDK/python/pyproject.toml +++ b/SDK/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "csm-tcp-router-client" -version = "0.2.0" +version = "0.3.0" description = "Python client SDK for the CSM-TCP-Router LabVIEW server" readme = "README.md" license = { text = "MIT" } @@ -43,11 +43,12 @@ classifiers = [ Homepage = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App" Repository = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App" Issues = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/issues" -Documentation = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/blob/main/SDK/python-package/README.md" -Changelog = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/blob/main/SDK/python-package/CHANGELOG.md" +Documentation = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/blob/main/SDK/python/README.md" +Changelog = "https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/blob/main/SDK/python/CHANGELOG.md" [tool.hatch.build.targets.wheel] -packages = ["src/csm_tcp_router"] +only-include = ["src/csm_tcp_router_client.py"] +sources = ["src"] # --------------------------------------------------------------------------- # Testing diff --git a/SDK/python/src/csm_tcp_router/__init__.py b/SDK/python/src/csm_tcp_router/__init__.py deleted file mode 100644 index 1a8d875..0000000 --- a/SDK/python/src/csm_tcp_router/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -"""csm-tcp-router-client – Python client SDK for the CSM-TCP-Router server. - -Sync usage:: - - from csm_tcp_router import TcpRouterClient - - with TcpRouterClient() as client: - client.connect("localhost", 30007) - print(client.list_modules()) - -Async usage:: - - import asyncio - from csm_tcp_router import AsyncTcpRouterClient - - async def main(): - async with AsyncTcpRouterClient() as client: - await client.connect("localhost", 30007) - print(await client.list_modules()) - - asyncio.run(main()) -""" - -from .async_client import AsyncTcpRouterClient -from .client import TcpRouterClient -from .exceptions import ( - ConnectionError, - ProtocolError, - ServerError, - TcpRouterError, - TimeoutError, -) -from .models import ( - AsyncResponse, - CommandResponse, - PacketType, - StatusNotification, -) - -__version__ = "0.2.0" - -__all__ = [ - "TcpRouterClient", - "AsyncTcpRouterClient", - # Exceptions - "TcpRouterError", - "ConnectionError", - "TimeoutError", - "ProtocolError", - "ServerError", - # Models - "PacketType", - "CommandResponse", - "AsyncResponse", - "StatusNotification", - # Version - "__version__", -] diff --git a/SDK/python/src/csm_tcp_router/_errors.py b/SDK/python/src/csm_tcp_router/_errors.py deleted file mode 100644 index 41c6017..0000000 --- a/SDK/python/src/csm_tcp_router/_errors.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Shared server-error parsing helper. - -Internal module – nothing is re-exported from the package. -""" - -from __future__ import annotations - -from .exceptions import ServerError -from .models import Packet - -__all__: list = [] # internal; nothing re-exported - - -def _parse_server_error(packet: Packet) -> ServerError: - """Extract code and message from a CSM Error format ``[Error: ] ``.""" - text = packet.data.decode("utf-8", errors="replace").strip() - code = "" - msg = text - if text.startswith("[Error:"): - try: - end_idx = text.index("]") - code = text[7:end_idx].strip() - msg = text[end_idx + 1:].strip() - except ValueError: - pass - return ServerError(msg, code) diff --git a/SDK/python/src/csm_tcp_router/_protocol.py b/SDK/python/src/csm_tcp_router/_protocol.py deleted file mode 100644 index 4834ff0..0000000 --- a/SDK/python/src/csm_tcp_router/_protocol.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Internal protocol v0 codec. - -Wire format (8-byte header, big-endian):: - - | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | - ╰────────────────────────── Header (8B) ──────────────────────────╯ - -followed by exactly ``Data Length`` bytes of payload. - -This module is internal; nothing is re-exported from the package. -""" - -from __future__ import annotations - -import struct -from typing import Tuple - -from .exceptions import ProtocolError -from .models import Packet, PacketType - -__all__: list = [] # internal; nothing re-exported - -# Header layout: big-endian uint32 data_len + 4 x uint8 (version, type, flag1, flag2) -_HEADER_FORMAT = "!IBBBB" - -#: Number of bytes in the fixed packet header. -HEADER_SIZE: int = struct.calcsize(_HEADER_FORMAT) # == 8 - -#: Protocol version byte sent in every outgoing packet. -PROTOCOL_VERSION: int = 0x01 - - -def encode_packet( - data: bytes, - packet_type: PacketType, - flag1: int = 0, - flag2: int = 0, -) -> bytes: - """Encode *data* into a complete wire-format packet (header + body). - - :param data: Raw payload bytes. - :param packet_type: :class:`~csm_tcp_router.models.PacketType` for the header. - :param flag1: FLAG1 byte (currently unused; defaults to 0). - :param flag2: FLAG2 byte (currently unused; defaults to 0). - :returns: Concatenated header + payload bytes ready for ``sendall()``. - """ - header = struct.pack( - _HEADER_FORMAT, - len(data), - PROTOCOL_VERSION, - packet_type.value, - flag1, - flag2, - ) - return header + data - - -def decode_header(header_bytes: bytes) -> Tuple[int, int, int, int, int]: - """Decode an 8-byte header into its constituent fields. - - :returns: ``(data_len, version, type_byte, flag1, flag2)`` - :raises ProtocolError: if *header_bytes* is not exactly :data:`HEADER_SIZE` bytes. - """ - if len(header_bytes) != HEADER_SIZE: - raise ProtocolError( - f"Expected {HEADER_SIZE}-byte header, got {len(header_bytes)} bytes." - ) - return struct.unpack(_HEADER_FORMAT, header_bytes) # type: ignore[return-value] - - -def parse_packet(header_bytes: bytes, body: bytes) -> Packet: - """Build a :class:`~csm_tcp_router.models.Packet` from raw header + body. - - Unknown packet type bytes are mapped to :attr:`PacketType.INFO` for - forward compatibility (the server may introduce new types in future - protocol revisions). - - :raises ProtocolError: on header size mismatch or body length mismatch. - """ - data_len, version, type_byte, flag1, flag2 = decode_header(header_bytes) - if len(body) != data_len: - raise ProtocolError( - f"Payload length mismatch: header says {data_len} bytes, " - f"got {len(body)} bytes." - ) - try: - ptype = PacketType(type_byte) - except ValueError: - # Forward-compatible: treat unknown type as INFO - ptype = PacketType.INFO - return Packet(type=ptype, data=body, version=version, flag1=flag1, flag2=flag2) diff --git a/SDK/python/src/csm_tcp_router/_transport.py b/SDK/python/src/csm_tcp_router/_transport.py deleted file mode 100644 index 2de9938..0000000 --- a/SDK/python/src/csm_tcp_router/_transport.py +++ /dev/null @@ -1,176 +0,0 @@ -"""Internal TCP transport layer with a background receive thread. - -This module is internal; nothing is re-exported from the package. -""" - -from __future__ import annotations - -import socket -import struct -import threading -from typing import Callable, Optional - -from ._protocol import HEADER_SIZE, parse_packet -from .exceptions import ConnectionError as RouterConnectionError -from .exceptions import ProtocolError -from .models import Packet - -__all__: list = [] # internal; nothing re-exported - - -class Transport: - """Thread-safe, blocking TCP transport. - - A background daemon thread continuously reads packets from the socket and - dispatches them via *on_packet*. Callers are responsible for keeping - callbacks fast and non-blocking, as they run in the receive thread. - - Lifecycle:: - - t = Transport(on_packet=..., on_disconnect=...) - t.connect("localhost", 30007) - t.send_raw(wire_bytes) - t.disconnect() - """ - - def __init__( - self, - on_packet: Callable[[Packet], None], - on_disconnect: Callable[[], None], - ) -> None: - self._sock: Optional[socket.socket] = None - self._send_lock = threading.Lock() - self._stop_event = threading.Event() - self._recv_thread: Optional[threading.Thread] = None - self._on_packet = on_packet - self._on_disconnect = on_disconnect - - # ------------------------------------------------------------------ - # Public interface - # ------------------------------------------------------------------ - - @property - def connected(self) -> bool: - """``True`` while the socket is open and the stop event has not fired.""" - return self._sock is not None and not self._stop_event.is_set() - - def connect(self, host: str, port: int, timeout: float = 5.0) -> None: - """Open a TCP connection and start the receive thread. - - :param host: Target hostname or IP address. - :param port: Target TCP port. - :param timeout: Connect timeout in seconds. - :raises ConnectionError: if already connected or if the OS refuses. - """ - if self.connected: - raise RouterConnectionError( - "Already connected; call disconnect() first." - ) - sock: Optional[socket.socket] = None - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(timeout) - sock.connect((host, port)) - sock.settimeout(None) # switch to blocking for the recv loop - except OSError as exc: - if sock is not None: - try: - sock.close() - except OSError: - pass - raise RouterConnectionError( - f"Cannot connect to {host}:{port}: {exc}" - ) from exc - - self._sock = sock - self._stop_event.clear() - self._recv_thread = threading.Thread( - target=self._recv_loop, - daemon=True, - name="csm-tcp-router-recv", - ) - self._recv_thread.start() - - def disconnect(self, join_timeout: float = 2.0) -> None: - """Close the connection and stop the receive thread. - - Safe to call even if not connected. - """ - self._stop_event.set() - if self._sock is not None: - try: - self._sock.shutdown(socket.SHUT_RDWR) - except OSError: - pass - try: - self._sock.close() - except OSError: - pass - self._sock = None - if self._recv_thread is not None and self._recv_thread.is_alive(): - self._recv_thread.join(timeout=join_timeout) - - def send_raw(self, data: bytes) -> None: - """Send *data* atomically. Thread-safe. - - :raises ConnectionError: if not connected or if the send fails. - """ - if not self.connected: - raise RouterConnectionError("Not connected.") - with self._send_lock: - try: - self._sock.sendall(data) # type: ignore[union-attr] - except OSError as exc: - self._stop_event.set() - raise RouterConnectionError(f"Send failed: {exc}") from exc - - # ------------------------------------------------------------------ - # Private helpers - # ------------------------------------------------------------------ - - def _recv_all(self, size: int) -> bytes: - """Read exactly *size* bytes; returns empty bytes on clean EOF or disconnect.""" - buf = bytearray(size) - view = memoryview(buf) - received = 0 - while received < size: - sock = self._sock # capture locally to avoid TOCTOU race with disconnect() - if sock is None: - return b"" - try: - n = sock.recv_into(view[received:], size - received) - except OSError: - return b"" - if n == 0: - return b"" - received += n - return bytes(buf) - - def _recv_loop(self) -> None: - """Background thread: read packets and dispatch via callback.""" - try: - while not self._stop_event.is_set(): - header = self._recv_all(HEADER_SIZE) - if not header: - break - - # Extract data_len from the first 4 bytes without full decode - (data_len,) = struct.unpack("!I", header[:4]) - body = self._recv_all(data_len) - if len(body) != data_len: - break - - try: - packet = parse_packet(header, body) - except ProtocolError: - # Corrupted frame – skip it and keep the loop alive - continue - - self._on_packet(packet) - - except OSError: - pass - finally: - if not self._stop_event.is_set(): - self._stop_event.set() - self._on_disconnect() diff --git a/SDK/python/src/csm_tcp_router/async_client.py b/SDK/python/src/csm_tcp_router/async_client.py deleted file mode 100644 index de64c00..0000000 --- a/SDK/python/src/csm_tcp_router/async_client.py +++ /dev/null @@ -1,523 +0,0 @@ -"""Asyncio-based CSM-TCP-Router client.""" - -from __future__ import annotations - -import asyncio -import inspect -import struct -import time -from typing import Any, Callable, Coroutine, Dict, Optional, Tuple, Union - -from ._errors import _parse_server_error -from ._protocol import HEADER_SIZE, encode_packet, parse_packet -from .exceptions import ConnectionError as RouterConnectionError -from .exceptions import ProtocolError, ServerError -from .exceptions import TimeoutError as RouterTimeoutError -from .models import ( - AsyncResponse, - CommandResponse, - Packet, - PacketType, - StatusNotification, -) - -__all__ = ["AsyncTcpRouterClient"] - -# --------------------------------------------------------------------------- -# Callback type aliases – both plain callables and async coroutines are accepted -# --------------------------------------------------------------------------- - -_SyncStatusCb = Callable[[StatusNotification], None] -_AsyncStatusCb = Callable[[StatusNotification], "Coroutine[Any, Any, None]"] -StatusCallback = Union[_SyncStatusCb, _AsyncStatusCb] - -_SyncAsyncRespCb = Callable[[AsyncResponse], None] -_AsyncAsyncRespCb = Callable[[AsyncResponse], "Coroutine[Any, Any, None]"] -AsyncRespCallback = Union[_SyncAsyncRespCb, _AsyncAsyncRespCb] - -_SubKey = Tuple[str, str] - - -class AsyncTcpRouterClient: - """Asyncio client for a CSM-TCP-Router server. - - Provides the same interface as :class:`~csm_tcp_router.TcpRouterClient` but - as ``async def`` coroutines, suitable for use inside an asyncio event loop. - - **Quickstart**:: - - import asyncio - from csm_tcp_router import AsyncTcpRouterClient - - async def main(): - async with AsyncTcpRouterClient() as client: - await client.connect("localhost", 30007) - print(await client.list_modules()) - resp = await client.send_and_wait("API: Read -@ DAQmx") - print(resp.text) - - asyncio.run(main()) - - **Protocol flows** are identical to :class:`~csm_tcp_router.TcpRouterClient`. - - **Callbacks** passed to :meth:`subscribe_status` and - :meth:`register_async_callback` may be either a plain callable *or* an - ``async def`` coroutine — both are supported. - - **Polling queues** (:attr:`async_response_queue`, :attr:`status_queue`) are - created when :meth:`connect` is called and are bound to the running event - loop. Access them only after :meth:`connect` has been awaited. - """ - - def __init__(self) -> None: - self._reader: Optional[asyncio.StreamReader] = None - self._writer: Optional[asyncio.StreamWriter] = None - self._recv_task: Optional[asyncio.Task[None]] = None - - # Asyncio objects created lazily in connect() to bind to the running loop - self._resp_queue: Optional[asyncio.Queue[object]] = None - self._cmd_resp_queue: Optional[asyncio.Queue[object]] = None - self._send_lock: Optional[asyncio.Lock] = None - # Serialisation locks – at most one in-flight RESP / CMD_RESP waiter - self._resp_lock: Optional[asyncio.Lock] = None - self._cmd_resp_lock: Optional[asyncio.Lock] = None - - #: Polling queue for :class:`~csm_tcp_router.models.AsyncResponse` objects - #: received from the server. Available after :meth:`connect` is called. - self.async_response_queue: Optional[asyncio.Queue[AsyncResponse]] = None - - #: Polling queue for :class:`~csm_tcp_router.models.StatusNotification` - #: objects received from the server. Available after :meth:`connect`. - self.status_queue: Optional[asyncio.Queue[StatusNotification]] = None - - # Callback registries – plain dicts (asyncio is single-threaded) - self._status_callbacks: Dict[_SubKey, Optional[StatusCallback]] = {} - self._async_callbacks: Dict[str, AsyncRespCallback] = {} - - # ------------------------------------------------------------------ - # Connection management - # ------------------------------------------------------------------ - - def _init_async_objects(self) -> None: - """(Re)create asyncio objects bound to the current running loop.""" - self._resp_queue = asyncio.Queue() - self._cmd_resp_queue = asyncio.Queue() - self._send_lock = asyncio.Lock() - self._resp_lock = asyncio.Lock() - self._cmd_resp_lock = asyncio.Lock() - self.async_response_queue = asyncio.Queue() - self.status_queue = asyncio.Queue() - - @property - def connected(self) -> bool: - """``True`` while the writer is open and not being closed.""" - return self._writer is not None and not self._writer.is_closing() - - async def connect(self, host: str, port: int, timeout: float = 5.0) -> None: - """Open a TCP connection and start the background receive task. - - :param host: Server hostname or IP address. - :param port: Server TCP port (the reference app defaults to 30007). - :param timeout: Connection timeout in seconds. - :raises ConnectionError: if already connected or the OS refuses. - """ - if self.connected: - raise RouterConnectionError( - "Already connected; call disconnect() first." - ) - self._init_async_objects() - try: - self._reader, self._writer = await asyncio.wait_for( - asyncio.open_connection(host, port), timeout=timeout - ) - except asyncio.TimeoutError: - raise RouterConnectionError( - f"Connection to {host}:{port} timed out after {timeout:.1f}s." - ) from None - except OSError as exc: - raise RouterConnectionError( - f"Cannot connect to {host}:{port}: {exc}" - ) from exc - self._recv_task = asyncio.ensure_future(self._recv_loop()) - - async def disconnect(self) -> None: - """Close the connection and stop the background receive task. - - Safe to call even if not currently connected. Any coroutines currently - blocked inside :meth:`send_and_wait`, :meth:`post`, or similar methods - will receive a :exc:`~csm_tcp_router.exceptions.ConnectionError` - immediately rather than waiting for their timeout to expire. - """ - # Wake blocked waiters *before* cancelling the recv task. - sentinel = RouterConnectionError("Disconnected from server.") - if self._resp_queue is not None: - self._resp_queue.put_nowait(sentinel) - if self._cmd_resp_queue is not None: - self._cmd_resp_queue.put_nowait(sentinel) - # Cancel the recv task first; its finally block notifies pending waiters - if self._recv_task is not None and not self._recv_task.done(): - self._recv_task.cancel() - try: - await self._recv_task - except (asyncio.CancelledError, Exception): - pass - self._recv_task = None - - if self._writer is not None: - try: - self._writer.close() - await self._writer.wait_closed() - except OSError: - pass - self._writer = None - self._reader = None - - async def wait_for_server( - self, - host: str, - port: int, - timeout: float = 30.0, - retry_interval: float = 0.5, - ) -> bool: - """Poll until *host*:*port* accepts a connection or *timeout* elapses. - - :param host: Server hostname or IP address. - :param port: Server TCP port. - :param timeout: Maximum time to wait in seconds. - :param retry_interval: Pause between retries in seconds. - :returns: ``True`` when the server is reachable; ``False`` on timeout. - """ - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - try: - _, writer = await asyncio.wait_for( - asyncio.open_connection(host, port), timeout=1.0 - ) - writer.close() - try: - await writer.wait_closed() - except OSError: - pass - return True - except (OSError, asyncio.TimeoutError): - pass - await asyncio.sleep(retry_interval) - return False - - # ------------------------------------------------------------------ - # Core command methods - # ------------------------------------------------------------------ - - async def send_and_wait( - self, command: str, timeout: float = 5.0 - ) -> CommandResponse: - """Send a **synchronous** command and await the response. - - Use the CSM synchronous suffix ``-@`` in *command*:: - - resp = await client.send_and_wait("API: Read -@ DAQmx") - print(resp.text) - - :param command: CSM command string. - :param timeout: Seconds to wait for the ``resp`` packet. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no response arrives within *timeout*. - :raises ServerError: if the server returns an error packet. - """ - wire = encode_packet(command.encode("utf-8"), PacketType.CMD) - assert self._resp_lock is not None - async with self._resp_lock: - await self._send_raw(wire) - return await self._wait_for_resp(timeout) - - async def post(self, command: str, timeout: float = 5.0) -> None: - """Send an **asynchronous** command and await the ``cmd-resp`` handshake. - - Use the CSM async suffix ``->`` in *command*:: - - await client.post("API: Start Sampling -> DAQmx") - - :param command: CSM command string including the ``->`` suffix. - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the command. - """ - wire = encode_packet(command.encode("utf-8"), PacketType.CMD) - assert self._cmd_resp_lock is not None - async with self._cmd_resp_lock: - await self._send_raw(wire) - await self._wait_for_cmd_resp(timeout) - - async def post_no_reply(self, command: str, timeout: float = 5.0) -> None: - """Send an **async no-reply** command and await the ``cmd-resp`` handshake. - - Use the CSM no-reply suffix ``->|`` in *command*:: - - await client.post_no_reply("API: Reset ->| DAQmx") - - :param command: CSM command string including the ``->|`` suffix. - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the command. - """ - wire = encode_packet(command.encode("utf-8"), PacketType.CMD) - assert self._cmd_resp_lock is not None - async with self._cmd_resp_lock: - await self._send_raw(wire) - await self._wait_for_cmd_resp(timeout) - - async def ping(self, timeout: float = 2.0) -> Tuple[bool, float]: - """Send a ``Ping`` command and measure round-trip latency. - - :returns: ``(True, elapsed_seconds)`` on success, - ``(False, 0.0)`` on failure. - """ - try: - t0 = time.monotonic() - await self.send_and_wait("Ping", timeout=timeout) - return True, time.monotonic() - t0 - except (RouterConnectionError, RouterTimeoutError, ServerError): - return False, 0.0 - - # ------------------------------------------------------------------ - # Router management helpers - # ------------------------------------------------------------------ - - async def list_modules(self, timeout: float = 5.0) -> str: - """Return the server's loaded CSM module list as plain text.""" - return (await self.send_and_wait("List", timeout=timeout)).text - - async def list_api(self, module: str, timeout: float = 5.0) -> str: - """Return the API list for *module* as plain text.""" - return (await self.send_and_wait(f"List API {module}", timeout=timeout)).text - - async def list_states(self, module: str, timeout: float = 5.0) -> str: - """Return the CSM state list for *module* as plain text.""" - return (await self.send_and_wait(f"List State {module}", timeout=timeout)).text - - async def help(self, module: str, timeout: float = 5.0) -> str: - """Return the help text for *module* as plain text.""" - return (await self.send_and_wait(f"Help {module}", timeout=timeout)).text - - # ------------------------------------------------------------------ - # Status / interrupt subscriptions - # ------------------------------------------------------------------ - - async def subscribe_status( - self, - status_name: str, - module_name: str, - callback: Optional[StatusCallback] = None, - timeout: float = 5.0, - ) -> None: - """Subscribe to a CSM module's status broadcast. - - Sends ``"@ ->"`` and awaits the - ``cmd-resp`` handshake. Once subscribed, - :class:`~csm_tcp_router.models.StatusNotification` objects will be: - - * delivered to *callback* (if provided – sync or async both accepted), and - * added to :attr:`status_queue`. - - :param status_name: Name of the status (e.g. ``"Status"``). - :param module_name: Name of the CSM module (e.g. ``"AI"``). - :param callback: Optional callable or coroutine invoked per notification. - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the subscription. - """ - # Register the callback before sending to eliminate the race where a - # STATUS packet could arrive before the callback is stored. - self._status_callbacks[(status_name, module_name)] = callback - cmd = f"{status_name}@{module_name} ->" - wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) - assert self._cmd_resp_lock is not None - try: - async with self._cmd_resp_lock: - await self._send_raw(wire) - await self._wait_for_cmd_resp(timeout) - except Exception: - self._status_callbacks.pop((status_name, module_name), None) - raise - - async def unsubscribe_status( - self, - status_name: str, - module_name: str, - timeout: float = 5.0, - ) -> None: - """Cancel a status subscription. - - :param status_name: Name of the subscribed status. - :param module_name: Name of the CSM module. - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the request. - """ - cmd = f"{status_name}@{module_name} ->" - wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) - assert self._cmd_resp_lock is not None - async with self._cmd_resp_lock: - await self._send_raw(wire) - await self._wait_for_cmd_resp(timeout) - self._status_callbacks.pop((status_name, module_name), None) - - def register_async_callback( - self, - original_command: str, - callback: AsyncRespCallback, - ) -> None: - """Register a callback for ``async-resp`` packets. - - The callback is matched by *original_command* (the command text - echoed in the ``async-resp`` payload after the `` <- `` separator). - - Callbacks may be either a plain callable or an ``async def`` coroutine. - - :param original_command: The command text echoed in the ``async-resp``. - :param callback: Callable or coroutine receiving an - :class:`~csm_tcp_router.models.AsyncResponse`. - """ - self._async_callbacks[original_command] = callback - - def unregister_async_callback(self, original_command: str) -> None: - """Remove a previously registered async-response callback.""" - self._async_callbacks.pop(original_command, None) - - # ------------------------------------------------------------------ - # Async context-manager support - # ------------------------------------------------------------------ - - async def __aenter__(self) -> AsyncTcpRouterClient: - return self - - async def __aexit__(self, *_args: object) -> None: - await self.disconnect() - - # ------------------------------------------------------------------ - # Internal: send - # ------------------------------------------------------------------ - - async def _send_raw(self, data: bytes) -> None: - if not self.connected: - raise RouterConnectionError("Not connected.") - assert self._writer is not None - assert self._send_lock is not None - async with self._send_lock: - self._writer.write(data) - await self._writer.drain() - - # ------------------------------------------------------------------ - # Internal: receive loop (background task) - # ------------------------------------------------------------------ - - async def _recv_loop(self) -> None: - """Background task: read frames and dispatch them.""" - assert self._reader is not None - try: - while True: - header = await self._reader.readexactly(HEADER_SIZE) - (data_len,) = struct.unpack("!I", header[:4]) - body = ( - await self._reader.readexactly(data_len) if data_len else b"" - ) - try: - packet = parse_packet(header, body) - except ProtocolError: - continue # skip corrupted frame; keep connection alive - await self._dispatch_packet(packet) - except (asyncio.IncompleteReadError, asyncio.CancelledError, OSError): - pass - finally: - self._notify_disconnect() - - async def _dispatch_packet(self, packet: Packet) -> None: - """Route a received packet to the correct queue and/or callback.""" - assert self._resp_queue is not None - assert self._cmd_resp_queue is not None - assert self.async_response_queue is not None - assert self.status_queue is not None - - ptype = packet.type - - if ptype == PacketType.RESP: - self._resp_queue.put_nowait(packet) - - elif ptype == PacketType.CMD_RESP: - self._cmd_resp_queue.put_nowait(packet) - - elif ptype == PacketType.ASYNC_RESP: - resp = AsyncResponse.from_packet(packet) - self.async_response_queue.put_nowait(resp) - cb = self._async_callbacks.get(resp.original_command) - if cb is not None: - try: - result = cb(resp) # type: ignore[arg-type] - if inspect.isawaitable(result): - await result - except Exception: - pass - - elif ptype in (PacketType.STATUS, PacketType.INTERRUPT): - notif = StatusNotification.from_packet(packet) - self.status_queue.put_nowait(notif) - cb = self._status_callbacks.get( # type: ignore[assignment] - (notif.status_name, notif.module_name) - ) - if cb is not None: - try: - result = cb(notif) # type: ignore[arg-type] - if inspect.isawaitable(result): - await result - except Exception: - pass - - elif ptype == PacketType.ERROR: - err = _parse_server_error(packet) - self._resp_queue.put_nowait(err) - self._cmd_resp_queue.put_nowait(err) - - # PacketType.INFO is silently discarded (welcome / goodbye messages) - - def _notify_disconnect(self) -> None: - """Put sentinels in waiter queues when the connection is lost.""" - if self._resp_queue is None: - return - sentinel = RouterConnectionError("Connection lost unexpectedly.") - self._resp_queue.put_nowait(sentinel) - self._cmd_resp_queue.put_nowait(sentinel) - - # ------------------------------------------------------------------ - # Internal: synchronised waiters - # ------------------------------------------------------------------ - - async def _wait_for_resp(self, timeout: float) -> CommandResponse: - assert self._resp_queue is not None - try: - item = await asyncio.wait_for(self._resp_queue.get(), timeout=timeout) - except asyncio.TimeoutError: - raise RouterTimeoutError( - f"No response received within {timeout:.1f}s." - ) from None - if isinstance(item, Exception): - raise item - assert isinstance(item, Packet) - return CommandResponse(raw=item.data) - - async def _wait_for_cmd_resp(self, timeout: float) -> None: - assert self._cmd_resp_queue is not None - try: - item = await asyncio.wait_for( - self._cmd_resp_queue.get(), timeout=timeout - ) - except asyncio.TimeoutError: - raise RouterTimeoutError( - f"No cmd-resp received within {timeout:.1f}s." - ) from None - if isinstance(item, Exception): - raise item - # CMD_RESP payload is a handshake acknowledgment; discard it diff --git a/SDK/python/src/csm_tcp_router/client.py b/SDK/python/src/csm_tcp_router/client.py deleted file mode 100644 index 2f870d0..0000000 --- a/SDK/python/src/csm_tcp_router/client.py +++ /dev/null @@ -1,456 +0,0 @@ -"""High-level CSM-TCP-Router client.""" - -from __future__ import annotations - -import queue -import threading -import time -from typing import Callable, Dict, Optional, Tuple - -from ._errors import _parse_server_error -from ._protocol import encode_packet -from ._transport import Transport -from .exceptions import ConnectionError as RouterConnectionError -from .exceptions import ServerError -from .exceptions import TimeoutError as RouterTimeoutError -from .models import ( - AsyncResponse, - CommandResponse, - Packet, - PacketType, - StatusNotification, -) - -__all__ = ["TcpRouterClient"] - -# Type aliases -_SubKey = Tuple[str, str] -StatusCallback = Callable[[StatusNotification], None] -AsyncCallback = Callable[[AsyncResponse], None] - -# Items held in the internal queues are either Packet or Exception instances. -_QueueItem = object - - -class TcpRouterClient: - """Python client for a CSM-TCP-Router server. - - This class mirrors the LabVIEW ClientAPI VIs and speaks the - CSM-TCP-Router protocol v0. It is thread-safe in that its internal - state is protected by locks; however, the protocol allows at most one - in-flight *synchronous* command at a time and at most one in-flight - *async* command / subscription at a time. Concurrent callers are - serialised by ``_resp_lock`` and ``_cmd_resp_lock`` respectively. - - **Quickstart**:: - - from csm_tcp_router import TcpRouterClient - - with TcpRouterClient() as client: - client.connect("localhost", 30007) - print(client.list_modules()) - - **Protocol flows**: - - - *Synchronous* command (``-@``): :meth:`send_and_wait` – sends a ``CMD`` - packet and blocks until a ``RESP`` (or ``ERROR``) is received. - - *Asynchronous* command (``->``): :meth:`post` – sends a ``CMD`` packet - and blocks until the ``CMD_RESP`` handshake is received; the eventual - ``ASYNC_RESP`` is delivered asynchronously. - - *No-reply async* command (``->|``): :meth:`post_no_reply` – same as - :meth:`post` but no ``ASYNC_RESP`` will ever arrive. - - *Subscribe / unsubscribe*: :meth:`subscribe_status` / - :meth:`unsubscribe_status` – sends a ```` / ```` - command and waits for the ``CMD_RESP`` handshake. - - **Received-packet routing** (on the background receive thread): - - - ``RESP`` (0x04) – unblocks the caller of :meth:`send_and_wait`. - - ``CMD_RESP`` (0x03) – unblocks callers of :meth:`post`, - :meth:`post_no_reply`, :meth:`subscribe_status`, and - :meth:`unsubscribe_status`. - - ``ASYNC_RESP`` (0x05) – added to :attr:`async_response_queue` and - dispatched to any matching :meth:`register_async_callback`. - - ``STATUS`` / ``INTERRUPT`` (0x06 / 0x07) – added to - :attr:`status_queue` and dispatched to any matching - :meth:`subscribe_status` callback. - - ``ERROR`` (0x01) – unblocks any pending synchronous waiter with a - :exc:`~csm_tcp_router.exceptions.ServerError`. - - ``INFO`` (0x00) – silently discarded (welcome / goodbye messages). - """ - - def __init__(self) -> None: - self._transport = Transport( - on_packet=self._on_packet, - on_disconnect=self._on_disconnect, - ) - - # One-item-deep queues for synchronised waits. - # Items are either Packet or Exception instances. - self._resp_queue: queue.Queue[_QueueItem] = queue.Queue() - self._cmd_resp_queue: queue.Queue[_QueueItem] = queue.Queue() - - #: Polling queue for :class:`~csm_tcp_router.models.AsyncResponse` - #: objects received from the server. - self.async_response_queue: queue.Queue[AsyncResponse] = queue.Queue() - - #: Polling queue for :class:`~csm_tcp_router.models.StatusNotification` - #: objects received from the server. - self.status_queue: queue.Queue[StatusNotification] = queue.Queue() - - # Callback registries (protected by _lock) - self._status_callbacks: Dict[_SubKey, Optional[StatusCallback]] = {} - self._async_callbacks: Dict[str, AsyncCallback] = {} - self._lock = threading.Lock() - - # Serialisation locks – at most one in-flight RESP / CMD_RESP waiter - # at a time. This prevents concurrent callers from consuming each - # other's response packets. - self._resp_lock = threading.Lock() - self._cmd_resp_lock = threading.Lock() - - # ------------------------------------------------------------------ - # Connection management - # ------------------------------------------------------------------ - - def connect(self, host: str, port: int, timeout: float = 5.0) -> None: - """Connect to a CSM-TCP-Router server. - - :param host: Server hostname or IP address. - :param port: Server TCP port (the reference app defaults to 30007). - :param timeout: Connect timeout in seconds. - :raises ConnectionError: if the connection cannot be established. - """ - self._transport.connect(host, port, timeout=timeout) - - def disconnect(self) -> None: - """Disconnect from the server and release all resources. - - Safe to call even if not currently connected. Any threads currently - blocked inside :meth:`send_and_wait`, :meth:`post`, or similar methods - will receive a :exc:`~csm_tcp_router.exceptions.ConnectionError` - immediately rather than waiting for their timeout to expire. - """ - # Wake blocked waiters *before* tearing down the transport. - sentinel = RouterConnectionError("Disconnected from server.") - self._resp_queue.put(sentinel) - self._cmd_resp_queue.put(sentinel) - self._transport.disconnect() - - @property - def connected(self) -> bool: - """``True`` if the underlying transport is currently connected.""" - return self._transport.connected - - def wait_for_server( - self, - host: str, - port: int, - timeout: float = 30.0, - retry_interval: float = 0.5, - ) -> bool: - """Poll until *host*:*port* accepts a connection or *timeout* elapses. - - :param host: Server hostname or IP address. - :param port: Server TCP port. - :param timeout: Maximum time to wait in seconds. - :param retry_interval: Pause between retries in seconds. - :returns: ``True`` when the server is reachable; ``False`` on timeout. - """ - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - probe = Transport( - on_packet=lambda _p: None, - on_disconnect=lambda: None, - ) - try: - probe.connect(host, port, timeout=1.0) - probe.disconnect() - return True - except RouterConnectionError: - pass - time.sleep(retry_interval) - return False - - # ------------------------------------------------------------------ - # Core command methods - # ------------------------------------------------------------------ - - def send_and_wait(self, command: str, timeout: float = 5.0) -> CommandResponse: - """Send a **synchronous** command and block until the response arrives. - - Use the CSM synchronous message suffix ``-@`` in *command*:: - - resp = client.send_and_wait("API: Read -@ DAQmx") - print(resp.text) - - The built-in router management commands (``List``, ``Ping``, …) are - also synchronous and do not require a suffix. - - :param command: CSM command string. - :param timeout: Seconds to wait for the ``resp`` packet. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no response arrives within *timeout*. - :raises ServerError: if the server returns an error packet. - """ - wire = encode_packet(command.encode("utf-8"), PacketType.CMD) - with self._resp_lock: - self._transport.send_raw(wire) - return self._wait_for_resp(timeout) - - def post(self, command: str, timeout: float = 5.0) -> None: - """Send an **asynchronous** command and wait for the ``cmd-resp`` handshake. - - Use the CSM async message suffix ``->`` in *command*:: - - client.post("API: Start Sampling -> DAQmx") - - The eventual ``async-resp`` payload will be delivered to any callback - registered with :meth:`register_async_callback` and added to - :attr:`async_response_queue`. - - :param command: CSM command string including the ``->`` suffix. - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the command. - """ - wire = encode_packet(command.encode("utf-8"), PacketType.CMD) - with self._cmd_resp_lock: - self._transport.send_raw(wire) - self._wait_for_cmd_resp(timeout) - - def post_no_reply(self, command: str, timeout: float = 5.0) -> None: - """Send an **async no-reply** command and wait for the ``cmd-resp`` handshake. - - Use the CSM no-reply suffix ``->|`` in *command*:: - - client.post_no_reply("API: Reset ->| DAQmx") - - After the handshake the server will not send any further response. - - :param command: CSM command string including the ``->|`` suffix. - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the command. - """ - wire = encode_packet(command.encode("utf-8"), PacketType.CMD) - with self._cmd_resp_lock: - self._transport.send_raw(wire) - self._wait_for_cmd_resp(timeout) - - def ping(self, timeout: float = 2.0) -> Tuple[bool, float]: - """Send a ``Ping`` command and measure round-trip latency. - - :param timeout: Seconds to wait for the reply. - :returns: ``(True, elapsed_seconds)`` on success, - ``(False, 0.0)`` on failure or error. - """ - try: - t0 = time.monotonic() - self.send_and_wait("Ping", timeout=timeout) - return True, time.monotonic() - t0 - except (RouterConnectionError, RouterTimeoutError, ServerError): - return False, 0.0 - - # ------------------------------------------------------------------ - # Router management helpers - # ------------------------------------------------------------------ - - def list_modules(self, timeout: float = 5.0) -> str: - """Return the server's loaded CSM module list as plain text. - - Equivalent to the ``List`` router management command. - """ - return self.send_and_wait("List", timeout=timeout).text - - def list_api(self, module: str, timeout: float = 5.0) -> str: - """Return the API list for *module* as plain text.""" - return self.send_and_wait(f"List API {module}", timeout=timeout).text - - def list_states(self, module: str, timeout: float = 5.0) -> str: - """Return the CSM state list for *module* as plain text.""" - return self.send_and_wait(f"List State {module}", timeout=timeout).text - - def help(self, module: str, timeout: float = 5.0) -> str: - """Return the help text for *module* as plain text.""" - return self.send_and_wait(f"Help {module}", timeout=timeout).text - - # ------------------------------------------------------------------ - # Status / interrupt subscriptions - # ------------------------------------------------------------------ - - def subscribe_status( - self, - status_name: str, - module_name: str, - callback: Optional[StatusCallback] = None, - timeout: float = 5.0, - ) -> None: - """Subscribe to a CSM module's status broadcast. - - Sends ``"@ ->"`` and waits for - the ``cmd-resp`` handshake. Once subscribed, - :class:`~csm_tcp_router.models.StatusNotification` objects will be: - - * delivered to *callback* (if provided), and - * added to :attr:`status_queue`. - - :param status_name: Name of the status to subscribe to (e.g. ``"Status"``). - :param module_name: Name of the CSM module (e.g. ``"AI"``). - :param callback: Optional callable invoked on each notification. - Must be fast and non-blocking (runs in the recv thread). - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the subscription. - """ - # Register the callback *before* sending to eliminate the race where - # a STATUS packet could arrive before the callback is stored. - with self._lock: - self._status_callbacks[(status_name, module_name)] = callback - cmd = f"{status_name}@{module_name} ->" - wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) - try: - with self._cmd_resp_lock: - self._transport.send_raw(wire) - self._wait_for_cmd_resp(timeout) - except Exception: - with self._lock: - self._status_callbacks.pop((status_name, module_name), None) - raise - - def unsubscribe_status( - self, - status_name: str, - module_name: str, - timeout: float = 5.0, - ) -> None: - """Cancel a status subscription. - - :param status_name: Name of the subscribed status. - :param module_name: Name of the CSM module. - :param timeout: Seconds to wait for the ``cmd-resp`` handshake. - :raises ConnectionError: if not connected. - :raises TimeoutError: if no handshake arrives within *timeout*. - :raises ServerError: if the server rejects the request. - """ - cmd = f"{status_name}@{module_name} ->" - wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) - with self._cmd_resp_lock: - self._transport.send_raw(wire) - self._wait_for_cmd_resp(timeout) - with self._lock: - self._status_callbacks.pop((status_name, module_name), None) - - def register_async_callback( - self, - original_command: str, - callback: AsyncCallback, - ) -> None: - """Register a callback for ``async-resp`` packets. - - The callback is matched by *original_command* (the command text - echoed in the ``async-resp`` payload after the `` <- `` separator). - - :param original_command: The command text that will appear in the - ``async-resp`` echo - (e.g. ``"API: Read -> DAQmx"``). - :param callback: Callable receiving an - :class:`~csm_tcp_router.models.AsyncResponse`. - """ - with self._lock: - self._async_callbacks[original_command] = callback - - def unregister_async_callback(self, original_command: str) -> None: - """Remove a previously registered async callback.""" - with self._lock: - self._async_callbacks.pop(original_command, None) - - # ------------------------------------------------------------------ - # Context-manager support - # ------------------------------------------------------------------ - - def __enter__(self) -> TcpRouterClient: - return self - - def __exit__(self, *_args: object) -> None: - self.disconnect() - - # ------------------------------------------------------------------ - # Internal: packet dispatch (runs in the receive thread) - # ------------------------------------------------------------------ - - def _on_packet(self, packet: Packet) -> None: - ptype = packet.type - if ptype == PacketType.RESP: - self._resp_queue.put(packet) - - elif ptype == PacketType.CMD_RESP: - self._cmd_resp_queue.put(packet) - - elif ptype == PacketType.ASYNC_RESP: - resp = AsyncResponse.from_packet(packet) - self.async_response_queue.put(resp) - with self._lock: - cb = self._async_callbacks.get(resp.original_command) - if cb is not None: - try: - cb(resp) - except Exception: - pass - - elif ptype in (PacketType.STATUS, PacketType.INTERRUPT): - notif = StatusNotification.from_packet(packet) - self.status_queue.put(notif) - with self._lock: - cb = self._status_callbacks.get( # type: ignore[assignment] - (notif.status_name, notif.module_name) - ) - if cb is not None: - try: - cb(notif) # type: ignore[call-arg] - except Exception: - pass - - elif ptype == PacketType.ERROR: - err = _parse_server_error(packet) - # Unblock any pending synchronous waiter - self._resp_queue.put(err) - self._cmd_resp_queue.put(err) - - # PacketType.INFO is silently discarded (welcome / goodbye messages) - - def _on_disconnect(self) -> None: - """Called from the receive thread when the connection drops unexpectedly.""" - sentinel = RouterConnectionError("Connection lost unexpectedly.") - self._resp_queue.put(sentinel) - self._cmd_resp_queue.put(sentinel) - - # ------------------------------------------------------------------ - # Internal: synchronised waiters - # ------------------------------------------------------------------ - - def _wait_for_resp(self, timeout: float) -> CommandResponse: - try: - item = self._resp_queue.get(timeout=timeout) - except queue.Empty: - raise RouterTimeoutError( - f"No response received within {timeout:.1f}s." - ) from None - if isinstance(item, Exception): - raise item - assert isinstance(item, Packet) - return CommandResponse(raw=item.data) - - def _wait_for_cmd_resp(self, timeout: float) -> None: - try: - item = self._cmd_resp_queue.get(timeout=timeout) - except queue.Empty: - raise RouterTimeoutError( - f"No cmd-resp received within {timeout:.1f}s." - ) from None - if isinstance(item, Exception): - raise item - # CMD_RESP payload is a handshake acknowledgment; discard it diff --git a/SDK/python/src/csm_tcp_router/exceptions.py b/SDK/python/src/csm_tcp_router/exceptions.py deleted file mode 100644 index e79ad48..0000000 --- a/SDK/python/src/csm_tcp_router/exceptions.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Exception hierarchy for csm-tcp-router-client.""" - -__all__ = [ - "ConnectionError", - "ProtocolError", - "ServerError", - "TcpRouterError", - "TimeoutError", -] - - -class TcpRouterError(Exception): - """Base exception for all CSM-TCP-Router client errors.""" - - -class ConnectionError(TcpRouterError): - """Raised when a connection cannot be established or is lost.""" - - -class TimeoutError(TcpRouterError): - """Raised when a synchronous operation exceeds its timeout.""" - - -class ProtocolError(TcpRouterError): - """Raised when an invalid or unexpected protocol frame is received.""" - - -class ServerError(TcpRouterError): - """Raised when the server returns an error packet. - - Attributes: - message: Human-readable error text from the server. - code: Optional error code extracted from the CSM Error format - ``[Error: ] ``. - """ - - def __init__(self, message: str, code: str = "") -> None: - super().__init__(message) - self.message = message - self.code = code - - def __str__(self) -> str: - if self.code: - return f"[Error: {self.code}] {self.message}" - return self.message diff --git a/SDK/python/src/csm_tcp_router/models.py b/SDK/python/src/csm_tcp_router/models.py deleted file mode 100644 index 70ec0e2..0000000 --- a/SDK/python/src/csm_tcp_router/models.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Public data models and enumerations for the CSM-TCP-Router protocol.""" - -from __future__ import annotations - -from dataclasses import dataclass -from enum import IntEnum - -__all__ = [ - "AsyncResponse", - "CommandResponse", - "Packet", - "PacketType", - "StatusNotification", -] - - -class PacketType(IntEnum): - """Packet type constants as defined in the CSM-TCP-Router protocol v0. - - Wire values - ----------- - ``INFO`` 0x00 – informational messages (welcome / goodbye) - ``ERROR`` 0x01 – error messages from the server - ``CMD`` 0x02 – command sent by the client - ``CMD_RESP`` 0x03 – server handshake for async / no-reply / subscribe - ``RESP`` 0x04 – synchronous response payload - ``ASYNC_RESP`` 0x05 – asynchronous response payload - ``STATUS`` 0x06 – status broadcast from a subscribed CSM module - ``INTERRUPT`` 0x07 – interrupt broadcast from a subscribed CSM module - """ - - INFO = 0x00 - ERROR = 0x01 - CMD = 0x02 - CMD_RESP = 0x03 - RESP = 0x04 - ASYNC_RESP = 0x05 - STATUS = 0x06 - INTERRUPT = 0x07 - - -@dataclass(frozen=True) -class Packet: - """A decoded packet received from the server (internal representation).""" - - type: PacketType - data: bytes - version: int = 1 - flag1: int = 0 - flag2: int = 0 - - -@dataclass(frozen=True) -class CommandResponse: - """The result of a synchronous command (:meth:`~csm_tcp_router.TcpRouterClient.send_and_wait`).""" - - raw: bytes - - @property - def text(self) -> str: - """Decoded UTF-8 text of the response payload.""" - return self.raw.decode("utf-8", errors="replace") - - def __repr__(self) -> str: - return f"CommandResponse({self.text!r})" - - -@dataclass(frozen=True) -class AsyncResponse: - """An asynchronous response payload delivered via an ``async-resp`` packet. - - Attributes: - raw: Raw response bytes (the part *before* the `` <- `` separator). - original_command: The original command text echoed back by the server - (the part *after* the `` <- `` separator). - """ - - raw: bytes - original_command: str = "" - - @property - def text(self) -> str: - """Decoded UTF-8 text of the response payload.""" - return self.raw.decode("utf-8", errors="replace") - - @classmethod - def from_packet(cls, packet: Packet) -> AsyncResponse: - """Parse an ``ASYNC_RESP`` packet. - - Server format: ``" <- "``. - """ - text = packet.data.decode("utf-8", errors="replace") - parts = text.split(" <- ", 1) - if len(parts) == 2: - return cls(raw=parts[0].encode("utf-8"), original_command=parts[1]) - return cls(raw=packet.data) - - def __repr__(self) -> str: - return f"AsyncResponse({self.text!r}, cmd={self.original_command!r})" - - -@dataclass(frozen=True) -class StatusNotification: - """A status broadcast delivered via a ``status`` or ``interrupt`` packet. - - Attributes: - raw: Full raw payload bytes. - packet_type: Either :attr:`PacketType.STATUS` or - :attr:`PacketType.INTERRUPT`. - status_name: The name of the broadcasted status (left of ``>>``). - data: The status payload (between ``>>`` and ``<-``). - module_name: The sending CSM module name (right of ``<-``). - """ - - raw: bytes - packet_type: PacketType = PacketType.STATUS - status_name: str = "" - data: str = "" - module_name: str = "" - - @classmethod - def from_packet(cls, packet: Packet) -> StatusNotification: - """Parse a ``STATUS`` or ``INTERRUPT`` packet. - - Server format: ``" >> <- "``. - """ - text = packet.data.decode("utf-8", errors="replace") - module = "" - left = text - if " <- " in text: - left, module = text.rsplit(" <- ", 1) - module = module.strip() - status_name = "" - data = left.strip() - if " >> " in left: - status_name, data = left.split(" >> ", 1) - status_name = status_name.strip() - data = data.strip() - return cls( - raw=packet.data, - packet_type=packet.type, - status_name=status_name, - data=data, - module_name=module, - ) - - def __repr__(self) -> str: - return ( - f"StatusNotification(status={self.status_name!r}, " - f"data={self.data!r}, module={self.module_name!r})" - ) diff --git a/SDK/python/src/csm_tcp_router_client.py b/SDK/python/src/csm_tcp_router_client.py new file mode 100644 index 0000000..4058fab --- /dev/null +++ b/SDK/python/src/csm_tcp_router_client.py @@ -0,0 +1,1316 @@ +"""csm-tcp-router-client – single-file Python client SDK for the CSM-TCP-Router server. + +This module bundles the entire client implementation (sync and async) along +with the wire-protocol codec, exception hierarchy and public data models +into a single importable file. + +Sync usage:: + + from csm_tcp_router_client import TcpRouterClient + + with TcpRouterClient() as client: + client.connect("localhost", 30007) + print(client.list_modules()) + +Async usage:: + + import asyncio + from csm_tcp_router_client import AsyncTcpRouterClient + + async def main(): + async with AsyncTcpRouterClient() as client: + await client.connect("localhost", 30007) + print(await client.list_modules()) + + asyncio.run(main()) + +Wire format (8-byte header, big-endian):: + + | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | + ╰────────────────────────── Header (8B) ──────────────────────────╯ + +followed by exactly ``Data Length`` bytes of payload. +""" + +from __future__ import annotations + +import asyncio +import inspect +import queue +import socket +import struct +import threading +import time +from dataclasses import dataclass +from enum import IntEnum +from typing import Any, Callable, Coroutine, Dict, Optional, Tuple, Union + +__all__ = [ + # Clients + "TcpRouterClient", + "AsyncTcpRouterClient", + # Exceptions + "TcpRouterError", + "ConnectionError", + "TimeoutError", + "ProtocolError", + "ServerError", + # Models + "PacketType", + "Packet", + "CommandResponse", + "AsyncResponse", + "StatusNotification", + # Version + "__version__", +] + +__version__ = "0.3.0" + + +# =========================================================================== +# Exceptions +# =========================================================================== + + +class TcpRouterError(Exception): + """Base exception for all CSM-TCP-Router client errors.""" + + +class ConnectionError(TcpRouterError): + """Raised when a connection cannot be established or is lost.""" + + +class TimeoutError(TcpRouterError): + """Raised when a synchronous operation exceeds its timeout.""" + + +class ProtocolError(TcpRouterError): + """Raised when an invalid or unexpected protocol frame is received.""" + + +class ServerError(TcpRouterError): + """Raised when the server returns an error packet. + + Attributes: + message: Human-readable error text from the server. + code: Optional error code extracted from the CSM Error format + ``[Error: ] ``. + """ + + def __init__(self, message: str, code: str = "") -> None: + super().__init__(message) + self.message = message + self.code = code + + def __str__(self) -> str: + if self.code: + return f"[Error: {self.code}] {self.message}" + return self.message + + +# Internal aliases used by the transport / receive code below to avoid +# ambiguity with the module-level shadowed builtins. +_RouterConnectionError = ConnectionError +_RouterTimeoutError = TimeoutError + + +# =========================================================================== +# Public data models +# =========================================================================== + + +class PacketType(IntEnum): + """Packet type constants as defined in the CSM-TCP-Router protocol v0. + + Wire values + ----------- + ``INFO`` 0x00 – informational messages (welcome / goodbye) + ``ERROR`` 0x01 – error messages from the server + ``CMD`` 0x02 – command sent by the client + ``CMD_RESP`` 0x03 – server handshake for async / no-reply / subscribe + ``RESP`` 0x04 – synchronous response payload + ``ASYNC_RESP`` 0x05 – asynchronous response payload + ``STATUS`` 0x06 – status broadcast from a subscribed CSM module + ``INTERRUPT`` 0x07 – interrupt broadcast from a subscribed CSM module + """ + + INFO = 0x00 + ERROR = 0x01 + CMD = 0x02 + CMD_RESP = 0x03 + RESP = 0x04 + ASYNC_RESP = 0x05 + STATUS = 0x06 + INTERRUPT = 0x07 + + +@dataclass(frozen=True) +class Packet: + """A decoded packet received from the server (internal representation).""" + + type: PacketType + data: bytes + version: int = 1 + flag1: int = 0 + flag2: int = 0 + + +@dataclass(frozen=True) +class CommandResponse: + """The result of a synchronous command (:meth:`TcpRouterClient.send_and_wait`).""" + + raw: bytes + + @property + def text(self) -> str: + """Decoded UTF-8 text of the response payload.""" + return self.raw.decode("utf-8", errors="replace") + + def __repr__(self) -> str: + return f"CommandResponse({self.text!r})" + + +@dataclass(frozen=True) +class AsyncResponse: + """An asynchronous response payload delivered via an ``async-resp`` packet. + + Attributes: + raw: Raw response bytes (the part *before* the `` <- `` separator). + original_command: The original command text echoed back by the server + (the part *after* the `` <- `` separator). + """ + + raw: bytes + original_command: str = "" + + @property + def text(self) -> str: + """Decoded UTF-8 text of the response payload.""" + return self.raw.decode("utf-8", errors="replace") + + @classmethod + def from_packet(cls, packet: Packet) -> AsyncResponse: + """Parse an ``ASYNC_RESP`` packet. + + Server format: ``" <- "``. + """ + text = packet.data.decode("utf-8", errors="replace") + parts = text.split(" <- ", 1) + if len(parts) == 2: + return cls(raw=parts[0].encode("utf-8"), original_command=parts[1]) + return cls(raw=packet.data) + + def __repr__(self) -> str: + return f"AsyncResponse({self.text!r}, cmd={self.original_command!r})" + + +@dataclass(frozen=True) +class StatusNotification: + """A status broadcast delivered via a ``status`` or ``interrupt`` packet. + + Attributes: + raw: Full raw payload bytes. + packet_type: Either :attr:`PacketType.STATUS` or + :attr:`PacketType.INTERRUPT`. + status_name: The name of the broadcasted status (left of ``>>``). + data: The status payload (between ``>>`` and ``<-``). + module_name: The sending CSM module name (right of ``<-``). + """ + + raw: bytes + packet_type: PacketType = PacketType.STATUS + status_name: str = "" + data: str = "" + module_name: str = "" + + @classmethod + def from_packet(cls, packet: Packet) -> StatusNotification: + """Parse a ``STATUS`` or ``INTERRUPT`` packet. + + Server format: ``" >> <- "``. + """ + text = packet.data.decode("utf-8", errors="replace") + module = "" + left = text + if " <- " in text: + left, module = text.rsplit(" <- ", 1) + module = module.strip() + status_name = "" + data = left.strip() + if " >> " in left: + status_name, data = left.split(" >> ", 1) + status_name = status_name.strip() + data = data.strip() + return cls( + raw=packet.data, + packet_type=packet.type, + status_name=status_name, + data=data, + module_name=module, + ) + + def __repr__(self) -> str: + return ( + f"StatusNotification(status={self.status_name!r}, " + f"data={self.data!r}, module={self.module_name!r})" + ) + + +# =========================================================================== +# Protocol codec (internal but importable for advanced use / testing) +# =========================================================================== + +# Header layout: big-endian uint32 data_len + 4 x uint8 (version, type, flag1, flag2) +_HEADER_FORMAT = "!IBBBB" + +#: Number of bytes in the fixed packet header. +HEADER_SIZE: int = struct.calcsize(_HEADER_FORMAT) # == 8 + +#: Protocol version byte sent in every outgoing packet. +PROTOCOL_VERSION: int = 0x01 + + +def encode_packet( + data: bytes, + packet_type: PacketType, + flag1: int = 0, + flag2: int = 0, +) -> bytes: + """Encode *data* into a complete wire-format packet (header + body). + + :param data: Raw payload bytes. + :param packet_type: :class:`PacketType` for the header. + :param flag1: FLAG1 byte (currently unused; defaults to 0). + :param flag2: FLAG2 byte (currently unused; defaults to 0). + :returns: Concatenated header + payload bytes ready for ``sendall()``. + """ + header = struct.pack( + _HEADER_FORMAT, + len(data), + PROTOCOL_VERSION, + packet_type.value, + flag1, + flag2, + ) + return header + data + + +def decode_header(header_bytes: bytes) -> Tuple[int, int, int, int, int]: + """Decode an 8-byte header into its constituent fields. + + :returns: ``(data_len, version, type_byte, flag1, flag2)`` + :raises ProtocolError: if *header_bytes* is not exactly :data:`HEADER_SIZE` bytes. + """ + if len(header_bytes) != HEADER_SIZE: + raise ProtocolError( + f"Expected {HEADER_SIZE}-byte header, got {len(header_bytes)} bytes." + ) + return struct.unpack(_HEADER_FORMAT, header_bytes) # type: ignore[return-value] + + +def parse_packet(header_bytes: bytes, body: bytes) -> Packet: + """Build a :class:`Packet` from raw header + body. + + Unknown packet type bytes are mapped to :attr:`PacketType.INFO` for + forward compatibility (the server may introduce new types in future + protocol revisions). + + :raises ProtocolError: on header size mismatch or body length mismatch. + """ + data_len, version, type_byte, flag1, flag2 = decode_header(header_bytes) + if len(body) != data_len: + raise ProtocolError( + f"Payload length mismatch: header says {data_len} bytes, " + f"got {len(body)} bytes." + ) + try: + ptype = PacketType(type_byte) + except ValueError: + # Forward-compatible: treat unknown type as INFO + ptype = PacketType.INFO + return Packet(type=ptype, data=body, version=version, flag1=flag1, flag2=flag2) + + +# =========================================================================== +# Shared server-error parsing helper +# =========================================================================== + + +def _parse_server_error(packet: Packet) -> ServerError: + """Extract code and message from a CSM Error format ``[Error: ] ``.""" + text = packet.data.decode("utf-8", errors="replace").strip() + code = "" + msg = text + if text.startswith("[Error:"): + try: + end_idx = text.index("]") + code = text[7:end_idx].strip() + msg = text[end_idx + 1 :].strip() + except ValueError: + pass + return ServerError(msg, code) + + +# =========================================================================== +# Internal: thread-based TCP transport (used by the sync client) +# =========================================================================== + + +class _Transport: + """Thread-safe, blocking TCP transport. + + A background daemon thread continuously reads packets from the socket and + dispatches them via *on_packet*. Callers are responsible for keeping + callbacks fast and non-blocking, as they run in the receive thread. + """ + + def __init__( + self, + on_packet: Callable[[Packet], None], + on_disconnect: Callable[[], None], + ) -> None: + self._sock: Optional[socket.socket] = None + self._send_lock = threading.Lock() + self._stop_event = threading.Event() + self._recv_thread: Optional[threading.Thread] = None + self._on_packet = on_packet + self._on_disconnect = on_disconnect + + @property + def connected(self) -> bool: + """``True`` while the socket is open and the stop event has not fired.""" + return self._sock is not None and not self._stop_event.is_set() + + def connect(self, host: str, port: int, timeout: float = 5.0) -> None: + """Open a TCP connection and start the receive thread.""" + if self.connected: + raise _RouterConnectionError( + "Already connected; call disconnect() first." + ) + sock: Optional[socket.socket] = None + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((host, port)) + sock.settimeout(None) # switch to blocking for the recv loop + except OSError as exc: + if sock is not None: + try: + sock.close() + except OSError: + pass + raise _RouterConnectionError( + f"Cannot connect to {host}:{port}: {exc}" + ) from exc + + self._sock = sock + self._stop_event.clear() + self._recv_thread = threading.Thread( + target=self._recv_loop, + daemon=True, + name="csm-tcp-router-recv", + ) + self._recv_thread.start() + + def disconnect(self, join_timeout: float = 2.0) -> None: + """Close the connection and stop the receive thread.""" + self._stop_event.set() + if self._sock is not None: + try: + self._sock.shutdown(socket.SHUT_RDWR) + except OSError: + pass + try: + self._sock.close() + except OSError: + pass + self._sock = None + if self._recv_thread is not None and self._recv_thread.is_alive(): + self._recv_thread.join(timeout=join_timeout) + + def send_raw(self, data: bytes) -> None: + """Send *data* atomically. Thread-safe.""" + if not self.connected: + raise _RouterConnectionError("Not connected.") + with self._send_lock: + try: + self._sock.sendall(data) # type: ignore[union-attr] + except OSError as exc: + self._stop_event.set() + raise _RouterConnectionError(f"Send failed: {exc}") from exc + + def _recv_all(self, size: int) -> bytes: + """Read exactly *size* bytes; returns empty bytes on clean EOF or disconnect.""" + buf = bytearray(size) + view = memoryview(buf) + received = 0 + while received < size: + sock = self._sock # capture locally to avoid TOCTOU race with disconnect() + if sock is None: + return b"" + try: + n = sock.recv_into(view[received:], size - received) + except OSError: + return b"" + if n == 0: + return b"" + received += n + return bytes(buf) + + def _recv_loop(self) -> None: + """Background thread: read packets and dispatch via callback.""" + try: + while not self._stop_event.is_set(): + header = self._recv_all(HEADER_SIZE) + if not header: + break + + # Extract data_len from the first 4 bytes without full decode + (data_len,) = struct.unpack("!I", header[:4]) + body = self._recv_all(data_len) + if len(body) != data_len: + break + + try: + packet = parse_packet(header, body) + except ProtocolError: + # Corrupted frame – skip it and keep the loop alive + continue + + self._on_packet(packet) + + except OSError: + pass + finally: + if not self._stop_event.is_set(): + self._stop_event.set() + self._on_disconnect() + + +# =========================================================================== +# TcpRouterClient – thread-based synchronous client +# =========================================================================== + +# Type aliases +_SubKey = Tuple[str, str] +StatusCallback = Callable[[StatusNotification], None] +AsyncCallback = Callable[[AsyncResponse], None] + +# Items held in the internal queues are either Packet or Exception instances. +_QueueItem = object + + +class TcpRouterClient: + """Python client for a CSM-TCP-Router server. + + This class mirrors the LabVIEW ClientAPI VIs and speaks the + CSM-TCP-Router protocol v0. It is thread-safe in that its internal + state is protected by locks; however, the protocol allows at most one + in-flight *synchronous* command at a time and at most one in-flight + *async* command / subscription at a time. Concurrent callers are + serialised by ``_resp_lock`` and ``_cmd_resp_lock`` respectively. + + **Quickstart**:: + + from csm_tcp_router_client import TcpRouterClient + + with TcpRouterClient() as client: + client.connect("localhost", 30007) + print(client.list_modules()) + + **Protocol flows**: + + - *Synchronous* command (``-@``): :meth:`send_and_wait` – sends a ``CMD`` + packet and blocks until a ``RESP`` (or ``ERROR``) is received. + - *Asynchronous* command (``->``): :meth:`post` – sends a ``CMD`` packet + and blocks until the ``CMD_RESP`` handshake is received; the eventual + ``ASYNC_RESP`` is delivered asynchronously. + - *No-reply async* command (``->|``): :meth:`post_no_reply` – same as + :meth:`post` but no ``ASYNC_RESP`` will ever arrive. + - *Subscribe / unsubscribe*: :meth:`subscribe_status` / + :meth:`unsubscribe_status` – sends a ```` / ```` + command and waits for the ``CMD_RESP`` handshake. + + **Received-packet routing** (on the background receive thread): + + - ``RESP`` (0x04) – unblocks the caller of :meth:`send_and_wait`. + - ``CMD_RESP`` (0x03) – unblocks callers of :meth:`post`, + :meth:`post_no_reply`, :meth:`subscribe_status`, and + :meth:`unsubscribe_status`. + - ``ASYNC_RESP`` (0x05) – added to :attr:`async_response_queue` and + dispatched to any matching :meth:`register_async_callback`. + - ``STATUS`` / ``INTERRUPT`` (0x06 / 0x07) – added to + :attr:`status_queue` and dispatched to any matching + :meth:`subscribe_status` callback. + - ``ERROR`` (0x01) – unblocks any pending synchronous waiter with a + :exc:`ServerError`. + - ``INFO`` (0x00) – silently discarded (welcome / goodbye messages). + """ + + def __init__(self) -> None: + self._transport = _Transport( + on_packet=self._on_packet, + on_disconnect=self._on_disconnect, + ) + + # One-item-deep queues for synchronised waits. + # Items are either Packet or Exception instances. + self._resp_queue: queue.Queue[_QueueItem] = queue.Queue() + self._cmd_resp_queue: queue.Queue[_QueueItem] = queue.Queue() + + #: Polling queue for :class:`AsyncResponse` objects received from the server. + self.async_response_queue: queue.Queue[AsyncResponse] = queue.Queue() + + #: Polling queue for :class:`StatusNotification` objects received + #: from the server. + self.status_queue: queue.Queue[StatusNotification] = queue.Queue() + + # Callback registries (protected by _lock) + self._status_callbacks: Dict[_SubKey, Optional[StatusCallback]] = {} + self._async_callbacks: Dict[str, AsyncCallback] = {} + self._lock = threading.Lock() + + # Serialisation locks – at most one in-flight RESP / CMD_RESP waiter + # at a time. This prevents concurrent callers from consuming each + # other's response packets. + self._resp_lock = threading.Lock() + self._cmd_resp_lock = threading.Lock() + + # ------------------------------------------------------------------ + # Connection management + # ------------------------------------------------------------------ + + def connect(self, host: str, port: int, timeout: float = 5.0) -> None: + """Connect to a CSM-TCP-Router server. + + :param host: Server hostname or IP address. + :param port: Server TCP port (the reference app defaults to 30007). + :param timeout: Connect timeout in seconds. + :raises ConnectionError: if the connection cannot be established. + """ + self._transport.connect(host, port, timeout=timeout) + + def disconnect(self) -> None: + """Disconnect from the server and release all resources. + + Safe to call even if not currently connected. Any threads currently + blocked inside :meth:`send_and_wait`, :meth:`post`, or similar methods + will receive a :exc:`ConnectionError` immediately rather than + waiting for their timeout to expire. + """ + # Wake blocked waiters *before* tearing down the transport. + sentinel = _RouterConnectionError("Disconnected from server.") + self._resp_queue.put(sentinel) + self._cmd_resp_queue.put(sentinel) + self._transport.disconnect() + + @property + def connected(self) -> bool: + """``True`` if the underlying transport is currently connected.""" + return self._transport.connected + + def wait_for_server( + self, + host: str, + port: int, + timeout: float = 30.0, + retry_interval: float = 0.5, + ) -> bool: + """Poll until *host*:*port* accepts a connection or *timeout* elapses. + + :returns: ``True`` when the server is reachable; ``False`` on timeout. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + probe = _Transport( + on_packet=lambda _p: None, + on_disconnect=lambda: None, + ) + try: + probe.connect(host, port, timeout=1.0) + probe.disconnect() + return True + except _RouterConnectionError: + pass + time.sleep(retry_interval) + return False + + # ------------------------------------------------------------------ + # Core command methods + # ------------------------------------------------------------------ + + def send_and_wait(self, command: str, timeout: float = 5.0) -> CommandResponse: + """Send a **synchronous** command and block until the response arrives. + + Use the CSM synchronous message suffix ``-@`` in *command*:: + + resp = client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + + The built-in router management commands (``List``, ``Ping``, …) are + also synchronous and do not require a suffix. + + :raises ConnectionError: if not connected. + :raises TimeoutError: if no response arrives within *timeout*. + :raises ServerError: if the server returns an error packet. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + with self._resp_lock: + self._transport.send_raw(wire) + return self._wait_for_resp(timeout) + + def post(self, command: str, timeout: float = 5.0) -> None: + """Send an **asynchronous** command and wait for the ``cmd-resp`` handshake. + + Use the CSM async message suffix ``->`` in *command*:: + + client.post("API: Start Sampling -> DAQmx") + + The eventual ``async-resp`` payload will be delivered to any callback + registered with :meth:`register_async_callback` and added to + :attr:`async_response_queue`. + + :raises ConnectionError: if not connected. + :raises TimeoutError: if no handshake arrives within *timeout*. + :raises ServerError: if the server rejects the command. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + + def post_no_reply(self, command: str, timeout: float = 5.0) -> None: + """Send an **async no-reply** command and wait for the ``cmd-resp`` handshake. + + Use the CSM no-reply suffix ``->|`` in *command*:: + + client.post_no_reply("API: Reset ->| DAQmx") + + After the handshake the server will not send any further response. + """ + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + + def ping(self, timeout: float = 2.0) -> Tuple[bool, float]: + """Send a ``Ping`` command and measure round-trip latency. + + :returns: ``(True, elapsed_seconds)`` on success, + ``(False, 0.0)`` on failure or error. + """ + try: + t0 = time.monotonic() + self.send_and_wait("Ping", timeout=timeout) + return True, time.monotonic() - t0 + except (_RouterConnectionError, _RouterTimeoutError, ServerError): + return False, 0.0 + + # ------------------------------------------------------------------ + # Router management helpers + # ------------------------------------------------------------------ + + def list_modules(self, timeout: float = 5.0) -> str: + """Return the server's loaded CSM module list as plain text.""" + return self.send_and_wait("List", timeout=timeout).text + + def list_api(self, module: str, timeout: float = 5.0) -> str: + """Return the API list for *module* as plain text.""" + return self.send_and_wait(f"List API {module}", timeout=timeout).text + + def list_states(self, module: str, timeout: float = 5.0) -> str: + """Return the CSM state list for *module* as plain text.""" + return self.send_and_wait(f"List State {module}", timeout=timeout).text + + def help(self, module: str, timeout: float = 5.0) -> str: + """Return the help text for *module* as plain text.""" + return self.send_and_wait(f"Help {module}", timeout=timeout).text + + # ------------------------------------------------------------------ + # Status / interrupt subscriptions + # ------------------------------------------------------------------ + + def subscribe_status( + self, + status_name: str, + module_name: str, + callback: Optional[StatusCallback] = None, + timeout: float = 5.0, + ) -> None: + """Subscribe to a CSM module's status broadcast. + + Sends ``"@ ->"`` and waits for + the ``cmd-resp`` handshake. Once subscribed, + :class:`StatusNotification` objects will be: + + * delivered to *callback* (if provided), and + * added to :attr:`status_queue`. + + :param callback: Optional callable invoked on each notification. + Must be fast and non-blocking (runs in the recv thread). + """ + # Register the callback *before* sending to eliminate the race where + # a STATUS packet could arrive before the callback is stored. + with self._lock: + self._status_callbacks[(status_name, module_name)] = callback + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + try: + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + except Exception: + with self._lock: + self._status_callbacks.pop((status_name, module_name), None) + raise + + def unsubscribe_status( + self, + status_name: str, + module_name: str, + timeout: float = 5.0, + ) -> None: + """Cancel a status subscription.""" + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + with self._cmd_resp_lock: + self._transport.send_raw(wire) + self._wait_for_cmd_resp(timeout) + with self._lock: + self._status_callbacks.pop((status_name, module_name), None) + + def register_async_callback( + self, + original_command: str, + callback: AsyncCallback, + ) -> None: + """Register a callback for ``async-resp`` packets. + + The callback is matched by *original_command* (the command text + echoed in the ``async-resp`` payload after the `` <- `` separator). + """ + with self._lock: + self._async_callbacks[original_command] = callback + + def unregister_async_callback(self, original_command: str) -> None: + """Remove a previously registered async callback.""" + with self._lock: + self._async_callbacks.pop(original_command, None) + + # ------------------------------------------------------------------ + # Context-manager support + # ------------------------------------------------------------------ + + def __enter__(self) -> TcpRouterClient: + return self + + def __exit__(self, *_args: object) -> None: + self.disconnect() + + # ------------------------------------------------------------------ + # Internal: packet dispatch (runs in the receive thread) + # ------------------------------------------------------------------ + + def _on_packet(self, packet: Packet) -> None: + ptype = packet.type + if ptype == PacketType.RESP: + self._resp_queue.put(packet) + + elif ptype == PacketType.CMD_RESP: + self._cmd_resp_queue.put(packet) + + elif ptype == PacketType.ASYNC_RESP: + resp = AsyncResponse.from_packet(packet) + self.async_response_queue.put(resp) + with self._lock: + cb = self._async_callbacks.get(resp.original_command) + if cb is not None: + try: + cb(resp) + except Exception: + pass + + elif ptype in (PacketType.STATUS, PacketType.INTERRUPT): + notif = StatusNotification.from_packet(packet) + self.status_queue.put(notif) + with self._lock: + cb = self._status_callbacks.get( # type: ignore[assignment] + (notif.status_name, notif.module_name) + ) + if cb is not None: + try: + cb(notif) # type: ignore[call-arg] + except Exception: + pass + + elif ptype == PacketType.ERROR: + err = _parse_server_error(packet) + # Unblock any pending synchronous waiter + self._resp_queue.put(err) + self._cmd_resp_queue.put(err) + + # PacketType.INFO is silently discarded (welcome / goodbye messages) + + def _on_disconnect(self) -> None: + """Called from the receive thread when the connection drops unexpectedly.""" + sentinel = _RouterConnectionError("Connection lost unexpectedly.") + self._resp_queue.put(sentinel) + self._cmd_resp_queue.put(sentinel) + + # ------------------------------------------------------------------ + # Internal: synchronised waiters + # ------------------------------------------------------------------ + + def _wait_for_resp(self, timeout: float) -> CommandResponse: + try: + item = self._resp_queue.get(timeout=timeout) + except queue.Empty: + raise _RouterTimeoutError( + f"No response received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + assert isinstance(item, Packet) + return CommandResponse(raw=item.data) + + def _wait_for_cmd_resp(self, timeout: float) -> None: + try: + item = self._cmd_resp_queue.get(timeout=timeout) + except queue.Empty: + raise _RouterTimeoutError( + f"No cmd-resp received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + # CMD_RESP payload is a handshake acknowledgment; discard it + + +# =========================================================================== +# AsyncTcpRouterClient – asyncio-based client +# =========================================================================== + +# Async callback type aliases – both plain callables and coroutines accepted +_SyncStatusCb = Callable[[StatusNotification], None] +_AsyncStatusCb = Callable[[StatusNotification], "Coroutine[Any, Any, None]"] +AsyncStatusCallback = Union[_SyncStatusCb, _AsyncStatusCb] + +_SyncAsyncRespCb = Callable[[AsyncResponse], None] +_AsyncAsyncRespCb = Callable[[AsyncResponse], "Coroutine[Any, Any, None]"] +AsyncRespCallback = Union[_SyncAsyncRespCb, _AsyncAsyncRespCb] + + +class AsyncTcpRouterClient: + """Asyncio client for a CSM-TCP-Router server. + + Provides the same interface as :class:`TcpRouterClient` but as + ``async def`` coroutines, suitable for use inside an asyncio event loop. + + **Quickstart**:: + + import asyncio + from csm_tcp_router_client import AsyncTcpRouterClient + + async def main(): + async with AsyncTcpRouterClient() as client: + await client.connect("localhost", 30007) + print(await client.list_modules()) + resp = await client.send_and_wait("API: Read -@ DAQmx") + print(resp.text) + + asyncio.run(main()) + + **Protocol flows** are identical to :class:`TcpRouterClient`. + + **Callbacks** passed to :meth:`subscribe_status` and + :meth:`register_async_callback` may be either a plain callable *or* an + ``async def`` coroutine — both are supported. + + **Polling queues** (:attr:`async_response_queue`, :attr:`status_queue`) are + created when :meth:`connect` is called and are bound to the running event + loop. Access them only after :meth:`connect` has been awaited. + """ + + def __init__(self) -> None: + self._reader: Optional[asyncio.StreamReader] = None + self._writer: Optional[asyncio.StreamWriter] = None + self._recv_task: Optional[asyncio.Task[None]] = None + + # Asyncio objects created lazily in connect() to bind to the running loop + self._resp_queue: Optional[asyncio.Queue[object]] = None + self._cmd_resp_queue: Optional[asyncio.Queue[object]] = None + self._send_lock: Optional[asyncio.Lock] = None + # Serialisation locks – at most one in-flight RESP / CMD_RESP waiter + self._resp_lock: Optional[asyncio.Lock] = None + self._cmd_resp_lock: Optional[asyncio.Lock] = None + + #: Polling queue for :class:`AsyncResponse` objects received from the + #: server. Available after :meth:`connect` is called. + self.async_response_queue: Optional[asyncio.Queue[AsyncResponse]] = None + + #: Polling queue for :class:`StatusNotification` objects received from + #: the server. Available after :meth:`connect`. + self.status_queue: Optional[asyncio.Queue[StatusNotification]] = None + + # Callback registries – plain dicts (asyncio is single-threaded) + self._status_callbacks: Dict[_SubKey, Optional[AsyncStatusCallback]] = {} + self._async_callbacks: Dict[str, AsyncRespCallback] = {} + + # ------------------------------------------------------------------ + # Connection management + # ------------------------------------------------------------------ + + def _init_async_objects(self) -> None: + """(Re)create asyncio objects bound to the current running loop.""" + self._resp_queue = asyncio.Queue() + self._cmd_resp_queue = asyncio.Queue() + self._send_lock = asyncio.Lock() + self._resp_lock = asyncio.Lock() + self._cmd_resp_lock = asyncio.Lock() + self.async_response_queue = asyncio.Queue() + self.status_queue = asyncio.Queue() + + @property + def connected(self) -> bool: + """``True`` while the writer is open and not being closed.""" + return self._writer is not None and not self._writer.is_closing() + + async def connect(self, host: str, port: int, timeout: float = 5.0) -> None: + """Open a TCP connection and start the background receive task.""" + if self.connected: + raise _RouterConnectionError( + "Already connected; call disconnect() first." + ) + self._init_async_objects() + try: + self._reader, self._writer = await asyncio.wait_for( + asyncio.open_connection(host, port), timeout=timeout + ) + except asyncio.TimeoutError: + raise _RouterConnectionError( + f"Connection to {host}:{port} timed out after {timeout:.1f}s." + ) from None + except OSError as exc: + raise _RouterConnectionError( + f"Cannot connect to {host}:{port}: {exc}" + ) from exc + self._recv_task = asyncio.ensure_future(self._recv_loop()) + + async def disconnect(self) -> None: + """Close the connection and stop the background receive task. + + Safe to call even if not currently connected. Any coroutines currently + blocked inside :meth:`send_and_wait`, :meth:`post`, or similar methods + will receive a :exc:`ConnectionError` immediately rather than waiting + for their timeout to expire. + """ + # Wake blocked waiters *before* cancelling the recv task. + sentinel = _RouterConnectionError("Disconnected from server.") + if self._resp_queue is not None: + self._resp_queue.put_nowait(sentinel) + if self._cmd_resp_queue is not None: + self._cmd_resp_queue.put_nowait(sentinel) + # Cancel the recv task first; its finally block notifies pending waiters + if self._recv_task is not None and not self._recv_task.done(): + self._recv_task.cancel() + try: + await self._recv_task + except (asyncio.CancelledError, Exception): + pass + self._recv_task = None + + if self._writer is not None: + try: + self._writer.close() + await self._writer.wait_closed() + except OSError: + pass + self._writer = None + self._reader = None + + async def wait_for_server( + self, + host: str, + port: int, + timeout: float = 30.0, + retry_interval: float = 0.5, + ) -> bool: + """Poll until *host*:*port* accepts a connection or *timeout* elapses.""" + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + _, writer = await asyncio.wait_for( + asyncio.open_connection(host, port), timeout=1.0 + ) + writer.close() + try: + await writer.wait_closed() + except OSError: + pass + return True + except (OSError, asyncio.TimeoutError): + pass + await asyncio.sleep(retry_interval) + return False + + # ------------------------------------------------------------------ + # Core command methods + # ------------------------------------------------------------------ + + async def send_and_wait( + self, command: str, timeout: float = 5.0 + ) -> CommandResponse: + """Send a **synchronous** command and await the response.""" + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + assert self._resp_lock is not None + async with self._resp_lock: + await self._send_raw(wire) + return await self._wait_for_resp(timeout) + + async def post(self, command: str, timeout: float = 5.0) -> None: + """Send an **asynchronous** command and await the ``cmd-resp`` handshake.""" + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + + async def post_no_reply(self, command: str, timeout: float = 5.0) -> None: + """Send an **async no-reply** command and await the ``cmd-resp`` handshake.""" + wire = encode_packet(command.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + + async def ping(self, timeout: float = 2.0) -> Tuple[bool, float]: + """Send a ``Ping`` command and measure round-trip latency. + + :returns: ``(True, elapsed_seconds)`` on success, ``(False, 0.0)`` on failure. + """ + try: + t0 = time.monotonic() + await self.send_and_wait("Ping", timeout=timeout) + return True, time.monotonic() - t0 + except (_RouterConnectionError, _RouterTimeoutError, ServerError): + return False, 0.0 + + # ------------------------------------------------------------------ + # Router management helpers + # ------------------------------------------------------------------ + + async def list_modules(self, timeout: float = 5.0) -> str: + """Return the server's loaded CSM module list as plain text.""" + return (await self.send_and_wait("List", timeout=timeout)).text + + async def list_api(self, module: str, timeout: float = 5.0) -> str: + """Return the API list for *module* as plain text.""" + return (await self.send_and_wait(f"List API {module}", timeout=timeout)).text + + async def list_states(self, module: str, timeout: float = 5.0) -> str: + """Return the CSM state list for *module* as plain text.""" + return ( + await self.send_and_wait(f"List State {module}", timeout=timeout) + ).text + + async def help(self, module: str, timeout: float = 5.0) -> str: + """Return the help text for *module* as plain text.""" + return (await self.send_and_wait(f"Help {module}", timeout=timeout)).text + + # ------------------------------------------------------------------ + # Status / interrupt subscriptions + # ------------------------------------------------------------------ + + async def subscribe_status( + self, + status_name: str, + module_name: str, + callback: Optional[AsyncStatusCallback] = None, + timeout: float = 5.0, + ) -> None: + """Subscribe to a CSM module's status broadcast. + + Sends ``"@ ->"`` and awaits the + ``cmd-resp`` handshake. Once subscribed, :class:`StatusNotification` + objects will be: + + * delivered to *callback* (if provided – sync or async both accepted), and + * added to :attr:`status_queue`. + """ + # Register the callback before sending to eliminate the race where a + # STATUS packet could arrive before the callback is stored. + self._status_callbacks[(status_name, module_name)] = callback + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + try: + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + except Exception: + self._status_callbacks.pop((status_name, module_name), None) + raise + + async def unsubscribe_status( + self, + status_name: str, + module_name: str, + timeout: float = 5.0, + ) -> None: + """Cancel a status subscription.""" + cmd = f"{status_name}@{module_name} ->" + wire = encode_packet(cmd.encode("utf-8"), PacketType.CMD) + assert self._cmd_resp_lock is not None + async with self._cmd_resp_lock: + await self._send_raw(wire) + await self._wait_for_cmd_resp(timeout) + self._status_callbacks.pop((status_name, module_name), None) + + def register_async_callback( + self, + original_command: str, + callback: AsyncRespCallback, + ) -> None: + """Register a callback for ``async-resp`` packets. + + Callbacks may be either a plain callable or an ``async def`` coroutine. + """ + self._async_callbacks[original_command] = callback + + def unregister_async_callback(self, original_command: str) -> None: + """Remove a previously registered async-response callback.""" + self._async_callbacks.pop(original_command, None) + + # ------------------------------------------------------------------ + # Async context-manager support + # ------------------------------------------------------------------ + + async def __aenter__(self) -> AsyncTcpRouterClient: + return self + + async def __aexit__(self, *_args: object) -> None: + await self.disconnect() + + # ------------------------------------------------------------------ + # Internal: send + # ------------------------------------------------------------------ + + async def _send_raw(self, data: bytes) -> None: + if not self.connected: + raise _RouterConnectionError("Not connected.") + assert self._writer is not None + assert self._send_lock is not None + async with self._send_lock: + self._writer.write(data) + await self._writer.drain() + + # ------------------------------------------------------------------ + # Internal: receive loop (background task) + # ------------------------------------------------------------------ + + async def _recv_loop(self) -> None: + """Background task: read frames and dispatch them.""" + assert self._reader is not None + try: + while True: + header = await self._reader.readexactly(HEADER_SIZE) + (data_len,) = struct.unpack("!I", header[:4]) + body = ( + await self._reader.readexactly(data_len) if data_len else b"" + ) + try: + packet = parse_packet(header, body) + except ProtocolError: + continue # skip corrupted frame; keep connection alive + await self._dispatch_packet(packet) + except (asyncio.IncompleteReadError, asyncio.CancelledError, OSError): + pass + finally: + self._notify_disconnect() + + async def _dispatch_packet(self, packet: Packet) -> None: + """Route a received packet to the correct queue and/or callback.""" + assert self._resp_queue is not None + assert self._cmd_resp_queue is not None + assert self.async_response_queue is not None + assert self.status_queue is not None + + ptype = packet.type + + if ptype == PacketType.RESP: + self._resp_queue.put_nowait(packet) + + elif ptype == PacketType.CMD_RESP: + self._cmd_resp_queue.put_nowait(packet) + + elif ptype == PacketType.ASYNC_RESP: + resp = AsyncResponse.from_packet(packet) + self.async_response_queue.put_nowait(resp) + cb = self._async_callbacks.get(resp.original_command) + if cb is not None: + try: + result = cb(resp) # type: ignore[arg-type] + if inspect.isawaitable(result): + await result + except Exception: + pass + + elif ptype in (PacketType.STATUS, PacketType.INTERRUPT): + notif = StatusNotification.from_packet(packet) + self.status_queue.put_nowait(notif) + cb = self._status_callbacks.get( # type: ignore[assignment] + (notif.status_name, notif.module_name) + ) + if cb is not None: + try: + result = cb(notif) # type: ignore[arg-type] + if inspect.isawaitable(result): + await result + except Exception: + pass + + elif ptype == PacketType.ERROR: + err = _parse_server_error(packet) + self._resp_queue.put_nowait(err) + self._cmd_resp_queue.put_nowait(err) + + # PacketType.INFO is silently discarded (welcome / goodbye messages) + + def _notify_disconnect(self) -> None: + """Put sentinels in waiter queues when the connection is lost.""" + if self._resp_queue is None: + return + sentinel = _RouterConnectionError("Connection lost unexpectedly.") + self._resp_queue.put_nowait(sentinel) + self._cmd_resp_queue.put_nowait(sentinel) + + # ------------------------------------------------------------------ + # Internal: synchronised waiters + # ------------------------------------------------------------------ + + async def _wait_for_resp(self, timeout: float) -> CommandResponse: + assert self._resp_queue is not None + try: + item = await asyncio.wait_for(self._resp_queue.get(), timeout=timeout) + except asyncio.TimeoutError: + raise _RouterTimeoutError( + f"No response received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + assert isinstance(item, Packet) + return CommandResponse(raw=item.data) + + async def _wait_for_cmd_resp(self, timeout: float) -> None: + assert self._cmd_resp_queue is not None + try: + item = await asyncio.wait_for( + self._cmd_resp_queue.get(), timeout=timeout + ) + except asyncio.TimeoutError: + raise _RouterTimeoutError( + f"No cmd-resp received within {timeout:.1f}s." + ) from None + if isinstance(item, Exception): + raise item + # CMD_RESP payload is a handshake acknowledgment; discard it diff --git a/SDK/python/tests/conftest.py b/SDK/python/tests/conftest.py index 8055db0..ae07cc6 100644 --- a/SDK/python/tests/conftest.py +++ b/SDK/python/tests/conftest.py @@ -10,8 +10,7 @@ import pytest -from csm_tcp_router._protocol import HEADER_SIZE, encode_packet -from csm_tcp_router.models import PacketType +from csm_tcp_router_client import HEADER_SIZE, PacketType, encode_packet # --------------------------------------------------------------------------- # Internal helpers diff --git a/SDK/python/tests/test_async_client.py b/SDK/python/tests/test_async_client.py index 7047ec2..07be0c0 100644 --- a/SDK/python/tests/test_async_client.py +++ b/SDK/python/tests/test_async_client.py @@ -8,18 +8,17 @@ import pytest -from csm_tcp_router import AsyncTcpRouterClient -from csm_tcp_router.async_client import _parse_server_error -from csm_tcp_router.exceptions import ( - ConnectionError as RouterConnectionError, -) -from csm_tcp_router.exceptions import ( +from csm_tcp_router_client import ( + AsyncResponse, + AsyncTcpRouterClient, + Packet, + PacketType, ServerError, + StatusNotification, + _parse_server_error, ) -from csm_tcp_router.exceptions import ( - TimeoutError as RouterTimeoutError, -) -from csm_tcp_router.models import AsyncResponse, Packet, PacketType, StatusNotification +from csm_tcp_router_client import ConnectionError as RouterConnectionError +from csm_tcp_router_client import TimeoutError as RouterTimeoutError # --------------------------------------------------------------------------- # Helpers diff --git a/SDK/python/tests/test_client.py b/SDK/python/tests/test_client.py index 4f824df..14cb2b9 100644 --- a/SDK/python/tests/test_client.py +++ b/SDK/python/tests/test_client.py @@ -9,17 +9,17 @@ import pytest -from csm_tcp_router.client import TcpRouterClient, _parse_server_error -from csm_tcp_router.exceptions import ( - ConnectionError as RouterConnectionError, -) -from csm_tcp_router.exceptions import ( +from csm_tcp_router_client import ( + AsyncResponse, + Packet, + PacketType, ServerError, + StatusNotification, + TcpRouterClient, + _parse_server_error, ) -from csm_tcp_router.exceptions import ( - TimeoutError as RouterTimeoutError, -) -from csm_tcp_router.models import AsyncResponse, Packet, PacketType, StatusNotification +from csm_tcp_router_client import ConnectionError as RouterConnectionError +from csm_tcp_router_client import TimeoutError as RouterTimeoutError # --------------------------------------------------------------------------- # Helpers: inject packets directly into the client's dispatch method diff --git a/SDK/python/tests/test_integration.py b/SDK/python/tests/test_integration.py index 563dfd1..86cf964 100644 --- a/SDK/python/tests/test_integration.py +++ b/SDK/python/tests/test_integration.py @@ -7,10 +7,8 @@ import pytest -from csm_tcp_router import TcpRouterClient -from csm_tcp_router.exceptions import ServerError -from csm_tcp_router.exceptions import TimeoutError as RouterTimeoutError -from csm_tcp_router.models import StatusNotification +from csm_tcp_router_client import ServerError, StatusNotification, TcpRouterClient +from csm_tcp_router_client import TimeoutError as RouterTimeoutError # All tests in this module use the `mock_server` fixture from conftest.py. @@ -35,7 +33,7 @@ def test_context_manager(self, mock_server): def test_connect_bad_port_raises(self): client = TcpRouterClient() - from csm_tcp_router.exceptions import ConnectionError as RouterConnectionError + from csm_tcp_router_client import ConnectionError as RouterConnectionError with pytest.raises(RouterConnectionError): client.connect("127.0.0.1", 1, timeout=0.5) diff --git a/SDK/python/tests/test_protocol.py b/SDK/python/tests/test_protocol.py index bb5c12e..41d5223 100644 --- a/SDK/python/tests/test_protocol.py +++ b/SDK/python/tests/test_protocol.py @@ -6,15 +6,15 @@ import pytest -from csm_tcp_router._protocol import ( +from csm_tcp_router_client import ( HEADER_SIZE, PROTOCOL_VERSION, + PacketType, + ProtocolError, decode_header, encode_packet, parse_packet, ) -from csm_tcp_router.exceptions import ProtocolError -from csm_tcp_router.models import PacketType # --------------------------------------------------------------------------- # encode_packet From 11937394212a39013e37d2cb38dacb7694857602 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 20:21:22 +0800 Subject: [PATCH 06/12] Add C# csm-tcp-router-client SDK (#38) * Initial plan * Add C# csm-tcp-router-client SDK (single-file), VS solution, xUnit tests, example, NuGet packaging, and GitHub Actions workflow Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/1c9d260f-6983-4c60-9d7a-f07538723b70 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Address review: deterministic closed-port helper, ManualResetEventSlim in unsubscribe test, observe WaitForServerAsync connect task, force disconnect on RESP/CMD_RESP timeout Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a52165ab-7829-42b0-91f9-e4ba84e0b6b9 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- .github/workflows/CSharp_SDK.yml | 124 +++ .gitignore | 23 +- SDK/csharp/CHANGELOG.md | 40 + SDK/csharp/CsmTcpRouter.sln | 73 ++ SDK/csharp/LICENSE | 21 + SDK/csharp/README.md | 189 ++++ SDK/csharp/README.zh-cn.md | 189 ++++ .../examples/BasicUsage/BasicUsage.csproj | 17 + SDK/csharp/examples/BasicUsage/Program.cs | 91 ++ SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs | 924 ++++++++++++++++++ .../src/CsmTcpRouter/CsmTcpRouter.csproj | 51 + .../ClientIntegrationTests.cs | 364 +++++++ .../CsmTcpRouter.Tests.csproj | 28 + .../tests/CsmTcpRouter.Tests/MockServer.cs | 218 +++++ .../tests/CsmTcpRouter.Tests/ProtocolTests.cs | 306 ++++++ 15 files changed, 2657 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/CSharp_SDK.yml create mode 100644 SDK/csharp/CHANGELOG.md create mode 100644 SDK/csharp/CsmTcpRouter.sln create mode 100644 SDK/csharp/LICENSE create mode 100644 SDK/csharp/README.md create mode 100644 SDK/csharp/README.zh-cn.md create mode 100644 SDK/csharp/examples/BasicUsage/BasicUsage.csproj create mode 100644 SDK/csharp/examples/BasicUsage/Program.cs create mode 100644 SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs create mode 100644 SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.csproj create mode 100644 SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs create mode 100644 SDK/csharp/tests/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj create mode 100644 SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs create mode 100644 SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs diff --git a/.github/workflows/CSharp_SDK.yml b/.github/workflows/CSharp_SDK.yml new file mode 100644 index 0000000..a402bde --- /dev/null +++ b/.github/workflows/CSharp_SDK.yml @@ -0,0 +1,124 @@ +name: CSharp SDK + +on: + push: + paths: + - 'SDK/csharp/**' + - '.github/workflows/CSharp_SDK.yml' + tags: + - 'csharp-sdk-v*' + pull_request: + paths: + - 'SDK/csharp/**' + - '.github/workflows/CSharp_SDK.yml' + workflow_dispatch: + +defaults: + run: + working-directory: SDK/csharp + +jobs: + # ------------------------------------------------------------------------- + # Build + test on multiple OSes + # ------------------------------------------------------------------------- + test: + name: Build & Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + permissions: + contents: read + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET 8 SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore + run: dotnet restore CsmTcpRouter.sln + + - name: Build + run: dotnet build CsmTcpRouter.sln -c Release --no-restore + + - name: Test + run: dotnet test tests/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj -c Release --no-build --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage" + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ matrix.os }} + path: SDK/csharp/tests/CsmTcpRouter.Tests/TestResults/ + if-no-files-found: ignore + + # ------------------------------------------------------------------------- + # Pack the NuGet package + # ------------------------------------------------------------------------- + pack: + name: Pack NuGet + runs-on: ubuntu-latest + needs: test + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET 8 SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Pack + run: dotnet pack src/CsmTcpRouter/CsmTcpRouter.csproj -c Release -o nupkg + + - name: Upload NuGet artifact + uses: actions/upload-artifact@v4 + with: + name: csharp-sdk-nupkg + path: SDK/csharp/nupkg/ + + # ------------------------------------------------------------------------- + # Publish to NuGet.org (on tag push only) + # ------------------------------------------------------------------------- + publish: + name: Publish to NuGet.org + runs-on: ubuntu-latest + needs: pack + if: startsWith(github.ref, 'refs/tags/csharp-sdk-v') + environment: + name: nuget + url: https://www.nuget.org/packages/CsmTcpRouter.Client/ + + permissions: + contents: read + + steps: + - name: Download NuGet artifact + uses: actions/download-artifact@v4 + with: + name: csharp-sdk-nupkg + path: nupkg + + - name: Set up .NET 8 SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Push to NuGet.org + # NUGET_API_KEY must be configured as a secret in the `nuget` + # environment. The workflow is gated by the `csharp-sdk-v*` tag + # filter above to prevent accidental publishes. + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + dotnet nuget push "nupkg/*.nupkg" \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate + working-directory: ${{ github.workspace }} diff --git a/.gitignore b/.gitignore index 10380b3..105600d 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,25 @@ Thumbs.db ._* .Spotlight-V100 .Trashes -ehthumbs.db \ No newline at end of file +ehthumbs.db + +# C# / .NET +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ +*.user +*.suo +*.userosscache +*.sln.docstates +.vs/ +*.nupkg +*.snupkg +nupkg/ +TestResults/ +[Tt]est[Rr]esults/ +coverage.cobertura.xml +*.received.* +project.lock.json +project.fragment.lock.json +artifacts/ \ No newline at end of file diff --git a/SDK/csharp/CHANGELOG.md b/SDK/csharp/CHANGELOG.md new file mode 100644 index 0000000..14506f9 --- /dev/null +++ b/SDK/csharp/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +All notable changes to `CsmTcpRouter.Client` are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +--- + +## [0.1.0] – 2026-04-27 + +### Added + +- Initial release of the C# / .NET client SDK for the CSM-TCP-Router LabVIEW + server. +- Single-file implementation in `src/CsmTcpRouter/CsmTcpRouter.cs`. +- Multi-targets `netstandard2.0` (for .NET Framework 4.6.2+, .NET Core 3.1+, + .NET 5/6/7/8/9) and `net8.0`. Zero third-party runtime dependencies. +- Public types: + - `TcpRouterClient` – synchronous and asynchronous APIs: + `Connect/ConnectAsync`, `Disconnect`, `WaitForServer/WaitForServerAsync`, + `SendAndWait/SendAndWaitAsync`, `Post/PostAsync`, + `PostNoReply/PostNoReplyAsync`, `Ping/PingAsync`, `ListModules`, + `ListApi`, `ListStates`, `Help`, `SubscribeStatus/UnsubscribeStatus`, + `RegisterAsyncCallback/UnregisterAsyncCallback`. + - `PacketType` enum and data models `Packet`, `CommandResponse`, + `AsyncResponse`, `StatusNotification`. + - Exception hierarchy: `CsmTcpRouterException`, `RouterConnectionException`, + `RouterTimeoutException`, `ProtocolException`, `ServerException`. +- xUnit test suite (51 tests) covering protocol codec, error parsing, model + parsing, and end-to-end client behaviour against an in-process MockServer. +- Runnable console example `examples/BasicUsage/`. +- VS 2022 / 2026 compatible solution `CsmTcpRouter.sln`. +- GitHub Actions workflow `.github/workflows/CSharp_SDK.yml` for build, test, + pack, and conditional NuGet publish on `csharp-sdk-v*` tag pushes. +- English (`README.md`) and Chinese (`README.zh-cn.md`) documentation. diff --git a/SDK/csharp/CsmTcpRouter.sln b/SDK/csharp/CsmTcpRouter.sln new file mode 100644 index 0000000..337b5e9 --- /dev/null +++ b/SDK/csharp/CsmTcpRouter.sln @@ -0,0 +1,73 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsmTcpRouter", "src\CsmTcpRouter\CsmTcpRouter.csproj", "{AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsmTcpRouter.Tests", "tests\CsmTcpRouter.Tests\CsmTcpRouter.Tests.csproj", "{1EFBB3D5-6452-41A9-8501-897D421B33DB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicUsage", "examples\BasicUsage\BasicUsage.csproj", "{4DD90760-BF7D-45EB-BAE9-E47F65B053C9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Debug|x64.Build.0 = Debug|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Debug|x86.Build.0 = Debug|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Release|Any CPU.Build.0 = Release|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Release|x64.ActiveCfg = Release|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Release|x64.Build.0 = Release|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Release|x86.ActiveCfg = Release|Any CPU + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C}.Release|x86.Build.0 = Release|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Debug|x64.ActiveCfg = Debug|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Debug|x64.Build.0 = Debug|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Debug|x86.ActiveCfg = Debug|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Debug|x86.Build.0 = Debug|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Release|Any CPU.Build.0 = Release|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Release|x64.ActiveCfg = Release|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Release|x64.Build.0 = Release|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Release|x86.ActiveCfg = Release|Any CPU + {1EFBB3D5-6452-41A9-8501-897D421B33DB}.Release|x86.Build.0 = Release|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Debug|x64.ActiveCfg = Debug|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Debug|x64.Build.0 = Debug|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Debug|x86.Build.0 = Debug|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|Any CPU.Build.0 = Release|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|x64.ActiveCfg = Release|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|x64.Build.0 = Release|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|x86.ActiveCfg = Release|Any CPU + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {1EFBB3D5-6452-41A9-8501-897D421B33DB} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {4DD90760-BF7D-45EB-BAE9-E47F65B053C9} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} + EndGlobalSection +EndGlobal diff --git a/SDK/csharp/LICENSE b/SDK/csharp/LICENSE new file mode 100644 index 0000000..78e1cd2 --- /dev/null +++ b/SDK/csharp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 NEVSTOP-LAB + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SDK/csharp/README.md b/SDK/csharp/README.md new file mode 100644 index 0000000..99ce624 --- /dev/null +++ b/SDK/csharp/README.md @@ -0,0 +1,189 @@ +# CsmTcpRouter.Client + +[![NuGet](https://img.shields.io/nuget/v/CsmTcpRouter.Client.svg)](https://www.nuget.org/packages/CsmTcpRouter.Client/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/CSharp_SDK.yml/badge.svg)](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/CSharp_SDK.yml) + +C# / .NET client SDK for the [CSM-TCP-Router](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App) LabVIEW server. + +CSM-TCP-Router exposes a LabVIEW [Communicable State Machine (CSM)](https://github.com/NEVSTOP-LAB/Communicable-State-Machine) application over TCP so that any TCP client — including .NET applications, test harnesses, or CI pipelines — can send commands and receive responses without touching the LabVIEW code. + +> 📖 [中文文档 README.zh-cn.md](README.zh-cn.md) + +The entire SDK lives in a single source file: [`src/CsmTcpRouter/CsmTcpRouter.cs`](src/CsmTcpRouter/CsmTcpRouter.cs). + +--- + +## Installation + +```bash +dotnet add package CsmTcpRouter.Client +``` + +Targets `netstandard2.0` and `net8.0`. No third-party runtime dependencies — only the .NET BCL. + +Supported platforms include .NET Framework 4.6.2+, .NET Core 3.1+, and .NET 5/6/7/8/9. + +--- + +## Quickstart + +### Synchronous client + +```csharp +using CsmTcpRouter; + +using var client = new TcpRouterClient(); +client.Connect("localhost", 30007); + +// List all loaded CSM modules +Console.WriteLine(client.ListModules()); + +// Send a synchronous command and wait for the response +var resp = client.SendAndWait("API: Read -@ DAQmx"); +Console.WriteLine(resp.Text); + +// Ping the server +var (ok, elapsed) = client.Ping(); +Console.WriteLine($"Ping: {ok}, latency={elapsed.TotalMilliseconds:F1} ms"); +``` + +### Asynchronous client + +```csharp +using CsmTcpRouter; + +using var client = new TcpRouterClient(); +await client.ConnectAsync("localhost", 30007); + +Console.WriteLine(await client.ListModulesAsync()); +var resp = await client.SendAndWaitAsync("API: Read -@ DAQmx"); +Console.WriteLine(resp.Text); +``` + +### Subscribe to status broadcasts + +```csharp +client.SubscribeStatus("Status", "AI", notif => +{ + Console.WriteLine($"{notif.StatusName} = {notif.Data}"); +}); + +// ... later +client.UnsubscribeStatus("Status", "AI"); +``` + +### Async-response callbacks + +```csharp +client.RegisterAsyncCallback("API: Start Sampling -> DAQmx", ar => +{ + Console.WriteLine($"Async-resp: {ar.Text}"); +}); + +client.Post("API: Start Sampling -> DAQmx"); +``` + +See [`examples/BasicUsage/Program.cs`](examples/BasicUsage/Program.cs) for a complete runnable example. + +--- + +## API reference + +### `TcpRouterClient` + +| Method | Description | +| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| `Connect / ConnectAsync(host, port, timeout?)` | Open a TCP connection. | +| `Disconnect()` | Close the connection and unblock any pending waiters with `RouterConnectionException`. | +| `Connected` | `true` while the underlying socket is open. | +| `WaitForServer / WaitForServerAsync(...)` | Poll `host:port` until a connection succeeds or the timeout elapses. | +| `SendAndWait / SendAndWaitAsync(cmd, timeout?)` | Send a synchronous command (`-@`) and block until the `RESP` packet arrives. Returns `CommandResponse`. | +| `Post / PostAsync(cmd, timeout?)` | Send an async command (`->`) and wait for the `CMD_RESP` handshake. | +| `PostNoReply / PostNoReplyAsync(cmd, timeout?)` | Send a no-reply async command (`->\|`) and wait for the `CMD_RESP` handshake. | +| `Ping / PingAsync(timeout?)` | Round-trip latency check; returns `(bool ok, TimeSpan elapsed)`. | +| `ListModules / ListApi / ListStates / Help(...)` | Built-in router-management helpers. | +| `SubscribeStatus / UnsubscribeStatus(...)` | Register / cancel a `STATUS` (or `INTERRUPT`) broadcast subscription. | +| `RegisterAsyncCallback / UnregisterAsyncCallback(...)` | Register / remove a callback dispatched for matching `ASYNC_RESP` packets. | +| `AsyncResponseQueue`, `StatusQueue` | Polling alternatives to callbacks (`ConcurrentQueue<>`). | + +### Models + +* `PacketType` (enum) — wire-level packet types as in the protocol v0 spec. +* `Packet`, `CommandResponse`, `AsyncResponse`, `StatusNotification` — decoded payloads. + +### Exceptions + +* `CsmTcpRouterException` — base class. +* `RouterConnectionException` — connect / send / disconnect failures. +* `RouterTimeoutException` — synchronous waiter timeout. +* `ProtocolException` — invalid wire framing. +* `ServerException` — server returned an `ERROR` packet (`Code` and `ServerMessage`). + +--- + +## Protocol + +CSM-TCP-Router protocol v0 uses an 8-byte header (big-endian) followed by an arbitrary payload: + +``` +| Data Length (4B) | Version (1B = 0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | ++------------------------ Header (8B) ----------------------------+ +``` + +Packet types: + +| Value | Name | Direction | Use | +| ----- | ------------ | ----------------- | ---------------------------------------------- | +| 0x00 | `Info` | server → client | Welcome / goodbye message. | +| 0x01 | `Error` | server → client | Error reply (`[Error: ] `). | +| 0x02 | `Cmd` | client → server | Command from the client. | +| 0x03 | `CmdResp` | server → client | Async / no-reply / subscribe handshake. | +| 0x04 | `Resp` | server → client | Synchronous command response. | +| 0x05 | `AsyncResp` | server → client | Asynchronous command response (echoes cmd). | +| 0x06 | `Status` | server → client | Status broadcast from a subscribed module. | +| 0x07 | `Interrupt` | server → client | Interrupt broadcast from a subscribed module. | + +--- + +## Building from source + +```bash +# Restore + build the whole solution +dotnet build SDK/csharp/CsmTcpRouter.sln -c Release + +# Run the unit + integration test suite +dotnet test SDK/csharp/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj -c Release + +# Build the NuGet package +dotnet pack SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.csproj -c Release -o nupkg +``` + +The solution opens directly in **Visual Studio 2022 / 2026** (or **JetBrains Rider**); SDK-style projects are forward-compatible with all current Visual Studio versions. + +--- + +## Project layout + +``` +SDK/csharp/ +├── CsmTcpRouter.sln +├── README.md / README.zh-cn.md / CHANGELOG.md / LICENSE +├── src/CsmTcpRouter/ ← single-file SDK +│ ├── CsmTcpRouter.cs +│ └── CsmTcpRouter.csproj +├── tests/CsmTcpRouter.Tests/ ← xUnit test project +│ ├── ProtocolTests.cs +│ ├── ClientIntegrationTests.cs +│ ├── MockServer.cs +│ └── CsmTcpRouter.Tests.csproj +└── examples/BasicUsage/ ← runnable console example + ├── Program.cs + └── BasicUsage.csproj +``` + +--- + +## License + +Released under the [MIT License](LICENSE) — © 2026 NEVSTOP-LAB. diff --git a/SDK/csharp/README.zh-cn.md b/SDK/csharp/README.zh-cn.md new file mode 100644 index 0000000..ca7b6be --- /dev/null +++ b/SDK/csharp/README.zh-cn.md @@ -0,0 +1,189 @@ +# CsmTcpRouter.Client + +[![NuGet](https://img.shields.io/nuget/v/CsmTcpRouter.Client.svg)](https://www.nuget.org/packages/CsmTcpRouter.Client/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/CSharp_SDK.yml/badge.svg)](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/CSharp_SDK.yml) + +[CSM-TCP-Router](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App) LabVIEW 服务器的 C# / .NET 客户端 SDK。 + +CSM-TCP-Router 通过 TCP 暴露一个 LabVIEW [可通信状态机 (CSM)](https://github.com/NEVSTOP-LAB/Communicable-State-Machine) 应用程序,因此任何 TCP 客户端 — 包括 .NET 应用程序、测试夹具或 CI 流水线 — 都可以发送命令并接收响应,而无需修改 LabVIEW 代码。 + +> 📖 [English README.md](README.md) + +整个 SDK 实现位于单个源文件中:[`src/CsmTcpRouter/CsmTcpRouter.cs`](src/CsmTcpRouter/CsmTcpRouter.cs)。 + +--- + +## 安装 + +```bash +dotnet add package CsmTcpRouter.Client +``` + +目标框架:`netstandard2.0` 与 `net8.0`。无第三方运行时依赖 — 仅使用 .NET BCL。 + +支持的平台包括 .NET Framework 4.6.2+、.NET Core 3.1+ 以及 .NET 5/6/7/8/9。 + +--- + +## 快速上手 + +### 同步客户端 + +```csharp +using CsmTcpRouter; + +using var client = new TcpRouterClient(); +client.Connect("localhost", 30007); + +// 列出已加载的所有 CSM 模块 +Console.WriteLine(client.ListModules()); + +// 发送同步命令并等待响应 +var resp = client.SendAndWait("API: Read -@ DAQmx"); +Console.WriteLine(resp.Text); + +// Ping 服务器 +var (ok, elapsed) = client.Ping(); +Console.WriteLine($"Ping: {ok}, latency={elapsed.TotalMilliseconds:F1} ms"); +``` + +### 异步客户端 + +```csharp +using CsmTcpRouter; + +using var client = new TcpRouterClient(); +await client.ConnectAsync("localhost", 30007); + +Console.WriteLine(await client.ListModulesAsync()); +var resp = await client.SendAndWaitAsync("API: Read -@ DAQmx"); +Console.WriteLine(resp.Text); +``` + +### 订阅状态广播 + +```csharp +client.SubscribeStatus("Status", "AI", notif => +{ + Console.WriteLine($"{notif.StatusName} = {notif.Data}"); +}); + +// ... 之后 +client.UnsubscribeStatus("Status", "AI"); +``` + +### 异步响应回调 + +```csharp +client.RegisterAsyncCallback("API: Start Sampling -> DAQmx", ar => +{ + Console.WriteLine($"异步响应: {ar.Text}"); +}); + +client.Post("API: Start Sampling -> DAQmx"); +``` + +完整可运行示例见 [`examples/BasicUsage/Program.cs`](examples/BasicUsage/Program.cs)。 + +--- + +## API 参考 + +### `TcpRouterClient` + +| 方法 | 说明 | +| ------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `Connect / ConnectAsync(host, port, timeout?)` | 建立 TCP 连接。 | +| `Disconnect()` | 关闭连接,并以 `RouterConnectionException` 立即唤醒所有阻塞中的等待者。 | +| `Connected` | 套接字打开期间为 `true`。 | +| `WaitForServer / WaitForServerAsync(...)` | 轮询 `host:port` 直到连接成功或超时。 | +| `SendAndWait / SendAndWaitAsync(cmd, timeout?)` | 发送同步命令 (`-@`) 并阻塞直到 `RESP` 返回。返回 `CommandResponse`。 | +| `Post / PostAsync(cmd, timeout?)` | 发送异步命令 (`->`) 并等待 `CMD_RESP` 握手。 | +| `PostNoReply / PostNoReplyAsync(cmd, timeout?)` | 发送无响应异步命令 (`->\|`) 并等待 `CMD_RESP` 握手。 | +| `Ping / PingAsync(timeout?)` | 测量往返延迟;返回 `(bool ok, TimeSpan elapsed)`。 | +| `ListModules / ListApi / ListStates / Help(...)` | 内置的路由管理辅助命令。 | +| `SubscribeStatus / UnsubscribeStatus(...)` | 订阅 / 取消 `STATUS` (或 `INTERRUPT`) 广播。 | +| `RegisterAsyncCallback / UnregisterAsyncCallback(...)` | 注册 / 移除按原始命令匹配的 `ASYNC_RESP` 回调。 | +| `AsyncResponseQueue`、`StatusQueue` | 与回调互补的轮询队列 (`ConcurrentQueue<>`)。 | + +### 数据模型 + +* `PacketType` (枚举) — 协议 v0 中定义的报文类型。 +* `Packet`、`CommandResponse`、`AsyncResponse`、`StatusNotification` — 解析后的负载。 + +### 异常 + +* `CsmTcpRouterException` — 基类。 +* `RouterConnectionException` — 连接 / 发送 / 断开失败。 +* `RouterTimeoutException` — 同步等待超时。 +* `ProtocolException` — 报文格式非法。 +* `ServerException` — 服务器返回 `ERROR` 报文 (`Code` 与 `ServerMessage`)。 + +--- + +## 协议 + +CSM-TCP-Router 协议 v0 使用 8 字节大端头部加上任意长度的负载: + +``` +| Data Length (4B) | Version (1B = 0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | ++------------------------ Header (8B) ----------------------------+ +``` + +报文类型: + +| 值 | 名称 | 方向 | 用途 | +| ----- | ------------ | ------------- | ------------------------------------------ | +| 0x00 | `Info` | 服务器 → 客户 | 欢迎 / 告别消息。 | +| 0x01 | `Error` | 服务器 → 客户 | 错误响应 (`[Error: ] `)。 | +| 0x02 | `Cmd` | 客户 → 服务器 | 客户端命令。 | +| 0x03 | `CmdResp` | 服务器 → 客户 | 异步 / 无响应 / 订阅命令的握手。 | +| 0x04 | `Resp` | 服务器 → 客户 | 同步命令响应。 | +| 0x05 | `AsyncResp` | 服务器 → 客户 | 异步命令响应 (回显原始命令)。 | +| 0x06 | `Status` | 服务器 → 客户 | 已订阅模块的状态广播。 | +| 0x07 | `Interrupt` | 服务器 → 客户 | 已订阅模块的中断广播。 | + +--- + +## 从源码构建 + +```bash +# 还原并构建整个解决方案 +dotnet build SDK/csharp/CsmTcpRouter.sln -c Release + +# 运行单元 + 集成测试 +dotnet test SDK/csharp/tests/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj -c Release + +# 打包 NuGet +dotnet pack SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.csproj -c Release -o nupkg +``` + +解决方案文件可直接用 **Visual Studio 2022 / 2026**(或 **JetBrains Rider**)打开;SDK 风格项目向前兼容所有当前 Visual Studio 版本。 + +--- + +## 项目结构 + +``` +SDK/csharp/ +├── CsmTcpRouter.sln +├── README.md / README.zh-cn.md / CHANGELOG.md / LICENSE +├── src/CsmTcpRouter/ ← 单文件 SDK +│ ├── CsmTcpRouter.cs +│ └── CsmTcpRouter.csproj +├── tests/CsmTcpRouter.Tests/ ← xUnit 测试工程 +│ ├── ProtocolTests.cs +│ ├── ClientIntegrationTests.cs +│ ├── MockServer.cs +│ └── CsmTcpRouter.Tests.csproj +└── examples/BasicUsage/ ← 可运行控制台示例 + ├── Program.cs + └── BasicUsage.csproj +``` + +--- + +## 许可证 + +基于 [MIT 许可证](LICENSE) 发布 — © 2026 NEVSTOP-LAB。 diff --git a/SDK/csharp/examples/BasicUsage/BasicUsage.csproj b/SDK/csharp/examples/BasicUsage/BasicUsage.csproj new file mode 100644 index 0000000..5230e09 --- /dev/null +++ b/SDK/csharp/examples/BasicUsage/BasicUsage.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + latest + disable + CsmTcpRouter.Examples.BasicUsage + BasicUsage + false + + + + + + + diff --git a/SDK/csharp/examples/BasicUsage/Program.cs b/SDK/csharp/examples/BasicUsage/Program.cs new file mode 100644 index 0000000..7384c05 --- /dev/null +++ b/SDK/csharp/examples/BasicUsage/Program.cs @@ -0,0 +1,91 @@ +using System; +using CsmTcpRouter; + +namespace CsmTcpRouter.Examples.BasicUsage +{ + ///

+ /// Basic usage example for csm-tcp-router-client (C#). + /// Mirrors SDK/python/examples/basic_usage.py. + /// + /// Prerequisites: a running CSM-TCP-Router server (LabVIEW app). + /// The reference app defaults to port 30007. + /// + public static class Program + { + private const string Host = "localhost"; + private const int Port = 30007; + + public static int Main(string[] args) + { + // 1. Wait until the server is ready. + Console.Write("Waiting for server ... "); + using (var probe = new TcpRouterClient()) + { + bool ok = probe.WaitForServer(Host, Port, TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(500)); + if (!ok) + { + Console.WriteLine("TIMEOUT - server did not start within 30 s."); + return 1; + } + } + Console.WriteLine("ready."); + + // 2. Connect (use as IDisposable so Disconnect is always called). + using (var client = new TcpRouterClient()) + { + try + { + client.Connect(Host, Port, TimeSpan.FromSeconds(5)); + } + catch (RouterConnectionException exc) + { + Console.WriteLine($"Connection failed: {exc.Message}"); + return 1; + } + + Console.WriteLine($"Connected to {Host}:{Port}"); + + // 3. Ping + var (ok, elapsed) = client.Ping(TimeSpan.FromSeconds(2)); + Console.WriteLine(ok + ? $"Ping OK latency={elapsed.TotalMilliseconds:F1} ms" + : "Ping failed."); + + // 4. List CSM modules + string modules = client.ListModules(); + Console.WriteLine($"\nLoaded modules:\n{modules}"); + + // 5. List API for the first module (if any) + string firstModule = null; + foreach (var line in modules.Split('\n')) + { + var trimmed = line.Trim(); + if (!string.IsNullOrEmpty(trimmed)) + { + firstModule = trimmed; + break; + } + } + if (firstModule != null) + { + string api = client.ListApi(firstModule); + Console.WriteLine($"\nAPI for '{firstModule}':\n{api}"); + } + + // 6. Send a synchronous command (uncomment & adapt for your CSM): + // var resp = client.SendAndWait("API: Read -@ DAQmx"); + // Console.WriteLine($"\nSync response: {resp.Text}"); + + // 7. Send an async command + wait for cmd-resp handshake: + // client.Post("API: Start Sampling -> DAQmx"); + + // 8. Send a no-reply command: + // client.PostNoReply("API: Reset ->| DAQmx"); + + Console.WriteLine("\nDone."); + } + Console.WriteLine("Disconnected."); + return 0; + } + } +} diff --git a/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs b/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs new file mode 100644 index 0000000..1a73673 --- /dev/null +++ b/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs @@ -0,0 +1,924 @@ +// CsmTcpRouter.cs +// --------------------------------------------------------------------------- +// csm-tcp-router-client - C# client SDK for the CSM-TCP-Router LabVIEW server. +// +// Single-file SDK implementing CSM-TCP-Router protocol v0. Mirrors the +// Python `csm_tcp_router` package layout and feature set: +// +// * Protocol codec (8-byte header, big-endian, 8 packet types). +// * Background-receiver TCP transport. +// * High-level TcpRouterClient with sync and async APIs: +// - SendAndWait / SendAndWaitAsync (synchronous CMD/RESP) +// - Post / PostAsync (async CMD with cmd-resp handshake) +// - PostNoReply / PostNoReplyAsync (no-reply async CMD) +// - Ping / PingAsync (round-trip latency) +// - ListModules / ListApi / ListStates / Help +// - SubscribeStatus / UnsubscribeStatus +// - RegisterAsyncCallback / UnregisterAsyncCallback +// +// Wire format (8-byte header, big-endian):: +// +// | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | +// +------------------------ Header (8B) -----------------------+ +// +// followed by exactly `Data Length` bytes of payload. +// +// Copyright (c) 2026 NEVSTOP-LAB. Released under the MIT License. +// --------------------------------------------------------------------------- + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +[assembly: InternalsVisibleTo("CsmTcpRouter.Tests")] + +namespace CsmTcpRouter +{ + // ----------------------------------------------------------------------- + // Public enumerations + // ----------------------------------------------------------------------- + + /// + /// Packet type constants as defined in the CSM-TCP-Router protocol v0. + /// + public enum PacketType : byte + { + /// Informational message (welcome / goodbye). + Info = 0x00, + /// Error packet from the server. + Error = 0x01, + /// Command sent by the client. + Cmd = 0x02, + /// Server handshake for async / no-reply / subscribe. + CmdResp = 0x03, + /// Synchronous response payload. + Resp = 0x04, + /// Asynchronous response payload. + AsyncResp = 0x05, + /// Status broadcast from a subscribed CSM module. + Status = 0x06, + /// Interrupt broadcast from a subscribed CSM module. + Interrupt = 0x07, + } + + // ----------------------------------------------------------------------- + // Public data models + // ----------------------------------------------------------------------- + + /// A decoded packet received from the server. + public sealed class Packet + { + public PacketType Type { get; } + public byte[] Data { get; } + public byte Version { get; } + public byte Flag1 { get; } + public byte Flag2 { get; } + + public Packet(PacketType type, byte[] data, byte version = 1, byte flag1 = 0, byte flag2 = 0) + { + Type = type; + Data = data ?? Array.Empty(); + Version = version; + Flag1 = flag1; + Flag2 = flag2; + } + } + + /// The result of a synchronous command (). + public sealed class CommandResponse + { + public byte[] Raw { get; } + public string Text => Encoding.UTF8.GetString(Raw); + + public CommandResponse(byte[] raw) + { + Raw = raw ?? Array.Empty(); + } + + public override string ToString() => $"CommandResponse(\"{Text}\")"; + } + + /// An asynchronous response payload delivered via an async-resp packet. + public sealed class AsyncResponse + { + public byte[] Raw { get; } + public string OriginalCommand { get; } + public string Text => Encoding.UTF8.GetString(Raw); + + public AsyncResponse(byte[] raw, string originalCommand = "") + { + Raw = raw ?? Array.Empty(); + OriginalCommand = originalCommand ?? string.Empty; + } + + /// + /// Parse an ASYNC_RESP packet. Server format: + /// "<response-data> <- <original-command>". + /// + public static AsyncResponse FromPacket(Packet packet) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + string text = Encoding.UTF8.GetString(packet.Data); + int sep = text.IndexOf(" <- ", StringComparison.Ordinal); + if (sep >= 0) + { + string left = text.Substring(0, sep); + string right = text.Substring(sep + 4); + return new AsyncResponse(Encoding.UTF8.GetBytes(left), right); + } + return new AsyncResponse(packet.Data); + } + + public override string ToString() => $"AsyncResponse(\"{Text}\", cmd=\"{OriginalCommand}\")"; + } + + /// A status broadcast delivered via a STATUS or INTERRUPT packet. + public sealed class StatusNotification + { + public byte[] Raw { get; } + public PacketType PacketType { get; } + public string StatusName { get; } + public string Data { get; } + public string ModuleName { get; } + + public StatusNotification( + byte[] raw, + PacketType packetType = PacketType.Status, + string statusName = "", + string data = "", + string moduleName = "") + { + Raw = raw ?? Array.Empty(); + PacketType = packetType; + StatusName = statusName ?? string.Empty; + Data = data ?? string.Empty; + ModuleName = moduleName ?? string.Empty; + } + + /// + /// Parse a STATUS or INTERRUPT packet. Server format: + /// "<status-name> >> <data> <- <module>". + /// + public static StatusNotification FromPacket(Packet packet) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + string text = Encoding.UTF8.GetString(packet.Data); + string module = string.Empty; + string left = text; + int sepArrow = text.LastIndexOf(" <- ", StringComparison.Ordinal); + if (sepArrow >= 0) + { + left = text.Substring(0, sepArrow); + module = text.Substring(sepArrow + 4).Trim(); + } + string statusName = string.Empty; + string data = left.Trim(); + int sepGtGt = left.IndexOf(" >> ", StringComparison.Ordinal); + if (sepGtGt >= 0) + { + statusName = left.Substring(0, sepGtGt).Trim(); + data = left.Substring(sepGtGt + 4).Trim(); + } + return new StatusNotification(packet.Data, packet.Type, statusName, data, module); + } + + public override string ToString() => + $"StatusNotification(status=\"{StatusName}\", data=\"{Data}\", module=\"{ModuleName}\")"; + } + + // ----------------------------------------------------------------------- + // Exception hierarchy + // ----------------------------------------------------------------------- + + /// Base exception for all CSM-TCP-Router client errors. + public class CsmTcpRouterException : Exception + { + public CsmTcpRouterException() { } + public CsmTcpRouterException(string message) : base(message) { } + public CsmTcpRouterException(string message, Exception innerException) : base(message, innerException) { } + } + + /// Raised when a connection cannot be established or is lost. + public class RouterConnectionException : CsmTcpRouterException + { + public RouterConnectionException(string message) : base(message) { } + public RouterConnectionException(string message, Exception innerException) : base(message, innerException) { } + } + + /// Raised when a synchronous operation exceeds its timeout. + public class RouterTimeoutException : CsmTcpRouterException + { + public RouterTimeoutException(string message) : base(message) { } + } + + /// Raised when an invalid or unexpected protocol frame is received. + public class ProtocolException : CsmTcpRouterException + { + public ProtocolException(string message) : base(message) { } + } + + /// + /// Raised when the server returns an error packet. CSM Error format: + /// [Error: <code>] <message>. + /// + public class ServerException : CsmTcpRouterException + { + public string Code { get; } + public string ServerMessage { get; } + + public ServerException(string message, string code = "") + : base(message) + { + ServerMessage = message ?? string.Empty; + Code = code ?? string.Empty; + } + + public override string ToString() + { + return string.IsNullOrEmpty(Code) + ? ServerMessage + : $"[Error: {Code}] {ServerMessage}"; + } + } + + // ----------------------------------------------------------------------- + // Internal protocol codec + // ----------------------------------------------------------------------- + + internal static class ProtocolCodec + { + public const int HeaderSize = 8; + public const byte ProtocolVersion = 0x01; + + /// Encode into a complete wire-format packet (header + body). + public static byte[] EncodePacket(byte[] data, PacketType packetType, byte flag1 = 0, byte flag2 = 0) + { + data = data ?? Array.Empty(); + var wire = new byte[HeaderSize + data.Length]; + uint len = (uint)data.Length; + wire[0] = (byte)((len >> 24) & 0xFF); + wire[1] = (byte)((len >> 16) & 0xFF); + wire[2] = (byte)((len >> 8) & 0xFF); + wire[3] = (byte)(len & 0xFF); + wire[4] = ProtocolVersion; + wire[5] = (byte)packetType; + wire[6] = flag1; + wire[7] = flag2; + Buffer.BlockCopy(data, 0, wire, HeaderSize, data.Length); + return wire; + } + + /// Decode an 8-byte header into its constituent fields. + public static (uint DataLen, byte Version, byte TypeByte, byte Flag1, byte Flag2) DecodeHeader(byte[] header) + { + if (header == null || header.Length != HeaderSize) + throw new ProtocolException( + $"Expected {HeaderSize}-byte header, got {(header == null ? 0 : header.Length)} bytes."); + uint dataLen = ((uint)header[0] << 24) | ((uint)header[1] << 16) | ((uint)header[2] << 8) | header[3]; + return (dataLen, header[4], header[5], header[6], header[7]); + } + + /// Build a from raw header + body. + public static Packet ParsePacket(byte[] header, byte[] body) + { + var (dataLen, version, typeByte, flag1, flag2) = DecodeHeader(header); + body = body ?? Array.Empty(); + if ((uint)body.Length != dataLen) + throw new ProtocolException( + $"Payload length mismatch: header says {dataLen} bytes, got {body.Length} bytes."); + // Forward-compatible: unknown type bytes are mapped to Info. + PacketType ptype = Enum.IsDefined(typeof(PacketType), typeByte) + ? (PacketType)typeByte + : PacketType.Info; + return new Packet(ptype, body, version, flag1, flag2); + } + + /// Extract code and message from a CSM Error format [Error: code] msg. + public static ServerException ParseServerError(Packet packet) + { + string text = Encoding.UTF8.GetString(packet.Data).Trim(); + string code = string.Empty; + string msg = text; + if (text.StartsWith("[Error:", StringComparison.Ordinal)) + { + int end = text.IndexOf(']'); + if (end > 0) + { + code = text.Substring(7, end - 7).Trim(); + msg = text.Substring(end + 1).Trim(); + } + } + return new ServerException(msg, code); + } + } + + // ----------------------------------------------------------------------- + // Internal TCP transport (background receive task) + // ----------------------------------------------------------------------- + + internal sealed class Transport : IDisposable + { + private readonly object _sendLock = new object(); + private readonly Action _onPacket; + private readonly Action _onDisconnect; + + private TcpClient _client; + private NetworkStream _stream; + private CancellationTokenSource _cts; + private Task _recvTask; + private volatile bool _stopped; + + public Transport(Action onPacket, Action onDisconnect) + { + _onPacket = onPacket ?? throw new ArgumentNullException(nameof(onPacket)); + _onDisconnect = onDisconnect ?? throw new ArgumentNullException(nameof(onDisconnect)); + } + + public bool Connected + { + get + { + var c = _client; + return c != null && c.Connected && !_stopped; + } + } + + public void Connect(string host, int port, TimeSpan? timeout = null) + { + ConnectAsync(host, port, timeout).GetAwaiter().GetResult(); + } + + public async Task ConnectAsync(string host, int port, TimeSpan? timeout = null) + { + if (Connected) + throw new RouterConnectionException("Already connected; call Disconnect() first."); + var to = timeout ?? TimeSpan.FromSeconds(5); + var client = new TcpClient(); + try + { + var connectTask = client.ConnectAsync(host, port); + var winner = await Task.WhenAny(connectTask, Task.Delay(to)).ConfigureAwait(false); + if (winner != connectTask) + { + try { client.Close(); } catch { /* ignore */ } + throw new RouterConnectionException( + $"Cannot connect to {host}:{port}: timed out after {to.TotalSeconds:F1}s."); + } + await connectTask.ConfigureAwait(false); // surface any connect exception + } + catch (RouterConnectionException) + { + throw; + } + catch (Exception exc) + { + try { client.Close(); } catch { /* ignore */ } + throw new RouterConnectionException($"Cannot connect to {host}:{port}: {exc.Message}", exc); + } + + _client = client; + _stream = client.GetStream(); + _stopped = false; + _cts = new CancellationTokenSource(); + _recvTask = Task.Run(() => RecvLoopAsync(_cts.Token)); + } + + public void Disconnect(TimeSpan? joinTimeout = null) + { + _stopped = true; + try { _cts?.Cancel(); } catch { /* ignore */ } + try { _stream?.Close(); } catch { /* ignore */ } + try { _client?.Close(); } catch { /* ignore */ } + _stream = null; + _client = null; + var jt = joinTimeout ?? TimeSpan.FromSeconds(2); + try { _recvTask?.Wait(jt); } catch { /* ignore */ } + _recvTask = null; + try { _cts?.Dispose(); } catch { /* ignore */ } + _cts = null; + } + + public void SendRaw(byte[] data) + { + if (!Connected) throw new RouterConnectionException("Not connected."); + lock (_sendLock) + { + try + { + var s = _stream; + if (s == null) throw new RouterConnectionException("Not connected."); + s.Write(data, 0, data.Length); + } + catch (Exception exc) when (!(exc is RouterConnectionException)) + { + _stopped = true; + throw new RouterConnectionException($"Send failed: {exc.Message}", exc); + } + } + } + + public void Dispose() => Disconnect(); + + // --------------------------------------------------------------- + // Internal: background receive loop + // --------------------------------------------------------------- + + private async Task RecvLoopAsync(CancellationToken ct) + { + var stream = _stream; + try + { + var headerBuf = new byte[ProtocolCodec.HeaderSize]; + while (!ct.IsCancellationRequested) + { + if (!await ReadExactlyAsync(stream, headerBuf, 0, headerBuf.Length, ct).ConfigureAwait(false)) + break; + + uint dataLen = ((uint)headerBuf[0] << 24) | ((uint)headerBuf[1] << 16) | ((uint)headerBuf[2] << 8) | headerBuf[3]; + byte[] body = dataLen == 0 ? Array.Empty() : new byte[dataLen]; + if (dataLen > 0 && !await ReadExactlyAsync(stream, body, 0, body.Length, ct).ConfigureAwait(false)) + break; + + Packet packet; + try + { + packet = ProtocolCodec.ParsePacket(headerBuf, body); + } + catch (ProtocolException) + { + // Corrupted frame -- skip it and keep the loop alive. + continue; + } + + try { _onPacket(packet); } catch { /* swallow callback errors */ } + } + } + catch (IOException) { /* connection dropped */ } + catch (ObjectDisposedException) { /* socket closed during read */ } + catch (OperationCanceledException) { /* shutdown */ } + finally + { + if (!_stopped) + { + _stopped = true; + try { _onDisconnect(); } catch { /* ignore */ } + } + } + } + + private static async Task ReadExactlyAsync(Stream stream, byte[] buf, int offset, int count, CancellationToken ct) + { + int read = 0; + while (read < count) + { + int n; + try + { + n = await stream.ReadAsync(buf, offset + read, count - read, ct).ConfigureAwait(false); + } + catch (IOException) { return false; } + catch (ObjectDisposedException) { return false; } + if (n == 0) return false; + read += n; + } + return true; + } + } + + // ----------------------------------------------------------------------- + // High-level client + // ----------------------------------------------------------------------- + + /// Callback delegate for status / interrupt broadcasts. + public delegate void StatusCallback(StatusNotification notification); + + /// Callback delegate for asynchronous-response packets. + public delegate void AsyncResponseCallback(AsyncResponse response); + + /// + /// C# client for a CSM-TCP-Router server. Mirrors the LabVIEW ClientAPI + /// VIs and the Python TcpRouterClient; speaks protocol v0. + /// + /// The class is thread-safe. At most one in-flight synchronous command + /// and one in-flight async / subscription command may be outstanding at a + /// time; concurrent callers are serialised by internal semaphores. + /// + public sealed class TcpRouterClient : IDisposable + { + // One-item-deep "queues" for synchronised waits, implemented via TCS. + // Reset to a fresh TCS by each waiter inside the corresponding lock. + private TaskCompletionSource _respTcs; + private TaskCompletionSource _cmdRespTcs; + + private readonly SemaphoreSlim _respLock = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim _cmdRespLock = new SemaphoreSlim(1, 1); + + private readonly object _stateLock = new object(); + private readonly Dictionary<(string Status, string Module), StatusCallback> _statusCallbacks + = new Dictionary<(string, string), StatusCallback>(); + private readonly Dictionary _asyncCallbacks + = new Dictionary(); + + private readonly Transport _transport; + + /// Polling queue for async-resp packets received from the server. + public ConcurrentQueue AsyncResponseQueue { get; } = new ConcurrentQueue(); + + /// Polling queue for status / interrupt notifications. + public ConcurrentQueue StatusQueue { get; } = new ConcurrentQueue(); + + public TcpRouterClient() + { + _transport = new Transport(OnPacket, OnDisconnect); + } + + // --------------------------------------------------------------- + // Connection management + // --------------------------------------------------------------- + + /// Connect to a CSM-TCP-Router server. + public void Connect(string host, int port, TimeSpan? timeout = null) + => _transport.Connect(host, port, timeout); + + /// Connect to a CSM-TCP-Router server (async). + public Task ConnectAsync(string host, int port, TimeSpan? timeout = null) + => _transport.ConnectAsync(host, port, timeout); + + /// + /// Disconnect from the server and release all resources. Any threads + /// currently blocked in / + /// will receive a immediately + /// rather than waiting for their timeout to expire. + /// + public void Disconnect() + { + // Unblock any pending waiters before tearing down the transport. + var sentinel = new RouterConnectionException("Disconnected from server."); + UnblockWaiters(sentinel); + _transport.Disconnect(); + } + + /// true when the underlying transport is connected. + public bool Connected => _transport.Connected; + + /// + /// Poll until : accepts + /// a connection or elapses. + /// + public bool WaitForServer(string host, int port, TimeSpan? timeout = null, TimeSpan? retryInterval = null) + => WaitForServerAsync(host, port, timeout, retryInterval).GetAwaiter().GetResult(); + + /// Async version of . + public async Task WaitForServerAsync( + string host, int port, TimeSpan? timeout = null, TimeSpan? retryInterval = null) + { + var deadline = DateTime.UtcNow + (timeout ?? TimeSpan.FromSeconds(30)); + var interval = retryInterval ?? TimeSpan.FromMilliseconds(500); + while (DateTime.UtcNow < deadline) + { + using (var probe = new TcpClient()) + { + var connectTask = probe.ConnectAsync(host, port); + var winner = await Task.WhenAny(connectTask, Task.Delay(TimeSpan.FromSeconds(1))).ConfigureAwait(false); + if (winner == connectTask) + { + try + { + // Observe any connect exception (faulted task); + // success means the server is reachable. + await connectTask.ConfigureAwait(false); + try { probe.Close(); } catch { /* ignore */ } + return true; + } + catch (SocketException) { /* not ready yet */ } + catch (IOException) { /* not ready yet */ } + } + else + { + // Delay won; abort the in-flight connect attempt by + // closing the probe socket, then observe any pending + // exception so it is not unobserved. + try { probe.Close(); } catch { /* ignore */ } + try + { + await connectTask.ConfigureAwait(false); + } + catch (SocketException) { /* not ready yet */ } + catch (IOException) { /* not ready yet */ } + catch (ObjectDisposedException) { /* connect aborted by closing probe */ } + } + } + await Task.Delay(interval).ConfigureAwait(false); + } + return false; + } + + // --------------------------------------------------------------- + // Core command methods (sync wrappers) + // --------------------------------------------------------------- + + public CommandResponse SendAndWait(string command, TimeSpan? timeout = null) + => SendAndWaitAsync(command, timeout).GetAwaiter().GetResult(); + + public void Post(string command, TimeSpan? timeout = null) + => PostAsync(command, timeout).GetAwaiter().GetResult(); + + public void PostNoReply(string command, TimeSpan? timeout = null) + => PostNoReplyAsync(command, timeout).GetAwaiter().GetResult(); + + public (bool Ok, TimeSpan Elapsed) Ping(TimeSpan? timeout = null) + => PingAsync(timeout).GetAwaiter().GetResult(); + + public string ListModules(TimeSpan? timeout = null) => SendAndWait("List", timeout).Text; + public string ListApi(string module, TimeSpan? timeout = null) => SendAndWait($"List API {module}", timeout).Text; + public string ListStates(string module, TimeSpan? timeout = null) => SendAndWait($"List State {module}", timeout).Text; + public string Help(string module, TimeSpan? timeout = null) => SendAndWait($"Help {module}", timeout).Text; + + public void SubscribeStatus(string statusName, string moduleName, StatusCallback callback = null, TimeSpan? timeout = null) + => SubscribeStatusAsync(statusName, moduleName, callback, timeout).GetAwaiter().GetResult(); + + public void UnsubscribeStatus(string statusName, string moduleName, TimeSpan? timeout = null) + => UnsubscribeStatusAsync(statusName, moduleName, timeout).GetAwaiter().GetResult(); + + // --------------------------------------------------------------- + // Core command methods (async) + // --------------------------------------------------------------- + + /// + /// Send a synchronous command (suffix -@) and wait for the response. + /// + public async Task SendAndWaitAsync(string command, TimeSpan? timeout = null) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + var to = timeout ?? TimeSpan.FromSeconds(5); + byte[] wire = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes(command), PacketType.Cmd); + + await _respLock.WaitAsync().ConfigureAwait(false); + try + { + _respTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _transport.SendRaw(wire); + return await WaitForRespAsync(to).ConfigureAwait(false); + } + finally + { + _respLock.Release(); + } + } + + /// + /// Send an async command (suffix ->) and wait for the cmd-resp handshake. + /// + public Task PostAsync(string command, TimeSpan? timeout = null) + => SendAndAwaitCmdRespAsync(command, timeout); + + /// + /// Send an async no-reply command (suffix ->|) and wait for the cmd-resp handshake. + /// + public Task PostNoReplyAsync(string command, TimeSpan? timeout = null) + => SendAndAwaitCmdRespAsync(command, timeout); + + /// Send a Ping and measure round-trip latency. + public async Task<(bool Ok, TimeSpan Elapsed)> PingAsync(TimeSpan? timeout = null) + { + var to = timeout ?? TimeSpan.FromSeconds(2); + try + { + var sw = Stopwatch.StartNew(); + await SendAndWaitAsync("Ping", to).ConfigureAwait(false); + sw.Stop(); + return (true, sw.Elapsed); + } + catch (RouterConnectionException) { return (false, TimeSpan.Zero); } + catch (RouterTimeoutException) { return (false, TimeSpan.Zero); } + catch (ServerException) { return (false, TimeSpan.Zero); } + } + + public Task ListModulesAsync(TimeSpan? timeout = null) + => SendAndWaitAsync("List", timeout).ContinueWithText(); + + public Task ListApiAsync(string module, TimeSpan? timeout = null) + => SendAndWaitAsync($"List API {module}", timeout).ContinueWithText(); + + public Task ListStatesAsync(string module, TimeSpan? timeout = null) + => SendAndWaitAsync($"List State {module}", timeout).ContinueWithText(); + + public Task HelpAsync(string module, TimeSpan? timeout = null) + => SendAndWaitAsync($"Help {module}", timeout).ContinueWithText(); + + /// Subscribe to a CSM module's status broadcast. + public async Task SubscribeStatusAsync( + string statusName, string moduleName, StatusCallback callback = null, TimeSpan? timeout = null) + { + if (statusName == null) throw new ArgumentNullException(nameof(statusName)); + if (moduleName == null) throw new ArgumentNullException(nameof(moduleName)); + + var key = (statusName, moduleName); + // Register the callback *before* sending to eliminate the race + // where a STATUS packet could arrive before the callback is stored. + lock (_stateLock) { _statusCallbacks[key] = callback; } + + string cmd = $"{statusName}@{moduleName} ->"; + try + { + await SendAndAwaitCmdRespAsync(cmd, timeout).ConfigureAwait(false); + } + catch + { + lock (_stateLock) { _statusCallbacks.Remove(key); } + throw; + } + } + + /// Cancel a status subscription. + public async Task UnsubscribeStatusAsync(string statusName, string moduleName, TimeSpan? timeout = null) + { + if (statusName == null) throw new ArgumentNullException(nameof(statusName)); + if (moduleName == null) throw new ArgumentNullException(nameof(moduleName)); + + string cmd = $"{statusName}@{moduleName} ->"; + await SendAndAwaitCmdRespAsync(cmd, timeout).ConfigureAwait(false); + lock (_stateLock) { _statusCallbacks.Remove((statusName, moduleName)); } + } + + /// + /// Register a callback for async-resp packets, matched by the original + /// command echoed in the async-resp payload (after the <- separator). + /// + public void RegisterAsyncCallback(string originalCommand, AsyncResponseCallback callback) + { + if (originalCommand == null) throw new ArgumentNullException(nameof(originalCommand)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + lock (_stateLock) { _asyncCallbacks[originalCommand] = callback; } + } + + /// Remove a previously registered async callback. + public void UnregisterAsyncCallback(string originalCommand) + { + if (originalCommand == null) return; + lock (_stateLock) { _asyncCallbacks.Remove(originalCommand); } + } + + // --------------------------------------------------------------- + // IDisposable + // --------------------------------------------------------------- + + public void Dispose() + { + Disconnect(); + _respLock.Dispose(); + _cmdRespLock.Dispose(); + } + + // --------------------------------------------------------------- + // Internal helpers + // --------------------------------------------------------------- + + private async Task SendAndAwaitCmdRespAsync(string command, TimeSpan? timeout) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + var to = timeout ?? TimeSpan.FromSeconds(5); + byte[] wire = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes(command), PacketType.Cmd); + + await _cmdRespLock.WaitAsync().ConfigureAwait(false); + try + { + _cmdRespTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _transport.SendRaw(wire); + await WaitForCmdRespAsync(to).ConfigureAwait(false); + } + finally + { + _cmdRespLock.Release(); + } + } + + private async Task WaitForRespAsync(TimeSpan timeout) + { + var tcs = _respTcs; + var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false); + if (winner != tcs.Task) + { + // Protocol v0 has no correlation id, so a late RESP for the + // timed-out command could be misattributed to the *next* + // SendAndWait call. Force a disconnect so the connection + // is unusable until the caller reconnects. + try { _transport.Disconnect(); } catch { /* ignore */ } + throw new RouterTimeoutException($"No response received within {timeout.TotalSeconds:F1}s."); + } + object item = await tcs.Task.ConfigureAwait(false); + if (item is Exception exc) throw exc; + var packet = (Packet)item; + return new CommandResponse(packet.Data); + } + + private async Task WaitForCmdRespAsync(TimeSpan timeout) + { + var tcs = _cmdRespTcs; + var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false); + if (winner != tcs.Task) + { + // Same desync risk as WaitForRespAsync: a late CMD_RESP could + // complete the next in-flight waiter. Force a disconnect so + // the connection cannot be reused after a handshake timeout. + try { _transport.Disconnect(); } catch { /* ignore */ } + throw new RouterTimeoutException($"No cmd-resp received within {timeout.TotalSeconds:F1}s."); + } + object item = await tcs.Task.ConfigureAwait(false); + if (item is Exception exc) throw exc; + // CMD_RESP payload is a handshake acknowledgment; discard it. + } + + private void UnblockWaiters(Exception sentinel) + { + _respTcs?.TrySetResult(sentinel); + _cmdRespTcs?.TrySetResult(sentinel); + } + + // --------------------------------------------------------------- + // Internal: packet dispatch (runs on the receive task thread) + // --------------------------------------------------------------- + + internal void OnPacket(Packet packet) + { + switch (packet.Type) + { + case PacketType.Resp: + _respTcs?.TrySetResult(packet); + break; + + case PacketType.CmdResp: + _cmdRespTcs?.TrySetResult(packet); + break; + + case PacketType.AsyncResp: + { + var resp = AsyncResponse.FromPacket(packet); + AsyncResponseQueue.Enqueue(resp); + AsyncResponseCallback cb; + lock (_stateLock) { _asyncCallbacks.TryGetValue(resp.OriginalCommand, out cb); } + if (cb != null) + { + try { cb(resp); } catch { /* swallow callback errors */ } + } + break; + } + + case PacketType.Status: + case PacketType.Interrupt: + { + var notif = StatusNotification.FromPacket(packet); + StatusQueue.Enqueue(notif); + StatusCallback cb; + lock (_stateLock) { _statusCallbacks.TryGetValue((notif.StatusName, notif.ModuleName), out cb); } + if (cb != null) + { + try { cb(notif); } catch { /* swallow callback errors */ } + } + break; + } + + case PacketType.Error: + { + var err = ProtocolCodec.ParseServerError(packet); + _respTcs?.TrySetResult(err); + _cmdRespTcs?.TrySetResult(err); + break; + } + + case PacketType.Info: + // Silently discarded (welcome / goodbye messages). + break; + + case PacketType.Cmd: + // Server should never send CMD; ignore for forward compatibility. + break; + } + } + + internal void OnDisconnect() + { + UnblockWaiters(new RouterConnectionException("Connection lost unexpectedly.")); + } + } + + // ----------------------------------------------------------------------- + // Small convenience extensions + // ----------------------------------------------------------------------- + + internal static class TaskExtensions + { + public static async Task ContinueWithText(this Task task) + { + var resp = await task.ConfigureAwait(false); + return resp.Text; + } + } +} diff --git a/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.csproj b/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.csproj new file mode 100644 index 0000000..b3d7c2b --- /dev/null +++ b/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.csproj @@ -0,0 +1,51 @@ + + + + + netstandard2.0;net8.0 + latest + disable + false + true + $(NoWarn);CS1591 + + + true + CsmTcpRouter.Client + 0.1.0 + 0.1.0.0 + 0.1.0.0 + NEVSTOP-LAB + NEVSTOP-LAB + CsmTcpRouter.Client + CSM-TCP-Router .NET Client + C# client SDK for the CSM-TCP-Router LabVIEW server. Speaks the CSM-TCP-Router protocol v0 over TCP and exposes a high-level synchronous and asynchronous API for sending commands, awaiting responses, and subscribing to status / interrupt broadcasts from a Communicable State Machine (CSM). + Copyright (c) 2026 NEVSTOP-LAB + MIT + https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App + https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App + git + csm;labview;tcp;router;client;sdk;daq;communicable-state-machine;async + README.md + See CHANGELOG.md for release notes. + true + snupkg + true + true + + + + + + + + + + + true + + + diff --git a/SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs b/SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs new file mode 100644 index 0000000..ba7dbad --- /dev/null +++ b/SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs @@ -0,0 +1,364 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace CsmTcpRouter.Tests +{ + /// + /// End-to-end client tests against a real loopback . + /// Mirrors SDK/python/tests/test_integration.py + portions of test_client.py. + /// + public class ClientIntegrationTests + { + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(2); + + /// + /// Bind a TcpListener to port 0 (OS-assigned), grab the port, then stop + /// the listener. The port is then almost certainly closed for the + /// duration of the test, so we can rely on connect attempts to fail + /// without depending on system state (e.g. port 1 may be open). + /// + private static int GetClosedPort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + + // --------------------------------------------------------------- + // Connect / Disconnect + // --------------------------------------------------------------- + + [Fact] + public void Connect_Disconnect_RoundTrip() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + Assert.True(client.Connected); + client.Disconnect(); + Assert.False(client.Connected); + } + + [Fact] + public async Task ConnectAsync_RoundTrip() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + await client.ConnectAsync(server.Host, server.Port, DefaultTimeout); + Assert.True(client.Connected); + client.Disconnect(); + } + + [Fact] + public void Connect_Twice_Throws() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + Assert.Throws( + () => client.Connect(server.Host, server.Port, DefaultTimeout)); + } + + [Fact] + public void Connect_BadPort_Throws() + { + using var client = new TcpRouterClient(); + int closedPort = GetClosedPort(); + Assert.Throws( + () => client.Connect("127.0.0.1", closedPort, TimeSpan.FromMilliseconds(500))); + } + + // --------------------------------------------------------------- + // SendAndWait / built-ins + // --------------------------------------------------------------- + + [Fact] + public void Ping_ReturnsTrueAndElapsed() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + var (ok, elapsed) = client.Ping(DefaultTimeout); + Assert.True(ok); + Assert.True(elapsed >= TimeSpan.Zero); + } + + [Fact] + public void ListModules_ReturnsServerText() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + var modules = client.ListModules(DefaultTimeout); + Assert.Contains("AI", modules); + Assert.Contains("DIO", modules); + } + + [Fact] + public void ListApi_FormatsCommand() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + var api = client.ListApi("DAQmx", DefaultTimeout); + Assert.Contains("DAQmx", api); + } + + [Fact] + public void SendAndWait_CustomResponse() + { + using var server = new MockServer(); + server.Start(); + server.SetResponse("Custom Cmd", "Custom Reply"); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + var resp = client.SendAndWait("Custom Cmd", DefaultTimeout); + Assert.Equal("Custom Reply", resp.Text); + } + + [Fact] + public void SendAndWait_ServerError_Throws() + { + using var server = new MockServer(); + server.Start(); + server.SetErrorResponse("Bad Cmd", "[Error: 42] bad"); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + var ex = Assert.Throws(() => client.SendAndWait("Bad Cmd", DefaultTimeout)); + Assert.Equal("42", ex.Code); + Assert.Equal("bad", ex.ServerMessage); + } + + [Fact] + public void SendAndWait_Timeout_Throws() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + // Server replies with CmdResp by default for unknown commands, not Resp, + // so a SendAndWait will time out waiting for a Resp. + Assert.Throws( + () => client.SendAndWait("Unknown XYZ", TimeSpan.FromMilliseconds(200))); + } + + [Fact] + public void SendAndWait_NotConnected_Throws() + { + using var client = new TcpRouterClient(); + Assert.Throws( + () => client.SendAndWait("Ping", DefaultTimeout)); + } + + // --------------------------------------------------------------- + // Post (async cmd-resp handshake) + // --------------------------------------------------------------- + + [Fact] + public void Post_CompletesOnHandshake() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + client.Post("API: Start -> DAQmx", DefaultTimeout); + // Make sure the server actually saw the command. + string cmd = server.GetReceived(DefaultTimeout); + Assert.Equal("API: Start -> DAQmx", cmd); + } + + [Fact] + public void PostNoReply_CompletesOnHandshake() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + client.PostNoReply("API: Reset ->| DAQmx", DefaultTimeout); + string cmd = server.GetReceived(DefaultTimeout); + Assert.Equal("API: Reset ->| DAQmx", cmd); + } + + // --------------------------------------------------------------- + // Subscribe / status broadcast + // --------------------------------------------------------------- + + [Fact] + public void SubscribeStatus_DeliversToCallbackAndQueue() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + + StatusNotification received = null; + using var ev = new ManualResetEventSlim(); + client.SubscribeStatus("Status", "AI", n => { received = n; ev.Set(); }, DefaultTimeout); + + server.PushStatus("Status >> v1 <- AI"); + + Assert.True(ev.Wait(DefaultTimeout), "callback was not invoked in time"); + Assert.Equal("Status", received.StatusName); + Assert.Equal("v1", received.Data); + Assert.Equal("AI", received.ModuleName); + + Assert.True(client.StatusQueue.TryDequeue(out var queued)); + Assert.Equal("Status", queued.StatusName); + } + + [Fact] + public void UnsubscribeStatus_RemovesCallback() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + + int hits = 0; + using var ev = new ManualResetEventSlim(); + client.SubscribeStatus( + "Status", "AI", + _ => { Interlocked.Increment(ref hits); ev.Set(); }, + DefaultTimeout); + client.UnsubscribeStatus("Status", "AI", DefaultTimeout); + + server.PushStatus("Status >> v1 <- AI"); + Assert.False(ev.Wait(TimeSpan.FromMilliseconds(150)), "callback was invoked after unsubscribe"); + Assert.Equal(0, hits); + } + + [Fact] + public void RegisterAsyncCallback_DeliversAsyncResponse() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + + AsyncResponse received = null; + using var ev = new ManualResetEventSlim(); + client.RegisterAsyncCallback("API: Start -> DIO", ar => { received = ar; ev.Set(); }); + + server.PushAsyncResponse("done <- API: Start -> DIO"); + + Assert.True(ev.Wait(DefaultTimeout), "async callback not invoked"); + Assert.Equal("done", received.Text); + Assert.Equal("API: Start -> DIO", received.OriginalCommand); + } + + // --------------------------------------------------------------- + // Disconnect-while-waiting unblocks waiters + // --------------------------------------------------------------- + + [Fact] + public void Disconnect_WhileWaiting_RaisesConnectionError() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + client.Connect(server.Host, server.Port, DefaultTimeout); + + // Issue a SendAndWait whose response never comes; disconnect while waiting. + var task = Task.Run(() => client.SendAndWait("Unknown XYZ", TimeSpan.FromSeconds(10))); + Thread.Sleep(100); + client.Disconnect(); + var agg = Assert.Throws(() => task.Wait(TimeSpan.FromSeconds(2))); + Assert.IsType(agg.InnerException); + } + + // --------------------------------------------------------------- + // WaitForServer + // --------------------------------------------------------------- + + [Fact] + public void WaitForServer_ReturnsTrueWhenReady() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + bool ready = client.WaitForServer(server.Host, server.Port, TimeSpan.FromSeconds(2), TimeSpan.FromMilliseconds(50)); + Assert.True(ready); + } + + [Fact] + public void WaitForServer_ReturnsFalseOnTimeout() + { + using var client = new TcpRouterClient(); + int closedPort = GetClosedPort(); + bool ready = client.WaitForServer("127.0.0.1", closedPort, TimeSpan.FromMilliseconds(300), TimeSpan.FromMilliseconds(50)); + Assert.False(ready); + } + + // --------------------------------------------------------------- + // Async API + // --------------------------------------------------------------- + + [Fact] + public async Task SendAndWaitAsync_RoundTrip() + { + using var server = new MockServer(); + server.Start(); + server.SetResponse("Hello", "World"); + using var client = new TcpRouterClient(); + await client.ConnectAsync(server.Host, server.Port, DefaultTimeout); + var resp = await client.SendAndWaitAsync("Hello", DefaultTimeout); + Assert.Equal("World", resp.Text); + } + + [Fact] + public async Task PingAsync_ReturnsOkAndElapsed() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + await client.ConnectAsync(server.Host, server.Port, DefaultTimeout); + var (ok, elapsed) = await client.PingAsync(DefaultTimeout); + Assert.True(ok); + Assert.True(elapsed >= TimeSpan.Zero); + } + + [Fact] + public async Task ListModulesAsync_ReturnsText() + { + using var server = new MockServer(); + server.Start(); + using var client = new TcpRouterClient(); + await client.ConnectAsync(server.Host, server.Port, DefaultTimeout); + string modules = await client.ListModulesAsync(DefaultTimeout); + Assert.Contains("AI", modules); + } + + [Fact] + public async Task SendAndWaitAsync_SerialisesConcurrentCallers() + { + using var server = new MockServer(); + server.Start(); + server.SetResponse("A", "alpha"); + server.SetResponse("B", "bravo"); + using var client = new TcpRouterClient(); + await client.ConnectAsync(server.Host, server.Port, DefaultTimeout); + + var tasks = Enumerable.Range(0, 10).Select(i => + client.SendAndWaitAsync(i % 2 == 0 ? "A" : "B", DefaultTimeout)).ToArray(); + var results = await Task.WhenAll(tasks); + for (int i = 0; i < tasks.Length; i++) + { + Assert.Equal(i % 2 == 0 ? "alpha" : "bravo", results[i].Text); + } + } + } +} diff --git a/SDK/csharp/tests/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj b/SDK/csharp/tests/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj new file mode 100644 index 0000000..3713b48 --- /dev/null +++ b/SDK/csharp/tests/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + latest + disable + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs b/SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs new file mode 100644 index 0000000..7472167 --- /dev/null +++ b/SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CsmTcpRouter; + +namespace CsmTcpRouter.Tests +{ + /// Minimal TCP server that emulates a CSM-TCP-Router for tests. + /// Mirrors the Python tests/conftest.py MockServer. + internal sealed class MockServer : IDisposable + { + private TcpListener _listener; + private CancellationTokenSource _cts; + private Task _acceptTask; + + public string Host => "127.0.0.1"; + public int Port { get; private set; } + + public BlockingCollection ReceivedCommands { get; } + = new BlockingCollection(new ConcurrentQueue()); + + private readonly Dictionary _responses + = new Dictionary(); + private readonly object _stateLock = new object(); + + private readonly List _clients = new List(); + + public void Start() + { + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + Port = ((IPEndPoint)_listener.LocalEndpoint).Port; + _cts = new CancellationTokenSource(); + _acceptTask = Task.Run(() => AcceptLoopAsync(_cts.Token)); + } + + public void Stop() + { + try { _cts?.Cancel(); } catch { } + try { _listener?.Stop(); } catch { } + lock (_stateLock) + { + foreach (var c in _clients) + { + try { c.Close(); } catch { } + } + _clients.Clear(); + } + try { _acceptTask?.Wait(TimeSpan.FromSeconds(2)); } catch { } + } + + public void Dispose() => Stop(); + + public void SetResponse(string cmd, string respText) + { + lock (_stateLock) { _responses[cmd] = (PacketType.Resp, Encoding.UTF8.GetBytes(respText)); } + } + + public void SetErrorResponse(string cmd, string errorText) + { + lock (_stateLock) { _responses[cmd] = (PacketType.Error, Encoding.UTF8.GetBytes(errorText)); } + } + + public void PushStatus(string payload) + { + byte[] wire = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes(payload), PacketType.Status); + List snapshot; + lock (_stateLock) { snapshot = new List(_clients); } + foreach (var c in snapshot) + { + try { c.GetStream().Write(wire, 0, wire.Length); } + catch { /* ignore */ } + } + } + + public void PushAsyncResponse(string payload) + { + byte[] wire = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes(payload), PacketType.AsyncResp); + List snapshot; + lock (_stateLock) { snapshot = new List(_clients); } + foreach (var c in snapshot) + { + try { c.GetStream().Write(wire, 0, wire.Length); } + catch { /* ignore */ } + } + } + + public string GetReceived(TimeSpan? timeout = null) + { + if (ReceivedCommands.TryTake(out var cmd, (int)(timeout ?? TimeSpan.FromSeconds(1)).TotalMilliseconds)) + return cmd; + return null; + } + + // ----------------------------------------------------------------- + // Internal + // ----------------------------------------------------------------- + + private async Task AcceptLoopAsync(CancellationToken ct) + { + try + { + while (!ct.IsCancellationRequested) + { + TcpClient client; + try { client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false); } + catch (ObjectDisposedException) { break; } + catch (SocketException) { break; } + lock (_stateLock) { _clients.Add(client); } + _ = Task.Run(() => HandleClientAsync(client, ct)); + } + } + catch { /* ignore */ } + } + + private async Task HandleClientAsync(TcpClient client, CancellationToken ct) + { + try + { + var stream = client.GetStream(); + // Welcome info packet + var welcome = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes("Welcome to mock server"), PacketType.Info); + await stream.WriteAsync(welcome, 0, welcome.Length, ct).ConfigureAwait(false); + + var headerBuf = new byte[8]; + while (!ct.IsCancellationRequested) + { + if (!await ReadExactlyAsync(stream, headerBuf, 0, headerBuf.Length, ct).ConfigureAwait(false)) + break; + uint dataLen = ((uint)headerBuf[0] << 24) | ((uint)headerBuf[1] << 16) | ((uint)headerBuf[2] << 8) | headerBuf[3]; + var body = dataLen == 0 ? Array.Empty() : new byte[dataLen]; + if (dataLen > 0 && !await ReadExactlyAsync(stream, body, 0, body.Length, ct).ConfigureAwait(false)) + break; + byte typeByte = headerBuf[5]; + if (typeByte == (byte)PacketType.Cmd) + { + string cmdText = Encoding.UTF8.GetString(body).Trim(); + ReceivedCommands.Add(cmdText, ct); + HandleCommand(stream, cmdText); + } + } + } + catch (IOException) { } + catch (ObjectDisposedException) { } + catch (OperationCanceledException) { } + finally + { + lock (_stateLock) { _clients.Remove(client); } + try { client.Close(); } catch { } + } + } + + private void HandleCommand(NetworkStream stream, string cmd) + { + (PacketType Type, byte[] Data) custom; + bool hasCustom; + lock (_stateLock) { hasCustom = _responses.TryGetValue(cmd, out custom); } + if (hasCustom) + { + var wire = ProtocolCodec.EncodePacket(custom.Data, custom.Type); + stream.Write(wire, 0, wire.Length); + return; + } + + byte[] reply; + if (cmd == "Ping") + reply = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes("Pong"), PacketType.Resp); + else if (cmd == "List") + reply = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes("AI\nDIO\nSystem"), PacketType.Resp); + else if (cmd.StartsWith("List API ", StringComparison.Ordinal)) + { + string module = cmd.Substring("List API ".Length).Trim(); + reply = ProtocolCodec.EncodePacket( + Encoding.UTF8.GetBytes($"API: Start -> {module}\nAPI: Stop -> {module}"), + PacketType.Resp); + } + else if (cmd.StartsWith("List State ", StringComparison.Ordinal)) + { + string module = cmd.Substring("List State ".Length).Trim(); + reply = ProtocolCodec.EncodePacket( + Encoding.UTF8.GetBytes($"Idle <- {module}\nRunning <- {module}"), + PacketType.Resp); + } + else if (cmd.Contains("->") || cmd.Contains("->")) + { + reply = ProtocolCodec.EncodePacket(Array.Empty(), PacketType.CmdResp); + } + else + { + // Generic async handshake for any other command. + reply = ProtocolCodec.EncodePacket(Array.Empty(), PacketType.CmdResp); + } + + stream.Write(reply, 0, reply.Length); + } + + private static async Task ReadExactlyAsync(Stream s, byte[] buf, int off, int count, CancellationToken ct) + { + int read = 0; + while (read < count) + { + int n; + try { n = await s.ReadAsync(buf, off + read, count - read, ct).ConfigureAwait(false); } + catch (IOException) { return false; } + catch (ObjectDisposedException) { return false; } + if (n == 0) return false; + read += n; + } + return true; + } + } +} diff --git a/SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs b/SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs new file mode 100644 index 0000000..14d8e65 --- /dev/null +++ b/SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs @@ -0,0 +1,306 @@ +using System; +using System.Text; +using CsmTcpRouter; +using Xunit; + +namespace CsmTcpRouter.Tests +{ + // Mirrors SDK/python/tests/test_protocol.py. + public class ProtocolTests + { + private const int HeaderSize = 8; + private const byte ProtocolVersion = 0x01; + + private static (byte DataLenHi3, byte DataLenHi2, byte DataLenHi1, byte DataLenLo, + byte Version, byte Type, byte Flag1, byte Flag2) + ParseHeader(byte[] wire) + { + return (wire[0], wire[1], wire[2], wire[3], wire[4], wire[5], wire[6], wire[7]); + } + + private static uint ReadDataLen(byte[] wire) => + ((uint)wire[0] << 24) | ((uint)wire[1] << 16) | ((uint)wire[2] << 8) | wire[3]; + + // ------------------------------------------------------------------- + // EncodePacket + // ------------------------------------------------------------------- + + [Fact] + public void Encode_ReturnsHeaderPlusBody() + { + var data = Encoding.UTF8.GetBytes("hello"); + var wire = ProtocolCodec.EncodePacket(data, PacketType.Cmd); + Assert.Equal(HeaderSize + data.Length, wire.Length); + } + + [Fact] + public void Encode_HeaderFormat() + { + var data = Encoding.UTF8.GetBytes("hello"); + var wire = ProtocolCodec.EncodePacket(data, PacketType.Cmd); + Assert.Equal((uint)data.Length, ReadDataLen(wire)); + Assert.Equal(ProtocolVersion, wire[4]); + Assert.Equal((byte)PacketType.Cmd, wire[5]); + Assert.Equal(0, wire[6]); + Assert.Equal(0, wire[7]); + } + + [Fact] + public void Encode_BodyAppendedVerbatim() + { + var data = Encoding.UTF8.GetBytes("test payload"); + var wire = ProtocolCodec.EncodePacket(data, PacketType.Resp); + var slice = new byte[data.Length]; + Buffer.BlockCopy(wire, HeaderSize, slice, 0, data.Length); + Assert.Equal(data, slice); + } + + [Fact] + public void Encode_EmptyBody() + { + var wire = ProtocolCodec.EncodePacket(Array.Empty(), PacketType.CmdResp); + Assert.Equal(HeaderSize, wire.Length); + Assert.Equal(0u, ReadDataLen(wire)); + } + + [Fact] + public void Encode_CustomFlags() + { + var wire = ProtocolCodec.EncodePacket(new byte[] { 0x78 }, PacketType.Info, flag1: 0xAB, flag2: 0xCD); + Assert.Equal(0xAB, wire[6]); + Assert.Equal(0xCD, wire[7]); + } + + [Fact] + public void Encode_AllPacketTypes() + { + foreach (PacketType ptype in Enum.GetValues(typeof(PacketType))) + { + var wire = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes("data"), ptype); + Assert.Equal((byte)ptype, wire[5]); + } + } + + [Fact] + public void Encode_Utf8CommandString() + { + const string cmd = "API: Start Sampling -@ DAQmx"; + var wire = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes(cmd), PacketType.Cmd); + Assert.Equal(cmd, Encoding.UTF8.GetString(wire, HeaderSize, wire.Length - HeaderSize)); + } + + [Fact] + public void Encode_LargePayloadLengthField() + { + var data = new byte[1024]; + var wire = ProtocolCodec.EncodePacket(data, PacketType.Resp); + Assert.Equal(1024u, ReadDataLen(wire)); + } + + [Fact] + public void Encode_NullDataTreatedAsEmpty() + { + var wire = ProtocolCodec.EncodePacket(null, PacketType.Cmd); + Assert.Equal(HeaderSize, wire.Length); + Assert.Equal(0u, ReadDataLen(wire)); + } + + // ------------------------------------------------------------------- + // DecodeHeader + // ------------------------------------------------------------------- + + [Fact] + public void DecodeHeader_RoundTrip() + { + var wire = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes("body"), PacketType.AsyncResp, flag1: 1, flag2: 2); + var header = new byte[HeaderSize]; + Buffer.BlockCopy(wire, 0, header, 0, HeaderSize); + var (dataLen, version, typeByte, flag1, flag2) = ProtocolCodec.DecodeHeader(header); + Assert.Equal(4u, dataLen); + Assert.Equal(ProtocolVersion, version); + Assert.Equal((byte)PacketType.AsyncResp, typeByte); + Assert.Equal(1, flag1); + Assert.Equal(2, flag2); + } + + [Fact] + public void DecodeHeader_WrongLengthThrows() + { + Assert.Throws(() => ProtocolCodec.DecodeHeader(new byte[7])); + } + + [Fact] + public void DecodeHeader_ZeroLengthThrows() + { + Assert.Throws(() => ProtocolCodec.DecodeHeader(Array.Empty())); + } + + // ------------------------------------------------------------------- + // ParsePacket + // ------------------------------------------------------------------- + + private static (byte[] Header, byte[] Body) MakeWire(byte[] data, PacketType ptype) + { + var wire = ProtocolCodec.EncodePacket(data, ptype); + var header = new byte[HeaderSize]; + var body = new byte[wire.Length - HeaderSize]; + Buffer.BlockCopy(wire, 0, header, 0, HeaderSize); + Buffer.BlockCopy(wire, HeaderSize, body, 0, body.Length); + return (header, body); + } + + [Fact] + public void Parse_BasicRoundTrip() + { + var (header, body) = MakeWire(Encoding.UTF8.GetBytes("hello"), PacketType.Resp); + var pkt = ProtocolCodec.ParsePacket(header, body); + Assert.Equal(PacketType.Resp, pkt.Type); + Assert.Equal("hello", Encoding.UTF8.GetString(pkt.Data)); + Assert.Equal(ProtocolVersion, pkt.Version); + } + + [Fact] + public void Parse_AllKnownTypes() + { + foreach (PacketType ptype in Enum.GetValues(typeof(PacketType))) + { + var (header, body) = MakeWire(Encoding.UTF8.GetBytes("data"), ptype); + var pkt = ProtocolCodec.ParsePacket(header, body); + Assert.Equal(ptype, pkt.Type); + } + } + + [Fact] + public void Parse_UnknownTypeMappedToInfo() + { + // Manually craft a packet with an unknown type byte (0xFF). + var header = new byte[] { 0, 0, 0, 4, ProtocolVersion, 0xFF, 0, 0 }; + var body = Encoding.UTF8.GetBytes("data"); + var pkt = ProtocolCodec.ParsePacket(header, body); + Assert.Equal(PacketType.Info, pkt.Type); + } + + [Fact] + public void Parse_BodyLengthMismatchThrows() + { + var (header, _) = MakeWire(Encoding.UTF8.GetBytes("hello"), PacketType.Cmd); + Assert.Throws(() => ProtocolCodec.ParsePacket(header, Encoding.UTF8.GetBytes("hi"))); + } + + [Fact] + public void Parse_EmptyBody() + { + var (header, body) = MakeWire(Array.Empty(), PacketType.CmdResp); + var pkt = ProtocolCodec.ParsePacket(header, body); + Assert.Empty(pkt.Data); + } + + [Fact] + public void Parse_FlagsPreserved() + { + var wire = ProtocolCodec.EncodePacket(new byte[] { 0x78 }, PacketType.Status, flag1: 3, flag2: 7); + var header = new byte[HeaderSize]; + var body = new byte[wire.Length - HeaderSize]; + Buffer.BlockCopy(wire, 0, header, 0, HeaderSize); + Buffer.BlockCopy(wire, HeaderSize, body, 0, body.Length); + var pkt = ProtocolCodec.ParsePacket(header, body); + Assert.Equal(3, pkt.Flag1); + Assert.Equal(7, pkt.Flag2); + } + + [Fact] + public void Parse_HeaderTooShortThrows() + { + Assert.Throws(() => ProtocolCodec.ParsePacket(new byte[4], Array.Empty())); + } + + [Fact] + public void HeaderSize_IsEight() + { + Assert.Equal(8, ProtocolCodec.HeaderSize); + } + + // ------------------------------------------------------------------- + // ParseServerError + // ------------------------------------------------------------------- + + [Fact] + public void ParseServerError_PlainMessage() + { + var pkt = new Packet(PacketType.Error, Encoding.UTF8.GetBytes("something went wrong")); + var err = ProtocolCodec.ParseServerError(pkt); + Assert.Equal("something went wrong", err.ServerMessage); + Assert.Equal(string.Empty, err.Code); + } + + [Fact] + public void ParseServerError_CsmFormat() + { + var pkt = new Packet(PacketType.Error, Encoding.UTF8.GetBytes("[Error: 42] module not found")); + var err = ProtocolCodec.ParseServerError(pkt); + Assert.Equal("42", err.Code); + Assert.Equal("module not found", err.ServerMessage); + Assert.Equal("[Error: 42] module not found", err.ToString()); + } + + [Fact] + public void ParseServerError_CsmFormatNoMessage() + { + var pkt = new Packet(PacketType.Error, Encoding.UTF8.GetBytes("[Error: 0]")); + var err = ProtocolCodec.ParseServerError(pkt); + Assert.Equal("0", err.Code); + Assert.Equal(string.Empty, err.ServerMessage); + } + + [Fact] + public void ParseServerError_MalformedBracketNoCrash() + { + var pkt = new Packet(PacketType.Error, Encoding.UTF8.GetBytes("[Error: no closing bracket")); + var err = ProtocolCodec.ParseServerError(pkt); + Assert.Equal(string.Empty, err.Code); + Assert.Contains("no closing bracket", err.ServerMessage); + } + + // ------------------------------------------------------------------- + // Model parsing helpers + // ------------------------------------------------------------------- + + [Fact] + public void AsyncResponse_FromPacket_SplitsOnSeparator() + { + var pkt = new Packet(PacketType.AsyncResp, Encoding.UTF8.GetBytes("result <- API: Start -> DIO")); + var resp = AsyncResponse.FromPacket(pkt); + Assert.Equal("result", resp.Text); + Assert.Equal("API: Start -> DIO", resp.OriginalCommand); + } + + [Fact] + public void AsyncResponse_FromPacket_NoSeparator() + { + var pkt = new Packet(PacketType.AsyncResp, Encoding.UTF8.GetBytes("just text")); + var resp = AsyncResponse.FromPacket(pkt); + Assert.Equal("just text", resp.Text); + Assert.Equal(string.Empty, resp.OriginalCommand); + } + + [Fact] + public void StatusNotification_FromPacket_FullForm() + { + var pkt = new Packet(PacketType.Status, Encoding.UTF8.GetBytes("Status >> value42 <- AI")); + var notif = StatusNotification.FromPacket(pkt); + Assert.Equal("Status", notif.StatusName); + Assert.Equal("value42", notif.Data); + Assert.Equal("AI", notif.ModuleName); + Assert.Equal(PacketType.Status, notif.PacketType); + } + + [Fact] + public void StatusNotification_FromPacket_InterruptType() + { + var pkt = new Packet(PacketType.Interrupt, Encoding.UTF8.GetBytes("Alarm >> fire <- Safety")); + var notif = StatusNotification.FromPacket(pkt); + Assert.Equal(PacketType.Interrupt, notif.PacketType); + Assert.Equal("Alarm", notif.StatusName); + } + } +} From aa7d605572ca5acbd4be0a5e46601befffd77acf Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:30:06 +0800 Subject: [PATCH 07/12] Add C csm-tcp-router-client SDK (multi-platform, VS2026, CMake, tests) (#40) * Add C csm-tcp-router-client SDK with VS2026 + CMake + tests Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/ea6c42b2-b6f8-4ebe-8438-786601832ef5 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Address PR review comments on the C SDK Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/de04612d-484c-4905-9c04-06c451a01e15 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Drop redundant g_wsa_lock_inited; route cleanup through InitOnceExecuteOnce Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/de04612d-484c-4905-9c04-06c451a01e15 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- .github/workflows/C_SDK.yml | 67 + SDK/c/.gitignore | 34 + SDK/c/CHANGELOG.md | 42 + SDK/c/CMakeLists.txt | 100 + SDK/c/LICENSE | 21 + SDK/c/README.md | 258 +++ SDK/c/README.zh-cn.md | 250 +++ SDK/c/examples/basic_usage.c | 61 + SDK/c/examples/subscribe_status.c | 65 + SDK/c/include/csm_tcp_router_client.h | 401 ++++ SDK/c/src/csm_tcp_router_client.c | 1631 +++++++++++++++++ SDK/c/tests/mock_server.c | 558 ++++++ SDK/c/tests/mock_server.h | 48 + SDK/c/tests/test_client.c | 51 + SDK/c/tests/test_harness.h | 62 + SDK/c/tests/test_integration.c | 165 ++ SDK/c/tests/test_main.c | 94 + SDK/c/tests/test_protocol.c | 84 + SDK/c/vs2026/README.md | 61 + SDK/c/vs2026/csm_tcp_router_client.sln | 43 + .../csm_tcp_router_client.tests.vcxproj | 94 + ...sm_tcp_router_client.tests.vcxproj.filters | 22 + .../csm_tcp_router_client.vcxproj | 87 + .../csm_tcp_router_client.vcxproj.filters | 23 + 24 files changed, 4322 insertions(+) create mode 100644 .github/workflows/C_SDK.yml create mode 100644 SDK/c/.gitignore create mode 100644 SDK/c/CHANGELOG.md create mode 100644 SDK/c/CMakeLists.txt create mode 100644 SDK/c/LICENSE create mode 100644 SDK/c/README.md create mode 100644 SDK/c/README.zh-cn.md create mode 100644 SDK/c/examples/basic_usage.c create mode 100644 SDK/c/examples/subscribe_status.c create mode 100644 SDK/c/include/csm_tcp_router_client.h create mode 100644 SDK/c/src/csm_tcp_router_client.c create mode 100644 SDK/c/tests/mock_server.c create mode 100644 SDK/c/tests/mock_server.h create mode 100644 SDK/c/tests/test_client.c create mode 100644 SDK/c/tests/test_harness.h create mode 100644 SDK/c/tests/test_integration.c create mode 100644 SDK/c/tests/test_main.c create mode 100644 SDK/c/tests/test_protocol.c create mode 100644 SDK/c/vs2026/README.md create mode 100644 SDK/c/vs2026/csm_tcp_router_client.sln create mode 100644 SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj create mode 100644 SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj.filters create mode 100644 SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj create mode 100644 SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj.filters diff --git a/.github/workflows/C_SDK.yml b/.github/workflows/C_SDK.yml new file mode 100644 index 0000000..81d4ee2 --- /dev/null +++ b/.github/workflows/C_SDK.yml @@ -0,0 +1,67 @@ +name: C SDK + +on: + push: + paths: + - 'SDK/c/**' + - '.github/workflows/C_SDK.yml' + tags: + - 'c-sdk-v*' + pull_request: + paths: + - 'SDK/c/**' + - '.github/workflows/C_SDK.yml' + workflow_dispatch: + +defaults: + run: + working-directory: SDK/c + +jobs: + # ------------------------------------------------------------------------- + # Build & test on Linux, macOS, and Windows. + # ------------------------------------------------------------------------- + build-test: + name: Build & test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + + - name: Configure (CMake) + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build --config Release -j + + - name: Run tests (ctest) + run: ctest --test-dir build --output-on-failure -C Release + + # ------------------------------------------------------------------------- + # Sanitizer build on Linux to catch undefined behaviour, leaks, and races. + # ------------------------------------------------------------------------- + sanitizers: + name: Sanitizers (Linux) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + + - name: Configure with ASan + UBSan + run: | + cmake -S . -B build-san \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -O1 -g" \ + -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" + + - name: Build (sanitizers) + run: cmake --build build-san -j + + - name: Run tests (sanitizers) + run: ctest --test-dir build-san --output-on-failure diff --git a/SDK/c/.gitignore b/SDK/c/.gitignore new file mode 100644 index 0000000..f5ae8e1 --- /dev/null +++ b/SDK/c/.gitignore @@ -0,0 +1,34 @@ +# CMake / build artefacts +build/ +build-*/ +out/ +cmake-build-*/ + +# Visual Studio +vs2026/build/ +*.user +*.suo +*.sdf +*.opensdf +*.aps +*.ipch +.vs/ +[Dd]ebug/ +[Rr]elease/ +[Xx]64/ +[Ww]in32/ +*.obj +*.lib +*.dll +*.exe +*.pdb +*.idb +*.ilk +*.exp + +# Compiled objects +*.o +*.a +*.so +*.so.* +*.dylib diff --git a/SDK/c/CHANGELOG.md b/SDK/c/CHANGELOG.md new file mode 100644 index 0000000..fa92df1 --- /dev/null +++ b/SDK/c/CHANGELOG.md @@ -0,0 +1,42 @@ +# Changelog + +All notable changes to the C `csm-tcp-router-client` SDK will be +documented in this file. The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this +project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2026-04-27 + +Initial public release of the C SDK. + +### Added + +- Cross-platform TCP client implementation (Windows + POSIX). + - Single source file `src/csm_tcp_router_client.c`. + - Single header `include/csm_tcp_router_client.h`. + - Background receive thread with thread-safe public API. +- CSM-TCP-Router protocol v0 codec (`csm_encode_packet`, + `csm_decode_header`, `csm_parse_packet`). +- Synchronous command API (`csm_client_send_and_wait`). +- Asynchronous command API (`csm_client_post`, + `csm_client_post_no_reply`) with callback + polling-queue delivery. +- Status / interrupt subscription API + (`csm_client_subscribe_status` / `csm_client_unsubscribe_status`, + `csm_client_poll_status`). +- Router management helpers: `csm_client_list_modules`, + `csm_client_list_api`, `csm_client_list_states`, `csm_client_help`. +- Connection utilities: `csm_client_ping`, + `csm_client_wait_for_server`, `csm_client_is_connected`. +- Server error inspection via `csm_client_last_server_error`. +- Examples: `examples/basic_usage.c`, `examples/subscribe_status.c`. +- Test suite using an in-process `MockServer` fixture + (`tests/mock_server.[ch]`) and a tiny custom test harness + (`tests/test_harness.h` + `tests/test_main.c`): + protocol codec tests, client-lifecycle tests, end-to-end integration + tests. +- CMake build (`CMakeLists.txt`) with options for tests, examples, and + shared-library output; `ctest` integration. +- Visual Studio 2026 (toolset `v144`) solution + projects under + `vs2026/` for IDE-driven build & test on Windows. +- GitHub Actions CI workflow (`.github/workflows/C_SDK.yml`) building + and running tests on Ubuntu, Windows and macOS. diff --git a/SDK/c/CMakeLists.txt b/SDK/c/CMakeLists.txt new file mode 100644 index 0000000..1c42a1f --- /dev/null +++ b/SDK/c/CMakeLists.txt @@ -0,0 +1,100 @@ +# CMake build for the csm-tcp-router-client C SDK. +# +# Usage: +# mkdir build && cd build +# cmake .. +# cmake --build . --config Release +# ctest --output-on-failure -C Release +# +# Options: +# -DCSM_BUILD_TESTS=ON|OFF (default ON) +# -DCSM_BUILD_EXAMPLES=ON|OFF (default ON) +# -DCSM_BUILD_SHARED=ON|OFF (default OFF; ON builds a shared library) +cmake_minimum_required(VERSION 3.15) +project(csm_tcp_router_client + VERSION 0.1.0 + DESCRIPTION "C client SDK for CSM-TCP-Router" + LANGUAGES C) + +option(CSM_BUILD_TESTS "Build the test suite" ON) +option(CSM_BUILD_EXAMPLES "Build the example apps" ON) +option(CSM_BUILD_SHARED "Build as a shared library" OFF) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +if(MSVC) + add_compile_options(/W4 /D_CRT_SECURE_NO_WARNINGS) +else() + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# ------------------------------------------------------------------------- +# Library +# ------------------------------------------------------------------------- +if(CSM_BUILD_SHARED) + add_library(csm_tcp_router_client SHARED src/csm_tcp_router_client.c) + target_compile_definitions(csm_tcp_router_client + PRIVATE CSM_BUILD_LIBRARY CSM_BUILD_SHARED + PUBLIC CSM_BUILD_SHARED) +else() + add_library(csm_tcp_router_client STATIC src/csm_tcp_router_client.c) +endif() + +target_include_directories(csm_tcp_router_client + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +set_target_properties(csm_tcp_router_client PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}) + +# Platform-specific link libraries. +if(WIN32) + target_link_libraries(csm_tcp_router_client PUBLIC ws2_32) +else() + find_package(Threads REQUIRED) + target_link_libraries(csm_tcp_router_client PUBLIC Threads::Threads) +endif() + +# ------------------------------------------------------------------------- +# Tests +# ------------------------------------------------------------------------- +if(CSM_BUILD_TESTS) + enable_testing() + add_executable(csm_tcp_router_client_tests + tests/mock_server.c + tests/test_protocol.c + tests/test_client.c + tests/test_integration.c + tests/test_main.c) + target_include_directories(csm_tcp_router_client_tests + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) + target_link_libraries(csm_tcp_router_client_tests + PRIVATE csm_tcp_router_client) + if(WIN32) + target_link_libraries(csm_tcp_router_client_tests PRIVATE ws2_32) + endif() + add_test(NAME csm_tcp_router_client_tests + COMMAND csm_tcp_router_client_tests) +endif() + +# ------------------------------------------------------------------------- +# Examples +# ------------------------------------------------------------------------- +if(CSM_BUILD_EXAMPLES) + add_executable(basic_usage examples/basic_usage.c) + add_executable(subscribe_status examples/subscribe_status.c) + target_link_libraries(basic_usage PRIVATE csm_tcp_router_client) + target_link_libraries(subscribe_status PRIVATE csm_tcp_router_client) +endif() + +# ------------------------------------------------------------------------- +# Install rules (header + library) +# ------------------------------------------------------------------------- +install(TARGETS csm_tcp_router_client + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) +install(FILES include/csm_tcp_router_client.h DESTINATION include) diff --git a/SDK/c/LICENSE b/SDK/c/LICENSE new file mode 100644 index 0000000..78e1cd2 --- /dev/null +++ b/SDK/c/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 NEVSTOP-LAB + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SDK/c/README.md b/SDK/c/README.md new file mode 100644 index 0000000..5a93a24 --- /dev/null +++ b/SDK/c/README.md @@ -0,0 +1,258 @@ +# csm-tcp-router-client (C SDK) + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/C_SDK.yml/badge.svg)](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/C_SDK.yml) + +C client SDK for the [CSM-TCP-Router](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App) LabVIEW server. + +CSM-TCP-Router exposes a LabVIEW [Communicable State Machine (CSM)](https://github.com/NEVSTOP-LAB/Communicable-State-Machine) application over TCP so that any TCP client — including native C/C++ programs, embedded devices, test harnesses, or CI pipelines — can send commands and receive responses without touching the LabVIEW code. + +> 📖 [中文文档 README.zh-cn.md](README.zh-cn.md) + +--- + +## Features + +- **Synchronous commands** (`-@`) – `csm_client_send_and_wait()` blocks until the server returns the response. +- **Asynchronous commands** (`->`) – `csm_client_post()` waits for the `cmd-resp` handshake; the eventual response is delivered via callback or polling queue. +- **No-reply commands** (`->|`) – `csm_client_post_no_reply()` waits for the `cmd-resp` handshake; no further response expected. +- **Status subscriptions** – `csm_client_subscribe_status()` / `csm_client_unsubscribe_status()` with optional callback or polling queue. +- **Router management helpers** – `csm_client_list_modules()`, `csm_client_list_api()`, `csm_client_list_states()`, `csm_client_help()`. +- **Connection utilities** – `csm_client_wait_for_server()` for polling during app startup. +- **Thread-safe client** – every public function is safe to call from multiple threads concurrently. +- **Multi-platform** – Windows (Winsock2 + Win32 threads) and POSIX (BSD sockets + pthreads); single source file. +- **Zero runtime dependencies** – C99 standard library + the OS sockets/threading APIs only. + +--- + +## Layout + +``` +SDK/c/ +├── include/ +│ └── csm_tcp_router_client.h # public API +├── src/ +│ └── csm_tcp_router_client.c # cross-platform implementation +├── examples/ +│ ├── basic_usage.c # mirrors examples/basic_usage.py +│ └── subscribe_status.c # mirrors examples/subscribe_status.py +├── tests/ +│ ├── test_harness.h # tiny in-process test harness +│ ├── mock_server.[ch] # in-process MockServer fixture +│ ├── test_protocol.c # codec unit tests +│ ├── test_client.c # client-lifecycle unit tests +│ ├── test_integration.c # end-to-end tests via MockServer +│ └── test_main.c # runner / TESTS table +├── vs2026/ +│ ├── csm_tcp_router_client.sln +│ ├── csm_tcp_router_client/ # static-library project +│ └── csm_tcp_router_client.tests/ # test-executable project +├── CMakeLists.txt # cross-platform CMake build +├── CHANGELOG.md +├── LICENSE +├── README.md +└── README.zh-cn.md +``` + +This mirrors the layout of the Python SDK at `SDK/python/`. + +--- + +## Building + +### CMake (Linux / macOS / Windows) + +```bash +cd SDK/c +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j +ctest --test-dir build --output-on-failure -C Release +``` + +CMake options: + +| Option | Default | Description | +|-------------------------|---------|--------------------------------------| +| `CSM_BUILD_TESTS` | `ON` | Build the test executable. | +| `CSM_BUILD_EXAMPLES` | `ON` | Build the example apps. | +| `CSM_BUILD_SHARED` | `OFF` | Build a shared library (DLL/.so). | + +### Visual Studio 2026 + +Open `SDK/c/vs2026/csm_tcp_router_client.sln` in Visual Studio 2026 and +build (Ctrl+Shift+B). The solution provides Debug/Release × Win32/x64 +configurations using the `v144` platform toolset. Two projects are +included: + +- `csm_tcp_router_client` – static library +- `csm_tcp_router_client.tests` – console test executable (run it + directly to execute all unit + integration tests; exit code 0 on + success). + +See [`vs2026/README.md`](vs2026/README.md) for details. + +--- + +## Quickstart + +```c +#include "csm_tcp_router_client.h" +#include + +int main(void) { + csm_client_t *c = csm_client_create(); + if (csm_client_connect(c, "localhost", 30007, 5000) != CSM_OK) { + fprintf(stderr, "Connect failed\n"); + csm_client_destroy(c); + return 1; + } + + char *modules = NULL; + if (csm_client_list_modules(c, &modules, 5000) == CSM_OK) { + printf("Modules:\n%s\n", modules); + csm_string_free(modules); + } + + csm_command_response_t resp = {0}; + if (csm_client_send_and_wait(c, "API: Read -@ DAQmx", 5000, &resp) == CSM_OK) { + printf("Response: %s\n", (char *)resp.raw); + } + csm_command_response_dispose(&resp); + + double ms = 0; + if (csm_client_ping(c, 2000, &ms) == CSM_OK) { + printf("Ping latency: %.1f ms\n", ms); + } + + csm_client_disconnect(c); + csm_client_destroy(c); + return 0; +} +``` + +--- + +## Protocol + +The SDK implements the CSM-TCP-Router **protocol v0**. + +``` +| Data Length (4B) | Version (1B) | TYPE (1B) | FLAG1 (1B) | FLAG2 (1B) | Text Data | +╰────────────────────────── Header (8B) ─────────────────────────────╯ +``` + +| TYPE byte | Constant | Direction | Description | +|-----------|---------------------|----------------|------------------------------------------------| +| `0x00` | `CSM_PT_INFO` | Server → Client| Welcome / goodbye informational message | +| `0x01` | `CSM_PT_ERROR` | Server → Client| CSM error: `[Error: ] ` | +| `0x02` | `CSM_PT_CMD` | Client → Server| Command string | +| `0x03` | `CSM_PT_CMD_RESP` | Server → Client| Handshake ACK for async / subscribe commands | +| `0x04` | `CSM_PT_RESP` | Server → Client| Synchronous response payload | +| `0x05` | `CSM_PT_ASYNC_RESP` | Server → Client| Async response: ` <- ` | +| `0x06` | `CSM_PT_STATUS` | Server → Client| Status broadcast: ` >> <- ` | +| `0x07` | `CSM_PT_INTERRUPT` | Server → Client| Interrupt broadcast (same format as STATUS) | + +--- + +## API at a glance + +### Lifecycle + +| Function | Description | +|---|---| +| `csm_client_create()` | Allocate a new client. | +| `csm_client_destroy(c)` | Disconnect (if connected) and free resources. | +| `csm_client_connect(c, host, port, timeout_ms)` | Open a TCP connection and start the receive thread. | +| `csm_client_disconnect(c)` | Close the connection; safe even when not connected. | +| `csm_client_is_connected(c)` | Non-zero while connected. | +| `csm_client_wait_for_server(host, port, timeout_ms, retry_ms)` | Poll until the server is reachable. | + +### Commands + +| Function | Description | +|---|---| +| `csm_client_send_and_wait(c, cmd, timeout, &resp)` | Synchronous command (`-@`). | +| `csm_client_post(c, cmd, timeout)` | Async command (`->`). | +| `csm_client_post_no_reply(c, cmd, timeout)` | No-reply async command (`->|`). | +| `csm_client_ping(c, timeout, &elapsed_ms)` | Round-trip latency check. | + +### Router management helpers + +| Function | Description | +|---|---| +| `csm_client_list_modules(c, &out_text, timeout)` | `List` command. | +| `csm_client_list_api(c, module, &out_text, timeout)` | `List API `. | +| `csm_client_list_states(c, module, &out_text, timeout)` | `List State `. | +| `csm_client_help(c, module, &out_text, timeout)` | `Help `. | + +Free `*out_text` with `csm_string_free()`. + +### Subscriptions + +| Function | Description | +|---|---| +| `csm_client_subscribe_status(c, status, module, cb, ud, timeout)` | Subscribe; callback invoked from receive thread. | +| `csm_client_unsubscribe_status(c, status, module, timeout)` | Cancel a subscription. | +| `csm_client_register_async_callback(c, cmd, cb, ud)` | Register callback for `ASYNC_RESP` packets. | +| `csm_client_unregister_async_callback(c, cmd)` | Remove a callback. | + +### Polling queues (alternative to callbacks) + +| Function | Description | +|---|---| +| `csm_client_poll_status(c, &out_notif, timeout)` | Block until next STATUS / INTERRUPT. | +| `csm_client_poll_async_response(c, &out_resp, timeout)` | Block until next `ASYNC_RESP`. | + +--- + +## Result codes + +All public functions return a `csm_result_t`: + +| Code | Meaning | +|-----------------------|-------------------------------------------------| +| `CSM_OK` | Operation succeeded. | +| `CSM_ERR_INVALID` | Invalid argument or NULL pointer. | +| `CSM_ERR_CONNECTION` | Connection failed or was lost. | +| `CSM_ERR_TIMEOUT` | Operation exceeded its timeout. | +| `CSM_ERR_PROTOCOL` | Invalid / malformed protocol frame. | +| `CSM_ERR_SERVER` | Server returned an `ERROR` packet (see below). | +| `CSM_ERR_NOMEM` | Memory allocation failure. | +| `CSM_ERR_STATE` | Operation invalid in the current state. | +| `CSM_ERR_IO` | Underlying socket / OS I/O error. | + +After a `CSM_ERR_SERVER`, retrieve the error code/message via: + +```c +csm_server_error_t err; +csm_client_last_server_error(c, &err); +fprintf(stderr, "[%s] %s\n", err.code, err.message); +``` + +`csm_result_str(code)` returns a static, human-readable string. + +--- + +## Tests + +The test suite (`SDK/c/tests/`) uses a tiny in-process harness and an +embedded `MockServer` (see `tests/mock_server.h`) that emulates the +LabVIEW CSM-TCP-Router on `127.0.0.1`. The same tests run on Linux, +macOS, and Windows. + +```bash +cmake --build build -j +ctest --test-dir build --output-on-failure +``` + +Or run the executable directly to see per-test progress: + +```bash +./build/csm_tcp_router_client_tests +``` + +--- + +## License + +[MIT](LICENSE) — © NEVSTOP-LAB diff --git a/SDK/c/README.zh-cn.md b/SDK/c/README.zh-cn.md new file mode 100644 index 0000000..2d2e02b --- /dev/null +++ b/SDK/c/README.zh-cn.md @@ -0,0 +1,250 @@ +# csm-tcp-router-client (C SDK) + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/C_SDK.yml/badge.svg)](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/actions/workflows/C_SDK.yml) + +[CSM-TCP-Router](https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App) LabVIEW 服务端的 C 语言客户端 SDK。 + +CSM-TCP-Router 通过 TCP 暴露一个基于 LabVIEW [Communicable State Machine (CSM)](https://github.com/NEVSTOP-LAB/Communicable-State-Machine) 框架的应用程序,使任何 TCP 客户端 —— 包括原生 C/C++ 程序、嵌入式设备、测试夹具或 CI 流水线 —— 都可以发送命令并接收响应,无需修改任何 LabVIEW 代码。 + +> 📖 [English README.md](README.md) + +--- + +## 特性 + +- **同步命令** (`-@`) – `csm_client_send_and_wait()` 阻塞直到服务器返回响应。 +- **异步命令** (`->`) – `csm_client_post()` 等待 `cmd-resp` 握手;最终响应通过回调或轮询队列送达。 +- **无回复命令** (`->|`) – `csm_client_post_no_reply()` 等待 `cmd-resp` 握手;不再有后续响应。 +- **状态订阅** – `csm_client_subscribe_status()` / `csm_client_unsubscribe_status()`,可选回调或轮询队列。 +- **路由器管理辅助函数** – `csm_client_list_modules()`、`csm_client_list_api()`、`csm_client_list_states()`、`csm_client_help()`。 +- **连接工具** – 应用启动期间使用 `csm_client_wait_for_server()` 进行轮询。 +- **线程安全的客户端** – 所有公开函数均可由多线程并发调用。 +- **多平台移植** – Windows(Winsock2 + Win32 线程)和 POSIX(BSD sockets + pthreads);单源文件实现。 +- **无运行时依赖** – 仅依赖 C99 标准库与操作系统的 sockets/线程 API。 + +--- + +## 目录结构 + +``` +SDK/c/ +├── include/ +│ └── csm_tcp_router_client.h # 公开 API 头文件 +├── src/ +│ └── csm_tcp_router_client.c # 跨平台实现 +├── examples/ +│ ├── basic_usage.c # 对应 examples/basic_usage.py +│ └── subscribe_status.c # 对应 examples/subscribe_status.py +├── tests/ +│ ├── test_harness.h # 进程内极简测试框架 +│ ├── mock_server.[ch] # 进程内 MockServer 测试夹具 +│ ├── test_protocol.c # 协议编解码单元测试 +│ ├── test_client.c # 客户端生命周期单元测试 +│ ├── test_integration.c # 通过 MockServer 的端到端测试 +│ └── test_main.c # 测试运行器/TESTS 表 +├── vs2026/ +│ ├── csm_tcp_router_client.sln +│ ├── csm_tcp_router_client/ # 静态库工程 +│ └── csm_tcp_router_client.tests/ # 测试可执行工程 +├── CMakeLists.txt # 跨平台 CMake 构建 +├── CHANGELOG.md +├── LICENSE +├── README.md +└── README.zh-cn.md +``` + +整体结构与 `SDK/python/` 下的 Python SDK 保持一致。 + +--- + +## 编译 + +### CMake(Linux / macOS / Windows) + +```bash +cd SDK/c +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j +ctest --test-dir build --output-on-failure -C Release +``` + +CMake 选项: + +| 选项 | 默认值 | 说明 | +|-------------------------|---------|-----------------------------------| +| `CSM_BUILD_TESTS` | `ON` | 是否构建测试可执行文件。 | +| `CSM_BUILD_EXAMPLES` | `ON` | 是否构建示例程序。 | +| `CSM_BUILD_SHARED` | `OFF` | 是否编译为动态库(DLL/.so)。 | + +### Visual Studio 2026 + +打开 `SDK/c/vs2026/csm_tcp_router_client.sln`,使用 Visual Studio 2026 直接构建(Ctrl+Shift+B)。该解决方案提供 Debug/Release × Win32/x64 四种配置,平台工具集为 `v144`,包含两个工程: + +- `csm_tcp_router_client` – 静态库 +- `csm_tcp_router_client.tests` – 控制台测试可执行文件(直接运行即可执行所有单元 + 集成测试,退出码为 0 即所有测试通过)。 + +详见 [`vs2026/README.md`](vs2026/README.md)。 + +--- + +## 快速开始 + +```c +#include "csm_tcp_router_client.h" +#include + +int main(void) { + csm_client_t *c = csm_client_create(); + if (csm_client_connect(c, "localhost", 30007, 5000) != CSM_OK) { + fprintf(stderr, "连接失败\n"); + csm_client_destroy(c); + return 1; + } + + char *modules = NULL; + if (csm_client_list_modules(c, &modules, 5000) == CSM_OK) { + printf("已加载模块:\n%s\n", modules); + csm_string_free(modules); + } + + csm_command_response_t resp = {0}; + if (csm_client_send_and_wait(c, "API: Read -@ DAQmx", 5000, &resp) == CSM_OK) { + printf("响应: %s\n", (char *)resp.raw); + } + csm_command_response_dispose(&resp); + + double ms = 0; + if (csm_client_ping(c, 2000, &ms) == CSM_OK) { + printf("Ping 延迟: %.1f ms\n", ms); + } + + csm_client_disconnect(c); + csm_client_destroy(c); + return 0; +} +``` + +--- + +## 协议 + +SDK 实现 CSM-TCP-Router **协议 v0**: + +``` +| 数据长度 (4B) | 版本 (1B) | TYPE (1B) | FLAG1 (1B) | FLAG2 (1B) | 文本数据 | +╰────────────────────────── 包头 (8B) ─────────────────────────────╯ +``` + +| TYPE 字节 | 常量 | 方向 | 说明 | +|-----------|---------------------|----------------|----------------------------------------------------| +| `0x00` | `CSM_PT_INFO` | 服务器 → 客户端 | 欢迎/告别等信息消息 | +| `0x01` | `CSM_PT_ERROR` | 服务器 → 客户端 | CSM 错误:`[Error: ] ` | +| `0x02` | `CSM_PT_CMD` | 客户端 → 服务器 | 命令字符串 | +| `0x03` | `CSM_PT_CMD_RESP` | 服务器 → 客户端 | 异步/订阅命令的握手 ACK | +| `0x04` | `CSM_PT_RESP` | 服务器 → 客户端 | 同步响应负载 | +| `0x05` | `CSM_PT_ASYNC_RESP` | 服务器 → 客户端 | 异步响应:` <- ` | +| `0x06` | `CSM_PT_STATUS` | 服务器 → 客户端 | 状态广播:` >> <- ` | +| `0x07` | `CSM_PT_INTERRUPT` | 服务器 → 客户端 | 中断广播(与 STATUS 格式相同) | + +--- + +## API 速览 + +### 生命周期 + +| 函数 | 说明 | +|---|---| +| `csm_client_create()` | 分配新的客户端。 | +| `csm_client_destroy(c)` | 如已连接则断开并释放资源。 | +| `csm_client_connect(c, host, port, timeout_ms)` | 建立 TCP 连接并启动接收线程。 | +| `csm_client_disconnect(c)` | 关闭连接;未连接时调用也安全。 | +| `csm_client_is_connected(c)` | 已连接时返回非零值。 | +| `csm_client_wait_for_server(host, port, timeout_ms, retry_ms)` | 轮询直到服务器可达。 | + +### 命令 + +| 函数 | 说明 | +|---|---| +| `csm_client_send_and_wait(c, cmd, timeout, &resp)` | 同步命令 (`-@`)。 | +| `csm_client_post(c, cmd, timeout)` | 异步命令 (`->`)。 | +| `csm_client_post_no_reply(c, cmd, timeout)` | 无回复异步命令 (`->|`)。 | +| `csm_client_ping(c, timeout, &elapsed_ms)` | 往返延迟检测。 | + +### 路由器管理辅助函数 + +| 函数 | 说明 | +|---|---| +| `csm_client_list_modules(c, &out_text, timeout)` | `List` 命令。 | +| `csm_client_list_api(c, module, &out_text, timeout)` | `List API `。 | +| `csm_client_list_states(c, module, &out_text, timeout)` | `List State `。 | +| `csm_client_help(c, module, &out_text, timeout)` | `Help `。 | + +请使用 `csm_string_free()` 释放 `*out_text`。 + +### 订阅 + +| 函数 | 说明 | +|---|---| +| `csm_client_subscribe_status(c, status, module, cb, ud, timeout)` | 订阅;回调由接收线程调用。 | +| `csm_client_unsubscribe_status(c, status, module, timeout)` | 取消订阅。 | +| `csm_client_register_async_callback(c, cmd, cb, ud)` | 为 `ASYNC_RESP` 包注册回调。 | +| `csm_client_unregister_async_callback(c, cmd)` | 移除回调。 | + +### 轮询队列(回调的替代方式) + +| 函数 | 说明 | +|---|---| +| `csm_client_poll_status(c, &out_notif, timeout)` | 阻塞直到下一条 STATUS / INTERRUPT。 | +| `csm_client_poll_async_response(c, &out_resp, timeout)` | 阻塞直到下一条 `ASYNC_RESP`。 | + +--- + +## 返回值 + +所有公开函数均返回 `csm_result_t`: + +| 代码 | 含义 | +|-----------------------|-------------------------------------------------| +| `CSM_OK` | 操作成功。 | +| `CSM_ERR_INVALID` | 参数无效或为 NULL。 | +| `CSM_ERR_CONNECTION` | 连接失败或连接丢失。 | +| `CSM_ERR_TIMEOUT` | 操作超时。 | +| `CSM_ERR_PROTOCOL` | 协议帧无效或损坏。 | +| `CSM_ERR_SERVER` | 服务器返回了 `ERROR` 数据包(见下文)。 | +| `CSM_ERR_NOMEM` | 内存分配失败。 | +| `CSM_ERR_STATE` | 当前状态下操作无效。 | +| `CSM_ERR_IO` | 底层 socket / 操作系统 I/O 错误。 | + +收到 `CSM_ERR_SERVER` 后,可通过以下方式获取错误码与消息: + +```c +csm_server_error_t err; +csm_client_last_server_error(c, &err); +fprintf(stderr, "[%s] %s\n", err.code, err.message); +``` + +`csm_result_str(code)` 返回静态可读字符串。 + +--- + +## 测试 + +测试套件(`SDK/c/tests/`)使用进程内极简测试框架与内嵌的 `MockServer`(详见 `tests/mock_server.h`),后者在 `127.0.0.1` 上模拟 LabVIEW CSM-TCP-Router。同一套测试在 Linux、macOS 与 Windows 上均可运行。 + +```bash +cmake --build build -j +ctest --test-dir build --output-on-failure +``` + +也可直接运行可执行文件以查看每条测试的进度: + +```bash +./build/csm_tcp_router_client_tests +``` + +--- + +## 许可证 + +[MIT](LICENSE) — © NEVSTOP-LAB diff --git a/SDK/c/examples/basic_usage.c b/SDK/c/examples/basic_usage.c new file mode 100644 index 0000000..c16fe98 --- /dev/null +++ b/SDK/c/examples/basic_usage.c @@ -0,0 +1,61 @@ +/* basic_usage.c - Demonstrates connecting, pinging, listing modules, and + * sending a synchronous command. Mirrors examples/basic_usage.py. */ +#include "csm_tcp_router_client.h" + +#include +#include + +#define HOST "localhost" +#define PORT 30007 + +int main(void) { + /* 1. Wait until the server is ready (optional). */ + printf("Waiting for server ... "); + fflush(stdout); + csm_result_t r = csm_client_wait_for_server(HOST, PORT, 30000, 500); + if (r != CSM_OK) { + printf("TIMEOUT - server did not start within 30s.\n"); + return 1; + } + printf("ready.\n"); + + /* 2. Create + connect. */ + csm_client_t *c = csm_client_create(); + if (!c) { fprintf(stderr, "Out of memory\n"); return 1; } + + r = csm_client_connect(c, HOST, PORT, 5000); + if (r != CSM_OK) { + fprintf(stderr, "Connection failed: %s\n", csm_result_str(r)); + csm_client_destroy(c); + return 1; + } + printf("Connected to %s:%d\n", HOST, PORT); + + /* 3. Ping. */ + double ms = 0; + r = csm_client_ping(c, 2000, &ms); + if (r == CSM_OK) printf("Ping OK latency=%.1f ms\n", ms); + else printf("Ping failed: %s\n", csm_result_str(r)); + + /* 4. List CSM modules. */ + char *modules = NULL; + if (csm_client_list_modules(c, &modules, 5000) == CSM_OK) { + printf("\nLoaded modules:\n%s\n", modules); + csm_string_free(modules); + } + + /* 5. Send a synchronous command (uncomment when wired to a real module). + * + * csm_command_response_t resp = {0}; + * if (csm_client_send_and_wait(c, "API: Read -@ DAQmx", 5000, &resp) == CSM_OK) { + * printf("Sync response: %s\n", (char *)resp.raw); + * } + * csm_command_response_dispose(&resp); + */ + + /* 6. Disconnect & clean up. */ + csm_client_disconnect(c); + csm_client_destroy(c); + printf("Disconnected.\n"); + return 0; +} diff --git a/SDK/c/examples/subscribe_status.c b/SDK/c/examples/subscribe_status.c new file mode 100644 index 0000000..9fd0df1 --- /dev/null +++ b/SDK/c/examples/subscribe_status.c @@ -0,0 +1,65 @@ +/* subscribe_status.c - Demonstrates real-time status subscription with a + * callback, mirroring examples/subscribe_status.py. */ + +#if !defined(_WIN32) +# ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 200809L +# endif +#endif + +#include "csm_tcp_router_client.h" + +#include +#include + +#if defined(_WIN32) +# include +static void sleep_ms(unsigned int ms){ Sleep(ms); } +#else +# include +static void sleep_ms(unsigned int ms){ + struct timespec ts; ts.tv_sec=ms/1000; ts.tv_nsec=(long)(ms%1000)*1000000L; + nanosleep(&ts, NULL); +} +#endif + +#define HOST "localhost" +#define PORT 30007 + +static void on_status(const csm_status_notification_t *n, void *ud) { + (void)ud; + printf("[%s @ %s] %s\n", n->status_name, n->module_name, n->data); +} + +int main(int argc, char **argv) { + const char *status_name = (argc > 1) ? argv[1] : "Status"; + const char *module_name = (argc > 2) ? argv[2] : "DAQmx"; + + csm_client_t *c = csm_client_create(); + if (!c) return 1; + + csm_result_t r = csm_client_connect(c, HOST, PORT, 5000); + if (r != CSM_OK) { + fprintf(stderr, "Connection failed: %s\n", csm_result_str(r)); + csm_client_destroy(c); + return 1; + } + + r = csm_client_subscribe_status(c, status_name, module_name, + on_status, NULL, 5000); + if (r != CSM_OK) { + fprintf(stderr, "Subscribe failed: %s\n", csm_result_str(r)); + csm_client_disconnect(c); + csm_client_destroy(c); + return 1; + } + + printf("Subscribed to %s@%s. Listening for 30s ...\n", + status_name, module_name); + sleep_ms(30000); + + csm_client_unsubscribe_status(c, status_name, module_name, 5000); + csm_client_disconnect(c); + csm_client_destroy(c); + return 0; +} diff --git a/SDK/c/include/csm_tcp_router_client.h b/SDK/c/include/csm_tcp_router_client.h new file mode 100644 index 0000000..500cdac --- /dev/null +++ b/SDK/c/include/csm_tcp_router_client.h @@ -0,0 +1,401 @@ +/* csm_tcp_router_client.h - Single-header public C API for the CSM-TCP-Router + * client SDK. + * + * This SDK is the C counterpart of the Python `csm_tcp_router_client` module. + * It implements the CSM-TCP-Router protocol v0 over TCP and exposes a + * thread-safe synchronous client (`csm_client_t`) with both blocking calls + * and asynchronous callback / polling-queue delivery. + * + * Wire format (8-byte header, big-endian): + * + * | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | Payload | + * +---------------------------- Header (8B) ----------------------------+ + * + * Quickstart: + * + * csm_client_t *c = csm_client_create(); + * if (csm_client_connect(c, "localhost", 30007, 5000) == CSM_OK) { + * char *modules = NULL; + * if (csm_client_list_modules(c, &modules, 5000) == CSM_OK) { + * printf("%s\n", modules); + * csm_string_free(modules); + * } + * csm_client_disconnect(c); + * } + * csm_client_destroy(c); + * + * The library is portable across Windows (Winsock2 + Win32 threads) and + * POSIX systems (BSD sockets + pthreads), and is built as either a static + * or shared library by the bundled CMake / Visual Studio projects. + */ +#ifndef CSM_TCP_ROUTER_CLIENT_H +#define CSM_TCP_ROUTER_CLIENT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------------------------------------------------------------- */ +/* Versioning and DLL export */ +/* ------------------------------------------------------------------------- */ + +#define CSM_VERSION_MAJOR 0 +#define CSM_VERSION_MINOR 1 +#define CSM_VERSION_PATCH 0 +#define CSM_VERSION_STRING "0.1.0" + +#if defined(_WIN32) && defined(CSM_BUILD_SHARED) +# ifdef CSM_BUILD_LIBRARY +# define CSM_API __declspec(dllexport) +# else +# define CSM_API __declspec(dllimport) +# endif +#else +# define CSM_API +#endif + +/* ------------------------------------------------------------------------- */ +/* Return codes */ +/* ------------------------------------------------------------------------- */ + +/** Result codes returned by every public SDK function. */ +typedef enum csm_result { + CSM_OK = 0, /**< Operation succeeded. */ + CSM_ERR_INVALID = -1, /**< Invalid argument or NULL pointer. */ + CSM_ERR_CONNECTION = -2, /**< Connection failed or was lost. */ + CSM_ERR_TIMEOUT = -3, /**< Operation exceeded its timeout. */ + CSM_ERR_PROTOCOL = -4, /**< Invalid / malformed protocol frame. */ + CSM_ERR_SERVER = -5, /**< Server returned an ERROR packet. */ + CSM_ERR_NOMEM = -6, /**< Memory allocation failure. */ + CSM_ERR_STATE = -7, /**< Operation invalid in current state. */ + CSM_ERR_IO = -8 /**< Underlying socket / OS I/O error. */ +} csm_result_t; + +/** Return a static, human-readable string for *code*. */ +CSM_API const char *csm_result_str(csm_result_t code); + +/* ------------------------------------------------------------------------- */ +/* Protocol constants */ +/* ------------------------------------------------------------------------- */ + +/** Packet type byte values (CSM-TCP-Router protocol v0). */ +typedef enum csm_packet_type { + CSM_PT_INFO = 0x00, /**< Welcome / goodbye informational text. */ + CSM_PT_ERROR = 0x01, /**< Server error: "[Error: ] " */ + CSM_PT_CMD = 0x02, /**< Command packet (client -> server). */ + CSM_PT_CMD_RESP = 0x03, /**< Async / subscribe handshake ACK. */ + CSM_PT_RESP = 0x04, /**< Synchronous response payload. */ + CSM_PT_ASYNC_RESP = 0x05, /**< Async response: " <- ". */ + CSM_PT_STATUS = 0x06, /**< Status broadcast. */ + CSM_PT_INTERRUPT = 0x07 /**< Interrupt broadcast. */ +} csm_packet_type_t; + +/** Number of bytes in the fixed wire-format header. */ +#define CSM_HEADER_SIZE 8 + +/** Protocol version byte sent in every outgoing packet. */ +#define CSM_PROTOCOL_VERSION 0x01 + +/* ------------------------------------------------------------------------- */ +/* Public data models */ +/* ------------------------------------------------------------------------- */ + +/** A decoded packet (header fields + heap-allocated body). */ +typedef struct csm_packet { + csm_packet_type_t type; + uint8_t version; + uint8_t flag1; + uint8_t flag2; + uint8_t *data; /**< Owned payload buffer (or NULL). */ + size_t data_len; /**< Length of `data` in bytes. */ +} csm_packet_t; + +/** A successful synchronous response. */ +typedef struct csm_command_response { + uint8_t *raw; /**< NUL-terminated UTF-8 payload (owned). */ + size_t raw_len; /**< Length of `raw` in bytes (excluding NUL). */ +} csm_command_response_t; + +/** An ASYNC_RESP packet: payload + the original command echoed by the server. + * + * Server format: ``" <- "``. */ +typedef struct csm_async_response { + char *raw; /**< Response payload (owned, NUL-terminated). */ + size_t raw_len; + char *original_command; /**< Echoed command text (owned). */ +} csm_async_response_t; + +/** A STATUS or INTERRUPT broadcast. + * + * Server format: ``" >> <- "``. */ +typedef struct csm_status_notification { + csm_packet_type_t packet_type; /**< CSM_PT_STATUS or CSM_PT_INTERRUPT */ + char *raw; /**< Full payload (owned). */ + size_t raw_len; + char *status_name; /**< Owned, NUL-terminated. */ + char *data; /**< Owned, NUL-terminated. */ + char *module_name; /**< Owned, NUL-terminated. */ +} csm_status_notification_t; + +/** Free a string previously returned via an out-parameter (e.g. by + * `csm_client_list_modules`). Safe to call with NULL. */ +CSM_API void csm_string_free(char *s); + +/** Free heap members of a `csm_command_response_t` (does not free the struct). */ +CSM_API void csm_command_response_dispose(csm_command_response_t *resp); + +/** Free heap members of a `csm_async_response_t` (does not free the struct). */ +CSM_API void csm_async_response_dispose(csm_async_response_t *resp); + +/** Free heap members of a `csm_status_notification_t` (does not free struct). */ +CSM_API void csm_status_notification_dispose(csm_status_notification_t *n); + +/** Free heap members of a `csm_packet_t` (does not free the struct). */ +CSM_API void csm_packet_dispose(csm_packet_t *pkt); + +/* ------------------------------------------------------------------------- */ +/* Server error info */ +/* ------------------------------------------------------------------------- */ + +/** Information about the most recent CSM_ERR_SERVER returned by a function. */ +typedef struct csm_server_error { + char code[32]; /**< NUL-terminated CSM error code (may be empty). */ + char message[256]; /**< NUL-terminated error message (truncated). */ +} csm_server_error_t; + +/* ------------------------------------------------------------------------- */ +/* Protocol codec (exposed for advanced use / testing) */ +/* ------------------------------------------------------------------------- */ + +/** Encode *data* (`data_len` bytes) into a complete wire-format packet. + * + * The caller must pass `out_buf` with at least `CSM_HEADER_SIZE + data_len` + * bytes. On success, `*out_len` is set to the number of bytes written. + */ +CSM_API csm_result_t csm_encode_packet(const void *data, + size_t data_len, + csm_packet_type_t type, + uint8_t flag1, + uint8_t flag2, + uint8_t *out_buf, + size_t out_buf_size, + size_t *out_len); + +/** Decode an 8-byte header into its constituent fields. */ +CSM_API csm_result_t csm_decode_header(const uint8_t *header_bytes, + size_t header_len, + uint32_t *out_data_len, + uint8_t *out_version, + uint8_t *out_type, + uint8_t *out_flag1, + uint8_t *out_flag2); + +/** Build a `csm_packet_t` from raw header + body. The returned packet + * **owns** a copy of the body; release it with `csm_packet_dispose`. + * + * Unknown packet type bytes are mapped to `CSM_PT_INFO` for forward + * compatibility (the server may introduce new types in future revisions). + */ +CSM_API csm_result_t csm_parse_packet(const uint8_t *header_bytes, + size_t header_len, + const uint8_t *body, + size_t body_len, + csm_packet_t *out_packet); + +/* ------------------------------------------------------------------------- */ +/* Callback signatures */ +/* ------------------------------------------------------------------------- */ + +/** Status/interrupt notification callback. + * + * Invoked from the receive thread. Must be fast and non-blocking. The + * `notif` pointer and its members are valid only for the duration of the + * call; copy any data you need before returning. + */ +typedef void (*csm_status_callback_fn)(const csm_status_notification_t *notif, + void *user_data); + +/** Async response callback. Same threading rules as `csm_status_callback_fn`. */ +typedef void (*csm_async_callback_fn)(const csm_async_response_t *resp, + void *user_data); + +/* ------------------------------------------------------------------------- */ +/* Client lifecycle */ +/* ------------------------------------------------------------------------- */ + +/** Opaque thread-safe client handle. */ +typedef struct csm_client csm_client_t; + +/** Create a new client instance. Returns NULL on allocation failure. */ +CSM_API csm_client_t *csm_client_create(void); + +/** Disconnect (if connected) and free all resources held by *client*. */ +CSM_API void csm_client_destroy(csm_client_t *client); + +/** Open a TCP connection and start the background receive thread. + * + * @param connect_timeout_ms Connect timeout in milliseconds. + * @return CSM_OK or CSM_ERR_CONNECTION / CSM_ERR_STATE / CSM_ERR_INVALID. + */ +CSM_API csm_result_t csm_client_connect(csm_client_t *client, + const char *host, + uint16_t port, + unsigned int connect_timeout_ms); + +/** Close the connection and stop the receive thread. Safe to call when not + * connected; any blocked callers receive CSM_ERR_CONNECTION immediately. */ +CSM_API csm_result_t csm_client_disconnect(csm_client_t *client); + +/** Return non-zero while the underlying socket is open. */ +CSM_API int csm_client_is_connected(const csm_client_t *client); + +/** Poll until *host*:*port* accepts a connection or *timeout_ms* elapses. + * + * @return CSM_OK when the server is reachable; CSM_ERR_TIMEOUT otherwise. */ +CSM_API csm_result_t csm_client_wait_for_server(const char *host, + uint16_t port, + unsigned int timeout_ms, + unsigned int retry_interval_ms); + +/* ------------------------------------------------------------------------- */ +/* Core command methods */ +/* ------------------------------------------------------------------------- */ + +/** Send a synchronous command and block until the response arrives. + * + * On CSM_OK the caller owns `*out_resp` and must release it via + * `csm_command_response_dispose`. */ +CSM_API csm_result_t csm_client_send_and_wait(csm_client_t *client, + const char *command, + unsigned int timeout_ms, + csm_command_response_t *out_resp); + +/** Send an asynchronous command (`->` suffix) and block until the + * `cmd-resp` handshake arrives. The eventual `async-resp` is delivered to + * any callback registered via `csm_client_register_async_callback` and to + * the polling queue (`csm_client_poll_async_response`). */ +CSM_API csm_result_t csm_client_post(csm_client_t *client, + const char *command, + unsigned int timeout_ms); + +/** Send an async no-reply command (`->|` suffix) and block until the + * `cmd-resp` handshake arrives. */ +CSM_API csm_result_t csm_client_post_no_reply(csm_client_t *client, + const char *command, + unsigned int timeout_ms); + +/** Send a `Ping` and measure round-trip latency. + * + * @param out_elapsed_ms Set to round-trip time in milliseconds on success. + * @return CSM_OK or one of the regular error codes. + */ +CSM_API csm_result_t csm_client_ping(csm_client_t *client, + unsigned int timeout_ms, + double *out_elapsed_ms); + +/* ------------------------------------------------------------------------- */ +/* Router management helpers */ +/* ------------------------------------------------------------------------- */ + +/** Run `List` and return the response text. Caller frees `*out_text` via + * `csm_string_free`. */ +CSM_API csm_result_t csm_client_list_modules(csm_client_t *client, + char **out_text, + unsigned int timeout_ms); + +/** Run `List API `. Caller frees `*out_text`. */ +CSM_API csm_result_t csm_client_list_api(csm_client_t *client, + const char *module, + char **out_text, + unsigned int timeout_ms); + +/** Run `List State `. Caller frees `*out_text`. */ +CSM_API csm_result_t csm_client_list_states(csm_client_t *client, + const char *module, + char **out_text, + unsigned int timeout_ms); + +/** Run `Help `. Caller frees `*out_text`. */ +CSM_API csm_result_t csm_client_help(csm_client_t *client, + const char *module, + char **out_text, + unsigned int timeout_ms); + +/* ------------------------------------------------------------------------- */ +/* Status / interrupt subscriptions */ +/* ------------------------------------------------------------------------- */ + +/** Subscribe to a CSM module's status broadcast. + * + * Sends ``"@ ->"`` and blocks until the + * `cmd-resp` handshake arrives. `callback` (if non-NULL) is invoked from the + * receive thread for each notification; notifications are also enqueued for + * polling via `csm_client_poll_status`. + */ +CSM_API csm_result_t csm_client_subscribe_status(csm_client_t *client, + const char *status_name, + const char *module_name, + csm_status_callback_fn callback, + void *user_data, + unsigned int timeout_ms); + +/** Cancel a status subscription. */ +CSM_API csm_result_t csm_client_unsubscribe_status(csm_client_t *client, + const char *status_name, + const char *module_name, + unsigned int timeout_ms); + +/** Register a callback for `async-resp` packets matching *original_command*. */ +CSM_API csm_result_t csm_client_register_async_callback(csm_client_t *client, + const char *original_command, + csm_async_callback_fn callback, + void *user_data); + +/** Remove a previously registered async callback. */ +CSM_API csm_result_t csm_client_unregister_async_callback(csm_client_t *client, + const char *original_command); + +/* ------------------------------------------------------------------------- */ +/* Polling queues (alternative to callbacks) */ +/* ------------------------------------------------------------------------- */ + +/** Pop the next status/interrupt notification from the polling queue. + * + * @param timeout_ms 0 = non-blocking; >0 = block up to N ms. + * @return CSM_OK with `*out_notif` populated (caller disposes via + * `csm_status_notification_dispose`); CSM_ERR_TIMEOUT if the queue + * is empty within the timeout; CSM_ERR_CONNECTION if disconnected. + */ +CSM_API csm_result_t csm_client_poll_status(csm_client_t *client, + csm_status_notification_t *out_notif, + unsigned int timeout_ms); + +/** Pop the next async response from the polling queue. */ +CSM_API csm_result_t csm_client_poll_async_response(csm_client_t *client, + csm_async_response_t *out_resp, + unsigned int timeout_ms); + +/* ------------------------------------------------------------------------- */ +/* Last server error */ +/* ------------------------------------------------------------------------- */ + +/** Retrieve information about the last CSM_ERR_SERVER observed by *client*. + * + * Returns CSM_OK and fills *out_err* with the most recently captured + * server-error code/message; otherwise (no server error has ever been + * observed for this client) returns CSM_ERR_STATE. The stored error is + * kept indefinitely until the next CSM_ERR_SERVER overwrites it, so it + * is safe to call this immediately after a failing operation without + * worrying about it being cleared by an unrelated success in between. + */ +CSM_API csm_result_t csm_client_last_server_error(const csm_client_t *client, + csm_server_error_t *out_err); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* CSM_TCP_ROUTER_CLIENT_H */ diff --git a/SDK/c/src/csm_tcp_router_client.c b/SDK/c/src/csm_tcp_router_client.c new file mode 100644 index 0000000..f7c35d7 --- /dev/null +++ b/SDK/c/src/csm_tcp_router_client.c @@ -0,0 +1,1631 @@ +/* csm_tcp_router_client.c - Cross-platform implementation of the + * CSM-TCP-Router C client SDK. + * + * Threading: the receive loop runs on a single background thread. All + * public functions are safe to call from any thread; the client serialises + * concurrent waiters for synchronous (RESP) and command-handshake + * (CMD_RESP) responses respectively, mirroring the Python SDK. + * + * Sockets / threads abstraction: + * - Windows: Winsock2 + Win32 CRITICAL_SECTION / CONDITION_VARIABLE / threads. + * - POSIX: BSD sockets + pthreads. + */ + +#if !defined(_WIN32) +# ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 200809L +# endif +# ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE 1 +# endif +#endif + +#include "csm_tcp_router_client.h" + +#include +#include +#include +#include +#include +#include +#include + +/* CSM_BUILD_LIBRARY is defined by the build system (CMake / MSBuild) when + * compiling the library, so that csm_tcp_router_client.h decorates the + * exported symbols with the correct __declspec for shared builds. + * Defining it unconditionally here would break consumers that compile this + * .c file directly into their own DLL with a different export contract. */ + +/* ========================================================================= */ +/* Platform abstraction */ +/* ========================================================================= */ + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +# pragma comment(lib, "Ws2_32.lib") + +typedef SOCKET csm_socket_t; +# define CSM_INVALID_SOCKET INVALID_SOCKET +# define csm_close_socket(s) closesocket(s) +# define csm_socket_errno() WSAGetLastError() + +typedef CRITICAL_SECTION csm_mutex_t; +typedef CONDITION_VARIABLE csm_cond_t; +typedef HANDLE csm_thread_t; + +static void csm_mutex_init(csm_mutex_t *m) { InitializeCriticalSection(m); } +static void csm_mutex_destroy(csm_mutex_t *m) { DeleteCriticalSection(m); } +static void csm_mutex_lock(csm_mutex_t *m) { EnterCriticalSection(m); } +static void csm_mutex_unlock(csm_mutex_t *m) { LeaveCriticalSection(m); } + +static void csm_cond_init(csm_cond_t *c) { InitializeConditionVariable(c); } +static void csm_cond_destroy(csm_cond_t *c) { (void)c; } +static void csm_cond_signal(csm_cond_t *c) { WakeConditionVariable(c); } +#if 0 /* reserved for future broadcast use */ +static void csm_cond_broadcast(csm_cond_t *c) { WakeAllConditionVariable(c); } +#endif + +/* Returns 1 on signal, 0 on timeout. */ +static int csm_cond_wait_ms(csm_cond_t *c, csm_mutex_t *m, unsigned int ms) { + BOOL ok = SleepConditionVariableCS(c, m, ms == 0 ? INFINITE : ms); + if (ok) return 1; + return 0; +} + +static void csm_sleep_ms(unsigned int ms) { Sleep(ms); } + +static double csm_monotonic_ms(void) { + LARGE_INTEGER freq, now; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&now); + return (double)now.QuadPart * 1000.0 / (double)freq.QuadPart; +} + +#else /* POSIX */ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +typedef int csm_socket_t; +# define CSM_INVALID_SOCKET (-1) +# define csm_close_socket(s) close(s) +# define csm_socket_errno() errno + +typedef pthread_mutex_t csm_mutex_t; +typedef pthread_cond_t csm_cond_t; +typedef pthread_t csm_thread_t; + +static void csm_mutex_init(csm_mutex_t *m) { pthread_mutex_init(m, NULL); } +static void csm_mutex_destroy(csm_mutex_t *m) { pthread_mutex_destroy(m); } +static void csm_mutex_lock(csm_mutex_t *m) { pthread_mutex_lock(m); } +static void csm_mutex_unlock(csm_mutex_t *m) { pthread_mutex_unlock(m); } + +static void csm_cond_init(csm_cond_t *c) { pthread_cond_init(c, NULL); } +static void csm_cond_destroy(csm_cond_t *c) { pthread_cond_destroy(c); } +static void csm_cond_signal(csm_cond_t *c) { pthread_cond_signal(c); } +#if 0 /* reserved for future broadcast use */ +static void csm_cond_broadcast(csm_cond_t *c) { pthread_cond_broadcast(c); } +#endif + +static int csm_cond_wait_ms(csm_cond_t *c, csm_mutex_t *m, unsigned int ms) { + if (ms == 0) { + pthread_cond_wait(c, m); + return 1; + } + struct timespec ts; +# if defined(CLOCK_REALTIME) + clock_gettime(CLOCK_REALTIME, &ts); +# else + struct timeval tv; gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; +# endif + ts.tv_sec += ms / 1000; + ts.tv_nsec += (long)(ms % 1000) * 1000000L; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec += ts.tv_nsec / 1000000000L; + ts.tv_nsec = ts.tv_nsec % 1000000000L; + } + int rc = pthread_cond_timedwait(c, m, &ts); + return rc == 0 ? 1 : 0; +} + +static void csm_sleep_ms(unsigned int ms) { + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (long)(ms % 1000) * 1000000L; + nanosleep(&ts, NULL); +} + +static double csm_monotonic_ms(void) { + struct timespec ts; +# if defined(CLOCK_MONOTONIC) + clock_gettime(CLOCK_MONOTONIC, &ts); +# else + struct timeval tv; gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; +# endif + return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1.0e6; +} +#endif + +/* ========================================================================= */ +/* WSA bootstrap (Windows only) - reference-counted */ +/* ========================================================================= */ + +#if defined(_WIN32) +static csm_mutex_t g_wsa_lock; +static INIT_ONCE g_wsa_lock_init_once_state = INIT_ONCE_STATIC_INIT; +static int g_wsa_refcount = 0; + +static BOOL CALLBACK csm_wsa_lock_init_once_cb(PINIT_ONCE init_once, + PVOID param, + PVOID *context) { + (void)init_once; (void)param; (void)context; + csm_mutex_init(&g_wsa_lock); + return TRUE; +} + +static void csm_wsa_lock_init_once(void) { + /* InitOnceExecuteOnce guarantees the callback runs exactly once + * across all threads in the process, so the critical section is + * initialised exactly once even under concurrent client creation. */ + InitOnceExecuteOnce(&g_wsa_lock_init_once_state, + csm_wsa_lock_init_once_cb, NULL, NULL); +} + +static int csm_wsa_startup(void) { + csm_wsa_lock_init_once(); + csm_mutex_lock(&g_wsa_lock); + if (g_wsa_refcount == 0) { + WSADATA d; + if (WSAStartup(MAKEWORD(2, 2), &d) != 0) { + csm_mutex_unlock(&g_wsa_lock); + return -1; + } + } + g_wsa_refcount++; + csm_mutex_unlock(&g_wsa_lock); + return 0; +} + +static void csm_wsa_cleanup(void) { + csm_wsa_lock_init_once(); + csm_mutex_lock(&g_wsa_lock); + if (g_wsa_refcount > 0) { + g_wsa_refcount--; + if (g_wsa_refcount == 0) WSACleanup(); + } + csm_mutex_unlock(&g_wsa_lock); +} +#else +static int csm_wsa_startup(void) { return 0; } +static void csm_wsa_cleanup(void) {} +#endif + +/* ========================================================================= */ +/* Result code helpers */ +/* ========================================================================= */ + +const char *csm_result_str(csm_result_t code) { + switch (code) { + case CSM_OK: return "OK"; + case CSM_ERR_INVALID: return "Invalid argument"; + case CSM_ERR_CONNECTION: return "Connection error"; + case CSM_ERR_TIMEOUT: return "Timeout"; + case CSM_ERR_PROTOCOL: return "Protocol error"; + case CSM_ERR_SERVER: return "Server error"; + case CSM_ERR_NOMEM: return "Out of memory"; + case CSM_ERR_STATE: return "Invalid state"; + case CSM_ERR_IO: return "I/O error"; + } + return "Unknown"; +} + +/* ========================================================================= */ +/* Memory helpers */ +/* ========================================================================= */ + +static char *csm_strdup_n(const char *s, size_t n) { + char *out = (char *)malloc(n + 1); + if (!out) return NULL; + if (n) memcpy(out, s, n); + out[n] = '\0'; + return out; +} + +static char *csm_strdup_str(const char *s) { + return csm_strdup_n(s ? s : "", s ? strlen(s) : 0); +} + +void csm_string_free(char *s) { free(s); } + +void csm_command_response_dispose(csm_command_response_t *resp) { + if (!resp) return; + free(resp->raw); + resp->raw = NULL; + resp->raw_len = 0; +} + +void csm_async_response_dispose(csm_async_response_t *resp) { + if (!resp) return; + free(resp->raw); + free(resp->original_command); + resp->raw = NULL; + resp->original_command = NULL; + resp->raw_len = 0; +} + +void csm_status_notification_dispose(csm_status_notification_t *n) { + if (!n) return; + free(n->raw); + free(n->status_name); + free(n->data); + free(n->module_name); + n->raw = NULL; + n->status_name = NULL; + n->data = NULL; + n->module_name = NULL; + n->raw_len = 0; +} + +void csm_packet_dispose(csm_packet_t *pkt) { + if (!pkt) return; + free(pkt->data); + pkt->data = NULL; + pkt->data_len = 0; +} + +/* ========================================================================= */ +/* Protocol codec */ +/* ========================================================================= */ + +static void csm_pack_be32(uint8_t *buf, uint32_t v) { + buf[0] = (uint8_t)((v >> 24) & 0xFF); + buf[1] = (uint8_t)((v >> 16) & 0xFF); + buf[2] = (uint8_t)((v >> 8) & 0xFF); + buf[3] = (uint8_t)( v & 0xFF); +} + +static uint32_t csm_unpack_be32(const uint8_t *buf) { + return ((uint32_t)buf[0] << 24) | + ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | + (uint32_t)buf[3]; +} + +csm_result_t csm_encode_packet(const void *data, + size_t data_len, + csm_packet_type_t type, + uint8_t flag1, + uint8_t flag2, + uint8_t *out_buf, + size_t out_buf_size, + size_t *out_len) { + if (!out_buf || (data_len > 0 && !data)) return CSM_ERR_INVALID; + if (out_buf_size < CSM_HEADER_SIZE + data_len) return CSM_ERR_INVALID; + + csm_pack_be32(out_buf, (uint32_t)data_len); + out_buf[4] = CSM_PROTOCOL_VERSION; + out_buf[5] = (uint8_t)type; + out_buf[6] = flag1; + out_buf[7] = flag2; + if (data_len) memcpy(out_buf + CSM_HEADER_SIZE, data, data_len); + if (out_len) *out_len = CSM_HEADER_SIZE + data_len; + return CSM_OK; +} + +csm_result_t csm_decode_header(const uint8_t *header_bytes, + size_t header_len, + uint32_t *out_data_len, + uint8_t *out_version, + uint8_t *out_type, + uint8_t *out_flag1, + uint8_t *out_flag2) { + if (!header_bytes || header_len != CSM_HEADER_SIZE) return CSM_ERR_PROTOCOL; + if (out_data_len) *out_data_len = csm_unpack_be32(header_bytes); + if (out_version) *out_version = header_bytes[4]; + if (out_type) *out_type = header_bytes[5]; + if (out_flag1) *out_flag1 = header_bytes[6]; + if (out_flag2) *out_flag2 = header_bytes[7]; + return CSM_OK; +} + +csm_result_t csm_parse_packet(const uint8_t *header_bytes, + size_t header_len, + const uint8_t *body, + size_t body_len, + csm_packet_t *out_packet) { + if (!out_packet) return CSM_ERR_INVALID; + uint32_t data_len = 0; + uint8_t version = 0, type_byte = 0, flag1 = 0, flag2 = 0; + csm_result_t r = csm_decode_header(header_bytes, header_len, &data_len, + &version, &type_byte, &flag1, &flag2); + if (r != CSM_OK) return r; + if ((size_t)data_len != body_len) return CSM_ERR_PROTOCOL; + + /* Forward-compatible: unknown type bytes are mapped to INFO. */ + csm_packet_type_t pt; + switch (type_byte) { + case CSM_PT_INFO: + case CSM_PT_ERROR: + case CSM_PT_CMD: + case CSM_PT_CMD_RESP: + case CSM_PT_RESP: + case CSM_PT_ASYNC_RESP: + case CSM_PT_STATUS: + case CSM_PT_INTERRUPT: + pt = (csm_packet_type_t)type_byte; + break; + default: + pt = CSM_PT_INFO; + break; + } + + out_packet->type = pt; + out_packet->version = version; + out_packet->flag1 = flag1; + out_packet->flag2 = flag2; + out_packet->data_len = body_len; + out_packet->data = NULL; + if (body_len > 0) { + out_packet->data = (uint8_t *)malloc(body_len); + if (!out_packet->data) return CSM_ERR_NOMEM; + memcpy(out_packet->data, body, body_len); + } + return CSM_OK; +} + +/* ========================================================================= */ +/* Internal: server-error parsing */ +/* ========================================================================= */ + +/* Parse a packet payload of the form "[Error: ] " into out_err. */ +static void csm_parse_server_error(const uint8_t *data, + size_t len, + csm_server_error_t *out_err) { + out_err->code[0] = '\0'; + out_err->message[0] = '\0'; + + /* Copy into a NUL-terminated stack buffer (capped). */ + char buf[1024]; + size_t copy_len = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1; + if (copy_len) memcpy(buf, data, copy_len); + buf[copy_len] = '\0'; + + /* Trim trailing whitespace. */ + while (copy_len > 0 && (buf[copy_len - 1] == ' ' || + buf[copy_len - 1] == '\r' || + buf[copy_len - 1] == '\n' || + buf[copy_len - 1] == '\t')) { + buf[--copy_len] = '\0'; + } + + const char *prefix = "[Error:"; + size_t prefix_len = strlen(prefix); + const char *msg = buf; + if (copy_len >= prefix_len && strncmp(buf, prefix, prefix_len) == 0) { + char *end = strchr(buf, ']'); + if (end) { + size_t code_len = (size_t)(end - (buf + prefix_len)); + /* Trim leading/trailing spaces from code. */ + const char *cs = buf + prefix_len; + while (code_len && *cs == ' ') { cs++; code_len--; } + while (code_len && cs[code_len - 1] == ' ') code_len--; + if (code_len >= sizeof(out_err->code)) + code_len = sizeof(out_err->code) - 1; + memcpy(out_err->code, cs, code_len); + out_err->code[code_len] = '\0'; + msg = end + 1; + while (*msg == ' ') msg++; + } + } + + size_t msg_len = strlen(msg); + if (msg_len >= sizeof(out_err->message)) + msg_len = sizeof(out_err->message) - 1; + memcpy(out_err->message, msg, msg_len); + out_err->message[msg_len] = '\0'; +} + +/* ========================================================================= */ +/* Internal: bounded queues */ +/* ========================================================================= */ + +/* Generic queue node. Items hold either a packet (for resp/cmd_resp), or + * a notification / async response (for the polling queues), or a sentinel + * (signaled via `is_disconnect`). */ +typedef struct csm_queue_node { + struct csm_queue_node *next; + void *item; /* type depends on queue */ + int is_disconnect; + int is_server_error; + csm_server_error_t server_error; +} csm_queue_node_t; + +typedef struct csm_queue { + csm_queue_node_t *head; + csm_queue_node_t *tail; + csm_mutex_t lock; + csm_cond_t cond; +} csm_queue_t; + +static void csm_queue_init(csm_queue_t *q) { + q->head = q->tail = NULL; + csm_mutex_init(&q->lock); + csm_cond_init(&q->cond); +} + +static void csm_queue_destroy_with(csm_queue_t *q, + void (*free_item)(void *)) { + csm_queue_node_t *n = q->head; + while (n) { + csm_queue_node_t *next = n->next; + if (n->item && free_item) free_item(n->item); + free(n); + n = next; + } + q->head = q->tail = NULL; + csm_cond_destroy(&q->cond); + csm_mutex_destroy(&q->lock); +} + +/* Push an item; takes ownership of *item* on success. */ +static int csm_queue_push(csm_queue_t *q, void *item, + int is_disconnect, int is_server_error, + const csm_server_error_t *err) { + csm_queue_node_t *n = (csm_queue_node_t *)calloc(1, sizeof(*n)); + if (!n) return -1; + n->item = item; + n->is_disconnect = is_disconnect; + n->is_server_error = is_server_error; + if (err) n->server_error = *err; + + csm_mutex_lock(&q->lock); + if (q->tail) q->tail->next = n; + else q->head = n; + q->tail = n; + csm_cond_signal(&q->cond); + csm_mutex_unlock(&q->lock); + return 0; +} + +/* Pop one item, blocking up to *timeout_ms*. Returns CSM_OK with *out_item + * set (and ownership transferred), CSM_ERR_TIMEOUT, CSM_ERR_CONNECTION + * (sentinel), or CSM_ERR_SERVER (with *out_err* populated). */ +static csm_result_t csm_queue_pop(csm_queue_t *q, + unsigned int timeout_ms, + void **out_item, + csm_server_error_t *out_err) { + if (out_item) *out_item = NULL; + double deadline = csm_monotonic_ms() + (double)timeout_ms; + csm_mutex_lock(&q->lock); + while (q->head == NULL) { + double remaining = deadline - csm_monotonic_ms(); + if (remaining <= 0) { + csm_mutex_unlock(&q->lock); + return CSM_ERR_TIMEOUT; + } + unsigned int wait_ms = (unsigned int)remaining; + if (wait_ms == 0) wait_ms = 1; + csm_cond_wait_ms(&q->cond, &q->lock, wait_ms); + } + csm_queue_node_t *n = q->head; + q->head = n->next; + if (q->head == NULL) q->tail = NULL; + csm_mutex_unlock(&q->lock); + + csm_result_t result = CSM_OK; + if (n->is_disconnect) { + result = CSM_ERR_CONNECTION; + } else if (n->is_server_error) { + if (out_err) *out_err = n->server_error; + result = CSM_ERR_SERVER; + } else if (out_item) { + *out_item = n->item; + n->item = NULL; + } + if (n->item) { + /* Item not consumed (e.g. caller passed NULL out_item). Leak-safe + * default is to free as bytes via the disposer set by the caller's + * queue-specific wrapper; here we just drop it. */ + free(n->item); + } + free(n); + return result; +} + +/* ========================================================================= */ +/* Subscription / async-callback registries */ +/* ========================================================================= */ + +typedef struct csm_status_sub { + struct csm_status_sub *next; + char *status_name; + char *module_name; + csm_status_callback_fn callback; + void *user_data; +} csm_status_sub_t; + +typedef struct csm_async_sub { + struct csm_async_sub *next; + char *original_command; + csm_async_callback_fn callback; + void *user_data; +} csm_async_sub_t; + +/* ========================================================================= */ +/* Client */ +/* ========================================================================= */ + +struct csm_client { + csm_socket_t sock; + csm_thread_t recv_thread; + int recv_thread_running; + int connected; /* set under state_lock */ + int stop_flag; /* set to request shutdown */ + + csm_mutex_t state_lock; /* protects connected/stop_flag/sock */ + csm_mutex_t send_lock; /* serialises sendall() */ + + csm_mutex_t resp_lock; /* at most one in-flight RESP waiter */ + csm_mutex_t cmd_resp_lock; /* at most one in-flight CMD_RESP waiter */ + + csm_queue_t resp_queue; /* items: csm_packet_t* */ + csm_queue_t cmd_resp_queue; /* items: csm_packet_t* (or NULL) */ + csm_queue_t status_queue; /* items: csm_status_notification_t* */ + csm_queue_t async_queue; /* items: csm_async_response_t* */ + + csm_mutex_t sub_lock; /* protects subscription registries */ + csm_status_sub_t *status_subs; + csm_async_sub_t *async_subs; + + csm_mutex_t err_lock; + int has_server_error; + csm_server_error_t last_server_error; +}; + +/* --- helpers --- */ + +static void csm_packet_free_void(void *p) { + csm_packet_t *pkt = (csm_packet_t *)p; + if (!pkt) return; + csm_packet_dispose(pkt); + free(pkt); +} + +static void csm_status_notif_free_void(void *p) { + csm_status_notification_t *n = (csm_status_notification_t *)p; + if (!n) return; + csm_status_notification_dispose(n); + free(n); +} + +static void csm_async_resp_free_void(void *p) { + csm_async_response_t *r = (csm_async_response_t *)p; + if (!r) return; + csm_async_response_dispose(r); + free(r); +} + +/* Set client.sock under state_lock; closes any old one. */ +static void csm_set_socket_locked(csm_client_t *c, csm_socket_t s) { + if (c->sock != CSM_INVALID_SOCKET) csm_close_socket(c->sock); + c->sock = s; +} + +/* Remember the most-recent server error so callers can fetch it after + * receiving a CSM_ERR_SERVER. */ +static void csm_record_server_error(csm_client_t *c, + const csm_server_error_t *err) { + csm_mutex_lock(&c->err_lock); + c->has_server_error = 1; + c->last_server_error = *err; + csm_mutex_unlock(&c->err_lock); +} + +/* --- subscription registries (under sub_lock) --- */ + +static csm_status_sub_t *csm_find_status_sub(csm_client_t *c, + const char *status_name, + const char *module_name) { + csm_status_sub_t *s = c->status_subs; + while (s) { + if (strcmp(s->status_name, status_name) == 0 && + strcmp(s->module_name, module_name) == 0) { + return s; + } + s = s->next; + } + return NULL; +} + +static csm_result_t csm_register_status_sub(csm_client_t *c, + const char *status_name, + const char *module_name, + csm_status_callback_fn callback, + void *user_data) { + csm_mutex_lock(&c->sub_lock); + csm_status_sub_t *existing = csm_find_status_sub(c, status_name, module_name); + if (existing) { + existing->callback = callback; + existing->user_data = user_data; + csm_mutex_unlock(&c->sub_lock); + return CSM_OK; + } + csm_status_sub_t *s = (csm_status_sub_t *)calloc(1, sizeof(*s)); + if (!s) { csm_mutex_unlock(&c->sub_lock); return CSM_ERR_NOMEM; } + s->status_name = csm_strdup_str(status_name); + s->module_name = csm_strdup_str(module_name); + if (!s->status_name || !s->module_name) { + free(s->status_name); free(s->module_name); free(s); + csm_mutex_unlock(&c->sub_lock); + return CSM_ERR_NOMEM; + } + s->callback = callback; + s->user_data = user_data; + s->next = c->status_subs; + c->status_subs = s; + csm_mutex_unlock(&c->sub_lock); + return CSM_OK; +} + +static void csm_remove_status_sub(csm_client_t *c, + const char *status_name, + const char *module_name) { + csm_mutex_lock(&c->sub_lock); + csm_status_sub_t **pp = &c->status_subs; + while (*pp) { + csm_status_sub_t *s = *pp; + if (strcmp(s->status_name, status_name) == 0 && + strcmp(s->module_name, module_name) == 0) { + *pp = s->next; + free(s->status_name); + free(s->module_name); + free(s); + break; + } + pp = &s->next; + } + csm_mutex_unlock(&c->sub_lock); +} + +static csm_result_t csm_register_async_sub(csm_client_t *c, + const char *original_command, + csm_async_callback_fn callback, + void *user_data) { + csm_mutex_lock(&c->sub_lock); + csm_async_sub_t *s = c->async_subs; + while (s) { + if (strcmp(s->original_command, original_command) == 0) { + s->callback = callback; + s->user_data = user_data; + csm_mutex_unlock(&c->sub_lock); + return CSM_OK; + } + s = s->next; + } + s = (csm_async_sub_t *)calloc(1, sizeof(*s)); + if (!s) { csm_mutex_unlock(&c->sub_lock); return CSM_ERR_NOMEM; } + s->original_command = csm_strdup_str(original_command); + if (!s->original_command) { free(s); csm_mutex_unlock(&c->sub_lock); return CSM_ERR_NOMEM; } + s->callback = callback; + s->user_data = user_data; + s->next = c->async_subs; + c->async_subs = s; + csm_mutex_unlock(&c->sub_lock); + return CSM_OK; +} + +static void csm_remove_async_sub(csm_client_t *c, const char *original_command) { + csm_mutex_lock(&c->sub_lock); + csm_async_sub_t **pp = &c->async_subs; + while (*pp) { + csm_async_sub_t *s = *pp; + if (strcmp(s->original_command, original_command) == 0) { + *pp = s->next; + free(s->original_command); + free(s); + break; + } + pp = &s->next; + } + csm_mutex_unlock(&c->sub_lock); +} + +static void csm_free_all_subs(csm_client_t *c) { + csm_mutex_lock(&c->sub_lock); + csm_status_sub_t *s = c->status_subs; + while (s) { csm_status_sub_t *n = s->next; free(s->status_name); free(s->module_name); free(s); s = n; } + c->status_subs = NULL; + csm_async_sub_t *a = c->async_subs; + while (a) { csm_async_sub_t *n = a->next; free(a->original_command); free(a); a = n; } + c->async_subs = NULL; + csm_mutex_unlock(&c->sub_lock); +} + +/* --- recv helpers --- */ + +/* Read exactly *size* bytes from sock; returns 0 on success, -1 on EOF/err. */ +static int csm_recv_all(csm_socket_t sock, uint8_t *buf, size_t size) { + size_t total = 0; + while (total < size) { +#if defined(_WIN32) + int n = recv(sock, (char *)buf + total, (int)(size - total), 0); +#else + ssize_t n = recv(sock, buf + total, size - total, 0); +#endif + if (n <= 0) return -1; + total += (size_t)n; + } + return 0; +} + +/* --- Parsing helpers for ASYNC_RESP / STATUS payloads --- */ + +static void csm_async_resp_free_void(void *p); +static void csm_status_notif_free_void(void *p); + +/* Build an csm_async_response_t from raw payload data. */ +static csm_async_response_t *csm_make_async_response(const uint8_t *data, + size_t len) { + csm_async_response_t *r = (csm_async_response_t *)calloc(1, sizeof(*r)); + if (!r) return NULL; + /* Server format: " <- ". */ + const char *sep = " <- "; + const size_t seplen = 4; + size_t split = (size_t)-1; + if (len >= seplen) { + for (size_t i = 0; i + seplen <= len; ++i) { + if (memcmp(data + i, sep, seplen) == 0) { split = i; break; } + } + } + if (split != (size_t)-1) { + r->raw = csm_strdup_n((const char *)data, split); + r->raw_len = split; + r->original_command = csm_strdup_n((const char *)data + split + seplen, + len - split - seplen); + } else { + r->raw = csm_strdup_n((const char *)data, len); + r->raw_len = len; + r->original_command = csm_strdup_str(""); + } + if (!r->raw || !r->original_command) { + csm_async_resp_free_void(r); + return NULL; + } + return r; +} + +/* Build a csm_status_notification_t from raw payload data. */ +static csm_status_notification_t *csm_make_status_notif(csm_packet_type_t pt, + const uint8_t *data, + size_t len) { + csm_status_notification_t *n = (csm_status_notification_t *)calloc(1, sizeof(*n)); + if (!n) return NULL; + n->packet_type = pt; + n->raw = (char *)malloc(len + 1); + if (!n->raw) { free(n); return NULL; } + if (len) memcpy(n->raw, data, len); + n->raw[len] = '\0'; + n->raw_len = len; + + /* Find rightmost " <- " separator (rsplit by 1). */ + const char *raw_str = n->raw; + const char *left = raw_str; + size_t left_len = len; + const char *module_start = NULL; + size_t module_len = 0; + if (len >= 4) { + for (size_t i = len - 4 + 1; i-- > 0; ) { + if (memcmp(raw_str + i, " <- ", 4) == 0) { + left_len = i; + module_start = raw_str + i + 4; + module_len = len - i - 4; + break; + } + } + } + + /* Trim whitespace from module. */ + while (module_len && (*module_start == ' ' || *module_start == '\t')) { + module_start++; module_len--; + } + while (module_len && (module_start[module_len - 1] == ' ' || + module_start[module_len - 1] == '\t' || + module_start[module_len - 1] == '\r' || + module_start[module_len - 1] == '\n')) { + module_len--; + } + + /* Split left by " >> " into status_name + data. */ + const char *status_start = NULL; + size_t status_len = 0; + const char *data_start = left; + size_t data_len_local = left_len; + if (left_len >= 4) { + for (size_t i = 0; i + 4 <= left_len; ++i) { + if (memcmp(left + i, " >> ", 4) == 0) { + status_start = left; + status_len = i; + data_start = left + i + 4; + data_len_local = left_len - i - 4; + break; + } + } + } + + /* Trim status_name and data. */ + while (status_len && (*status_start == ' ' || *status_start == '\t')) { status_start++; status_len--; } + while (status_len && (status_start[status_len - 1] == ' ' || status_start[status_len - 1] == '\t')) status_len--; + while (data_len_local && (*data_start == ' ' || *data_start == '\t')) { data_start++; data_len_local--; } + while (data_len_local && (data_start[data_len_local - 1] == ' ' || + data_start[data_len_local - 1] == '\t' || + data_start[data_len_local - 1] == '\r' || + data_start[data_len_local - 1] == '\n')) data_len_local--; + + n->status_name = csm_strdup_n(status_start ? status_start : "", status_len); + n->data = csm_strdup_n(data_start, data_len_local); + n->module_name = csm_strdup_n(module_start ? module_start : "", module_len); + if (!n->status_name || !n->data || !n->module_name) { + csm_status_notif_free_void(n); + return NULL; + } + return n; +} + +/* --- Receive thread --- */ + +static void csm_dispatch_packet(csm_client_t *c, csm_packet_t *pkt) { + /* On RESP / CMD_RESP / ERROR we transfer ownership of the packet + * (or err sentinel) into a queue. On STATUS / ASYNC_RESP / INTERRUPT + * we build a higher-level object and dispose of the raw packet. */ + switch (pkt->type) { + case CSM_PT_RESP: { + csm_packet_t *heap = (csm_packet_t *)malloc(sizeof(*heap)); + if (!heap) { csm_packet_free_void(pkt); return; } + *heap = *pkt; + /* Push to resp queue; queue-node owns it. */ + if (csm_queue_push(&c->resp_queue, heap, 0, 0, NULL) != 0) { + csm_packet_free_void(heap); + } + free(pkt); + return; + } + case CSM_PT_CMD_RESP: { + csm_packet_t *heap = (csm_packet_t *)malloc(sizeof(*heap)); + if (!heap) { csm_packet_free_void(pkt); return; } + *heap = *pkt; + if (csm_queue_push(&c->cmd_resp_queue, heap, 0, 0, NULL) != 0) { + csm_packet_free_void(heap); + } + free(pkt); + return; + } + case CSM_PT_ERROR: { + csm_server_error_t err; + csm_parse_server_error(pkt->data, pkt->data_len, &err); + csm_record_server_error(c, &err); + csm_queue_push(&c->resp_queue, NULL, 0, 1, &err); + csm_queue_push(&c->cmd_resp_queue, NULL, 0, 1, &err); + csm_packet_free_void(pkt); + return; + } + case CSM_PT_ASYNC_RESP: { + csm_async_response_t *r = csm_make_async_response(pkt->data, pkt->data_len); + if (r) { + /* Look up callback under sub_lock. */ + csm_mutex_lock(&c->sub_lock); + csm_async_callback_fn cb = NULL; void *ud = NULL; + csm_async_sub_t *s = c->async_subs; + while (s) { + if (strcmp(s->original_command, r->original_command) == 0) { + cb = s->callback; ud = s->user_data; break; + } + s = s->next; + } + csm_mutex_unlock(&c->sub_lock); + if (cb) cb(r, ud); + /* Push a copy onto polling queue so callback users and + * polling users are independent. */ + csm_async_response_t *queued = (csm_async_response_t *)calloc(1, sizeof(*queued)); + if (queued) { + queued->raw = csm_strdup_n(r->raw, r->raw_len); + queued->raw_len = r->raw_len; + queued->original_command = csm_strdup_str(r->original_command); + if (queued->raw && queued->original_command) { + if (csm_queue_push(&c->async_queue, queued, 0, 0, NULL) != 0) + csm_async_resp_free_void(queued); + } else { + csm_async_resp_free_void(queued); + } + } + csm_async_resp_free_void(r); + } + csm_packet_free_void(pkt); + return; + } + case CSM_PT_STATUS: + case CSM_PT_INTERRUPT: { + csm_status_notification_t *n = csm_make_status_notif(pkt->type, pkt->data, pkt->data_len); + if (n) { + csm_mutex_lock(&c->sub_lock); + csm_status_callback_fn cb = NULL; void *ud = NULL; + csm_status_sub_t *s = c->status_subs; + while (s) { + if (strcmp(s->status_name, n->status_name) == 0 && + strcmp(s->module_name, n->module_name) == 0) { + cb = s->callback; ud = s->user_data; break; + } + s = s->next; + } + csm_mutex_unlock(&c->sub_lock); + if (cb) cb(n, ud); + /* Push a copy onto polling queue. */ + csm_status_notification_t *q = (csm_status_notification_t *)calloc(1, sizeof(*q)); + if (q) { + q->packet_type = n->packet_type; + q->raw = csm_strdup_n(n->raw, n->raw_len); + q->raw_len = n->raw_len; + q->status_name = csm_strdup_str(n->status_name); + q->data = csm_strdup_str(n->data); + q->module_name = csm_strdup_str(n->module_name); + if (q->raw && q->status_name && q->data && q->module_name) { + if (csm_queue_push(&c->status_queue, q, 0, 0, NULL) != 0) + csm_status_notif_free_void(q); + } else { + csm_status_notif_free_void(q); + } + } + csm_status_notif_free_void(n); + } + csm_packet_free_void(pkt); + return; + } + case CSM_PT_INFO: + case CSM_PT_CMD: + default: + /* INFO is silently discarded; CMD never sent by server. */ + csm_packet_free_void(pkt); + return; + } +} + +#if defined(_WIN32) +static unsigned __stdcall csm_recv_thread_main(void *arg) +#else +static void *csm_recv_thread_main(void *arg) +#endif +{ + csm_client_t *c = (csm_client_t *)arg; + uint8_t header[CSM_HEADER_SIZE]; + for (;;) { + /* Snapshot stop_flag and sock under state_lock. csm_client_disconnect() + * mutates both fields under the same lock, so a torn read or a stale + * sock value cannot occur and TSAN/UBSan stay quiet. */ + csm_mutex_lock(&c->state_lock); + int stop_flag = c->stop_flag; + csm_socket_t sock = c->sock; + csm_mutex_unlock(&c->state_lock); + + if (stop_flag) break; + if (sock == CSM_INVALID_SOCKET) break; + if (csm_recv_all(sock, header, CSM_HEADER_SIZE) != 0) break; + uint32_t data_len = csm_unpack_be32(header); + uint8_t *body = NULL; + if (data_len > 0) { + body = (uint8_t *)malloc(data_len); + if (!body) break; + if (csm_recv_all(sock, body, data_len) != 0) { + free(body); + break; + } + } + csm_packet_t parsed = {0}; + csm_result_t r = csm_parse_packet(header, CSM_HEADER_SIZE, body, data_len, &parsed); + free(body); + if (r != CSM_OK) { + /* Skip corrupt frame; keep loop alive. */ + continue; + } + /* Allocate heap copy to pass ownership to dispatch. */ + csm_packet_t *heap_pkt = (csm_packet_t *)malloc(sizeof(*heap_pkt)); + if (!heap_pkt) { + csm_packet_dispose(&parsed); + continue; + } + *heap_pkt = parsed; + csm_dispatch_packet(c, heap_pkt); + } + + /* Notify any blocked waiters that the connection is gone. */ + csm_queue_push(&c->resp_queue, NULL, 1, 0, NULL); + csm_queue_push(&c->cmd_resp_queue, NULL, 1, 0, NULL); + csm_queue_push(&c->status_queue, NULL, 1, 0, NULL); + csm_queue_push(&c->async_queue, NULL, 1, 0, NULL); + + csm_mutex_lock(&c->state_lock); + c->connected = 0; + csm_mutex_unlock(&c->state_lock); +#if defined(_WIN32) + return 0; +#else + return NULL; +#endif +} + +/* --- Lifecycle --- */ + +csm_client_t *csm_client_create(void) { + if (csm_wsa_startup() != 0) return NULL; + csm_client_t *c = (csm_client_t *)calloc(1, sizeof(*c)); + if (!c) { csm_wsa_cleanup(); return NULL; } + c->sock = CSM_INVALID_SOCKET; + csm_mutex_init(&c->state_lock); + csm_mutex_init(&c->send_lock); + csm_mutex_init(&c->resp_lock); + csm_mutex_init(&c->cmd_resp_lock); + csm_mutex_init(&c->sub_lock); + csm_mutex_init(&c->err_lock); + csm_queue_init(&c->resp_queue); + csm_queue_init(&c->cmd_resp_queue); + csm_queue_init(&c->status_queue); + csm_queue_init(&c->async_queue); + return c; +} + +void csm_client_destroy(csm_client_t *client) { + if (!client) return; + csm_client_disconnect(client); + csm_queue_destroy_with(&client->resp_queue, csm_packet_free_void); + csm_queue_destroy_with(&client->cmd_resp_queue, csm_packet_free_void); + csm_queue_destroy_with(&client->status_queue, csm_status_notif_free_void); + csm_queue_destroy_with(&client->async_queue, csm_async_resp_free_void); + csm_free_all_subs(client); + csm_mutex_destroy(&client->state_lock); + csm_mutex_destroy(&client->send_lock); + csm_mutex_destroy(&client->resp_lock); + csm_mutex_destroy(&client->cmd_resp_lock); + csm_mutex_destroy(&client->sub_lock); + csm_mutex_destroy(&client->err_lock); + free(client); + csm_wsa_cleanup(); +} + +/* Resolve host and connect with a timeout. Returns CSM_OK or + * CSM_ERR_CONNECTION / CSM_ERR_TIMEOUT. */ +static csm_result_t csm_do_connect(const char *host, uint16_t port, + unsigned int timeout_ms, + csm_socket_t *out_sock) { + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%u", (unsigned)port); + + struct addrinfo hints, *res = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + if (getaddrinfo(host, port_str, &hints, &res) != 0 || !res) { + return CSM_ERR_CONNECTION; + } + + csm_result_t result = CSM_ERR_CONNECTION; + csm_socket_t sock = CSM_INVALID_SOCKET; + for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == CSM_INVALID_SOCKET) continue; + + /* Switch to non-blocking for connect-with-timeout. */ +#if defined(_WIN32) + u_long mode = 1; + ioctlsocket(sock, FIONBIO, &mode); +#else + int flags = fcntl(sock, F_GETFL, 0); + if (flags == -1 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { + csm_close_socket(sock); + sock = CSM_INVALID_SOCKET; + continue; + } +#endif + + int cr = connect(sock, ai->ai_addr, (int)ai->ai_addrlen); + if (cr == 0) { + result = CSM_OK; + } else { +#if defined(_WIN32) + int err = WSAGetLastError(); + int in_progress = (err == WSAEWOULDBLOCK); +#else + int in_progress = (errno == EINPROGRESS); +#endif + if (in_progress) { + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(sock, &wfds); + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (long)(timeout_ms % 1000) * 1000; + int sel = select((int)(sock + 1), NULL, &wfds, NULL, + timeout_ms > 0 ? &tv : NULL); + if (sel > 0) { + int so_err = 0; + socklen_t sl = sizeof(so_err); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, + (char *)&so_err, &sl) == 0 && so_err == 0) { + result = CSM_OK; + } + } else if (sel == 0) { + result = CSM_ERR_TIMEOUT; + } + } + } + + if (result == CSM_OK) { + /* Switch back to blocking for the recv loop. */ +#if defined(_WIN32) + u_long mode2 = 0; + ioctlsocket(sock, FIONBIO, &mode2); +#else + int flags2 = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags2 & ~O_NONBLOCK); +#endif + *out_sock = sock; + break; + } + csm_close_socket(sock); + sock = CSM_INVALID_SOCKET; + } + + freeaddrinfo(res); + return result; +} + +csm_result_t csm_client_connect(csm_client_t *client, + const char *host, + uint16_t port, + unsigned int connect_timeout_ms) { + if (!client || !host) return CSM_ERR_INVALID; + + csm_mutex_lock(&client->state_lock); + if (client->connected) { + csm_mutex_unlock(&client->state_lock); + return CSM_ERR_STATE; + } + csm_mutex_unlock(&client->state_lock); + + csm_socket_t sock = CSM_INVALID_SOCKET; + csm_result_t r = csm_do_connect(host, port, + connect_timeout_ms ? connect_timeout_ms : 5000, + &sock); + if (r != CSM_OK) return r; + + csm_mutex_lock(&client->state_lock); + csm_set_socket_locked(client, sock); + client->connected = 1; + client->stop_flag = 0; + csm_mutex_unlock(&client->state_lock); + + csm_mutex_lock(&client->err_lock); + client->has_server_error = 0; + csm_mutex_unlock(&client->err_lock); + +#if defined(_WIN32) + client->recv_thread = (HANDLE)_beginthreadex(NULL, 0, csm_recv_thread_main, + client, 0, NULL); + if (client->recv_thread == NULL) { + csm_mutex_lock(&client->state_lock); + csm_set_socket_locked(client, CSM_INVALID_SOCKET); + client->connected = 0; + csm_mutex_unlock(&client->state_lock); + return CSM_ERR_IO; + } +#else + if (pthread_create(&client->recv_thread, NULL, csm_recv_thread_main, client) != 0) { + csm_mutex_lock(&client->state_lock); + csm_set_socket_locked(client, CSM_INVALID_SOCKET); + client->connected = 0; + csm_mutex_unlock(&client->state_lock); + return CSM_ERR_IO; + } +#endif + client->recv_thread_running = 1; + return CSM_OK; +} + +csm_result_t csm_client_disconnect(csm_client_t *client) { + if (!client) return CSM_ERR_INVALID; + csm_mutex_lock(&client->state_lock); + int was_connected = client->connected; + client->stop_flag = 1; + client->connected = 0; + csm_socket_t s = client->sock; + client->sock = CSM_INVALID_SOCKET; + csm_mutex_unlock(&client->state_lock); + + /* Wake any blocked waiters before tearing down the socket. */ + csm_queue_push(&client->resp_queue, NULL, 1, 0, NULL); + csm_queue_push(&client->cmd_resp_queue, NULL, 1, 0, NULL); + csm_queue_push(&client->status_queue, NULL, 1, 0, NULL); + csm_queue_push(&client->async_queue, NULL, 1, 0, NULL); + + if (s != CSM_INVALID_SOCKET) { +#if defined(_WIN32) + shutdown(s, SD_BOTH); +#else + shutdown(s, SHUT_RDWR); +#endif + csm_close_socket(s); + } + + if (client->recv_thread_running) { +#if defined(_WIN32) + WaitForSingleObject(client->recv_thread, 2000); + CloseHandle(client->recv_thread); +#else + pthread_join(client->recv_thread, NULL); +#endif + client->recv_thread_running = 0; + } + return was_connected ? CSM_OK : CSM_OK; +} + +int csm_client_is_connected(const csm_client_t *client) { + if (!client) return 0; + /* Casting away const to take the lock; logically this is a read. */ + csm_client_t *mc = (csm_client_t *)client; + csm_mutex_lock(&mc->state_lock); + int v = mc->connected; + csm_mutex_unlock(&mc->state_lock); + return v; +} + +csm_result_t csm_client_wait_for_server(const char *host, + uint16_t port, + unsigned int timeout_ms, + unsigned int retry_interval_ms) { + if (!host) return CSM_ERR_INVALID; + if (csm_wsa_startup() != 0) return CSM_ERR_IO; + double deadline = csm_monotonic_ms() + (double)timeout_ms; + csm_result_t result = CSM_ERR_TIMEOUT; + while (csm_monotonic_ms() < deadline) { + csm_socket_t s = CSM_INVALID_SOCKET; + csm_result_t r = csm_do_connect(host, port, 1000, &s); + if (r == CSM_OK) { + csm_close_socket(s); + result = CSM_OK; + break; + } + csm_sleep_ms(retry_interval_ms ? retry_interval_ms : 500); + } + csm_wsa_cleanup(); + return result; +} + +/* --- Send helpers --- */ + +static csm_result_t csm_send_raw(csm_client_t *client, + const uint8_t *data, size_t len) { + csm_mutex_lock(&client->state_lock); + if (!client->connected) { + csm_mutex_unlock(&client->state_lock); + return CSM_ERR_CONNECTION; + } + csm_socket_t sock = client->sock; + csm_mutex_unlock(&client->state_lock); + + csm_mutex_lock(&client->send_lock); + size_t total = 0; +#if defined(MSG_NOSIGNAL) + int send_flags = MSG_NOSIGNAL; +#else + int send_flags = 0; +#endif + while (total < len) { +#if defined(_WIN32) + int n = send(sock, (const char *)data + total, (int)(len - total), 0); + (void)send_flags; +#else + ssize_t n = send(sock, data + total, len - total, send_flags); +#endif + if (n <= 0) { + csm_mutex_unlock(&client->send_lock); + csm_mutex_lock(&client->state_lock); + client->stop_flag = 1; + csm_mutex_unlock(&client->state_lock); + return CSM_ERR_CONNECTION; + } + total += (size_t)n; + } + csm_mutex_unlock(&client->send_lock); + return CSM_OK; +} + +/* Pack and send a CMD packet. */ +static csm_result_t csm_send_cmd(csm_client_t *client, const char *command) { + size_t len = strlen(command); + uint8_t *buf = (uint8_t *)malloc(CSM_HEADER_SIZE + len); + if (!buf) return CSM_ERR_NOMEM; + size_t out_len = 0; + csm_result_t r = csm_encode_packet(command, len, CSM_PT_CMD, 0, 0, + buf, CSM_HEADER_SIZE + len, &out_len); + if (r == CSM_OK) r = csm_send_raw(client, buf, out_len); + free(buf); + return r; +} + +/* --- Wait helpers --- */ + +static csm_result_t csm_wait_for_resp(csm_client_t *client, + unsigned int timeout_ms, + csm_command_response_t *out_resp) { + void *item = NULL; + csm_server_error_t err = {0}; + csm_result_t r = csm_queue_pop(&client->resp_queue, timeout_ms, &item, &err); + if (r == CSM_ERR_SERVER) { + csm_record_server_error(client, &err); + return CSM_ERR_SERVER; + } + if (r != CSM_OK) return r; + csm_packet_t *pkt = (csm_packet_t *)item; + if (out_resp) { + out_resp->raw_len = pkt->data_len; + out_resp->raw = (uint8_t *)malloc(pkt->data_len + 1); + if (!out_resp->raw) { csm_packet_free_void(pkt); return CSM_ERR_NOMEM; } + if (pkt->data_len) memcpy(out_resp->raw, pkt->data, pkt->data_len); + out_resp->raw[pkt->data_len] = 0; + } + csm_packet_free_void(pkt); + return CSM_OK; +} + +static csm_result_t csm_wait_for_cmd_resp(csm_client_t *client, + unsigned int timeout_ms) { + void *item = NULL; + csm_server_error_t err = {0}; + csm_result_t r = csm_queue_pop(&client->cmd_resp_queue, timeout_ms, &item, &err); + if (r == CSM_ERR_SERVER) { + csm_record_server_error(client, &err); + return CSM_ERR_SERVER; + } + if (r != CSM_OK) return r; + /* Discard handshake payload. */ + csm_packet_t *pkt = (csm_packet_t *)item; + csm_packet_free_void(pkt); + return CSM_OK; +} + +/* --- Public command API --- */ + +csm_result_t csm_client_send_and_wait(csm_client_t *client, + const char *command, + unsigned int timeout_ms, + csm_command_response_t *out_resp) { + if (!client || !command) return CSM_ERR_INVALID; + if (out_resp) { out_resp->raw = NULL; out_resp->raw_len = 0; } + + csm_mutex_lock(&client->resp_lock); + csm_result_t r = csm_send_cmd(client, command); + if (r == CSM_OK) r = csm_wait_for_resp(client, timeout_ms ? timeout_ms : 5000, out_resp); + csm_mutex_unlock(&client->resp_lock); + return r; +} + +csm_result_t csm_client_post(csm_client_t *client, const char *command, + unsigned int timeout_ms) { + if (!client || !command) return CSM_ERR_INVALID; + csm_mutex_lock(&client->cmd_resp_lock); + csm_result_t r = csm_send_cmd(client, command); + if (r == CSM_OK) r = csm_wait_for_cmd_resp(client, timeout_ms ? timeout_ms : 5000); + csm_mutex_unlock(&client->cmd_resp_lock); + return r; +} + +csm_result_t csm_client_post_no_reply(csm_client_t *client, const char *command, + unsigned int timeout_ms) { + return csm_client_post(client, command, timeout_ms); +} + +csm_result_t csm_client_ping(csm_client_t *client, unsigned int timeout_ms, + double *out_elapsed_ms) { + if (out_elapsed_ms) *out_elapsed_ms = 0.0; + csm_command_response_t resp = {0}; + double t0 = csm_monotonic_ms(); + csm_result_t r = csm_client_send_and_wait(client, "Ping", + timeout_ms ? timeout_ms : 2000, &resp); + csm_command_response_dispose(&resp); + if (r != CSM_OK) return r; + if (out_elapsed_ms) *out_elapsed_ms = csm_monotonic_ms() - t0; + return CSM_OK; +} + +/* Shared helper: send a fixed-text command and return the response text. */ +static csm_result_t csm_send_text_query(csm_client_t *client, + const char *command, + char **out_text, + unsigned int timeout_ms) { + if (!out_text) return CSM_ERR_INVALID; + *out_text = NULL; + csm_command_response_t resp = {0}; + csm_result_t r = csm_client_send_and_wait(client, command, timeout_ms, &resp); + if (r == CSM_OK) { + *out_text = (char *)resp.raw; /* transfer ownership; was NUL-terminated */ + resp.raw = NULL; + } else { + csm_command_response_dispose(&resp); + } + return r; +} + +csm_result_t csm_client_list_modules(csm_client_t *client, char **out_text, + unsigned int timeout_ms) { + return csm_send_text_query(client, "List", out_text, timeout_ms); +} + +/* Build a " " command and send. */ +static csm_result_t csm_send_text_query_2(csm_client_t *client, + const char *prefix, + const char *module, + char **out_text, + unsigned int timeout_ms) { + if (!module) return CSM_ERR_INVALID; + size_t plen = strlen(prefix); + size_t mlen = strlen(module); + char *cmd = (char *)malloc(plen + 1 + mlen + 1); + if (!cmd) return CSM_ERR_NOMEM; + memcpy(cmd, prefix, plen); + cmd[plen] = ' '; + memcpy(cmd + plen + 1, module, mlen); + cmd[plen + 1 + mlen] = '\0'; + csm_result_t r = csm_send_text_query(client, cmd, out_text, timeout_ms); + free(cmd); + return r; +} + +csm_result_t csm_client_list_api(csm_client_t *client, const char *module, + char **out_text, unsigned int timeout_ms) { + return csm_send_text_query_2(client, "List API", module, out_text, timeout_ms); +} + +csm_result_t csm_client_list_states(csm_client_t *client, const char *module, + char **out_text, unsigned int timeout_ms) { + return csm_send_text_query_2(client, "List State", module, out_text, timeout_ms); +} + +csm_result_t csm_client_help(csm_client_t *client, const char *module, + char **out_text, unsigned int timeout_ms) { + return csm_send_text_query_2(client, "Help", module, out_text, timeout_ms); +} + +/* --- Subscriptions --- */ + +csm_result_t csm_client_subscribe_status(csm_client_t *client, + const char *status_name, + const char *module_name, + csm_status_callback_fn callback, + void *user_data, + unsigned int timeout_ms) { + if (!client || !status_name || !module_name) return CSM_ERR_INVALID; + + /* Register first to eliminate the race where a STATUS arrives before + * the callback is stored. */ + csm_result_t r = csm_register_status_sub(client, status_name, module_name, + callback, user_data); + if (r != CSM_OK) return r; + + /* Build "@ ->". */ + size_t s_len = strlen(status_name); + size_t m_len = strlen(module_name); + const char *suffix = " ->"; + size_t suf_len = strlen(suffix); + char *cmd = (char *)malloc(s_len + 1 + m_len + suf_len + 1); + if (!cmd) { csm_remove_status_sub(client, status_name, module_name); return CSM_ERR_NOMEM; } + memcpy(cmd, status_name, s_len); + cmd[s_len] = '@'; + memcpy(cmd + s_len + 1, module_name, m_len); + memcpy(cmd + s_len + 1 + m_len, suffix, suf_len); + cmd[s_len + 1 + m_len + suf_len] = '\0'; + + csm_mutex_lock(&client->cmd_resp_lock); + r = csm_send_cmd(client, cmd); + if (r == CSM_OK) r = csm_wait_for_cmd_resp(client, timeout_ms ? timeout_ms : 5000); + csm_mutex_unlock(&client->cmd_resp_lock); + free(cmd); + + if (r != CSM_OK) csm_remove_status_sub(client, status_name, module_name); + return r; +} + +csm_result_t csm_client_unsubscribe_status(csm_client_t *client, + const char *status_name, + const char *module_name, + unsigned int timeout_ms) { + if (!client || !status_name || !module_name) return CSM_ERR_INVALID; + size_t s_len = strlen(status_name); + size_t m_len = strlen(module_name); + const char *suffix = " ->"; + size_t suf_len = strlen(suffix); + char *cmd = (char *)malloc(s_len + 1 + m_len + suf_len + 1); + if (!cmd) return CSM_ERR_NOMEM; + memcpy(cmd, status_name, s_len); + cmd[s_len] = '@'; + memcpy(cmd + s_len + 1, module_name, m_len); + memcpy(cmd + s_len + 1 + m_len, suffix, suf_len); + cmd[s_len + 1 + m_len + suf_len] = '\0'; + + csm_mutex_lock(&client->cmd_resp_lock); + csm_result_t r = csm_send_cmd(client, cmd); + if (r == CSM_OK) r = csm_wait_for_cmd_resp(client, timeout_ms ? timeout_ms : 5000); + csm_mutex_unlock(&client->cmd_resp_lock); + free(cmd); + + csm_remove_status_sub(client, status_name, module_name); + return r; +} + +csm_result_t csm_client_register_async_callback(csm_client_t *client, + const char *original_command, + csm_async_callback_fn callback, + void *user_data) { + if (!client || !original_command || !callback) return CSM_ERR_INVALID; + return csm_register_async_sub(client, original_command, callback, user_data); +} + +csm_result_t csm_client_unregister_async_callback(csm_client_t *client, + const char *original_command) { + if (!client || !original_command) return CSM_ERR_INVALID; + csm_remove_async_sub(client, original_command); + return CSM_OK; +} + +/* --- Polling queues --- */ + +csm_result_t csm_client_poll_status(csm_client_t *client, + csm_status_notification_t *out_notif, + unsigned int timeout_ms) { + if (!client || !out_notif) return CSM_ERR_INVALID; + memset(out_notif, 0, sizeof(*out_notif)); + void *item = NULL; + csm_result_t r = csm_queue_pop(&client->status_queue, timeout_ms, &item, NULL); + if (r != CSM_OK) return r; + csm_status_notification_t *src = (csm_status_notification_t *)item; + /* Move ownership of fields from src to out_notif. */ + *out_notif = *src; + free(src); + return CSM_OK; +} + +csm_result_t csm_client_poll_async_response(csm_client_t *client, + csm_async_response_t *out_resp, + unsigned int timeout_ms) { + if (!client || !out_resp) return CSM_ERR_INVALID; + memset(out_resp, 0, sizeof(*out_resp)); + void *item = NULL; + csm_result_t r = csm_queue_pop(&client->async_queue, timeout_ms, &item, NULL); + if (r != CSM_OK) return r; + csm_async_response_t *src = (csm_async_response_t *)item; + *out_resp = *src; + free(src); + return CSM_OK; +} + +csm_result_t csm_client_last_server_error(const csm_client_t *client, + csm_server_error_t *out_err) { + if (!client || !out_err) return CSM_ERR_INVALID; + csm_client_t *mc = (csm_client_t *)client; + csm_mutex_lock(&mc->err_lock); + csm_result_t r = mc->has_server_error ? CSM_OK : CSM_ERR_STATE; + if (r == CSM_OK) *out_err = mc->last_server_error; + csm_mutex_unlock(&mc->err_lock); + return r; +} diff --git a/SDK/c/tests/mock_server.c b/SDK/c/tests/mock_server.c new file mode 100644 index 0000000..9665e2d --- /dev/null +++ b/SDK/c/tests/mock_server.c @@ -0,0 +1,558 @@ +/* mock_server.c - cross-platform implementation of the test mock server. */ + +#if !defined(_WIN32) +# ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 200809L +# endif +# ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE 1 +# endif +#endif + +#include "mock_server.h" + +#include +#include +#include + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +# include +# pragma comment(lib, "Ws2_32.lib") +typedef SOCKET ms_socket_t; +# define MS_INVALID_SOCKET INVALID_SOCKET +# define ms_close_socket(s) closesocket(s) +typedef CRITICAL_SECTION ms_mutex_t; +typedef CONDITION_VARIABLE ms_cond_t; +typedef HANDLE ms_thread_t; +static void ms_mutex_init(ms_mutex_t *m){InitializeCriticalSection(m);} +static void ms_mutex_destroy(ms_mutex_t *m){DeleteCriticalSection(m);} +static void ms_mutex_lock(ms_mutex_t *m){EnterCriticalSection(m);} +static void ms_mutex_unlock(ms_mutex_t *m){LeaveCriticalSection(m);} +static void ms_cond_init(ms_cond_t *c){InitializeConditionVariable(c);} +static void ms_cond_destroy(ms_cond_t *c){(void)c;} +static void ms_cond_signal(ms_cond_t *c){WakeConditionVariable(c);} +static int ms_cond_wait_ms(ms_cond_t *c, ms_mutex_t *m, unsigned int ms){ + return SleepConditionVariableCS(c, m, ms == 0 ? INFINITE : ms) ? 1 : 0; +} +#if 0 /* reserved for future use */ +static void ms_sleep_ms(unsigned int ms){Sleep(ms);} +#endif +#else +# include +# include +# include +# include +# include +# include +# include +# include +typedef int ms_socket_t; +# define MS_INVALID_SOCKET (-1) +# define ms_close_socket(s) close(s) +typedef pthread_mutex_t ms_mutex_t; +typedef pthread_cond_t ms_cond_t; +typedef pthread_t ms_thread_t; +static void ms_mutex_init(ms_mutex_t *m){pthread_mutex_init(m,NULL);} +static void ms_mutex_destroy(ms_mutex_t *m){pthread_mutex_destroy(m);} +static void ms_mutex_lock(ms_mutex_t *m){pthread_mutex_lock(m);} +static void ms_mutex_unlock(ms_mutex_t *m){pthread_mutex_unlock(m);} +static void ms_cond_init(ms_cond_t *c){pthread_cond_init(c,NULL);} +static void ms_cond_destroy(ms_cond_t *c){pthread_cond_destroy(c);} +static void ms_cond_signal(ms_cond_t *c){pthread_cond_signal(c);} +static int ms_cond_wait_ms(ms_cond_t *c, ms_mutex_t *m, unsigned int ms){ + if (ms == 0){pthread_cond_wait(c,m);return 1;} + struct timespec ts; clock_gettime(CLOCK_REALTIME,&ts); + ts.tv_sec += ms/1000; + ts.tv_nsec += (long)(ms%1000)*1000000L; + if (ts.tv_nsec>=1000000000L){ts.tv_sec+=ts.tv_nsec/1000000000L;ts.tv_nsec%=1000000000L;} + return pthread_cond_timedwait(c,m,&ts) == 0 ? 1 : 0; +} +#if 0 /* reserved for future use */ +static void ms_sleep_ms(unsigned int ms){ + struct timespec ts; ts.tv_sec=ms/1000; ts.tv_nsec=(long)(ms%1000)*1000000L; + nanosleep(&ts,NULL); +} +#endif +#endif + +#define MS_MAX_CLIENTS 16 +#define MS_HEADER 8 +#define MS_VER 0x01 + +/* --- response map --- */ +typedef struct ms_resp { + struct ms_resp *next; + char *cmd; + csm_packet_type_t type; + uint8_t *data; + size_t data_len; +} ms_resp_t; + +/* --- received command queue --- */ +typedef struct ms_msg { + struct ms_msg *next; + char *text; +} ms_msg_t; + +struct csm_mock_server { + ms_socket_t listen_sock; + uint16_t port; + int stop; + int thread_started; + ms_thread_t accept_thread; + + ms_mutex_t resp_lock; + ms_resp_t *responses; + + ms_mutex_t recv_lock; + ms_cond_t recv_cond; + ms_msg_t *msg_head; + ms_msg_t *msg_tail; + + ms_mutex_t client_lock; + ms_socket_t clients[MS_MAX_CLIENTS]; + + ms_mutex_t handler_lock; + int handler_count; + ms_cond_t handler_done; +}; + +static int ms_wsa_init(void) { +#if defined(_WIN32) + WSADATA d; return WSAStartup(MAKEWORD(2,2), &d) == 0 ? 0 : -1; +#else + return 0; +#endif +} +static void ms_wsa_cleanup(void) { +#if defined(_WIN32) + WSACleanup(); +#endif +} + +static void ms_pack_be32(uint8_t *b, uint32_t v) { + b[0]=(uint8_t)((v>>24)&0xFF); b[1]=(uint8_t)((v>>16)&0xFF); + b[2]=(uint8_t)((v>>8)&0xFF); b[3]=(uint8_t)(v&0xFF); +} +static uint32_t ms_unpack_be32(const uint8_t *b){ + return ((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|(uint32_t)b[3]; +} + +/* Encode header + payload into newly-allocated buffer; caller frees. */ +static uint8_t *ms_encode(csm_packet_type_t type, const void *data, size_t len, size_t *out_len) { + uint8_t *buf = (uint8_t *)malloc(MS_HEADER + len); + if (!buf) return NULL; + ms_pack_be32(buf, (uint32_t)len); + buf[4] = MS_VER; buf[5] = (uint8_t)type; buf[6] = 0; buf[7] = 0; + if (len) memcpy(buf + MS_HEADER, data, len); + *out_len = MS_HEADER + len; + return buf; +} + +static int ms_send_all(ms_socket_t s, const uint8_t *buf, size_t len) { + size_t total = 0; +#if defined(MSG_NOSIGNAL) + int flags = MSG_NOSIGNAL; +#else + int flags = 0; +#endif + while (total < len) { +#if defined(_WIN32) + int n = send(s, (const char *)buf + total, (int)(len - total), 0); + (void)flags; +#else + ssize_t n = send(s, buf + total, len - total, flags); +#endif + if (n <= 0) return -1; + total += (size_t)n; + } + return 0; +} + +static int ms_recv_all(ms_socket_t s, uint8_t *buf, size_t len) { + size_t total = 0; + while (total < len) { +#if defined(_WIN32) + int n = recv(s, (char *)buf + total, (int)(len - total), 0); +#else + ssize_t n = recv(s, buf + total, len - total, 0); +#endif + if (n <= 0) return -1; + total += (size_t)n; + } + return 0; +} + +/* --- Public API --- */ + +csm_mock_server_t *csm_mock_server_create(void) { + if (ms_wsa_init() != 0) return NULL; + csm_mock_server_t *s = (csm_mock_server_t *)calloc(1, sizeof(*s)); + if (!s) { ms_wsa_cleanup(); return NULL; } + s->listen_sock = MS_INVALID_SOCKET; + ms_mutex_init(&s->resp_lock); + ms_mutex_init(&s->recv_lock); + ms_cond_init(&s->recv_cond); + ms_mutex_init(&s->client_lock); + ms_mutex_init(&s->handler_lock); + ms_cond_init(&s->handler_done); + for (int i = 0; i < MS_MAX_CLIENTS; ++i) s->clients[i] = MS_INVALID_SOCKET; + return s; +} + +static void ms_handle_command(csm_mock_server_t *s, ms_socket_t conn, + const char *cmd) { + /* Look up custom response. */ + ms_mutex_lock(&s->resp_lock); + ms_resp_t *r = s->responses; + while (r) { + if (strcmp(r->cmd, cmd) == 0) { + size_t out_len = 0; + uint8_t *wire = ms_encode(r->type, r->data, r->data_len, &out_len); + ms_mutex_unlock(&s->resp_lock); + if (wire) { ms_send_all(conn, wire, out_len); free(wire); } + return; + } + r = r->next; + } + ms_mutex_unlock(&s->resp_lock); + + /* Built-in defaults. */ + size_t out_len = 0; + uint8_t *wire = NULL; + if (strcmp(cmd, "Ping") == 0) { + wire = ms_encode(CSM_PT_RESP, "Pong", 4, &out_len); + } else if (strcmp(cmd, "List") == 0) { + const char *txt = "AI\nDIO\nSystem"; + wire = ms_encode(CSM_PT_RESP, txt, strlen(txt), &out_len); + } else if (strncmp(cmd, "List API ", 9) == 0) { + char buf[256]; + snprintf(buf, sizeof(buf), "API: Start -> %s\nAPI: Stop -> %s", + cmd + 9, cmd + 9); + wire = ms_encode(CSM_PT_RESP, buf, strlen(buf), &out_len); + } else if (strncmp(cmd, "List State ", 11) == 0) { + char buf[256]; + snprintf(buf, sizeof(buf), "Idle <- %s\nRunning <- %s", + cmd + 11, cmd + 11); + wire = ms_encode(CSM_PT_RESP, buf, strlen(buf), &out_len); + } else if (strstr(cmd, "->") || strstr(cmd, "->")) { + wire = ms_encode(CSM_PT_CMD_RESP, "", 0, &out_len); + } else { + /* Generic async handshake. */ + wire = ms_encode(CSM_PT_CMD_RESP, "", 0, &out_len); + } + if (wire) { ms_send_all(conn, wire, out_len); free(wire); } +} + +#if defined(_WIN32) +static unsigned __stdcall +#else +static void * +#endif +ms_client_thread(void *arg) { + typedef struct { csm_mock_server_t *s; ms_socket_t conn; } ms_ctx_t; + ms_ctx_t *ctx = (ms_ctx_t *)arg; + csm_mock_server_t *s = ctx->s; + ms_socket_t conn = ctx->conn; + free(ctx); + + /* Send welcome INFO. */ + size_t wlen = 0; + uint8_t *welcome = ms_encode(CSM_PT_INFO, "Welcome to mock server", 22, &wlen); + if (welcome) { ms_send_all(conn, welcome, wlen); free(welcome); } + + while (!s->stop) { + uint8_t hdr[MS_HEADER]; + if (ms_recv_all(conn, hdr, MS_HEADER) != 0) break; + uint32_t data_len = ms_unpack_be32(hdr); + uint8_t *body = NULL; + if (data_len) { + body = (uint8_t *)malloc(data_len); + if (!body) break; + if (ms_recv_all(conn, body, data_len) != 0) { free(body); break; } + } + if (hdr[5] == CSM_PT_CMD) { + char *cmd = (char *)malloc(data_len + 1); + if (cmd) { + if (data_len) memcpy(cmd, body, data_len); + cmd[data_len] = '\0'; + + /* Trim trailing whitespace. */ + size_t L = strlen(cmd); + while (L && (cmd[L-1]==' '||cmd[L-1]=='\r'||cmd[L-1]=='\n'||cmd[L-1]=='\t')) + cmd[--L] = '\0'; + + /* Handle the command first (using the local copy). */ + ms_handle_command(s, conn, cmd); + + /* Then enqueue a copy for the test to inspect. */ + ms_msg_t *m = (ms_msg_t *)calloc(1, sizeof(*m)); + if (m) { + m->text = (char *)malloc(strlen(cmd) + 1); + if (m->text) { + strcpy(m->text, cmd); + ms_mutex_lock(&s->recv_lock); + if (s->msg_tail) s->msg_tail->next = m; + else s->msg_head = m; + s->msg_tail = m; + ms_cond_signal(&s->recv_cond); + ms_mutex_unlock(&s->recv_lock); + } else { + free(m); + } + } + free(cmd); + } + } + free(body); + } + + /* Remove from clients list. */ + ms_mutex_lock(&s->client_lock); + for (int i = 0; i < MS_MAX_CLIENTS; ++i) { + if (s->clients[i] == conn) { s->clients[i] = MS_INVALID_SOCKET; break; } + } + ms_mutex_unlock(&s->client_lock); + ms_close_socket(conn); + + ms_mutex_lock(&s->handler_lock); + s->handler_count--; + if (s->handler_count == 0) ms_cond_signal(&s->handler_done); + ms_mutex_unlock(&s->handler_lock); +#if defined(_WIN32) + return 0; +#else + return NULL; +#endif +} + +#if defined(_WIN32) +static unsigned __stdcall +#else +static void * +#endif +ms_accept_thread(void *arg) { + csm_mock_server_t *s = (csm_mock_server_t *)arg; + while (!s->stop) { + fd_set rfds; FD_ZERO(&rfds); FD_SET(s->listen_sock, &rfds); + struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 200 * 1000; + int sel = select((int)(s->listen_sock + 1), &rfds, NULL, NULL, &tv); + if (sel <= 0) continue; + struct sockaddr_in addr; socklen_t alen = sizeof(addr); + ms_socket_t conn = accept(s->listen_sock, (struct sockaddr *)&addr, &alen); + if (conn == MS_INVALID_SOCKET) continue; + ms_mutex_lock(&s->client_lock); + for (int i = 0; i < MS_MAX_CLIENTS; ++i) { + if (s->clients[i] == MS_INVALID_SOCKET) { s->clients[i] = conn; break; } + } + ms_mutex_unlock(&s->client_lock); + + typedef struct { csm_mock_server_t *s; ms_socket_t conn; } ms_ctx_t; + ms_ctx_t *ctx = (ms_ctx_t *)malloc(sizeof(*ctx)); + if (!ctx) { ms_close_socket(conn); continue; } + ctx->s = s; ctx->conn = conn; + + ms_mutex_lock(&s->handler_lock); + s->handler_count++; + ms_mutex_unlock(&s->handler_lock); + +#if defined(_WIN32) + HANDLE t = (HANDLE)_beginthreadex(NULL, 0, ms_client_thread, ctx, 0, NULL); + if (t) CloseHandle(t); + else { + free(ctx); ms_close_socket(conn); + ms_mutex_lock(&s->handler_lock); s->handler_count--; ms_mutex_unlock(&s->handler_lock); + } +#else + pthread_t t; + if (pthread_create(&t, NULL, ms_client_thread, ctx) == 0) { + pthread_detach(t); + } else { + free(ctx); ms_close_socket(conn); + ms_mutex_lock(&s->handler_lock); s->handler_count--; ms_mutex_unlock(&s->handler_lock); + } +#endif + } +#if defined(_WIN32) + return 0; +#else + return NULL; +#endif +} + +int csm_mock_server_start(csm_mock_server_t *s) { + if (!s) return -1; + s->listen_sock = socket(AF_INET, SOCK_STREAM, 0); + if (s->listen_sock == MS_INVALID_SOCKET) return -1; + int yes = 1; + setsockopt(s->listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes)); + struct sockaddr_in a; memset(&a, 0, sizeof(a)); + a.sin_family = AF_INET; + a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.sin_port = 0; + if (bind(s->listen_sock, (struct sockaddr *)&a, sizeof(a)) != 0) { + ms_close_socket(s->listen_sock); s->listen_sock = MS_INVALID_SOCKET; return -1; + } + socklen_t alen = sizeof(a); + getsockname(s->listen_sock, (struct sockaddr *)&a, &alen); + s->port = ntohs(a.sin_port); + if (listen(s->listen_sock, 8) != 0) { + ms_close_socket(s->listen_sock); s->listen_sock = MS_INVALID_SOCKET; return -1; + } + s->stop = 0; +#if defined(_WIN32) + s->accept_thread = (HANDLE)_beginthreadex(NULL, 0, ms_accept_thread, s, 0, NULL); + if (s->accept_thread == NULL) return -1; +#else + if (pthread_create(&s->accept_thread, NULL, ms_accept_thread, s) != 0) return -1; +#endif + s->thread_started = 1; + return 0; +} + +void csm_mock_server_stop(csm_mock_server_t *s) { + if (!s) return; + s->stop = 1; + if (s->listen_sock != MS_INVALID_SOCKET) { +#if defined(_WIN32) + shutdown(s->listen_sock, SD_BOTH); +#else + shutdown(s->listen_sock, SHUT_RDWR); +#endif + ms_close_socket(s->listen_sock); + s->listen_sock = MS_INVALID_SOCKET; + } + /* Close client sockets to wake handlers. */ + ms_mutex_lock(&s->client_lock); + for (int i = 0; i < MS_MAX_CLIENTS; ++i) { + if (s->clients[i] != MS_INVALID_SOCKET) { +#if defined(_WIN32) + shutdown(s->clients[i], SD_BOTH); +#else + shutdown(s->clients[i], SHUT_RDWR); +#endif + ms_close_socket(s->clients[i]); + s->clients[i] = MS_INVALID_SOCKET; + } + } + ms_mutex_unlock(&s->client_lock); + + if (s->thread_started) { +#if defined(_WIN32) + WaitForSingleObject(s->accept_thread, 2000); + CloseHandle(s->accept_thread); +#else + pthread_join(s->accept_thread, NULL); +#endif + s->thread_started = 0; + } + /* Wait for handlers to finish (best-effort, brief). */ + ms_mutex_lock(&s->handler_lock); + int waited = 0; + while (s->handler_count > 0 && waited < 20) { + ms_cond_wait_ms(&s->handler_done, &s->handler_lock, 100); + waited++; + } + ms_mutex_unlock(&s->handler_lock); +} + +void csm_mock_server_destroy(csm_mock_server_t *s) { + if (!s) return; + csm_mock_server_stop(s); + ms_mutex_lock(&s->resp_lock); + ms_resp_t *r = s->responses; + while (r) { ms_resp_t *n = r->next; free(r->cmd); free(r->data); free(r); r = n; } + s->responses = NULL; + ms_mutex_unlock(&s->resp_lock); + + ms_mutex_lock(&s->recv_lock); + ms_msg_t *m = s->msg_head; + while (m) { ms_msg_t *n = m->next; free(m->text); free(m); m = n; } + s->msg_head = s->msg_tail = NULL; + ms_mutex_unlock(&s->recv_lock); + + ms_mutex_destroy(&s->resp_lock); + ms_mutex_destroy(&s->recv_lock); + ms_cond_destroy(&s->recv_cond); + ms_mutex_destroy(&s->client_lock); + ms_mutex_destroy(&s->handler_lock); + ms_cond_destroy(&s->handler_done); + free(s); + ms_wsa_cleanup(); +} + +uint16_t csm_mock_server_port(const csm_mock_server_t *s) { + return s ? s->port : 0; +} + +static void ms_set_response_typed(csm_mock_server_t *s, const char *cmd_text, + csm_packet_type_t type, const char *data) { + ms_resp_t *r = (ms_resp_t *)calloc(1, sizeof(*r)); + if (!r) return; + r->cmd = (char *)malloc(strlen(cmd_text) + 1); + if (!r->cmd) { + free(r); + return; + } + strcpy(r->cmd, cmd_text); + r->type = type; + size_t dl = strlen(data); + if (dl > 0) { + r->data = (uint8_t *)malloc(dl); + if (!r->data) { + free(r->cmd); + free(r); + return; + } + memcpy(r->data, data, dl); + } + r->data_len = dl; + ms_mutex_lock(&s->resp_lock); + r->next = s->responses; + s->responses = r; + ms_mutex_unlock(&s->resp_lock); +} + +void csm_mock_server_set_response(csm_mock_server_t *s, const char *cmd, const char *resp) { + ms_set_response_typed(s, cmd, CSM_PT_RESP, resp); +} +void csm_mock_server_set_error_response(csm_mock_server_t *s, const char *cmd, const char *err) { + ms_set_response_typed(s, cmd, CSM_PT_ERROR, err); +} + +void csm_mock_server_push_status(csm_mock_server_t *s, const char *payload) { + if (!s || !payload) return; + size_t len = 0; + uint8_t *wire = ms_encode(CSM_PT_STATUS, payload, strlen(payload), &len); + if (!wire) return; + ms_mutex_lock(&s->client_lock); + for (int i = 0; i < MS_MAX_CLIENTS; ++i) { + if (s->clients[i] != MS_INVALID_SOCKET) { + ms_send_all(s->clients[i], wire, len); + } + } + ms_mutex_unlock(&s->client_lock); + free(wire); +} + +char *csm_mock_server_get_received(csm_mock_server_t *s, unsigned int timeout_ms) { + if (!s) return NULL; + ms_mutex_lock(&s->recv_lock); + if (!s->msg_head) { + ms_cond_wait_ms(&s->recv_cond, &s->recv_lock, timeout_ms); + } + char *out = NULL; + if (s->msg_head) { + ms_msg_t *m = s->msg_head; + s->msg_head = m->next; + if (!s->msg_head) s->msg_tail = NULL; + out = m->text; + free(m); + } + ms_mutex_unlock(&s->recv_lock); + return out; +} diff --git a/SDK/c/tests/mock_server.h b/SDK/c/tests/mock_server.h new file mode 100644 index 0000000..1c8f611 --- /dev/null +++ b/SDK/c/tests/mock_server.h @@ -0,0 +1,48 @@ +/* mock_server.h - In-process TCP server emulating a CSM-TCP-Router for tests. + * + * Mirrors the Python `tests/conftest.py` MockServer fixture. + */ +#ifndef CSM_MOCK_SERVER_H +#define CSM_MOCK_SERVER_H + +#include "csm_tcp_router_client.h" + +#include + +typedef struct csm_mock_server csm_mock_server_t; + +/** Create a stopped mock server bound to 127.0.0.1; the actual port is + * assigned by the OS in csm_mock_server_start(). */ +csm_mock_server_t *csm_mock_server_create(void); + +/** Free a (running or stopped) mock server. */ +void csm_mock_server_destroy(csm_mock_server_t *s); + +/** Bind to 127.0.0.1, an ephemeral port, and start the accept thread. */ +int csm_mock_server_start(csm_mock_server_t *s); + +/** Stop the accept thread and close all client connections. */ +void csm_mock_server_stop(csm_mock_server_t *s); + +/** Return the port the server is listening on (valid after start()). */ +uint16_t csm_mock_server_port(const csm_mock_server_t *s); + +/** Register a custom RESP reply for an exact command string. */ +void csm_mock_server_set_response(csm_mock_server_t *s, + const char *cmd_text, + const char *resp_text); + +/** Register an ERROR reply for an exact command string. */ +void csm_mock_server_set_error_response(csm_mock_server_t *s, + const char *cmd_text, + const char *error_text); + +/** Push a STATUS packet to all currently connected clients. */ +void csm_mock_server_push_status(csm_mock_server_t *s, const char *payload); + +/** Pop the next received command, blocking up to *timeout_ms*. The returned + * string is owned by the caller and must be freed with csm_string_free. + * Returns NULL on timeout. */ +char *csm_mock_server_get_received(csm_mock_server_t *s, unsigned int timeout_ms); + +#endif /* CSM_MOCK_SERVER_H */ diff --git a/SDK/c/tests/test_client.c b/SDK/c/tests/test_client.c new file mode 100644 index 0000000..279447d --- /dev/null +++ b/SDK/c/tests/test_client.c @@ -0,0 +1,51 @@ +/* test_client.c - Unit-level tests for the client object lifecycle that do + * not require a running mock server. */ +#include "csm_tcp_router_client.h" +#include "test_harness.h" + +#include + +CSM_TEST(test_client_create_destroy) { + csm_client_t *c = csm_client_create(); + CSM_ASSERT(c != NULL); + CSM_ASSERT_EQ_INT(csm_client_is_connected(c), 0); + csm_client_destroy(c); +} + +CSM_TEST(test_destroy_null_safe) { + csm_client_destroy(NULL); +} + +CSM_TEST(test_send_when_not_connected_returns_connection_error) { + csm_client_t *c = csm_client_create(); + CSM_ASSERT(c != NULL); + csm_command_response_t resp = {0}; + csm_result_t r = csm_client_send_and_wait(c, "Ping", 100, &resp); + CSM_ASSERT_EQ_INT(r, CSM_ERR_CONNECTION); + csm_command_response_dispose(&resp); + csm_client_destroy(c); +} + +CSM_TEST(test_invalid_args_rejected) { + csm_client_t *c = csm_client_create(); + CSM_ASSERT_EQ_INT(csm_client_connect(c, NULL, 1234, 100), CSM_ERR_INVALID); + CSM_ASSERT_EQ_INT(csm_client_send_and_wait(NULL, "x", 100, NULL), CSM_ERR_INVALID); + CSM_ASSERT_EQ_INT(csm_client_send_and_wait(c, NULL, 100, NULL), CSM_ERR_INVALID); + char *out = NULL; + CSM_ASSERT_EQ_INT(csm_client_list_api(c, NULL, &out, 100), CSM_ERR_INVALID); + csm_client_destroy(c); +} + +CSM_TEST(test_wait_for_server_unreachable_times_out) { + /* Pick an arbitrary high port that should not be in use. */ + csm_result_t r = csm_client_wait_for_server("127.0.0.1", 1, 200, 50); + CSM_ASSERT_EQ_INT(r, CSM_ERR_TIMEOUT); +} + +CSM_TEST(test_connect_unreachable_returns_connection_error) { + csm_client_t *c = csm_client_create(); + csm_result_t r = csm_client_connect(c, "127.0.0.1", 1, 300); + /* Either CSM_ERR_CONNECTION (refused) or CSM_ERR_TIMEOUT depending on OS. */ + CSM_ASSERT(r == CSM_ERR_CONNECTION || r == CSM_ERR_TIMEOUT); + csm_client_destroy(c); +} diff --git a/SDK/c/tests/test_harness.h b/SDK/c/tests/test_harness.h new file mode 100644 index 0000000..ad123c3 --- /dev/null +++ b/SDK/c/tests/test_harness.h @@ -0,0 +1,62 @@ +/* test_harness.h - Minimal in-process unit-test harness used by the + * csm-tcp-router-client C SDK tests. + * + * Tests register themselves via the CSM_TEST() macro; the runner in + * test_main.c picks them up via the link-time TESTS array, executes them, + * and prints a summary. Failures abort the current test only; assertions + * use longjmp to unwind back to the runner. + */ +#ifndef CSM_TEST_HARNESS_H +#define CSM_TEST_HARNESS_H + +#include +#include +#include + +typedef void (*csm_test_fn)(void); + +typedef struct { + const char *name; + csm_test_fn fn; +} csm_test_t; + +/* Provided by test_main.c. */ +extern jmp_buf csm_test_jmp; +extern int csm_test_failed; +extern int csm_test_assertions; + +#define CSM_TEST_FAIL(...) do { \ + csm_test_failed = 1; \ + fprintf(stderr, " FAIL %s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + longjmp(csm_test_jmp, 1); \ +} while (0) + +#define CSM_ASSERT(cond) do { \ + csm_test_assertions++; \ + if (!(cond)) CSM_TEST_FAIL("assertion failed: %s", #cond); \ +} while (0) + +#define CSM_ASSERT_EQ_INT(a, b) do { \ + csm_test_assertions++; \ + long long _a = (long long)(a), _b = (long long)(b); \ + if (_a != _b) \ + CSM_TEST_FAIL("expected %lld, got %lld (%s == %s)", \ + _b, _a, #a, #b); \ +} while (0) + +#define CSM_ASSERT_EQ_STR(a, b) do { \ + csm_test_assertions++; \ + const char *_a = (a), *_b = (b); \ + if (_a == NULL || _b == NULL || strcmp(_a, _b) != 0) \ + CSM_TEST_FAIL("expected \"%s\", got \"%s\"", \ + _b ? _b : "(null)", _a ? _a : "(null)"); \ +} while (0) + +/* Define a test function. The runner declares each test as extern via the + * CSM_TEST_EXTERN macro and registers it in its tests table. */ +#define CSM_TEST(name) void name(void) +#define CSM_TEST_EXTERN(name) extern void name(void) + +#endif /* CSM_TEST_HARNESS_H */ diff --git a/SDK/c/tests/test_integration.c b/SDK/c/tests/test_integration.c new file mode 100644 index 0000000..24f1a86 --- /dev/null +++ b/SDK/c/tests/test_integration.c @@ -0,0 +1,165 @@ +/* test_integration.c - End-to-end tests against the in-process MockServer. */ + +#if !defined(_WIN32) +# ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 200809L +# endif +#endif + +#include "csm_tcp_router_client.h" +#include "mock_server.h" +#include "test_harness.h" + +#include +#include +#include + +#if defined(_WIN32) +# include +static void it_sleep_ms(unsigned int ms) { Sleep(ms); } +#else +# include +static void it_sleep_ms(unsigned int ms) { + struct timespec ts; ts.tv_sec = ms/1000; ts.tv_nsec = (long)(ms%1000)*1000000L; + nanosleep(&ts, NULL); +} +#endif + +/* Helper: spin up server + connect a client. */ +static void it_setup(csm_mock_server_t **out_s, csm_client_t **out_c) { + csm_mock_server_t *s = csm_mock_server_create(); + CSM_ASSERT(s != NULL); + CSM_ASSERT_EQ_INT(csm_mock_server_start(s), 0); + csm_client_t *c = csm_client_create(); + CSM_ASSERT(c != NULL); + csm_result_t r = csm_client_connect(c, "127.0.0.1", + csm_mock_server_port(s), 2000); + CSM_ASSERT_EQ_INT(r, CSM_OK); + *out_s = s; *out_c = c; +} + +static void it_teardown(csm_mock_server_t *s, csm_client_t *c) { + csm_client_destroy(c); + csm_mock_server_destroy(s); +} + +CSM_TEST(it_ping) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + double ms = 0; + csm_result_t r = csm_client_ping(c, 1000, &ms); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT(ms >= 0); + it_teardown(s, c); +} + +CSM_TEST(it_list_modules) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + char *txt = NULL; + csm_result_t r = csm_client_list_modules(c, &txt, 1000); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT_EQ_STR(txt, "AI\nDIO\nSystem"); + csm_string_free(txt); + it_teardown(s, c); +} + +CSM_TEST(it_list_api_includes_module_name) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + char *txt = NULL; + csm_result_t r = csm_client_list_api(c, "DAQmx", &txt, 1000); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT(strstr(txt, "DAQmx") != NULL); + csm_string_free(txt); + it_teardown(s, c); +} + +CSM_TEST(it_send_and_wait_custom_response) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + csm_mock_server_set_response(s, "API: Read -@ DAQmx", "42"); + csm_command_response_t resp = {0}; + csm_result_t r = csm_client_send_and_wait(c, "API: Read -@ DAQmx", 1000, &resp); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT_EQ_INT(resp.raw_len, 2); + CSM_ASSERT(memcmp(resp.raw, "42", 2) == 0); + csm_command_response_dispose(&resp); + it_teardown(s, c); +} + +CSM_TEST(it_server_error_propagated) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + csm_mock_server_set_error_response(s, "Bad", "[Error: 42] bad command"); + csm_command_response_t resp = {0}; + csm_result_t r = csm_client_send_and_wait(c, "Bad", 1000, &resp); + CSM_ASSERT_EQ_INT(r, CSM_ERR_SERVER); + csm_server_error_t err = {{0}, {0}}; + CSM_ASSERT_EQ_INT(csm_client_last_server_error(c, &err), CSM_OK); + CSM_ASSERT_EQ_STR(err.code, "42"); + CSM_ASSERT(strstr(err.message, "bad command") != NULL); + csm_command_response_dispose(&resp); + it_teardown(s, c); +} + +CSM_TEST(it_post_async_handshake) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + csm_result_t r = csm_client_post(c, "API: Start -> DAQmx", 1000); + CSM_ASSERT_EQ_INT(r, CSM_OK); + char *got = csm_mock_server_get_received(s, 500); + CSM_ASSERT(got != NULL); + CSM_ASSERT_EQ_STR(got, "API: Start -> DAQmx"); + csm_string_free(got); + it_teardown(s, c); +} + +static void it_status_cb(const csm_status_notification_t *n, void *ud) { + int *count = (int *)ud; + (*count)++; + /* Sanity-check parsed fields. */ + (void)n; +} + +CSM_TEST(it_subscribe_status_invokes_callback) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + int count = 0; + csm_result_t r = csm_client_subscribe_status(c, "Status", "DAQmx", + it_status_cb, &count, 1000); + CSM_ASSERT_EQ_INT(r, CSM_OK); + /* Drain handshake from received queue. */ + char *cmd = csm_mock_server_get_received(s, 500); + csm_string_free(cmd); + /* Push a STATUS notification matching the subscription. */ + csm_mock_server_push_status(s, "Status >> 1.23 <- DAQmx"); + /* Wait for callback (poll up to 1s). */ + for (int i = 0; i < 100 && count == 0; ++i) it_sleep_ms(10); + CSM_ASSERT(count >= 1); + + /* Same notification should also be available via polling. */ + csm_status_notification_t n = {0}; + r = csm_client_poll_status(c, &n, 500); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT_EQ_STR(n.status_name, "Status"); + CSM_ASSERT_EQ_STR(n.module_name, "DAQmx"); + CSM_ASSERT_EQ_STR(n.data, "1.23"); + csm_status_notification_dispose(&n); + + csm_client_unsubscribe_status(c, "Status", "DAQmx", 1000); + it_teardown(s, c); +} + +CSM_TEST(it_disconnect_unblocks_waiters) { + csm_mock_server_t *s = NULL; csm_client_t *c = NULL; + it_setup(&s, &c); + /* Send a command that has no canned response; mock returns CMD_RESP. */ + /* For this test simply disconnect immediately and verify subsequent send fails. */ + csm_client_disconnect(c); + csm_command_response_t resp = {0}; + csm_result_t r = csm_client_send_and_wait(c, "Ping", 200, &resp); + CSM_ASSERT_EQ_INT(r, CSM_ERR_CONNECTION); + csm_command_response_dispose(&resp); + it_teardown(s, c); +} diff --git a/SDK/c/tests/test_main.c b/SDK/c/tests/test_main.c new file mode 100644 index 0000000..4a6628b --- /dev/null +++ b/SDK/c/tests/test_main.c @@ -0,0 +1,94 @@ +/* test_main.c - Runner for the C SDK test suite. */ +#include "test_harness.h" + +#include +#include +#include + +jmp_buf csm_test_jmp; +int csm_test_failed = 0; +int csm_test_assertions = 0; + +/* --- test_protocol.c --- */ +CSM_TEST_EXTERN(test_header_size_constant); +CSM_TEST_EXTERN(test_encode_decode_roundtrip); +CSM_TEST_EXTERN(test_encode_empty_payload); +CSM_TEST_EXTERN(test_encode_buffer_too_small); +CSM_TEST_EXTERN(test_decode_header_bad_size); +CSM_TEST_EXTERN(test_parse_packet_unknown_type_maps_to_info); +CSM_TEST_EXTERN(test_parse_packet_length_mismatch); +CSM_TEST_EXTERN(test_result_str_known); + +/* --- test_client.c --- */ +CSM_TEST_EXTERN(test_client_create_destroy); +CSM_TEST_EXTERN(test_destroy_null_safe); +CSM_TEST_EXTERN(test_send_when_not_connected_returns_connection_error); +CSM_TEST_EXTERN(test_invalid_args_rejected); +CSM_TEST_EXTERN(test_wait_for_server_unreachable_times_out); +CSM_TEST_EXTERN(test_connect_unreachable_returns_connection_error); + +/* --- test_integration.c --- */ +CSM_TEST_EXTERN(it_ping); +CSM_TEST_EXTERN(it_list_modules); +CSM_TEST_EXTERN(it_list_api_includes_module_name); +CSM_TEST_EXTERN(it_send_and_wait_custom_response); +CSM_TEST_EXTERN(it_server_error_propagated); +CSM_TEST_EXTERN(it_post_async_handshake); +CSM_TEST_EXTERN(it_subscribe_status_invokes_callback); +CSM_TEST_EXTERN(it_disconnect_unblocks_waiters); + +static const csm_test_t TESTS[] = { + {"test_header_size_constant", test_header_size_constant}, + {"test_encode_decode_roundtrip", test_encode_decode_roundtrip}, + {"test_encode_empty_payload", test_encode_empty_payload}, + {"test_encode_buffer_too_small", test_encode_buffer_too_small}, + {"test_decode_header_bad_size", test_decode_header_bad_size}, + {"test_parse_packet_unknown_type_maps_to_info", test_parse_packet_unknown_type_maps_to_info}, + {"test_parse_packet_length_mismatch", test_parse_packet_length_mismatch}, + {"test_result_str_known", test_result_str_known}, + {"test_client_create_destroy", test_client_create_destroy}, + {"test_destroy_null_safe", test_destroy_null_safe}, + {"test_send_when_not_connected_returns_connection_error", test_send_when_not_connected_returns_connection_error}, + {"test_invalid_args_rejected", test_invalid_args_rejected}, + {"test_wait_for_server_unreachable_times_out", test_wait_for_server_unreachable_times_out}, + {"test_connect_unreachable_returns_connection_error", test_connect_unreachable_returns_connection_error}, + {"it_ping", it_ping}, + {"it_list_modules", it_list_modules}, + {"it_list_api_includes_module_name", it_list_api_includes_module_name}, + {"it_send_and_wait_custom_response", it_send_and_wait_custom_response}, + {"it_server_error_propagated", it_server_error_propagated}, + {"it_post_async_handshake", it_post_async_handshake}, + {"it_subscribe_status_invokes_callback", it_subscribe_status_invokes_callback}, + {"it_disconnect_unblocks_waiters", it_disconnect_unblocks_waiters}, +}; + +int main(int argc, char **argv) { + const char *only = (argc > 1) ? argv[1] : NULL; + /* `volatile` ensures these survive the longjmp performed by failing + * assertions inside individual test bodies. */ + volatile int passed = 0, failed = 0, skipped = 0; + volatile int total_assertions = 0; + size_t n = sizeof(TESTS) / sizeof(TESTS[0]); + volatile size_t i = 0; + for (; i < n; ++i) { + if (only && strcmp(only, TESTS[i].name) != 0) { skipped++; continue; } + printf("[RUN ] %s\n", TESTS[i].name); + csm_test_failed = 0; + int before = csm_test_assertions; + if (setjmp(csm_test_jmp) == 0) { + TESTS[i].fn(); + } + int delta = csm_test_assertions - before; + total_assertions += delta; + if (csm_test_failed) { + printf("[FAIL] %s (%d asserts)\n", TESTS[i].name, delta); + failed++; + } else { + printf("[ OK ] %s (%d asserts)\n", TESTS[i].name, delta); + passed++; + } + } + printf("\nResults: %d passed, %d failed, %d skipped (%d assertions)\n", + passed, failed, skipped, total_assertions); + return failed == 0 ? 0 : 1; +} diff --git a/SDK/c/tests/test_protocol.c b/SDK/c/tests/test_protocol.c new file mode 100644 index 0000000..03f509d --- /dev/null +++ b/SDK/c/tests/test_protocol.c @@ -0,0 +1,84 @@ +/* test_protocol.c - Unit tests for the protocol codec. */ +#include "csm_tcp_router_client.h" +#include "test_harness.h" + +#include + +CSM_TEST(test_header_size_constant) { + CSM_ASSERT_EQ_INT(CSM_HEADER_SIZE, 8); + CSM_ASSERT_EQ_INT(CSM_PROTOCOL_VERSION, 0x01); +} + +CSM_TEST(test_encode_decode_roundtrip) { + const char *payload = "Hello"; + uint8_t buf[64]; + size_t out_len = 0; + csm_result_t r = csm_encode_packet(payload, 5, CSM_PT_CMD, 0, 0, + buf, sizeof(buf), &out_len); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT_EQ_INT(out_len, 8 + 5); + + /* Header bytes: big-endian length, version, type, flag1, flag2. */ + CSM_ASSERT_EQ_INT(buf[0], 0); + CSM_ASSERT_EQ_INT(buf[1], 0); + CSM_ASSERT_EQ_INT(buf[2], 0); + CSM_ASSERT_EQ_INT(buf[3], 5); + CSM_ASSERT_EQ_INT(buf[4], CSM_PROTOCOL_VERSION); + CSM_ASSERT_EQ_INT(buf[5], CSM_PT_CMD); + CSM_ASSERT(memcmp(buf + 8, "Hello", 5) == 0); + + uint32_t len; uint8_t ver, type, f1, f2; + r = csm_decode_header(buf, 8, &len, &ver, &type, &f1, &f2); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT_EQ_INT(len, 5); + CSM_ASSERT_EQ_INT(ver, 1); + CSM_ASSERT_EQ_INT(type, CSM_PT_CMD); +} + +CSM_TEST(test_encode_empty_payload) { + uint8_t buf[16]; + size_t out_len = 0; + csm_result_t r = csm_encode_packet(NULL, 0, CSM_PT_INFO, 0, 0, + buf, sizeof(buf), &out_len); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT_EQ_INT(out_len, 8); +} + +CSM_TEST(test_encode_buffer_too_small) { + uint8_t buf[4]; + size_t out_len = 0; + csm_result_t r = csm_encode_packet("X", 1, CSM_PT_CMD, 0, 0, + buf, sizeof(buf), &out_len); + CSM_ASSERT_EQ_INT(r, CSM_ERR_INVALID); +} + +CSM_TEST(test_decode_header_bad_size) { + uint8_t hdr[4] = {0}; + csm_result_t r = csm_decode_header(hdr, 4, NULL, NULL, NULL, NULL, NULL); + CSM_ASSERT_EQ_INT(r, CSM_ERR_PROTOCOL); +} + +CSM_TEST(test_parse_packet_unknown_type_maps_to_info) { + uint8_t hdr[8] = {0,0,0,3, 0x01, 0xFE /* unknown */, 0, 0}; + uint8_t body[3] = {'a','b','c'}; + csm_packet_t pkt = {0}; + csm_result_t r = csm_parse_packet(hdr, 8, body, 3, &pkt); + CSM_ASSERT_EQ_INT(r, CSM_OK); + CSM_ASSERT_EQ_INT(pkt.type, CSM_PT_INFO); + CSM_ASSERT_EQ_INT(pkt.data_len, 3); + CSM_ASSERT(memcmp(pkt.data, "abc", 3) == 0); + csm_packet_dispose(&pkt); +} + +CSM_TEST(test_parse_packet_length_mismatch) { + uint8_t hdr[8] = {0,0,0,5, 0x01, CSM_PT_RESP, 0, 0}; + uint8_t body[3] = {'a','b','c'}; + csm_packet_t pkt = {0}; + csm_result_t r = csm_parse_packet(hdr, 8, body, 3, &pkt); + CSM_ASSERT_EQ_INT(r, CSM_ERR_PROTOCOL); +} + +CSM_TEST(test_result_str_known) { + CSM_ASSERT(strcmp(csm_result_str(CSM_OK), "OK") == 0); + CSM_ASSERT(strstr(csm_result_str(CSM_ERR_TIMEOUT), "imeout") != NULL); +} diff --git a/SDK/c/vs2026/README.md b/SDK/c/vs2026/README.md new file mode 100644 index 0000000..77376d2 --- /dev/null +++ b/SDK/c/vs2026/README.md @@ -0,0 +1,61 @@ +# Visual Studio 2026 build files + +This directory contains a Visual Studio 2026 (VS 18) solution and projects +for building the C SDK and its test suite directly from the IDE on +Windows. The CMake build at `SDK/c/CMakeLists.txt` remains the +authoritative cross-platform build; these project files are provided as a +convenience for Windows developers who prefer the VS IDE. + +## Layout + +``` +vs2026/ +├── csm_tcp_router_client.sln – solution file +├── csm_tcp_router_client/ +│ └── csm_tcp_router_client.vcxproj – static library project +└── csm_tcp_router_client.tests/ + └── csm_tcp_router_client.tests.vcxproj – test executable +``` + +## Toolset + +| Setting | Value | +|-------------------|-----------------------------------------| +| Solution format | Visual Studio 18 (2026) | +| Platform toolset | `v144` (Visual Studio 2026 C/C++) | +| C language | `/std:c11` (`stdc11` MSBuild metadata) | +| Configurations | Debug, Release | +| Platforms | Win32 (x86), x64 | +| Runtime | `/MT[d]` (statically linked CRT) | +| Subsystem (tests) | Console | +| Linked libraries | `Ws2_32.lib` | + +## Building + +Open `csm_tcp_router_client.sln` in Visual Studio 2026 and build the +solution (Ctrl+Shift+B), or from a developer command prompt: + +```cmd +msbuild csm_tcp_router_client.sln ^ + /p:Configuration=Release /p:Platform=x64 +``` + +## Running tests + +After building, run the test executable directly: + +```cmd +build\x64\Release\csm_tcp_router_client.tests\csm_tcp_router_client.tests.exe +``` + +Exit code `0` indicates all tests passed; any other exit code indicates +one or more failures (failed test names are printed to stderr). + +## Why VS2026 and not an older version? + +The user explicitly requested VS2026 (the latest Visual Studio at the +time the SDK was added). The project files use the new `v144` platform +toolset shipped with VS 2026; older VS versions will refuse to load +them. If you need to build with an older VS, prefer the `CMakeLists.txt` +in the SDK root – it works with every supported MSVC version (and on +Linux / macOS). diff --git a/SDK/c/vs2026/csm_tcp_router_client.sln b/SDK/c/vs2026/csm_tcp_router_client.sln new file mode 100644 index 0000000..08a0b08 --- /dev/null +++ b/SDK/c/vs2026/csm_tcp_router_client.sln @@ -0,0 +1,43 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.0.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "csm_tcp_router_client", "csm_tcp_router_client\csm_tcp_router_client.vcxproj", "{DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "csm_tcp_router_client.tests", "csm_tcp_router_client.tests\csm_tcp_router_client.tests.vcxproj", "{92C7CD36-6916-46D6-803F-137C11E423CF}" + ProjectSection(ProjectDependencies) = postProject + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD} = {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Debug|x64.ActiveCfg = Debug|x64 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Debug|x64.Build.0 = Debug|x64 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Release|x64.ActiveCfg = Release|x64 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Release|x64.Build.0 = Release|x64 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Debug|Win32.ActiveCfg = Debug|Win32 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Debug|Win32.Build.0 = Debug|Win32 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Release|Win32.ActiveCfg = Release|Win32 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD}.Release|Win32.Build.0 = Release|Win32 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Debug|x64.ActiveCfg = Debug|x64 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Debug|x64.Build.0 = Debug|x64 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Release|x64.ActiveCfg = Release|x64 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Release|x64.Build.0 = Release|x64 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Debug|Win32.ActiveCfg = Debug|Win32 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Debug|Win32.Build.0 = Debug|Win32 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Release|Win32.ActiveCfg = Release|Win32 + {92C7CD36-6916-46D6-803F-137C11E423CF}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1405610E-2603-4EE0-A392-54886017A89E} + EndGlobalSection +EndGlobal diff --git a/SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj b/SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj new file mode 100644 index 0000000..9ea1c0b --- /dev/null +++ b/SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj @@ -0,0 +1,94 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + 18.0 + {92C7CD36-6916-46D6-803F-137C11E423CF} + csm_tcp_router_client_tests + Win32Proj + 10.0 + + + + + + Application + v144 + Unicode + true + false + + + + + + $(SolutionDir)build\$(Platform)\$(Configuration)\ + $(SolutionDir)build\$(Platform)\$(Configuration)\$(ProjectName)\ + + + + + $(ProjectDir)..\..\include;$(ProjectDir)..\..\tests;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _DEBUG;%(PreprocessorDefinitions) + NDEBUG;%(PreprocessorDefinitions) + Level4 + true + true + CompileAsC + stdc11 + MultiThreadedDebug + MultiThreaded + Disabled + MaxSpeed + + + Console + Ws2_32.lib;%(AdditionalDependencies) + true + + + + + + + + + + + + + + + + + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD} + + + + + diff --git a/SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj.filters b/SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj.filters new file mode 100644 index 0000000..8c83bd3 --- /dev/null +++ b/SDK/c/vs2026/csm_tcp_router_client.tests/csm_tcp_router_client.tests.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + + + {93995380-89BD-4B04-88EB-625FBE52EBFB} + + + + Source Files + Source Files + Source Files + Source Files + Source Files + + + Header Files + Header Files + + diff --git a/SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj b/SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj new file mode 100644 index 0000000..2684b41 --- /dev/null +++ b/SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj @@ -0,0 +1,87 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + 18.0 + {DAE3271A-FD3C-4E9C-A4CC-54DF2EB32AFD} + csm_tcp_router_client + Win32Proj + 10.0 + + + + + + StaticLibrary + v144 + Unicode + true + false + true + + + + + + $(SolutionDir)build\$(Platform)\$(Configuration)\ + $(SolutionDir)build\$(Platform)\$(Configuration)\$(ProjectName)\ + + + + + $(ProjectDir)..\..\include;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;_CRT_SECURE_NO_WARNINGS;CSM_BUILD_LIBRARY;%(PreprocessorDefinitions) + _DEBUG;%(PreprocessorDefinitions) + NDEBUG;%(PreprocessorDefinitions) + Level4 + true + true + CompileAsC + stdc11 + MultiThreadedDebug + MultiThreaded + Disabled + MaxSpeed + true + true + + + Ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + + + + diff --git a/SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj.filters b/SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj.filters new file mode 100644 index 0000000..810a992 --- /dev/null +++ b/SDK/c/vs2026/csm_tcp_router_client/csm_tcp_router_client.vcxproj.filters @@ -0,0 +1,23 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + c;cpp + + + {93995380-89BD-4B04-88EB-625FBE52EBFB} + h + + + + + Source Files + + + + + Header Files + + + From 688ca013e66c5dac2516986e5b366adb06c7c86e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:45:00 +0800 Subject: [PATCH 08/12] Fix INADDR_LOOPBACK undeclared on macOS in mock_server.c (#41) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/eab9769d-1260-4715-8d72-1e3241719ef1 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- SDK/c/tests/mock_server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SDK/c/tests/mock_server.c b/SDK/c/tests/mock_server.c index 9665e2d..b16708d 100644 --- a/SDK/c/tests/mock_server.c +++ b/SDK/c/tests/mock_server.c @@ -79,6 +79,10 @@ static void ms_sleep_ms(unsigned int ms){ #endif #endif +#ifndef INADDR_LOOPBACK +# define INADDR_LOOPBACK 0x7F000001UL +#endif + #define MS_MAX_CLIENTS 16 #define MS_HEADER 8 #define MS_VER 0x01 From 6fc5eff34e9b3ac3b4c28d3b16b2ab197940aa8c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:35:30 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=E5=B0=86=20SDK/=20=E4=B8=8B=E6=89=80?= =?UTF-8?q?=E6=9C=89=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=BA=E4=B8=AD=E6=96=87=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * translate: convert all C SDK comments from English to Chinese Translated all code comments in 11 C SDK files from English to Chinese, preserving all code logic, variable names, function names, string literals, Doxygen tags (@param, @return, etc.) and technical proper names (TCP, CSM, WSA, POSIX, BSD, Win32, Winsock2, pthreads, CMake, NUL, DLL, etc.). Files translated: - SDK/c/include/csm_tcp_router_client.h - SDK/c/src/csm_tcp_router_client.c - SDK/c/examples/basic_usage.c - SDK/c/examples/subscribe_status.c - SDK/c/tests/test_harness.h - SDK/c/tests/mock_server.h - SDK/c/tests/mock_server.c - SDK/c/tests/test_main.c - SDK/c/tests/test_client.c - SDK/c/tests/test_protocol.c - SDK/c/tests/test_integration.c Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * translate: convert all C# SDK comments from English to Chinese Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * translate: convert all Python SDK comments from English to Chinese Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * translate: convert all Python SDK comments from English to Chinese (partial) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/097602f2-f319-49ea-84a2-3c8a30aa0915 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- SDK/c/examples/basic_usage.c | 16 +- SDK/c/examples/subscribe_status.c | 4 +- SDK/c/include/csm_tcp_router_client.h | 266 +++++++++--------- SDK/c/src/csm_tcp_router_client.c | 207 +++++++------- SDK/c/tests/mock_server.c | 34 +-- SDK/c/tests/mock_server.h | 28 +- SDK/c/tests/test_client.c | 8 +- SDK/c/tests/test_harness.h | 16 +- SDK/c/tests/test_integration.c | 18 +- SDK/c/tests/test_main.c | 5 +- SDK/c/tests/test_protocol.c | 4 +- SDK/csharp/examples/BasicUsage/Program.cs | 24 +- SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs | 246 ++++++++-------- .../ClientIntegrationTests.cs | 31 +- .../tests/CsmTcpRouter.Tests/MockServer.cs | 16 +- .../tests/CsmTcpRouter.Tests/ProtocolTests.cs | 6 +- SDK/python/src/csm_tcp_router_client.py | 257 ++++++++--------- 17 files changed, 577 insertions(+), 609 deletions(-) diff --git a/SDK/c/examples/basic_usage.c b/SDK/c/examples/basic_usage.c index c16fe98..bb9331f 100644 --- a/SDK/c/examples/basic_usage.c +++ b/SDK/c/examples/basic_usage.c @@ -1,5 +1,5 @@ -/* basic_usage.c - Demonstrates connecting, pinging, listing modules, and - * sending a synchronous command. Mirrors examples/basic_usage.py. */ +/* basic_usage.c - 演示连接、Ping、列出模块以及 + * 发送同步命令。对应 examples/basic_usage.py。 */ #include "csm_tcp_router_client.h" #include @@ -9,7 +9,7 @@ #define PORT 30007 int main(void) { - /* 1. Wait until the server is ready (optional). */ + /* 1. 等待服务器就绪(可选)。 */ printf("Waiting for server ... "); fflush(stdout); csm_result_t r = csm_client_wait_for_server(HOST, PORT, 30000, 500); @@ -19,7 +19,7 @@ int main(void) { } printf("ready.\n"); - /* 2. Create + connect. */ + /* 2. 创建 + 连接。 */ csm_client_t *c = csm_client_create(); if (!c) { fprintf(stderr, "Out of memory\n"); return 1; } @@ -31,20 +31,20 @@ int main(void) { } printf("Connected to %s:%d\n", HOST, PORT); - /* 3. Ping. */ + /* 3. Ping。 */ double ms = 0; r = csm_client_ping(c, 2000, &ms); if (r == CSM_OK) printf("Ping OK latency=%.1f ms\n", ms); else printf("Ping failed: %s\n", csm_result_str(r)); - /* 4. List CSM modules. */ + /* 4. 列出 CSM 模块。 */ char *modules = NULL; if (csm_client_list_modules(c, &modules, 5000) == CSM_OK) { printf("\nLoaded modules:\n%s\n", modules); csm_string_free(modules); } - /* 5. Send a synchronous command (uncomment when wired to a real module). + /* 5. 发送同步命令(连接到真实模块后取消注释)。 * * csm_command_response_t resp = {0}; * if (csm_client_send_and_wait(c, "API: Read -@ DAQmx", 5000, &resp) == CSM_OK) { @@ -53,7 +53,7 @@ int main(void) { * csm_command_response_dispose(&resp); */ - /* 6. Disconnect & clean up. */ + /* 6. 断开连接并清理资源。 */ csm_client_disconnect(c); csm_client_destroy(c); printf("Disconnected.\n"); diff --git a/SDK/c/examples/subscribe_status.c b/SDK/c/examples/subscribe_status.c index 9fd0df1..f4396bb 100644 --- a/SDK/c/examples/subscribe_status.c +++ b/SDK/c/examples/subscribe_status.c @@ -1,5 +1,5 @@ -/* subscribe_status.c - Demonstrates real-time status subscription with a - * callback, mirroring examples/subscribe_status.py. */ +/* subscribe_status.c - 演示使用回调进行实时状态订阅, + * 对应 examples/subscribe_status.py。 */ #if !defined(_WIN32) # ifndef _POSIX_C_SOURCE diff --git a/SDK/c/include/csm_tcp_router_client.h b/SDK/c/include/csm_tcp_router_client.h index 500cdac..378ad80 100644 --- a/SDK/c/include/csm_tcp_router_client.h +++ b/SDK/c/include/csm_tcp_router_client.h @@ -1,17 +1,16 @@ -/* csm_tcp_router_client.h - Single-header public C API for the CSM-TCP-Router - * client SDK. +/* csm_tcp_router_client.h - 适用于 CSM-TCP-Router 客户端 SDK 的单头文件公共 C API。 * - * This SDK is the C counterpart of the Python `csm_tcp_router_client` module. - * It implements the CSM-TCP-Router protocol v0 over TCP and exposes a - * thread-safe synchronous client (`csm_client_t`) with both blocking calls - * and asynchronous callback / polling-queue delivery. + * 本 SDK 是 Python `csm_tcp_router_client` 模块的 C 对应版本。 + * 它通过 TCP 实现 CSM-TCP-Router 协议 v0,并提供一个 + * 线程安全的同步客户端(`csm_client_t`),支持阻塞调用 + * 以及异步回调/轮询队列两种投递方式。 * - * Wire format (8-byte header, big-endian): + * 线帧格式(8 字节头部,大端序): * * | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | Payload | * +---------------------------- Header (8B) ----------------------------+ * - * Quickstart: + * 快速入门: * * csm_client_t *c = csm_client_create(); * if (csm_client_connect(c, "localhost", 30007, 5000) == CSM_OK) { @@ -24,9 +23,9 @@ * } * csm_client_destroy(c); * - * The library is portable across Windows (Winsock2 + Win32 threads) and - * POSIX systems (BSD sockets + pthreads), and is built as either a static - * or shared library by the bundled CMake / Visual Studio projects. + * 本库可跨平台运行于 Windows(Winsock2 + Win32 线程)和 + * POSIX 系统(BSD 套接字 + pthreads),通过附带的 CMake / Visual Studio 项目 + * 构建为静态库或共享库。 */ #ifndef CSM_TCP_ROUTER_CLIENT_H #define CSM_TCP_ROUTER_CLIENT_H @@ -39,7 +38,7 @@ extern "C" { #endif /* ------------------------------------------------------------------------- */ -/* Versioning and DLL export */ +/* 版本和 DLL 导出 */ /* ------------------------------------------------------------------------- */ #define CSM_VERSION_MAJOR 0 @@ -58,122 +57,122 @@ extern "C" { #endif /* ------------------------------------------------------------------------- */ -/* Return codes */ +/* 返回码 */ /* ------------------------------------------------------------------------- */ -/** Result codes returned by every public SDK function. */ +/** 所有公共 SDK 函数返回的结果码。 */ typedef enum csm_result { - CSM_OK = 0, /**< Operation succeeded. */ - CSM_ERR_INVALID = -1, /**< Invalid argument or NULL pointer. */ - CSM_ERR_CONNECTION = -2, /**< Connection failed or was lost. */ - CSM_ERR_TIMEOUT = -3, /**< Operation exceeded its timeout. */ - CSM_ERR_PROTOCOL = -4, /**< Invalid / malformed protocol frame. */ - CSM_ERR_SERVER = -5, /**< Server returned an ERROR packet. */ - CSM_ERR_NOMEM = -6, /**< Memory allocation failure. */ - CSM_ERR_STATE = -7, /**< Operation invalid in current state. */ - CSM_ERR_IO = -8 /**< Underlying socket / OS I/O error. */ + CSM_OK = 0, /**< 操作成功。 */ + CSM_ERR_INVALID = -1, /**< 无效参数或 NULL 指针。 */ + CSM_ERR_CONNECTION = -2, /**< 连接失败或已断开。 */ + CSM_ERR_TIMEOUT = -3, /**< 操作超时。 */ + CSM_ERR_PROTOCOL = -4, /**< 无效/格式错误的协议帧。 */ + CSM_ERR_SERVER = -5, /**< 服务器返回了 ERROR 数据包。 */ + CSM_ERR_NOMEM = -6, /**< 内存分配失败。 */ + CSM_ERR_STATE = -7, /**< 当前状态下操作无效。 */ + CSM_ERR_IO = -8 /**< 底层套接字/操作系统 I/O 错误。 */ } csm_result_t; -/** Return a static, human-readable string for *code*. */ +/** 返回 *code* 对应的静态可读字符串。 */ CSM_API const char *csm_result_str(csm_result_t code); /* ------------------------------------------------------------------------- */ -/* Protocol constants */ +/* 协议常量 */ /* ------------------------------------------------------------------------- */ -/** Packet type byte values (CSM-TCP-Router protocol v0). */ +/** 数据包类型字节值(CSM-TCP-Router 协议 v0)。 */ typedef enum csm_packet_type { - CSM_PT_INFO = 0x00, /**< Welcome / goodbye informational text. */ - CSM_PT_ERROR = 0x01, /**< Server error: "[Error: ] " */ - CSM_PT_CMD = 0x02, /**< Command packet (client -> server). */ - CSM_PT_CMD_RESP = 0x03, /**< Async / subscribe handshake ACK. */ - CSM_PT_RESP = 0x04, /**< Synchronous response payload. */ - CSM_PT_ASYNC_RESP = 0x05, /**< Async response: " <- ". */ - CSM_PT_STATUS = 0x06, /**< Status broadcast. */ - CSM_PT_INTERRUPT = 0x07 /**< Interrupt broadcast. */ + CSM_PT_INFO = 0x00, /**< 欢迎/再见信息文本。 */ + CSM_PT_ERROR = 0x01, /**< 服务器错误:"[Error: ] " */ + CSM_PT_CMD = 0x02, /**< 命令数据包(客户端 -> 服务器)。 */ + CSM_PT_CMD_RESP = 0x03, /**< 异步/订阅握手 ACK。 */ + CSM_PT_RESP = 0x04, /**< 同步响应载荷。 */ + CSM_PT_ASYNC_RESP = 0x05, /**< 异步响应:" <- "。 */ + CSM_PT_STATUS = 0x06, /**< 状态广播。 */ + CSM_PT_INTERRUPT = 0x07 /**< 中断广播。 */ } csm_packet_type_t; -/** Number of bytes in the fixed wire-format header. */ +/** 固定线帧格式头部的字节数。 */ #define CSM_HEADER_SIZE 8 -/** Protocol version byte sent in every outgoing packet. */ +/** 每个发出数据包中发送的协议版本字节。 */ #define CSM_PROTOCOL_VERSION 0x01 /* ------------------------------------------------------------------------- */ -/* Public data models */ +/* 公共数据模型 */ /* ------------------------------------------------------------------------- */ -/** A decoded packet (header fields + heap-allocated body). */ +/** 已解码的数据包(头部字段 + 堆分配的主体)。 */ typedef struct csm_packet { csm_packet_type_t type; uint8_t version; uint8_t flag1; uint8_t flag2; - uint8_t *data; /**< Owned payload buffer (or NULL). */ - size_t data_len; /**< Length of `data` in bytes. */ + uint8_t *data; /**< 拥有的载荷缓冲区(或 NULL)。 */ + size_t data_len; /**< `data` 的字节长度。 */ } csm_packet_t; -/** A successful synchronous response. */ +/** 成功的同步响应。 */ typedef struct csm_command_response { - uint8_t *raw; /**< NUL-terminated UTF-8 payload (owned). */ - size_t raw_len; /**< Length of `raw` in bytes (excluding NUL). */ + uint8_t *raw; /**< NUL 终止的 UTF-8 载荷(已拥有)。 */ + size_t raw_len; /**< `raw` 的字节长度(不含 NUL)。 */ } csm_command_response_t; -/** An ASYNC_RESP packet: payload + the original command echoed by the server. +/** ASYNC_RESP 数据包:载荷 + 服务器回显的原始命令。 * - * Server format: ``" <- "``. */ + * 服务器格式:``" <- "``。 */ typedef struct csm_async_response { - char *raw; /**< Response payload (owned, NUL-terminated). */ + char *raw; /**< 响应载荷(已拥有,NUL 终止)。 */ size_t raw_len; - char *original_command; /**< Echoed command text (owned). */ + char *original_command; /**< 回显的命令文本(已拥有)。 */ } csm_async_response_t; -/** A STATUS or INTERRUPT broadcast. +/** STATUS 或 INTERRUPT 广播。 * - * Server format: ``" >> <- "``. */ + * 服务器格式:``" >> <- "``。 */ typedef struct csm_status_notification { - csm_packet_type_t packet_type; /**< CSM_PT_STATUS or CSM_PT_INTERRUPT */ - char *raw; /**< Full payload (owned). */ + csm_packet_type_t packet_type; /**< CSM_PT_STATUS 或 CSM_PT_INTERRUPT */ + char *raw; /**< 完整载荷(已拥有)。 */ size_t raw_len; - char *status_name; /**< Owned, NUL-terminated. */ - char *data; /**< Owned, NUL-terminated. */ - char *module_name; /**< Owned, NUL-terminated. */ + char *status_name; /**< 已拥有,NUL 终止。 */ + char *data; /**< 已拥有,NUL 终止。 */ + char *module_name; /**< 已拥有,NUL 终止。 */ } csm_status_notification_t; -/** Free a string previously returned via an out-parameter (e.g. by - * `csm_client_list_modules`). Safe to call with NULL. */ +/** 释放通过出参(例如由 `csm_client_list_modules` 返回)分配的字符串。 + * 传入 NULL 时安全。 */ CSM_API void csm_string_free(char *s); -/** Free heap members of a `csm_command_response_t` (does not free the struct). */ +/** 释放 `csm_command_response_t` 的堆成员(不释放结构体本身)。 */ CSM_API void csm_command_response_dispose(csm_command_response_t *resp); -/** Free heap members of a `csm_async_response_t` (does not free the struct). */ +/** 释放 `csm_async_response_t` 的堆成员(不释放结构体本身)。 */ CSM_API void csm_async_response_dispose(csm_async_response_t *resp); -/** Free heap members of a `csm_status_notification_t` (does not free struct). */ +/** 释放 `csm_status_notification_t` 的堆成员(不释放结构体本身)。 */ CSM_API void csm_status_notification_dispose(csm_status_notification_t *n); -/** Free heap members of a `csm_packet_t` (does not free the struct). */ +/** 释放 `csm_packet_t` 的堆成员(不释放结构体本身)。 */ CSM_API void csm_packet_dispose(csm_packet_t *pkt); /* ------------------------------------------------------------------------- */ -/* Server error info */ +/* 服务器错误信息 */ /* ------------------------------------------------------------------------- */ -/** Information about the most recent CSM_ERR_SERVER returned by a function. */ +/** 函数最近返回 CSM_ERR_SERVER 时的相关信息。 */ typedef struct csm_server_error { - char code[32]; /**< NUL-terminated CSM error code (may be empty). */ - char message[256]; /**< NUL-terminated error message (truncated). */ + char code[32]; /**< NUL 终止的 CSM 错误码(可为空)。 */ + char message[256]; /**< NUL 终止的错误消息(已截断)。 */ } csm_server_error_t; /* ------------------------------------------------------------------------- */ -/* Protocol codec (exposed for advanced use / testing) */ +/* 协议编解码(暴露用于高级用途/测试) */ /* ------------------------------------------------------------------------- */ -/** Encode *data* (`data_len` bytes) into a complete wire-format packet. +/** 将 *data*(`data_len` 字节)编码为完整的线帧格式数据包。 * - * The caller must pass `out_buf` with at least `CSM_HEADER_SIZE + data_len` - * bytes. On success, `*out_len` is set to the number of bytes written. + * 调用者必须传入至少 `CSM_HEADER_SIZE + data_len` 字节的 `out_buf`。 + * 成功时 `*out_len` 将被设置为写入的字节数。 */ CSM_API csm_result_t csm_encode_packet(const void *data, size_t data_len, @@ -184,7 +183,7 @@ CSM_API csm_result_t csm_encode_packet(const void *data, size_t out_buf_size, size_t *out_len); -/** Decode an 8-byte header into its constituent fields. */ +/** 将 8 字节头部解码为各组成字段。 */ CSM_API csm_result_t csm_decode_header(const uint8_t *header_bytes, size_t header_len, uint32_t *out_data_len, @@ -193,11 +192,11 @@ CSM_API csm_result_t csm_decode_header(const uint8_t *header_bytes, uint8_t *out_flag1, uint8_t *out_flag2); -/** Build a `csm_packet_t` from raw header + body. The returned packet - * **owns** a copy of the body; release it with `csm_packet_dispose`. +/** 从原始头部 + 主体构建 `csm_packet_t`。返回的数据包 + * **拥有** 主体的副本;使用 `csm_packet_dispose` 释放。 * - * Unknown packet type bytes are mapped to `CSM_PT_INFO` for forward - * compatibility (the server may introduce new types in future revisions). + * 未知数据包类型字节将被映射到 `CSM_PT_INFO` 以实现前向 + * 兼容性(服务器可能在未来版本中引入新类型)。 */ CSM_API csm_result_t csm_parse_packet(const uint8_t *header_bytes, size_t header_len, @@ -206,134 +205,134 @@ CSM_API csm_result_t csm_parse_packet(const uint8_t *header_bytes, csm_packet_t *out_packet); /* ------------------------------------------------------------------------- */ -/* Callback signatures */ +/* 回调签名 */ /* ------------------------------------------------------------------------- */ -/** Status/interrupt notification callback. +/** 状态/中断通知回调。 * - * Invoked from the receive thread. Must be fast and non-blocking. The - * `notif` pointer and its members are valid only for the duration of the - * call; copy any data you need before returning. + * 从接收线程调用。必须快速且非阻塞。`notif` + * 指针及其成员仅在调用期间有效; + * 请在返回前复制所需数据。 */ typedef void (*csm_status_callback_fn)(const csm_status_notification_t *notif, void *user_data); -/** Async response callback. Same threading rules as `csm_status_callback_fn`. */ +/** 异步响应回调。与 `csm_status_callback_fn` 使用相同的线程规则。 */ typedef void (*csm_async_callback_fn)(const csm_async_response_t *resp, void *user_data); /* ------------------------------------------------------------------------- */ -/* Client lifecycle */ +/* 客户端生命周期 */ /* ------------------------------------------------------------------------- */ -/** Opaque thread-safe client handle. */ +/** 不透明的线程安全客户端句柄。 */ typedef struct csm_client csm_client_t; -/** Create a new client instance. Returns NULL on allocation failure. */ +/** 创建新的客户端实例。分配失败时返回 NULL。 */ CSM_API csm_client_t *csm_client_create(void); -/** Disconnect (if connected) and free all resources held by *client*. */ +/** 断开连接(如已连接)并释放 *client* 持有的所有资源。 */ CSM_API void csm_client_destroy(csm_client_t *client); -/** Open a TCP connection and start the background receive thread. +/** 打开 TCP 连接并启动后台接收线程。 * - * @param connect_timeout_ms Connect timeout in milliseconds. - * @return CSM_OK or CSM_ERR_CONNECTION / CSM_ERR_STATE / CSM_ERR_INVALID. + * @param connect_timeout_ms 连接超时时间(毫秒)。 + * @return CSM_OK 或 CSM_ERR_CONNECTION / CSM_ERR_STATE / CSM_ERR_INVALID。 */ CSM_API csm_result_t csm_client_connect(csm_client_t *client, const char *host, uint16_t port, unsigned int connect_timeout_ms); -/** Close the connection and stop the receive thread. Safe to call when not - * connected; any blocked callers receive CSM_ERR_CONNECTION immediately. */ +/** 关闭连接并停止接收线程。未连接时调用安全; + * 任何被阻塞的调用者将立即收到 CSM_ERR_CONNECTION。 */ CSM_API csm_result_t csm_client_disconnect(csm_client_t *client); -/** Return non-zero while the underlying socket is open. */ +/** 底层套接字打开时返回非零值。 */ CSM_API int csm_client_is_connected(const csm_client_t *client); -/** Poll until *host*:*port* accepts a connection or *timeout_ms* elapses. +/** 轮询直到 *host*:*port* 接受连接或 *timeout_ms* 超时。 * - * @return CSM_OK when the server is reachable; CSM_ERR_TIMEOUT otherwise. */ + * @return 服务器可达时返回 CSM_OK;否则返回 CSM_ERR_TIMEOUT。 */ CSM_API csm_result_t csm_client_wait_for_server(const char *host, uint16_t port, unsigned int timeout_ms, unsigned int retry_interval_ms); /* ------------------------------------------------------------------------- */ -/* Core command methods */ +/* 核心命令方法 */ /* ------------------------------------------------------------------------- */ -/** Send a synchronous command and block until the response arrives. +/** 发送同步命令并阻塞直到响应到达。 * - * On CSM_OK the caller owns `*out_resp` and must release it via - * `csm_command_response_dispose`. */ + * 返回 CSM_OK 时调用者拥有 `*out_resp`,必须通过 + * `csm_command_response_dispose` 释放。 */ CSM_API csm_result_t csm_client_send_and_wait(csm_client_t *client, const char *command, unsigned int timeout_ms, csm_command_response_t *out_resp); -/** Send an asynchronous command (`->` suffix) and block until the - * `cmd-resp` handshake arrives. The eventual `async-resp` is delivered to - * any callback registered via `csm_client_register_async_callback` and to - * the polling queue (`csm_client_poll_async_response`). */ +/** 发送异步命令(`->` 后缀)并阻塞直到 + * `cmd-resp` 握手到达。最终的 `async-resp` 将被投递到 + * 通过 `csm_client_register_async_callback` 注册的回调 + * 以及轮询队列(`csm_client_poll_async_response`)。 */ CSM_API csm_result_t csm_client_post(csm_client_t *client, const char *command, unsigned int timeout_ms); -/** Send an async no-reply command (`->|` suffix) and block until the - * `cmd-resp` handshake arrives. */ +/** 发送异步无回复命令(`->|` 后缀)并阻塞直到 + * `cmd-resp` 握手到达。 */ CSM_API csm_result_t csm_client_post_no_reply(csm_client_t *client, const char *command, unsigned int timeout_ms); -/** Send a `Ping` and measure round-trip latency. +/** 发送 `Ping` 并测量往返延迟。 * - * @param out_elapsed_ms Set to round-trip time in milliseconds on success. - * @return CSM_OK or one of the regular error codes. + * @param out_elapsed_ms 成功时设置为往返时间(毫秒)。 + * @return CSM_OK 或常规错误码之一。 */ CSM_API csm_result_t csm_client_ping(csm_client_t *client, unsigned int timeout_ms, double *out_elapsed_ms); /* ------------------------------------------------------------------------- */ -/* Router management helpers */ +/* 路由器管理辅助函数 */ /* ------------------------------------------------------------------------- */ -/** Run `List` and return the response text. Caller frees `*out_text` via - * `csm_string_free`. */ +/** 执行 `List` 并返回响应文本。调用者通过 + * `csm_string_free` 释放 `*out_text`。 */ CSM_API csm_result_t csm_client_list_modules(csm_client_t *client, char **out_text, unsigned int timeout_ms); -/** Run `List API `. Caller frees `*out_text`. */ +/** 执行 `List API `。调用者释放 `*out_text`。 */ CSM_API csm_result_t csm_client_list_api(csm_client_t *client, const char *module, char **out_text, unsigned int timeout_ms); -/** Run `List State `. Caller frees `*out_text`. */ +/** 执行 `List State `。调用者释放 `*out_text`。 */ CSM_API csm_result_t csm_client_list_states(csm_client_t *client, const char *module, char **out_text, unsigned int timeout_ms); -/** Run `Help `. Caller frees `*out_text`. */ +/** 执行 `Help `。调用者释放 `*out_text`。 */ CSM_API csm_result_t csm_client_help(csm_client_t *client, const char *module, char **out_text, unsigned int timeout_ms); /* ------------------------------------------------------------------------- */ -/* Status / interrupt subscriptions */ +/* 状态/中断订阅 */ /* ------------------------------------------------------------------------- */ -/** Subscribe to a CSM module's status broadcast. +/** 订阅 CSM 模块的状态广播。 * - * Sends ``"@ ->"`` and blocks until the - * `cmd-resp` handshake arrives. `callback` (if non-NULL) is invoked from the - * receive thread for each notification; notifications are also enqueued for - * polling via `csm_client_poll_status`. + * 发送 ``"@ ->"`` 并阻塞直到 + * `cmd-resp` 握手到达。`callback`(若非 NULL)将从 + * 接收线程对每个通知调用;通知同时也会入队以供 + * 通过 `csm_client_poll_status` 轮询。 */ CSM_API csm_result_t csm_client_subscribe_status(csm_client_t *client, const char *status_name, @@ -342,54 +341,53 @@ CSM_API csm_result_t csm_client_subscribe_status(csm_client_t *client, void *user_data, unsigned int timeout_ms); -/** Cancel a status subscription. */ +/** 取消状态订阅。 */ CSM_API csm_result_t csm_client_unsubscribe_status(csm_client_t *client, const char *status_name, const char *module_name, unsigned int timeout_ms); -/** Register a callback for `async-resp` packets matching *original_command*. */ +/** 为匹配 *original_command* 的 `async-resp` 数据包注册回调。 */ CSM_API csm_result_t csm_client_register_async_callback(csm_client_t *client, const char *original_command, csm_async_callback_fn callback, void *user_data); -/** Remove a previously registered async callback. */ +/** 移除之前注册的异步回调。 */ CSM_API csm_result_t csm_client_unregister_async_callback(csm_client_t *client, const char *original_command); /* ------------------------------------------------------------------------- */ -/* Polling queues (alternative to callbacks) */ +/* 轮询队列(回调的替代方案) */ /* ------------------------------------------------------------------------- */ -/** Pop the next status/interrupt notification from the polling queue. +/** 从轮询队列弹出下一个状态/中断通知。 * - * @param timeout_ms 0 = non-blocking; >0 = block up to N ms. - * @return CSM_OK with `*out_notif` populated (caller disposes via - * `csm_status_notification_dispose`); CSM_ERR_TIMEOUT if the queue - * is empty within the timeout; CSM_ERR_CONNECTION if disconnected. + * @param timeout_ms 0 = 非阻塞;>0 = 最多阻塞 N 毫秒。 + * @return 返回 CSM_OK 且 `*out_notif` 已填充(调用者通过 + * `csm_status_notification_dispose` 释放);若队列在 + * 超时内为空则返回 CSM_ERR_TIMEOUT;若断开连接则返回 CSM_ERR_CONNECTION。 */ CSM_API csm_result_t csm_client_poll_status(csm_client_t *client, csm_status_notification_t *out_notif, unsigned int timeout_ms); -/** Pop the next async response from the polling queue. */ +/** 从轮询队列弹出下一个异步响应。 */ CSM_API csm_result_t csm_client_poll_async_response(csm_client_t *client, csm_async_response_t *out_resp, unsigned int timeout_ms); /* ------------------------------------------------------------------------- */ -/* Last server error */ +/* 最后一次服务器错误 */ /* ------------------------------------------------------------------------- */ -/** Retrieve information about the last CSM_ERR_SERVER observed by *client*. +/** 获取 *client* 观察到的最后一次 CSM_ERR_SERVER 的信息。 * - * Returns CSM_OK and fills *out_err* with the most recently captured - * server-error code/message; otherwise (no server error has ever been - * observed for this client) returns CSM_ERR_STATE. The stored error is - * kept indefinitely until the next CSM_ERR_SERVER overwrites it, so it - * is safe to call this immediately after a failing operation without - * worrying about it being cleared by an unrelated success in between. + * 返回 CSM_OK 并用最近捕获的服务器错误码/消息填充 *out_err*; + * 否则(该客户端从未观察到服务器错误)返回 CSM_ERR_STATE。 + * 存储的错误将无限期保留,直到下一次 CSM_ERR_SERVER 将其覆盖, + * 因此可以在操作失败后立即调用此函数, + * 而无需担心被中间无关的成功操作清除。 */ CSM_API csm_result_t csm_client_last_server_error(const csm_client_t *client, csm_server_error_t *out_err); diff --git a/SDK/c/src/csm_tcp_router_client.c b/SDK/c/src/csm_tcp_router_client.c index f7c35d7..7c3849a 100644 --- a/SDK/c/src/csm_tcp_router_client.c +++ b/SDK/c/src/csm_tcp_router_client.c @@ -1,14 +1,12 @@ -/* csm_tcp_router_client.c - Cross-platform implementation of the - * CSM-TCP-Router C client SDK. +/* csm_tcp_router_client.c - CSM-TCP-Router C 客户端 SDK 的跨平台实现。 * - * Threading: the receive loop runs on a single background thread. All - * public functions are safe to call from any thread; the client serialises - * concurrent waiters for synchronous (RESP) and command-handshake - * (CMD_RESP) responses respectively, mirroring the Python SDK. + * 线程模型:接收循环运行于单个后台线程。所有公共函数均可从任意线程安全调用; + * 客户端分别对同步(RESP)和命令握手(CMD_RESP)响应的并发等待者进行串行化, + * 与 Python SDK 保持一致。 * - * Sockets / threads abstraction: - * - Windows: Winsock2 + Win32 CRITICAL_SECTION / CONDITION_VARIABLE / threads. - * - POSIX: BSD sockets + pthreads. + * 套接字 / 线程抽象: + * - Windows:Winsock2 + Win32 CRITICAL_SECTION / CONDITION_VARIABLE / 线程。 + * - POSIX: BSD 套接字 + pthreads。 */ #if !defined(_WIN32) @@ -30,14 +28,13 @@ #include #include -/* CSM_BUILD_LIBRARY is defined by the build system (CMake / MSBuild) when - * compiling the library, so that csm_tcp_router_client.h decorates the - * exported symbols with the correct __declspec for shared builds. - * Defining it unconditionally here would break consumers that compile this - * .c file directly into their own DLL with a different export contract. */ +/* CSM_BUILD_LIBRARY 由构建系统(CMake / MSBuild)在编译库时定义, + * 以便 csm_tcp_router_client.h 使用正确的 __declspec 装饰共享构建中 + * 导出的符号。在此处无条件定义会破坏那些将此 .c 文件直接编译进 + * 自己的 DLL(采用不同导出约定)的使用者。 */ /* ========================================================================= */ -/* Platform abstraction */ +/* 平台抽象 */ /* ========================================================================= */ #if defined(_WIN32) @@ -64,11 +61,11 @@ static void csm_mutex_unlock(csm_mutex_t *m) { LeaveCriticalSection(m); } static void csm_cond_init(csm_cond_t *c) { InitializeConditionVariable(c); } static void csm_cond_destroy(csm_cond_t *c) { (void)c; } static void csm_cond_signal(csm_cond_t *c) { WakeConditionVariable(c); } -#if 0 /* reserved for future broadcast use */ +#if 0 /* 保留供将来广播使用 */ static void csm_cond_broadcast(csm_cond_t *c) { WakeAllConditionVariable(c); } #endif -/* Returns 1 on signal, 0 on timeout. */ +/* 有信号时返回 1,超时返回 0。 */ static int csm_cond_wait_ms(csm_cond_t *c, csm_mutex_t *m, unsigned int ms) { BOOL ok = SleepConditionVariableCS(c, m, ms == 0 ? INFINITE : ms); if (ok) return 1; @@ -113,7 +110,7 @@ static void csm_mutex_unlock(csm_mutex_t *m) { pthread_mutex_unlock(m); } static void csm_cond_init(csm_cond_t *c) { pthread_cond_init(c, NULL); } static void csm_cond_destroy(csm_cond_t *c) { pthread_cond_destroy(c); } static void csm_cond_signal(csm_cond_t *c) { pthread_cond_signal(c); } -#if 0 /* reserved for future broadcast use */ +#if 0 /* 保留供将来广播使用 */ static void csm_cond_broadcast(csm_cond_t *c) { pthread_cond_broadcast(c); } #endif @@ -161,7 +158,7 @@ static double csm_monotonic_ms(void) { #endif /* ========================================================================= */ -/* WSA bootstrap (Windows only) - reference-counted */ +/* WSA 引导(仅 Windows)— 引用计数 */ /* ========================================================================= */ #if defined(_WIN32) @@ -178,9 +175,8 @@ static BOOL CALLBACK csm_wsa_lock_init_once_cb(PINIT_ONCE init_once, } static void csm_wsa_lock_init_once(void) { - /* InitOnceExecuteOnce guarantees the callback runs exactly once - * across all threads in the process, so the critical section is - * initialised exactly once even under concurrent client creation. */ + /* InitOnceExecuteOnce 保证回调在进程内所有线程中恰好执行一次, + * 因此即使在并发创建客户端时,临界区也只会被初始化一次。 */ InitOnceExecuteOnce(&g_wsa_lock_init_once_state, csm_wsa_lock_init_once_cb, NULL, NULL); } @@ -215,7 +211,7 @@ static void csm_wsa_cleanup(void) {} #endif /* ========================================================================= */ -/* Result code helpers */ +/* 结果码辅助函数 */ /* ========================================================================= */ const char *csm_result_str(csm_result_t code) { @@ -234,7 +230,7 @@ const char *csm_result_str(csm_result_t code) { } /* ========================================================================= */ -/* Memory helpers */ +/* 内存辅助函数 */ /* ========================================================================= */ static char *csm_strdup_n(const char *s, size_t n) { @@ -288,7 +284,7 @@ void csm_packet_dispose(csm_packet_t *pkt) { } /* ========================================================================= */ -/* Protocol codec */ +/* 协议编解码 */ /* ========================================================================= */ static void csm_pack_be32(uint8_t *buf, uint32_t v) { @@ -355,7 +351,7 @@ csm_result_t csm_parse_packet(const uint8_t *header_bytes, if (r != CSM_OK) return r; if ((size_t)data_len != body_len) return CSM_ERR_PROTOCOL; - /* Forward-compatible: unknown type bytes are mapped to INFO. */ + /* 向前兼容:未知类型字节映射为 INFO。 */ csm_packet_type_t pt; switch (type_byte) { case CSM_PT_INFO: @@ -388,23 +384,23 @@ csm_result_t csm_parse_packet(const uint8_t *header_bytes, } /* ========================================================================= */ -/* Internal: server-error parsing */ +/* 内部:服务器错误解析 */ /* ========================================================================= */ -/* Parse a packet payload of the form "[Error: ] " into out_err. */ +/* 将形如 "[Error: ] " 的数据包载荷解析到 out_err。 */ static void csm_parse_server_error(const uint8_t *data, size_t len, csm_server_error_t *out_err) { out_err->code[0] = '\0'; out_err->message[0] = '\0'; - /* Copy into a NUL-terminated stack buffer (capped). */ + /* 复制到以 NUL 结尾的栈缓冲区(截断至上限)。 */ char buf[1024]; size_t copy_len = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1; if (copy_len) memcpy(buf, data, copy_len); buf[copy_len] = '\0'; - /* Trim trailing whitespace. */ + /* 去除尾部空白字符。 */ while (copy_len > 0 && (buf[copy_len - 1] == ' ' || buf[copy_len - 1] == '\r' || buf[copy_len - 1] == '\n' || @@ -419,7 +415,7 @@ static void csm_parse_server_error(const uint8_t *data, char *end = strchr(buf, ']'); if (end) { size_t code_len = (size_t)(end - (buf + prefix_len)); - /* Trim leading/trailing spaces from code. */ + /* 去除错误码首尾的空格。 */ const char *cs = buf + prefix_len; while (code_len && *cs == ' ') { cs++; code_len--; } while (code_len && cs[code_len - 1] == ' ') code_len--; @@ -440,15 +436,14 @@ static void csm_parse_server_error(const uint8_t *data, } /* ========================================================================= */ -/* Internal: bounded queues */ +/* 内部:有界队列 */ /* ========================================================================= */ -/* Generic queue node. Items hold either a packet (for resp/cmd_resp), or - * a notification / async response (for the polling queues), or a sentinel - * (signaled via `is_disconnect`). */ +/* 通用队列节点。元素持有数据包(用于 resp/cmd_resp)、 + * 通知 / 异步响应(用于轮询队列),或哨兵(通过 `is_disconnect` 发出信号)。 */ typedef struct csm_queue_node { struct csm_queue_node *next; - void *item; /* type depends on queue */ + void *item; /* 类型取决于所属队列 */ int is_disconnect; int is_server_error; csm_server_error_t server_error; @@ -481,7 +476,7 @@ static void csm_queue_destroy_with(csm_queue_t *q, csm_mutex_destroy(&q->lock); } -/* Push an item; takes ownership of *item* on success. */ +/* 压入一个元素;成功时获得 *item* 的所有权。 */ static int csm_queue_push(csm_queue_t *q, void *item, int is_disconnect, int is_server_error, const csm_server_error_t *err) { @@ -501,9 +496,9 @@ static int csm_queue_push(csm_queue_t *q, void *item, return 0; } -/* Pop one item, blocking up to *timeout_ms*. Returns CSM_OK with *out_item - * set (and ownership transferred), CSM_ERR_TIMEOUT, CSM_ERR_CONNECTION - * (sentinel), or CSM_ERR_SERVER (with *out_err* populated). */ +/* 弹出一个元素,最多阻塞 *timeout_ms* 毫秒。成功时返回 CSM_OK 并设置 *out_item* + * (所有权转移),超时返回 CSM_ERR_TIMEOUT,连接断开返回 CSM_ERR_CONNECTION + *(哨兵),或返回 CSM_ERR_SERVER(同时填充 *out_err*)。 */ static csm_result_t csm_queue_pop(csm_queue_t *q, unsigned int timeout_ms, void **out_item, @@ -537,9 +532,9 @@ static csm_result_t csm_queue_pop(csm_queue_t *q, n->item = NULL; } if (n->item) { - /* Item not consumed (e.g. caller passed NULL out_item). Leak-safe - * default is to free as bytes via the disposer set by the caller's - * queue-specific wrapper; here we just drop it. */ + /* 元素未被消费(例如调用者传入了 NULL out_item)。 + * 默认以字节方式通过调用者队列特定包装器设置的释放函数释放, + * 此处直接丢弃以保证无内存泄漏。 */ free(n->item); } free(n); @@ -547,7 +542,7 @@ static csm_result_t csm_queue_pop(csm_queue_t *q, } /* ========================================================================= */ -/* Subscription / async-callback registries */ +/* 订阅 / 异步回调注册表 */ /* ========================================================================= */ typedef struct csm_status_sub { @@ -566,28 +561,28 @@ typedef struct csm_async_sub { } csm_async_sub_t; /* ========================================================================= */ -/* Client */ +/* 客户端 */ /* ========================================================================= */ struct csm_client { csm_socket_t sock; csm_thread_t recv_thread; int recv_thread_running; - int connected; /* set under state_lock */ - int stop_flag; /* set to request shutdown */ + int connected; /* 在 state_lock 下设置 */ + int stop_flag; /* 置位以请求关闭 */ - csm_mutex_t state_lock; /* protects connected/stop_flag/sock */ - csm_mutex_t send_lock; /* serialises sendall() */ + csm_mutex_t state_lock; /* 保护 connected/stop_flag/sock */ + csm_mutex_t send_lock; /* 串行化 sendall() */ - csm_mutex_t resp_lock; /* at most one in-flight RESP waiter */ - csm_mutex_t cmd_resp_lock; /* at most one in-flight CMD_RESP waiter */ + csm_mutex_t resp_lock; /* 最多一个在途 RESP 等待者 */ + csm_mutex_t cmd_resp_lock; /* 最多一个在途 CMD_RESP 等待者 */ - csm_queue_t resp_queue; /* items: csm_packet_t* */ - csm_queue_t cmd_resp_queue; /* items: csm_packet_t* (or NULL) */ - csm_queue_t status_queue; /* items: csm_status_notification_t* */ - csm_queue_t async_queue; /* items: csm_async_response_t* */ + csm_queue_t resp_queue; /* 元素:csm_packet_t* */ + csm_queue_t cmd_resp_queue; /* 元素:csm_packet_t*(或 NULL) */ + csm_queue_t status_queue; /* 元素:csm_status_notification_t* */ + csm_queue_t async_queue; /* 元素:csm_async_response_t* */ - csm_mutex_t sub_lock; /* protects subscription registries */ + csm_mutex_t sub_lock; /* 保护订阅注册表 */ csm_status_sub_t *status_subs; csm_async_sub_t *async_subs; @@ -596,7 +591,7 @@ struct csm_client { csm_server_error_t last_server_error; }; -/* --- helpers --- */ +/* --- 辅助函数 --- */ static void csm_packet_free_void(void *p) { csm_packet_t *pkt = (csm_packet_t *)p; @@ -619,14 +614,13 @@ static void csm_async_resp_free_void(void *p) { free(r); } -/* Set client.sock under state_lock; closes any old one. */ +/* 在 state_lock 下设置 client.sock;关闭旧套接字(如有)。 */ static void csm_set_socket_locked(csm_client_t *c, csm_socket_t s) { if (c->sock != CSM_INVALID_SOCKET) csm_close_socket(c->sock); c->sock = s; } -/* Remember the most-recent server error so callers can fetch it after - * receiving a CSM_ERR_SERVER. */ +/* 记录最近一次服务器错误,以便调用者在收到 CSM_ERR_SERVER 后获取。 */ static void csm_record_server_error(csm_client_t *c, const csm_server_error_t *err) { csm_mutex_lock(&c->err_lock); @@ -635,7 +629,7 @@ static void csm_record_server_error(csm_client_t *c, csm_mutex_unlock(&c->err_lock); } -/* --- subscription registries (under sub_lock) --- */ +/* --- 订阅注册表(在 sub_lock 下操作)--- */ static csm_status_sub_t *csm_find_status_sub(csm_client_t *c, const char *status_name, @@ -755,9 +749,9 @@ static void csm_free_all_subs(csm_client_t *c) { csm_mutex_unlock(&c->sub_lock); } -/* --- recv helpers --- */ +/* --- 接收辅助函数 --- */ -/* Read exactly *size* bytes from sock; returns 0 on success, -1 on EOF/err. */ +/* 从套接字精确读取 *size* 字节;成功返回 0,EOF/错误返回 -1。 */ static int csm_recv_all(csm_socket_t sock, uint8_t *buf, size_t size) { size_t total = 0; while (total < size) { @@ -772,17 +766,17 @@ static int csm_recv_all(csm_socket_t sock, uint8_t *buf, size_t size) { return 0; } -/* --- Parsing helpers for ASYNC_RESP / STATUS payloads --- */ +/* --- ASYNC_RESP / STATUS 载荷的解析辅助函数 --- */ static void csm_async_resp_free_void(void *p); static void csm_status_notif_free_void(void *p); -/* Build an csm_async_response_t from raw payload data. */ +/* 从原始载荷数据构造 csm_async_response_t。 */ static csm_async_response_t *csm_make_async_response(const uint8_t *data, size_t len) { csm_async_response_t *r = (csm_async_response_t *)calloc(1, sizeof(*r)); if (!r) return NULL; - /* Server format: " <- ". */ + /* 服务器格式:" <- "。 */ const char *sep = " <- "; const size_t seplen = 4; size_t split = (size_t)-1; @@ -808,7 +802,7 @@ static csm_async_response_t *csm_make_async_response(const uint8_t *data, return r; } -/* Build a csm_status_notification_t from raw payload data. */ +/* 从原始载荷数据构造 csm_status_notification_t。 */ static csm_status_notification_t *csm_make_status_notif(csm_packet_type_t pt, const uint8_t *data, size_t len) { @@ -821,7 +815,7 @@ static csm_status_notification_t *csm_make_status_notif(csm_packet_type_t pt, n->raw[len] = '\0'; n->raw_len = len; - /* Find rightmost " <- " separator (rsplit by 1). */ + /* 从右向左查找最后一个 " <- " 分隔符(rsplit by 1)。 */ const char *raw_str = n->raw; const char *left = raw_str; size_t left_len = len; @@ -838,7 +832,7 @@ static csm_status_notification_t *csm_make_status_notif(csm_packet_type_t pt, } } - /* Trim whitespace from module. */ + /* 去除模块名首尾空白字符。 */ while (module_len && (*module_start == ' ' || *module_start == '\t')) { module_start++; module_len--; } @@ -849,7 +843,7 @@ static csm_status_notification_t *csm_make_status_notif(csm_packet_type_t pt, module_len--; } - /* Split left by " >> " into status_name + data. */ + /* 以 " >> " 将左半部分拆分为 status_name 和 data。 */ const char *status_start = NULL; size_t status_len = 0; const char *data_start = left; @@ -866,7 +860,7 @@ static csm_status_notification_t *csm_make_status_notif(csm_packet_type_t pt, } } - /* Trim status_name and data. */ + /* 去除 status_name 和 data 首尾空白字符。 */ while (status_len && (*status_start == ' ' || *status_start == '\t')) { status_start++; status_len--; } while (status_len && (status_start[status_len - 1] == ' ' || status_start[status_len - 1] == '\t')) status_len--; while (data_len_local && (*data_start == ' ' || *data_start == '\t')) { data_start++; data_len_local--; } @@ -885,18 +879,17 @@ static csm_status_notification_t *csm_make_status_notif(csm_packet_type_t pt, return n; } -/* --- Receive thread --- */ +/* --- 接收线程 --- */ static void csm_dispatch_packet(csm_client_t *c, csm_packet_t *pkt) { - /* On RESP / CMD_RESP / ERROR we transfer ownership of the packet - * (or err sentinel) into a queue. On STATUS / ASYNC_RESP / INTERRUPT - * we build a higher-level object and dispose of the raw packet. */ + /* 收到 RESP / CMD_RESP / ERROR 时,将数据包(或错误哨兵)的所有权转入队列。 + * 收到 STATUS / ASYNC_RESP / INTERRUPT 时,构造高层对象并释放原始数据包。 */ switch (pkt->type) { case CSM_PT_RESP: { csm_packet_t *heap = (csm_packet_t *)malloc(sizeof(*heap)); if (!heap) { csm_packet_free_void(pkt); return; } *heap = *pkt; - /* Push to resp queue; queue-node owns it. */ + /* 压入 resp 队列;队列节点持有所有权。 */ if (csm_queue_push(&c->resp_queue, heap, 0, 0, NULL) != 0) { csm_packet_free_void(heap); } @@ -925,7 +918,7 @@ static void csm_dispatch_packet(csm_client_t *c, csm_packet_t *pkt) { case CSM_PT_ASYNC_RESP: { csm_async_response_t *r = csm_make_async_response(pkt->data, pkt->data_len); if (r) { - /* Look up callback under sub_lock. */ + /* 在 sub_lock 下查找回调。 */ csm_mutex_lock(&c->sub_lock); csm_async_callback_fn cb = NULL; void *ud = NULL; csm_async_sub_t *s = c->async_subs; @@ -937,8 +930,7 @@ static void csm_dispatch_packet(csm_client_t *c, csm_packet_t *pkt) { } csm_mutex_unlock(&c->sub_lock); if (cb) cb(r, ud); - /* Push a copy onto polling queue so callback users and - * polling users are independent. */ + /* 将副本压入轮询队列,使回调用户与轮询用户相互独立。 */ csm_async_response_t *queued = (csm_async_response_t *)calloc(1, sizeof(*queued)); if (queued) { queued->raw = csm_strdup_n(r->raw, r->raw_len); @@ -972,7 +964,7 @@ static void csm_dispatch_packet(csm_client_t *c, csm_packet_t *pkt) { } csm_mutex_unlock(&c->sub_lock); if (cb) cb(n, ud); - /* Push a copy onto polling queue. */ + /* 将副本压入轮询队列。 */ csm_status_notification_t *q = (csm_status_notification_t *)calloc(1, sizeof(*q)); if (q) { q->packet_type = n->packet_type; @@ -996,7 +988,7 @@ static void csm_dispatch_packet(csm_client_t *c, csm_packet_t *pkt) { case CSM_PT_INFO: case CSM_PT_CMD: default: - /* INFO is silently discarded; CMD never sent by server. */ + /* INFO 静默丢弃;CMD 不由服务器发送。 */ csm_packet_free_void(pkt); return; } @@ -1011,9 +1003,9 @@ static void *csm_recv_thread_main(void *arg) csm_client_t *c = (csm_client_t *)arg; uint8_t header[CSM_HEADER_SIZE]; for (;;) { - /* Snapshot stop_flag and sock under state_lock. csm_client_disconnect() - * mutates both fields under the same lock, so a torn read or a stale - * sock value cannot occur and TSAN/UBSan stay quiet. */ + /* 在 state_lock 下快照 stop_flag 和 sock。csm_client_disconnect() + * 在同一锁下修改这两个字段,因此不会出现撕裂读或过时的 sock 值, + * TSAN/UBSan 也不会产生警告。 */ csm_mutex_lock(&c->state_lock); int stop_flag = c->stop_flag; csm_socket_t sock = c->sock; @@ -1036,10 +1028,10 @@ static void *csm_recv_thread_main(void *arg) csm_result_t r = csm_parse_packet(header, CSM_HEADER_SIZE, body, data_len, &parsed); free(body); if (r != CSM_OK) { - /* Skip corrupt frame; keep loop alive. */ + /* 跳过损坏帧;保持循环运行。 */ continue; } - /* Allocate heap copy to pass ownership to dispatch. */ + /* 分配堆副本以将所有权传递给派发函数。 */ csm_packet_t *heap_pkt = (csm_packet_t *)malloc(sizeof(*heap_pkt)); if (!heap_pkt) { csm_packet_dispose(&parsed); @@ -1049,7 +1041,7 @@ static void *csm_recv_thread_main(void *arg) csm_dispatch_packet(c, heap_pkt); } - /* Notify any blocked waiters that the connection is gone. */ + /* 通知所有阻塞的等待者连接已断开。 */ csm_queue_push(&c->resp_queue, NULL, 1, 0, NULL); csm_queue_push(&c->cmd_resp_queue, NULL, 1, 0, NULL); csm_queue_push(&c->status_queue, NULL, 1, 0, NULL); @@ -1065,7 +1057,7 @@ static void *csm_recv_thread_main(void *arg) #endif } -/* --- Lifecycle --- */ +/* --- 生命周期 --- */ csm_client_t *csm_client_create(void) { if (csm_wsa_startup() != 0) return NULL; @@ -1103,8 +1095,8 @@ void csm_client_destroy(csm_client_t *client) { csm_wsa_cleanup(); } -/* Resolve host and connect with a timeout. Returns CSM_OK or - * CSM_ERR_CONNECTION / CSM_ERR_TIMEOUT. */ +/* 解析主机名并在超时限制内建立连接。返回 CSM_OK 或 + * CSM_ERR_CONNECTION / CSM_ERR_TIMEOUT。 */ static csm_result_t csm_do_connect(const char *host, uint16_t port, unsigned int timeout_ms, csm_socket_t *out_sock) { @@ -1126,7 +1118,7 @@ static csm_result_t csm_do_connect(const char *host, uint16_t port, sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == CSM_INVALID_SOCKET) continue; - /* Switch to non-blocking for connect-with-timeout. */ + /* 切换为非阻塞模式以实现带超时的 connect。 */ #if defined(_WIN32) u_long mode = 1; ioctlsocket(sock, FIONBIO, &mode); @@ -1172,7 +1164,7 @@ static csm_result_t csm_do_connect(const char *host, uint16_t port, } if (result == CSM_OK) { - /* Switch back to blocking for the recv loop. */ + /* 切换回阻塞模式供接收循环使用。 */ #if defined(_WIN32) u_long mode2 = 0; ioctlsocket(sock, FIONBIO, &mode2); @@ -1253,7 +1245,7 @@ csm_result_t csm_client_disconnect(csm_client_t *client) { client->sock = CSM_INVALID_SOCKET; csm_mutex_unlock(&client->state_lock); - /* Wake any blocked waiters before tearing down the socket. */ + /* 在关闭套接字前唤醒所有阻塞的等待者。 */ csm_queue_push(&client->resp_queue, NULL, 1, 0, NULL); csm_queue_push(&client->cmd_resp_queue, NULL, 1, 0, NULL); csm_queue_push(&client->status_queue, NULL, 1, 0, NULL); @@ -1282,7 +1274,7 @@ csm_result_t csm_client_disconnect(csm_client_t *client) { int csm_client_is_connected(const csm_client_t *client) { if (!client) return 0; - /* Casting away const to take the lock; logically this is a read. */ + /* 去除 const 以获取锁;逻辑上这是一次读操作。 */ csm_client_t *mc = (csm_client_t *)client; csm_mutex_lock(&mc->state_lock); int v = mc->connected; @@ -1312,7 +1304,7 @@ csm_result_t csm_client_wait_for_server(const char *host, return result; } -/* --- Send helpers --- */ +/* --- 发送辅助函数 --- */ static csm_result_t csm_send_raw(csm_client_t *client, const uint8_t *data, size_t len) { @@ -1351,7 +1343,7 @@ static csm_result_t csm_send_raw(csm_client_t *client, return CSM_OK; } -/* Pack and send a CMD packet. */ +/* 打包并发送 CMD 数据包。 */ static csm_result_t csm_send_cmd(csm_client_t *client, const char *command) { size_t len = strlen(command); uint8_t *buf = (uint8_t *)malloc(CSM_HEADER_SIZE + len); @@ -1364,7 +1356,7 @@ static csm_result_t csm_send_cmd(csm_client_t *client, const char *command) { return r; } -/* --- Wait helpers --- */ +/* --- 等待辅助函数 --- */ static csm_result_t csm_wait_for_resp(csm_client_t *client, unsigned int timeout_ms, @@ -1399,13 +1391,13 @@ static csm_result_t csm_wait_for_cmd_resp(csm_client_t *client, return CSM_ERR_SERVER; } if (r != CSM_OK) return r; - /* Discard handshake payload. */ + /* 丢弃握手载荷。 */ csm_packet_t *pkt = (csm_packet_t *)item; csm_packet_free_void(pkt); return CSM_OK; } -/* --- Public command API --- */ +/* --- 公共命令 API --- */ csm_result_t csm_client_send_and_wait(csm_client_t *client, const char *command, @@ -1449,7 +1441,7 @@ csm_result_t csm_client_ping(csm_client_t *client, unsigned int timeout_ms, return CSM_OK; } -/* Shared helper: send a fixed-text command and return the response text. */ +/* 共享辅助函数:发送固定文本命令并返回响应文本。 */ static csm_result_t csm_send_text_query(csm_client_t *client, const char *command, char **out_text, @@ -1459,7 +1451,7 @@ static csm_result_t csm_send_text_query(csm_client_t *client, csm_command_response_t resp = {0}; csm_result_t r = csm_client_send_and_wait(client, command, timeout_ms, &resp); if (r == CSM_OK) { - *out_text = (char *)resp.raw; /* transfer ownership; was NUL-terminated */ + *out_text = (char *)resp.raw; /* 转移所有权;已以 NUL 结尾 */ resp.raw = NULL; } else { csm_command_response_dispose(&resp); @@ -1472,7 +1464,7 @@ csm_result_t csm_client_list_modules(csm_client_t *client, char **out_text, return csm_send_text_query(client, "List", out_text, timeout_ms); } -/* Build a " " command and send. */ +/* 构造 " " 命令并发送。 */ static csm_result_t csm_send_text_query_2(csm_client_t *client, const char *prefix, const char *module, @@ -1507,7 +1499,7 @@ csm_result_t csm_client_help(csm_client_t *client, const char *module, return csm_send_text_query_2(client, "Help", module, out_text, timeout_ms); } -/* --- Subscriptions --- */ +/* --- 订阅 --- */ csm_result_t csm_client_subscribe_status(csm_client_t *client, const char *status_name, @@ -1517,13 +1509,12 @@ csm_result_t csm_client_subscribe_status(csm_client_t *client, unsigned int timeout_ms) { if (!client || !status_name || !module_name) return CSM_ERR_INVALID; - /* Register first to eliminate the race where a STATUS arrives before - * the callback is stored. */ + /* 先注册,以消除 STATUS 在回调存储前到达的竞态条件。 */ csm_result_t r = csm_register_status_sub(client, status_name, module_name, callback, user_data); if (r != CSM_OK) return r; - /* Build "@ ->". */ + /* 构造 "@ ->"。 */ size_t s_len = strlen(status_name); size_t m_len = strlen(module_name); const char *suffix = " ->"; @@ -1588,7 +1579,7 @@ csm_result_t csm_client_unregister_async_callback(csm_client_t *client, return CSM_OK; } -/* --- Polling queues --- */ +/* --- 轮询队列 --- */ csm_result_t csm_client_poll_status(csm_client_t *client, csm_status_notification_t *out_notif, @@ -1599,7 +1590,7 @@ csm_result_t csm_client_poll_status(csm_client_t *client, csm_result_t r = csm_queue_pop(&client->status_queue, timeout_ms, &item, NULL); if (r != CSM_OK) return r; csm_status_notification_t *src = (csm_status_notification_t *)item; - /* Move ownership of fields from src to out_notif. */ + /* 将字段所有权从 src 移至 out_notif。 */ *out_notif = *src; free(src); return CSM_OK; diff --git a/SDK/c/tests/mock_server.c b/SDK/c/tests/mock_server.c index b16708d..7c62f4b 100644 --- a/SDK/c/tests/mock_server.c +++ b/SDK/c/tests/mock_server.c @@ -1,4 +1,4 @@ -/* mock_server.c - cross-platform implementation of the test mock server. */ +/* mock_server.c - 测试模拟服务器的跨平台实现。 */ #if !defined(_WIN32) # ifndef _POSIX_C_SOURCE @@ -38,7 +38,7 @@ static void ms_cond_signal(ms_cond_t *c){WakeConditionVariable(c);} static int ms_cond_wait_ms(ms_cond_t *c, ms_mutex_t *m, unsigned int ms){ return SleepConditionVariableCS(c, m, ms == 0 ? INFINITE : ms) ? 1 : 0; } -#if 0 /* reserved for future use */ +#if 0 /* 保留供将来使用 */ static void ms_sleep_ms(unsigned int ms){Sleep(ms);} #endif #else @@ -71,7 +71,7 @@ static int ms_cond_wait_ms(ms_cond_t *c, ms_mutex_t *m, unsigned int ms){ if (ts.tv_nsec>=1000000000L){ts.tv_sec+=ts.tv_nsec/1000000000L;ts.tv_nsec%=1000000000L;} return pthread_cond_timedwait(c,m,&ts) == 0 ? 1 : 0; } -#if 0 /* reserved for future use */ +#if 0 /* 保留供将来使用 */ static void ms_sleep_ms(unsigned int ms){ struct timespec ts; ts.tv_sec=ms/1000; ts.tv_nsec=(long)(ms%1000)*1000000L; nanosleep(&ts,NULL); @@ -87,7 +87,7 @@ static void ms_sleep_ms(unsigned int ms){ #define MS_HEADER 8 #define MS_VER 0x01 -/* --- response map --- */ +/* --- 响应映射 --- */ typedef struct ms_resp { struct ms_resp *next; char *cmd; @@ -96,7 +96,7 @@ typedef struct ms_resp { size_t data_len; } ms_resp_t; -/* --- received command queue --- */ +/* --- 已接收命令队列 --- */ typedef struct ms_msg { struct ms_msg *next; char *text; @@ -146,7 +146,7 @@ static uint32_t ms_unpack_be32(const uint8_t *b){ return ((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|(uint32_t)b[3]; } -/* Encode header + payload into newly-allocated buffer; caller frees. */ +/* 将头部 + 载荷编码到新分配的缓冲区中;调用者负责释放。 */ static uint8_t *ms_encode(csm_packet_type_t type, const void *data, size_t len, size_t *out_len) { uint8_t *buf = (uint8_t *)malloc(MS_HEADER + len); if (!buf) return NULL; @@ -191,7 +191,7 @@ static int ms_recv_all(ms_socket_t s, uint8_t *buf, size_t len) { return 0; } -/* --- Public API --- */ +/* --- 公共 API --- */ csm_mock_server_t *csm_mock_server_create(void) { if (ms_wsa_init() != 0) return NULL; @@ -210,7 +210,7 @@ csm_mock_server_t *csm_mock_server_create(void) { static void ms_handle_command(csm_mock_server_t *s, ms_socket_t conn, const char *cmd) { - /* Look up custom response. */ + /* 查找自定义响应。 */ ms_mutex_lock(&s->resp_lock); ms_resp_t *r = s->responses; while (r) { @@ -225,7 +225,7 @@ static void ms_handle_command(csm_mock_server_t *s, ms_socket_t conn, } ms_mutex_unlock(&s->resp_lock); - /* Built-in defaults. */ + /* 内置默认值。 */ size_t out_len = 0; uint8_t *wire = NULL; if (strcmp(cmd, "Ping") == 0) { @@ -246,7 +246,7 @@ static void ms_handle_command(csm_mock_server_t *s, ms_socket_t conn, } else if (strstr(cmd, "->") || strstr(cmd, "->")) { wire = ms_encode(CSM_PT_CMD_RESP, "", 0, &out_len); } else { - /* Generic async handshake. */ + /* 通用异步握手。 */ wire = ms_encode(CSM_PT_CMD_RESP, "", 0, &out_len); } if (wire) { ms_send_all(conn, wire, out_len); free(wire); } @@ -264,7 +264,7 @@ ms_client_thread(void *arg) { ms_socket_t conn = ctx->conn; free(ctx); - /* Send welcome INFO. */ + /* 发送欢迎 INFO 包。 */ size_t wlen = 0; uint8_t *welcome = ms_encode(CSM_PT_INFO, "Welcome to mock server", 22, &wlen); if (welcome) { ms_send_all(conn, welcome, wlen); free(welcome); } @@ -285,15 +285,15 @@ ms_client_thread(void *arg) { if (data_len) memcpy(cmd, body, data_len); cmd[data_len] = '\0'; - /* Trim trailing whitespace. */ + /* 修剪尾部空白。 */ size_t L = strlen(cmd); while (L && (cmd[L-1]==' '||cmd[L-1]=='\r'||cmd[L-1]=='\n'||cmd[L-1]=='\t')) cmd[--L] = '\0'; - /* Handle the command first (using the local copy). */ + /* 先处理命令(使用本地副本)。 */ ms_handle_command(s, conn, cmd); - /* Then enqueue a copy for the test to inspect. */ + /* 然后将副本入队供测试检查。 */ ms_msg_t *m = (ms_msg_t *)calloc(1, sizeof(*m)); if (m) { m->text = (char *)malloc(strlen(cmd) + 1); @@ -315,7 +315,7 @@ ms_client_thread(void *arg) { free(body); } - /* Remove from clients list. */ + /* 从客户端列表中移除。 */ ms_mutex_lock(&s->client_lock); for (int i = 0; i < MS_MAX_CLIENTS; ++i) { if (s->clients[i] == conn) { s->clients[i] = MS_INVALID_SOCKET; break; } @@ -430,7 +430,7 @@ void csm_mock_server_stop(csm_mock_server_t *s) { ms_close_socket(s->listen_sock); s->listen_sock = MS_INVALID_SOCKET; } - /* Close client sockets to wake handlers. */ + /* 关闭客户端套接字以唤醒处理程序。 */ ms_mutex_lock(&s->client_lock); for (int i = 0; i < MS_MAX_CLIENTS; ++i) { if (s->clients[i] != MS_INVALID_SOCKET) { @@ -454,7 +454,7 @@ void csm_mock_server_stop(csm_mock_server_t *s) { #endif s->thread_started = 0; } - /* Wait for handlers to finish (best-effort, brief). */ + /* 等待处理程序完成(尽力而为,时间较短)。 */ ms_mutex_lock(&s->handler_lock); int waited = 0; while (s->handler_count > 0 && waited < 20) { diff --git a/SDK/c/tests/mock_server.h b/SDK/c/tests/mock_server.h index 1c8f611..8ac56d0 100644 --- a/SDK/c/tests/mock_server.h +++ b/SDK/c/tests/mock_server.h @@ -1,6 +1,6 @@ -/* mock_server.h - In-process TCP server emulating a CSM-TCP-Router for tests. +/* mock_server.h - 用于测试的进程内 TCP 服务器,模拟 CSM-TCP-Router。 * - * Mirrors the Python `tests/conftest.py` MockServer fixture. + * 对应 Python `tests/conftest.py` 中的 MockServer 夹具。 */ #ifndef CSM_MOCK_SERVER_H #define CSM_MOCK_SERVER_H @@ -11,38 +11,38 @@ typedef struct csm_mock_server csm_mock_server_t; -/** Create a stopped mock server bound to 127.0.0.1; the actual port is - * assigned by the OS in csm_mock_server_start(). */ +/** 创建一个已停止的模拟服务器,绑定到 127.0.0.1; + * 实际端口由操作系统在 csm_mock_server_start() 中分配。 */ csm_mock_server_t *csm_mock_server_create(void); -/** Free a (running or stopped) mock server. */ +/** 释放(运行中或已停止的)模拟服务器。 */ void csm_mock_server_destroy(csm_mock_server_t *s); -/** Bind to 127.0.0.1, an ephemeral port, and start the accept thread. */ +/** 绑定到 127.0.0.1 的临时端口,并启动接受连接线程。 */ int csm_mock_server_start(csm_mock_server_t *s); -/** Stop the accept thread and close all client connections. */ +/** 停止接受连接线程并关闭所有客户端连接。 */ void csm_mock_server_stop(csm_mock_server_t *s); -/** Return the port the server is listening on (valid after start()). */ +/** 返回服务器正在监听的端口(start() 之后有效)。 */ uint16_t csm_mock_server_port(const csm_mock_server_t *s); -/** Register a custom RESP reply for an exact command string. */ +/** 为精确匹配的命令字符串注册自定义 RESP 回复。 */ void csm_mock_server_set_response(csm_mock_server_t *s, const char *cmd_text, const char *resp_text); -/** Register an ERROR reply for an exact command string. */ +/** 为精确匹配的命令字符串注册 ERROR 回复。 */ void csm_mock_server_set_error_response(csm_mock_server_t *s, const char *cmd_text, const char *error_text); -/** Push a STATUS packet to all currently connected clients. */ +/** 向所有当前连接的客户端推送 STATUS 数据包。 */ void csm_mock_server_push_status(csm_mock_server_t *s, const char *payload); -/** Pop the next received command, blocking up to *timeout_ms*. The returned - * string is owned by the caller and must be freed with csm_string_free. - * Returns NULL on timeout. */ +/** 弹出下一条已接收的命令,最多阻塞 *timeout_ms* 毫秒。返回的 + * 字符串由调用者拥有,必须使用 csm_string_free 释放。 + * 超时时返回 NULL。 */ char *csm_mock_server_get_received(csm_mock_server_t *s, unsigned int timeout_ms); #endif /* CSM_MOCK_SERVER_H */ diff --git a/SDK/c/tests/test_client.c b/SDK/c/tests/test_client.c index 279447d..2d949cd 100644 --- a/SDK/c/tests/test_client.c +++ b/SDK/c/tests/test_client.c @@ -1,5 +1,5 @@ -/* test_client.c - Unit-level tests for the client object lifecycle that do - * not require a running mock server. */ +/* test_client.c - 客户端对象生命周期的单元测试, + * 无需运行中的模拟服务器。 */ #include "csm_tcp_router_client.h" #include "test_harness.h" @@ -37,7 +37,7 @@ CSM_TEST(test_invalid_args_rejected) { } CSM_TEST(test_wait_for_server_unreachable_times_out) { - /* Pick an arbitrary high port that should not be in use. */ + /* 选择一个不太可能被使用的任意高端口。 */ csm_result_t r = csm_client_wait_for_server("127.0.0.1", 1, 200, 50); CSM_ASSERT_EQ_INT(r, CSM_ERR_TIMEOUT); } @@ -45,7 +45,7 @@ CSM_TEST(test_wait_for_server_unreachable_times_out) { CSM_TEST(test_connect_unreachable_returns_connection_error) { csm_client_t *c = csm_client_create(); csm_result_t r = csm_client_connect(c, "127.0.0.1", 1, 300); - /* Either CSM_ERR_CONNECTION (refused) or CSM_ERR_TIMEOUT depending on OS. */ + /* 根据操作系统,返回 CSM_ERR_CONNECTION(拒绝连接)或 CSM_ERR_TIMEOUT。 */ CSM_ASSERT(r == CSM_ERR_CONNECTION || r == CSM_ERR_TIMEOUT); csm_client_destroy(c); } diff --git a/SDK/c/tests/test_harness.h b/SDK/c/tests/test_harness.h index ad123c3..568347c 100644 --- a/SDK/c/tests/test_harness.h +++ b/SDK/c/tests/test_harness.h @@ -1,10 +1,8 @@ -/* test_harness.h - Minimal in-process unit-test harness used by the - * csm-tcp-router-client C SDK tests. +/* test_harness.h - csm-tcp-router-client C SDK 测试使用的极简进程内单元测试框架。 * - * Tests register themselves via the CSM_TEST() macro; the runner in - * test_main.c picks them up via the link-time TESTS array, executes them, - * and prints a summary. Failures abort the current test only; assertions - * use longjmp to unwind back to the runner. + * 测试通过 CSM_TEST() 宏注册自身;test_main.c 中的运行器 + * 通过链接时的 TESTS 数组收集测试,依次执行并打印汇总结果。 + * 失败仅中止当前测试;断言使用 longjmp 回退到运行器。 */ #ifndef CSM_TEST_HARNESS_H #define CSM_TEST_HARNESS_H @@ -20,7 +18,7 @@ typedef struct { csm_test_fn fn; } csm_test_t; -/* Provided by test_main.c. */ +/* 由 test_main.c 提供。 */ extern jmp_buf csm_test_jmp; extern int csm_test_failed; extern int csm_test_assertions; @@ -54,8 +52,8 @@ extern int csm_test_assertions; _b ? _b : "(null)", _a ? _a : "(null)"); \ } while (0) -/* Define a test function. The runner declares each test as extern via the - * CSM_TEST_EXTERN macro and registers it in its tests table. */ +/* 定义一个测试函数。运行器通过 CSM_TEST_EXTERN 宏将每个测试声明为 extern, + * 并在测试表中注册。 */ #define CSM_TEST(name) void name(void) #define CSM_TEST_EXTERN(name) extern void name(void) diff --git a/SDK/c/tests/test_integration.c b/SDK/c/tests/test_integration.c index 24f1a86..75a3aa2 100644 --- a/SDK/c/tests/test_integration.c +++ b/SDK/c/tests/test_integration.c @@ -1,4 +1,4 @@ -/* test_integration.c - End-to-end tests against the in-process MockServer. */ +/* test_integration.c - 针对进程内 MockServer 的端到端测试。 */ #if !defined(_WIN32) # ifndef _POSIX_C_SOURCE @@ -25,7 +25,7 @@ static void it_sleep_ms(unsigned int ms) { } #endif -/* Helper: spin up server + connect a client. */ +/* 辅助函数:启动服务器 + 连接客户端。 */ static void it_setup(csm_mock_server_t **out_s, csm_client_t **out_c) { csm_mock_server_t *s = csm_mock_server_create(); CSM_ASSERT(s != NULL); @@ -118,7 +118,7 @@ CSM_TEST(it_post_async_handshake) { static void it_status_cb(const csm_status_notification_t *n, void *ud) { int *count = (int *)ud; (*count)++; - /* Sanity-check parsed fields. */ + /* 完整性检查已解析的字段。 */ (void)n; } @@ -129,16 +129,16 @@ CSM_TEST(it_subscribe_status_invokes_callback) { csm_result_t r = csm_client_subscribe_status(c, "Status", "DAQmx", it_status_cb, &count, 1000); CSM_ASSERT_EQ_INT(r, CSM_OK); - /* Drain handshake from received queue. */ + /* 从已接收队列中消耗握手数据。 */ char *cmd = csm_mock_server_get_received(s, 500); csm_string_free(cmd); - /* Push a STATUS notification matching the subscription. */ + /* 推送一条匹配订阅的 STATUS 通知。 */ csm_mock_server_push_status(s, "Status >> 1.23 <- DAQmx"); - /* Wait for callback (poll up to 1s). */ + /* 等待回调(最多轮询 1 秒)。 */ for (int i = 0; i < 100 && count == 0; ++i) it_sleep_ms(10); CSM_ASSERT(count >= 1); - /* Same notification should also be available via polling. */ + /* 同一通知也应可通过轮询获取。 */ csm_status_notification_t n = {0}; r = csm_client_poll_status(c, &n, 500); CSM_ASSERT_EQ_INT(r, CSM_OK); @@ -154,8 +154,8 @@ CSM_TEST(it_subscribe_status_invokes_callback) { CSM_TEST(it_disconnect_unblocks_waiters) { csm_mock_server_t *s = NULL; csm_client_t *c = NULL; it_setup(&s, &c); - /* Send a command that has no canned response; mock returns CMD_RESP. */ - /* For this test simply disconnect immediately and verify subsequent send fails. */ + /* 发送一个没有预置响应的命令;模拟服务器返回 CMD_RESP。 */ + /* 本测试中直接立即断开连接并验证后续发送失败。 */ csm_client_disconnect(c); csm_command_response_t resp = {0}; csm_result_t r = csm_client_send_and_wait(c, "Ping", 200, &resp); diff --git a/SDK/c/tests/test_main.c b/SDK/c/tests/test_main.c index 4a6628b..5fcdad9 100644 --- a/SDK/c/tests/test_main.c +++ b/SDK/c/tests/test_main.c @@ -1,4 +1,4 @@ -/* test_main.c - Runner for the C SDK test suite. */ +/* test_main.c - C SDK 测试套件的运行器。 */ #include "test_harness.h" #include @@ -64,8 +64,7 @@ static const csm_test_t TESTS[] = { int main(int argc, char **argv) { const char *only = (argc > 1) ? argv[1] : NULL; - /* `volatile` ensures these survive the longjmp performed by failing - * assertions inside individual test bodies. */ + /* `volatile` 确保这些变量在各个测试体内的失败断言执行 longjmp 后仍然存活。 */ volatile int passed = 0, failed = 0, skipped = 0; volatile int total_assertions = 0; size_t n = sizeof(TESTS) / sizeof(TESTS[0]); diff --git a/SDK/c/tests/test_protocol.c b/SDK/c/tests/test_protocol.c index 03f509d..a8c9e8a 100644 --- a/SDK/c/tests/test_protocol.c +++ b/SDK/c/tests/test_protocol.c @@ -1,4 +1,4 @@ -/* test_protocol.c - Unit tests for the protocol codec. */ +/* test_protocol.c - 协议编解码的单元测试。 */ #include "csm_tcp_router_client.h" #include "test_harness.h" @@ -18,7 +18,7 @@ CSM_TEST(test_encode_decode_roundtrip) { CSM_ASSERT_EQ_INT(r, CSM_OK); CSM_ASSERT_EQ_INT(out_len, 8 + 5); - /* Header bytes: big-endian length, version, type, flag1, flag2. */ + /* 头部字节:大端序长度、版本、类型、flag1、flag2。 */ CSM_ASSERT_EQ_INT(buf[0], 0); CSM_ASSERT_EQ_INT(buf[1], 0); CSM_ASSERT_EQ_INT(buf[2], 0); diff --git a/SDK/csharp/examples/BasicUsage/Program.cs b/SDK/csharp/examples/BasicUsage/Program.cs index 7384c05..2bdd24a 100644 --- a/SDK/csharp/examples/BasicUsage/Program.cs +++ b/SDK/csharp/examples/BasicUsage/Program.cs @@ -4,11 +4,11 @@ namespace CsmTcpRouter.Examples.BasicUsage { /// - /// Basic usage example for csm-tcp-router-client (C#). - /// Mirrors SDK/python/examples/basic_usage.py. + /// csm-tcp-router-client(C#)的基本用法示例。 + /// 镜像 SDK/python/examples/basic_usage.py。 /// - /// Prerequisites: a running CSM-TCP-Router server (LabVIEW app). - /// The reference app defaults to port 30007. + /// 前提条件:正在运行的 CSM-TCP-Router 服务器(LabVIEW 应用程序)。 + /// 参考应用程序默认使用端口 30007。 /// public static class Program { @@ -17,7 +17,7 @@ public static class Program public static int Main(string[] args) { - // 1. Wait until the server is ready. + // 1. 等待服务器就绪。 Console.Write("Waiting for server ... "); using (var probe = new TcpRouterClient()) { @@ -30,7 +30,7 @@ public static int Main(string[] args) } Console.WriteLine("ready."); - // 2. Connect (use as IDisposable so Disconnect is always called). + // 2. 连接(使用 IDisposable,确保始终调用 Disconnect)。 using (var client = new TcpRouterClient()) { try @@ -45,17 +45,17 @@ public static int Main(string[] args) Console.WriteLine($"Connected to {Host}:{Port}"); - // 3. Ping + // 3. Ping 测试 var (ok, elapsed) = client.Ping(TimeSpan.FromSeconds(2)); Console.WriteLine(ok ? $"Ping OK latency={elapsed.TotalMilliseconds:F1} ms" : "Ping failed."); - // 4. List CSM modules + // 4. 列出 CSM 模块 string modules = client.ListModules(); Console.WriteLine($"\nLoaded modules:\n{modules}"); - // 5. List API for the first module (if any) + // 5. 列出第一个模块的 API(如有) string firstModule = null; foreach (var line in modules.Split('\n')) { @@ -72,14 +72,14 @@ public static int Main(string[] args) Console.WriteLine($"\nAPI for '{firstModule}':\n{api}"); } - // 6. Send a synchronous command (uncomment & adapt for your CSM): + // 6. 发送同步命令(取消注释并适配您的 CSM): // var resp = client.SendAndWait("API: Read -@ DAQmx"); // Console.WriteLine($"\nSync response: {resp.Text}"); - // 7. Send an async command + wait for cmd-resp handshake: + // 7. 发送异步命令并等待 cmd-resp 握手: // client.Post("API: Start Sampling -> DAQmx"); - // 8. Send a no-reply command: + // 8. 发送无回复命令: // client.PostNoReply("API: Reset ->| DAQmx"); Console.WriteLine("\nDone."); diff --git a/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs b/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs index 1a73673..6d1fa91 100644 --- a/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs +++ b/SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs @@ -1,27 +1,27 @@ // CsmTcpRouter.cs // --------------------------------------------------------------------------- -// csm-tcp-router-client - C# client SDK for the CSM-TCP-Router LabVIEW server. +// csm-tcp-router-client - CSM-TCP-Router LabVIEW 服务器的 C# 客户端 SDK。 // -// Single-file SDK implementing CSM-TCP-Router protocol v0. Mirrors the -// Python `csm_tcp_router` package layout and feature set: +// 单文件 SDK,实现 CSM-TCP-Router 协议 v0。镜像了 +// Python `csm_tcp_router` 包的布局和功能: // -// * Protocol codec (8-byte header, big-endian, 8 packet types). -// * Background-receiver TCP transport. -// * High-level TcpRouterClient with sync and async APIs: -// - SendAndWait / SendAndWaitAsync (synchronous CMD/RESP) -// - Post / PostAsync (async CMD with cmd-resp handshake) -// - PostNoReply / PostNoReplyAsync (no-reply async CMD) -// - Ping / PingAsync (round-trip latency) +// * 协议编解码器(8 字节头,大端序,8 种数据包类型)。 +// * 后台接收 TCP 传输层。 +// * 高层 TcpRouterClient,提供同步和异步 API: +// - SendAndWait / SendAndWaitAsync (同步 CMD/RESP) +// - Post / PostAsync (带 cmd-resp 握手的异步 CMD) +// - PostNoReply / PostNoReplyAsync (无回复异步 CMD) +// - Ping / PingAsync (往返延迟) // - ListModules / ListApi / ListStates / Help // - SubscribeStatus / UnsubscribeStatus // - RegisterAsyncCallback / UnregisterAsyncCallback // -// Wire format (8-byte header, big-endian):: +// 线路格式(8 字节头,大端序): // // | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | // +------------------------ Header (8B) -----------------------+ // -// followed by exactly `Data Length` bytes of payload. +// 后跟恰好 `Data Length` 字节的有效载荷。 // // Copyright (c) 2026 NEVSTOP-LAB. Released under the MIT License. // --------------------------------------------------------------------------- @@ -42,37 +42,37 @@ namespace CsmTcpRouter { // ----------------------------------------------------------------------- - // Public enumerations + // 公共枚举 // ----------------------------------------------------------------------- /// - /// Packet type constants as defined in the CSM-TCP-Router protocol v0. + /// CSM-TCP-Router 协议 v0 中定义的数据包类型常量。 /// public enum PacketType : byte { - /// Informational message (welcome / goodbye). + /// 信息消息(欢迎/再见)。 Info = 0x00, - /// Error packet from the server. + /// 来自服务器的错误数据包。 Error = 0x01, - /// Command sent by the client. + /// 客户端发送的命令。 Cmd = 0x02, - /// Server handshake for async / no-reply / subscribe. + /// 服务器对异步/无回复/订阅命令的握手确认。 CmdResp = 0x03, - /// Synchronous response payload. + /// 同步响应有效载荷。 Resp = 0x04, - /// Asynchronous response payload. + /// 异步响应有效载荷。 AsyncResp = 0x05, - /// Status broadcast from a subscribed CSM module. + /// 来自已订阅 CSM 模块的状态广播。 Status = 0x06, - /// Interrupt broadcast from a subscribed CSM module. + /// 来自已订阅 CSM 模块的中断广播。 Interrupt = 0x07, } // ----------------------------------------------------------------------- - // Public data models + // 公共数据模型 // ----------------------------------------------------------------------- - /// A decoded packet received from the server. + /// 从服务器接收到的已解码数据包。 public sealed class Packet { public PacketType Type { get; } @@ -91,7 +91,7 @@ public Packet(PacketType type, byte[] data, byte version = 1, byte flag1 = 0, by } } - /// The result of a synchronous command (). + /// 同步命令()的结果。 public sealed class CommandResponse { public byte[] Raw { get; } @@ -105,7 +105,7 @@ public CommandResponse(byte[] raw) public override string ToString() => $"CommandResponse(\"{Text}\")"; } - /// An asynchronous response payload delivered via an async-resp packet. + /// 通过异步响应数据包传递的异步响应有效载荷。 public sealed class AsyncResponse { public byte[] Raw { get; } @@ -119,8 +119,8 @@ public AsyncResponse(byte[] raw, string originalCommand = "") } /// - /// Parse an ASYNC_RESP packet. Server format: - /// "<response-data> <- <original-command>". + /// 解析 ASYNC_RESP 数据包。服务器格式: + /// "<response-data> <- <original-command>"。 /// public static AsyncResponse FromPacket(Packet packet) { @@ -139,7 +139,7 @@ public static AsyncResponse FromPacket(Packet packet) public override string ToString() => $"AsyncResponse(\"{Text}\", cmd=\"{OriginalCommand}\")"; } - /// A status broadcast delivered via a STATUS or INTERRUPT packet. + /// 通过 STATUS 或 INTERRUPT 数据包传递的状态广播。 public sealed class StatusNotification { public byte[] Raw { get; } @@ -163,8 +163,8 @@ public StatusNotification( } /// - /// Parse a STATUS or INTERRUPT packet. Server format: - /// "<status-name> >> <data> <- <module>". + /// 解析 STATUS 或 INTERRUPT 数据包。服务器格式: + /// "<status-name> >> <data> <- <module>"。 /// public static StatusNotification FromPacket(Packet packet) { @@ -194,10 +194,10 @@ public override string ToString() => } // ----------------------------------------------------------------------- - // Exception hierarchy + // 异常层次结构 // ----------------------------------------------------------------------- - /// Base exception for all CSM-TCP-Router client errors. + /// 所有 CSM-TCP-Router 客户端错误的基础异常。 public class CsmTcpRouterException : Exception { public CsmTcpRouterException() { } @@ -205,28 +205,28 @@ public CsmTcpRouterException(string message) : base(message) { } public CsmTcpRouterException(string message, Exception innerException) : base(message, innerException) { } } - /// Raised when a connection cannot be established or is lost. + /// 当连接无法建立或连接丢失时引发。 public class RouterConnectionException : CsmTcpRouterException { public RouterConnectionException(string message) : base(message) { } public RouterConnectionException(string message, Exception innerException) : base(message, innerException) { } } - /// Raised when a synchronous operation exceeds its timeout. + /// 当同步操作超过其超时时间时引发。 public class RouterTimeoutException : CsmTcpRouterException { public RouterTimeoutException(string message) : base(message) { } } - /// Raised when an invalid or unexpected protocol frame is received. + /// 当收到无效或意外的协议帧时引发。 public class ProtocolException : CsmTcpRouterException { public ProtocolException(string message) : base(message) { } } /// - /// Raised when the server returns an error packet. CSM Error format: - /// [Error: <code>] <message>. + /// 当服务器返回错误数据包时引发。CSM 错误格式: + /// [Error: <code>] <message>。 /// public class ServerException : CsmTcpRouterException { @@ -249,7 +249,7 @@ public override string ToString() } // ----------------------------------------------------------------------- - // Internal protocol codec + // 内部协议编解码器 // ----------------------------------------------------------------------- internal static class ProtocolCodec @@ -257,7 +257,7 @@ internal static class ProtocolCodec public const int HeaderSize = 8; public const byte ProtocolVersion = 0x01; - /// Encode into a complete wire-format packet (header + body). + /// 编码为完整的线路格式数据包(头 + 体)。 public static byte[] EncodePacket(byte[] data, PacketType packetType, byte flag1 = 0, byte flag2 = 0) { data = data ?? Array.Empty(); @@ -275,7 +275,7 @@ public static byte[] EncodePacket(byte[] data, PacketType packetType, byte flag1 return wire; } - /// Decode an 8-byte header into its constituent fields. + /// 将 8 字节头解码为其组成字段。 public static (uint DataLen, byte Version, byte TypeByte, byte Flag1, byte Flag2) DecodeHeader(byte[] header) { if (header == null || header.Length != HeaderSize) @@ -285,7 +285,7 @@ public static (uint DataLen, byte Version, byte TypeByte, byte Flag1, byte Flag2 return (dataLen, header[4], header[5], header[6], header[7]); } - /// Build a from raw header + body. + /// 从原始头 + 体构建 public static Packet ParsePacket(byte[] header, byte[] body) { var (dataLen, version, typeByte, flag1, flag2) = DecodeHeader(header); @@ -293,14 +293,14 @@ public static Packet ParsePacket(byte[] header, byte[] body) if ((uint)body.Length != dataLen) throw new ProtocolException( $"Payload length mismatch: header says {dataLen} bytes, got {body.Length} bytes."); - // Forward-compatible: unknown type bytes are mapped to Info. + // 向前兼容:未知类型字节映射为 Info。 PacketType ptype = Enum.IsDefined(typeof(PacketType), typeByte) ? (PacketType)typeByte : PacketType.Info; return new Packet(ptype, body, version, flag1, flag2); } - /// Extract code and message from a CSM Error format [Error: code] msg. + /// 从 CSM 错误格式 [Error: code] msg 中提取代码和消息。 public static ServerException ParseServerError(Packet packet) { string text = Encoding.UTF8.GetString(packet.Data).Trim(); @@ -320,7 +320,7 @@ public static ServerException ParseServerError(Packet packet) } // ----------------------------------------------------------------------- - // Internal TCP transport (background receive task) + // 内部 TCP 传输层(后台接收任务) // ----------------------------------------------------------------------- internal sealed class Transport : IDisposable @@ -367,11 +367,11 @@ public async Task ConnectAsync(string host, int port, TimeSpan? timeout = null) var winner = await Task.WhenAny(connectTask, Task.Delay(to)).ConfigureAwait(false); if (winner != connectTask) { - try { client.Close(); } catch { /* ignore */ } + try { client.Close(); } catch { /* 忽略 */ } throw new RouterConnectionException( $"Cannot connect to {host}:{port}: timed out after {to.TotalSeconds:F1}s."); } - await connectTask.ConfigureAwait(false); // surface any connect exception + await connectTask.ConfigureAwait(false); // 让任何连接异常浮现 } catch (RouterConnectionException) { @@ -379,7 +379,7 @@ public async Task ConnectAsync(string host, int port, TimeSpan? timeout = null) } catch (Exception exc) { - try { client.Close(); } catch { /* ignore */ } + try { client.Close(); } catch { /* 忽略 */ } throw new RouterConnectionException($"Cannot connect to {host}:{port}: {exc.Message}", exc); } @@ -393,15 +393,15 @@ public async Task ConnectAsync(string host, int port, TimeSpan? timeout = null) public void Disconnect(TimeSpan? joinTimeout = null) { _stopped = true; - try { _cts?.Cancel(); } catch { /* ignore */ } - try { _stream?.Close(); } catch { /* ignore */ } - try { _client?.Close(); } catch { /* ignore */ } + try { _cts?.Cancel(); } catch { /* 忽略 */ } + try { _stream?.Close(); } catch { /* 忽略 */ } + try { _client?.Close(); } catch { /* 忽略 */ } _stream = null; _client = null; var jt = joinTimeout ?? TimeSpan.FromSeconds(2); - try { _recvTask?.Wait(jt); } catch { /* ignore */ } + try { _recvTask?.Wait(jt); } catch { /* 忽略 */ } _recvTask = null; - try { _cts?.Dispose(); } catch { /* ignore */ } + try { _cts?.Dispose(); } catch { /* 忽略 */ } _cts = null; } @@ -427,7 +427,7 @@ public void SendRaw(byte[] data) public void Dispose() => Disconnect(); // --------------------------------------------------------------- - // Internal: background receive loop + // 内部:后台接收循环 // --------------------------------------------------------------- private async Task RecvLoopAsync(CancellationToken ct) @@ -453,22 +453,22 @@ private async Task RecvLoopAsync(CancellationToken ct) } catch (ProtocolException) { - // Corrupted frame -- skip it and keep the loop alive. + // 损坏的帧——跳过并保持循环运行。 continue; } try { _onPacket(packet); } catch { /* swallow callback errors */ } } } - catch (IOException) { /* connection dropped */ } - catch (ObjectDisposedException) { /* socket closed during read */ } - catch (OperationCanceledException) { /* shutdown */ } + catch (IOException) { /* 连接已断开 */ } + catch (ObjectDisposedException) { /* 读取期间套接字已关闭 */ } + catch (OperationCanceledException) { /* 正在关闭 */ } finally { if (!_stopped) { _stopped = true; - try { _onDisconnect(); } catch { /* ignore */ } + try { _onDisconnect(); } catch { /* 忽略 */ } } } } @@ -493,27 +493,26 @@ private static async Task ReadExactlyAsync(Stream stream, byte[] buf, int } // ----------------------------------------------------------------------- - // High-level client + // 高层客户端 // ----------------------------------------------------------------------- - /// Callback delegate for status / interrupt broadcasts. + /// 用于状态/中断广播的回调委托。 public delegate void StatusCallback(StatusNotification notification); - /// Callback delegate for asynchronous-response packets. + /// 用于异步响应数据包的回调委托。 public delegate void AsyncResponseCallback(AsyncResponse response); /// - /// C# client for a CSM-TCP-Router server. Mirrors the LabVIEW ClientAPI - /// VIs and the Python TcpRouterClient; speaks protocol v0. + /// CSM-TCP-Router 服务器的 C# 客户端。镜像了 LabVIEW ClientAPI VI + /// 和 Python TcpRouterClient;使用协议 v0。 /// - /// The class is thread-safe. At most one in-flight synchronous command - /// and one in-flight async / subscription command may be outstanding at a - /// time; concurrent callers are serialised by internal semaphores. + /// 该类是线程安全的。任意时刻最多只能有一个正在执行的同步命令 + /// 和一个正在执行的异步/订阅命令;并发调用者由内部信号量序列化。 /// public sealed class TcpRouterClient : IDisposable { - // One-item-deep "queues" for synchronised waits, implemented via TCS. - // Reset to a fresh TCS by each waiter inside the corresponding lock. + // 通过 TCS 实现的单元素"队列",用于同步等待。 + // 每个等待者在相应的锁内将其重置为新的 TCS。 private TaskCompletionSource _respTcs; private TaskCompletionSource _cmdRespTcs; @@ -528,10 +527,10 @@ private readonly Dictionary _asyncCallbacks private readonly Transport _transport; - /// Polling queue for async-resp packets received from the server. + /// 用于轮询从服务器接收到的异步响应数据包的队列。 public ConcurrentQueue AsyncResponseQueue { get; } = new ConcurrentQueue(); - /// Polling queue for status / interrupt notifications. + /// 用于轮询状态/中断通知的队列。 public ConcurrentQueue StatusQueue { get; } = new ConcurrentQueue(); public TcpRouterClient() @@ -540,42 +539,41 @@ public TcpRouterClient() } // --------------------------------------------------------------- - // Connection management + // 连接管理 // --------------------------------------------------------------- - /// Connect to a CSM-TCP-Router server. + /// 连接到 CSM-TCP-Router 服务器。 public void Connect(string host, int port, TimeSpan? timeout = null) => _transport.Connect(host, port, timeout); - /// Connect to a CSM-TCP-Router server (async). + /// 连接到 CSM-TCP-Router 服务器(异步)。 public Task ConnectAsync(string host, int port, TimeSpan? timeout = null) => _transport.ConnectAsync(host, port, timeout); /// - /// Disconnect from the server and release all resources. Any threads - /// currently blocked in / - /// will receive a immediately - /// rather than waiting for their timeout to expire. + /// 从服务器断开连接并释放所有资源。当前阻塞在 + /// / 中的线程将立即 + /// 收到 ,而不是等待超时。 /// public void Disconnect() { - // Unblock any pending waiters before tearing down the transport. + // 在拆除传输层之前解除所有挂起等待者的阻塞。 var sentinel = new RouterConnectionException("Disconnected from server."); UnblockWaiters(sentinel); _transport.Disconnect(); } - /// true when the underlying transport is connected. + /// 当底层传输层已连接时为 true public bool Connected => _transport.Connected; /// - /// Poll until : accepts - /// a connection or elapses. + /// 轮询直到 : 接受连接 + /// 或 超时。 /// public bool WaitForServer(string host, int port, TimeSpan? timeout = null, TimeSpan? retryInterval = null) => WaitForServerAsync(host, port, timeout, retryInterval).GetAwaiter().GetResult(); - /// Async version of . + /// 的异步版本。 public async Task WaitForServerAsync( string host, int port, TimeSpan? timeout = null, TimeSpan? retryInterval = null) { @@ -591,28 +589,27 @@ public async Task WaitForServerAsync( { try { - // Observe any connect exception (faulted task); - // success means the server is reachable. + // 观察任何连接异常(已故障任务); + // 成功表示服务器可访问。 await connectTask.ConfigureAwait(false); - try { probe.Close(); } catch { /* ignore */ } + try { probe.Close(); } catch { /* 忽略 */ } return true; } - catch (SocketException) { /* not ready yet */ } - catch (IOException) { /* not ready yet */ } + catch (SocketException) { /* 尚未就绪 */ } + catch (IOException) { /* 尚未就绪 */ } } else { - // Delay won; abort the in-flight connect attempt by - // closing the probe socket, then observe any pending - // exception so it is not unobserved. - try { probe.Close(); } catch { /* ignore */ } + // 延迟获胜;通过关闭探测套接字终止正在进行的连接尝试, + // 然后观察任何挂起的异常,以免其未被观察到。 + try { probe.Close(); } catch { /* 忽略 */ } try { await connectTask.ConfigureAwait(false); } - catch (SocketException) { /* not ready yet */ } - catch (IOException) { /* not ready yet */ } - catch (ObjectDisposedException) { /* connect aborted by closing probe */ } + catch (SocketException) { /* 尚未就绪 */ } + catch (IOException) { /* 尚未就绪 */ } + catch (ObjectDisposedException) { /* 关闭探测套接字导致连接中止 */ } } } await Task.Delay(interval).ConfigureAwait(false); @@ -621,7 +618,7 @@ public async Task WaitForServerAsync( } // --------------------------------------------------------------- - // Core command methods (sync wrappers) + // 核心命令方法(同步包装) // --------------------------------------------------------------- public CommandResponse SendAndWait(string command, TimeSpan? timeout = null) @@ -648,11 +645,11 @@ public void UnsubscribeStatus(string statusName, string moduleName, TimeSpan? ti => UnsubscribeStatusAsync(statusName, moduleName, timeout).GetAwaiter().GetResult(); // --------------------------------------------------------------- - // Core command methods (async) + // 核心命令方法(异步) // --------------------------------------------------------------- /// - /// Send a synchronous command (suffix -@) and wait for the response. + /// 发送同步命令(后缀 -@)并等待响应。 /// public async Task SendAndWaitAsync(string command, TimeSpan? timeout = null) { @@ -674,18 +671,18 @@ public async Task SendAndWaitAsync(string command, TimeSpan? ti } /// - /// Send an async command (suffix ->) and wait for the cmd-resp handshake. + /// 发送异步命令(后缀 ->)并等待 cmd-resp 握手。 /// public Task PostAsync(string command, TimeSpan? timeout = null) => SendAndAwaitCmdRespAsync(command, timeout); /// - /// Send an async no-reply command (suffix ->|) and wait for the cmd-resp handshake. + /// 发送异步无回复命令(后缀 ->|)并等待 cmd-resp 握手。 /// public Task PostNoReplyAsync(string command, TimeSpan? timeout = null) => SendAndAwaitCmdRespAsync(command, timeout); - /// Send a Ping and measure round-trip latency. + /// 发送 Ping 并测量往返延迟。 public async Task<(bool Ok, TimeSpan Elapsed)> PingAsync(TimeSpan? timeout = null) { var to = timeout ?? TimeSpan.FromSeconds(2); @@ -713,7 +710,7 @@ public Task ListStatesAsync(string module, TimeSpan? timeout = null) public Task HelpAsync(string module, TimeSpan? timeout = null) => SendAndWaitAsync($"Help {module}", timeout).ContinueWithText(); - /// Subscribe to a CSM module's status broadcast. + /// 订阅 CSM 模块的状态广播。 public async Task SubscribeStatusAsync( string statusName, string moduleName, StatusCallback callback = null, TimeSpan? timeout = null) { @@ -721,8 +718,8 @@ public async Task SubscribeStatusAsync( if (moduleName == null) throw new ArgumentNullException(nameof(moduleName)); var key = (statusName, moduleName); - // Register the callback *before* sending to eliminate the race - // where a STATUS packet could arrive before the callback is stored. + // 在发送之前注册回调,以消除状态数据包在回调 + // 存储之前到达的竞争条件。 lock (_stateLock) { _statusCallbacks[key] = callback; } string cmd = $"{statusName}@{moduleName} ->"; @@ -737,7 +734,7 @@ public async Task SubscribeStatusAsync( } } - /// Cancel a status subscription. + /// 取消状态订阅。 public async Task UnsubscribeStatusAsync(string statusName, string moduleName, TimeSpan? timeout = null) { if (statusName == null) throw new ArgumentNullException(nameof(statusName)); @@ -749,8 +746,8 @@ public async Task UnsubscribeStatusAsync(string statusName, string moduleName, T } /// - /// Register a callback for async-resp packets, matched by the original - /// command echoed in the async-resp payload (after the <- separator). + /// 为异步响应数据包注册回调,通过异步响应有效载荷中回显的原始命令 + /// (在 <- 分隔符之后)进行匹配。 /// public void RegisterAsyncCallback(string originalCommand, AsyncResponseCallback callback) { @@ -759,7 +756,7 @@ public void RegisterAsyncCallback(string originalCommand, AsyncResponseCallback lock (_stateLock) { _asyncCallbacks[originalCommand] = callback; } } - /// Remove a previously registered async callback. + /// 移除之前注册的异步回调。 public void UnregisterAsyncCallback(string originalCommand) { if (originalCommand == null) return; @@ -778,7 +775,7 @@ public void Dispose() } // --------------------------------------------------------------- - // Internal helpers + // 内部辅助方法 // --------------------------------------------------------------- private async Task SendAndAwaitCmdRespAsync(string command, TimeSpan? timeout) @@ -806,11 +803,10 @@ private async Task WaitForRespAsync(TimeSpan timeout) var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false); if (winner != tcs.Task) { - // Protocol v0 has no correlation id, so a late RESP for the - // timed-out command could be misattributed to the *next* - // SendAndWait call. Force a disconnect so the connection - // is unusable until the caller reconnects. - try { _transport.Disconnect(); } catch { /* ignore */ } + // 协议 v0 没有关联 ID,因此超时命令的延迟 RESP 可能被 + // 错误地归属于*下一个* SendAndWait 调用。强制断开连接, + // 使连接在调用者重新连接之前不可用。 + try { _transport.Disconnect(); } catch { /* 忽略 */ } throw new RouterTimeoutException($"No response received within {timeout.TotalSeconds:F1}s."); } object item = await tcs.Task.ConfigureAwait(false); @@ -825,15 +821,15 @@ private async Task WaitForCmdRespAsync(TimeSpan timeout) var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false); if (winner != tcs.Task) { - // Same desync risk as WaitForRespAsync: a late CMD_RESP could - // complete the next in-flight waiter. Force a disconnect so - // the connection cannot be reused after a handshake timeout. - try { _transport.Disconnect(); } catch { /* ignore */ } + // 与 WaitForRespAsync 中相同的去同步风险:延迟的 CMD_RESP 可能 + // 完成下一个正在等待的调用。强制断开连接,使握手超时后 + // 连接无法被复用。 + try { _transport.Disconnect(); } catch { /* 忽略 */ } throw new RouterTimeoutException($"No cmd-resp received within {timeout.TotalSeconds:F1}s."); } object item = await tcs.Task.ConfigureAwait(false); if (item is Exception exc) throw exc; - // CMD_RESP payload is a handshake acknowledgment; discard it. + // CMD_RESP 有效载荷是握手确认;丢弃它。 } private void UnblockWaiters(Exception sentinel) @@ -843,7 +839,7 @@ private void UnblockWaiters(Exception sentinel) } // --------------------------------------------------------------- - // Internal: packet dispatch (runs on the receive task thread) + // 内部:数据包分发(在接收任务线程上运行) // --------------------------------------------------------------- internal void OnPacket(Packet packet) @@ -866,7 +862,7 @@ internal void OnPacket(Packet packet) lock (_stateLock) { _asyncCallbacks.TryGetValue(resp.OriginalCommand, out cb); } if (cb != null) { - try { cb(resp); } catch { /* swallow callback errors */ } + try { cb(resp); } catch { /* 吞掉回调错误 */ } } break; } @@ -880,7 +876,7 @@ internal void OnPacket(Packet packet) lock (_stateLock) { _statusCallbacks.TryGetValue((notif.StatusName, notif.ModuleName), out cb); } if (cb != null) { - try { cb(notif); } catch { /* swallow callback errors */ } + try { cb(notif); } catch { /* 吞掉回调错误 */ } } break; } @@ -894,11 +890,11 @@ internal void OnPacket(Packet packet) } case PacketType.Info: - // Silently discarded (welcome / goodbye messages). + // 静默丢弃(欢迎/再见消息)。 break; case PacketType.Cmd: - // Server should never send CMD; ignore for forward compatibility. + // 服务器永远不应发送 CMD;为向前兼容性忽略。 break; } } @@ -910,7 +906,7 @@ internal void OnDisconnect() } // ----------------------------------------------------------------------- - // Small convenience extensions + // 小型便利扩展 // ----------------------------------------------------------------------- internal static class TaskExtensions diff --git a/SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs b/SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs index ba7dbad..31f531f 100644 --- a/SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs +++ b/SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs @@ -10,18 +10,17 @@ namespace CsmTcpRouter.Tests { /// - /// End-to-end client tests against a real loopback . - /// Mirrors SDK/python/tests/test_integration.py + portions of test_client.py. + /// 针对真实回环 的端到端客户端测试。 + /// 镜像 SDK/python/tests/test_integration.py 以及 test_client.py 的部分内容。 /// public class ClientIntegrationTests { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(2); /// - /// Bind a TcpListener to port 0 (OS-assigned), grab the port, then stop - /// the listener. The port is then almost certainly closed for the - /// duration of the test, so we can rely on connect attempts to fail - /// without depending on system state (e.g. port 1 may be open). + /// 将 TcpListener 绑定到端口 0(由操作系统分配),获取端口号,然后停止 + /// 监听器。在测试期间该端口几乎可以确定是关闭的,因此我们可以依赖连接 + /// 尝试失败,而无需依赖系统状态(例如,端口 1 可能是开放的)。 /// private static int GetClosedPort() { @@ -33,7 +32,7 @@ private static int GetClosedPort() } // --------------------------------------------------------------- - // Connect / Disconnect + // 连接 / 断开连接 // --------------------------------------------------------------- [Fact] @@ -80,7 +79,7 @@ public void Connect_BadPort_Throws() } // --------------------------------------------------------------- - // SendAndWait / built-ins + // SendAndWait / 内置方法 // --------------------------------------------------------------- [Fact] @@ -150,8 +149,8 @@ public void SendAndWait_Timeout_Throws() server.Start(); using var client = new TcpRouterClient(); client.Connect(server.Host, server.Port, DefaultTimeout); - // Server replies with CmdResp by default for unknown commands, not Resp, - // so a SendAndWait will time out waiting for a Resp. + // 服务器默认对未知命令回复 CmdResp,而不是 Resp, + // 因此 SendAndWait 在等待 Resp 时会超时。 Assert.Throws( () => client.SendAndWait("Unknown XYZ", TimeSpan.FromMilliseconds(200))); } @@ -165,7 +164,7 @@ public void SendAndWait_NotConnected_Throws() } // --------------------------------------------------------------- - // Post (async cmd-resp handshake) + // Post(异步 cmd-resp 握手) // --------------------------------------------------------------- [Fact] @@ -176,7 +175,7 @@ public void Post_CompletesOnHandshake() using var client = new TcpRouterClient(); client.Connect(server.Host, server.Port, DefaultTimeout); client.Post("API: Start -> DAQmx", DefaultTimeout); - // Make sure the server actually saw the command. + // 确保服务器实际收到了命令。 string cmd = server.GetReceived(DefaultTimeout); Assert.Equal("API: Start -> DAQmx", cmd); } @@ -194,7 +193,7 @@ public void PostNoReply_CompletesOnHandshake() } // --------------------------------------------------------------- - // Subscribe / status broadcast + // 订阅 / 状态广播 // --------------------------------------------------------------- [Fact] @@ -261,7 +260,7 @@ public void RegisterAsyncCallback_DeliversAsyncResponse() } // --------------------------------------------------------------- - // Disconnect-while-waiting unblocks waiters + // 等待期间断开连接会解除等待者的阻塞 // --------------------------------------------------------------- [Fact] @@ -272,7 +271,7 @@ public void Disconnect_WhileWaiting_RaisesConnectionError() using var client = new TcpRouterClient(); client.Connect(server.Host, server.Port, DefaultTimeout); - // Issue a SendAndWait whose response never comes; disconnect while waiting. + // 发起一个永远不会收到响应的 SendAndWait;在等待期间断开连接。 var task = Task.Run(() => client.SendAndWait("Unknown XYZ", TimeSpan.FromSeconds(10))); Thread.Sleep(100); client.Disconnect(); @@ -304,7 +303,7 @@ public void WaitForServer_ReturnsFalseOnTimeout() } // --------------------------------------------------------------- - // Async API + // 异步 API // --------------------------------------------------------------- [Fact] diff --git a/SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs b/SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs index 7472167..40779ce 100644 --- a/SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs +++ b/SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs @@ -11,8 +11,8 @@ namespace CsmTcpRouter.Tests { - /// Minimal TCP server that emulates a CSM-TCP-Router for tests. - /// Mirrors the Python tests/conftest.py MockServer. + /// 用于测试的最小化 TCP 服务器,模拟 CSM-TCP-Router。 + /// 镜像 Python tests/conftest.py MockServer。 internal sealed class MockServer : IDisposable { private TcpListener _listener; @@ -75,7 +75,7 @@ public void PushStatus(string payload) foreach (var c in snapshot) { try { c.GetStream().Write(wire, 0, wire.Length); } - catch { /* ignore */ } + catch { /* 忽略 */ } } } @@ -87,7 +87,7 @@ public void PushAsyncResponse(string payload) foreach (var c in snapshot) { try { c.GetStream().Write(wire, 0, wire.Length); } - catch { /* ignore */ } + catch { /* 忽略 */ } } } @@ -99,7 +99,7 @@ public string GetReceived(TimeSpan? timeout = null) } // ----------------------------------------------------------------- - // Internal + // 内部方法 // ----------------------------------------------------------------- private async Task AcceptLoopAsync(CancellationToken ct) @@ -116,7 +116,7 @@ private async Task AcceptLoopAsync(CancellationToken ct) _ = Task.Run(() => HandleClientAsync(client, ct)); } } - catch { /* ignore */ } + catch { /* 忽略 */ } } private async Task HandleClientAsync(TcpClient client, CancellationToken ct) @@ -124,7 +124,7 @@ private async Task HandleClientAsync(TcpClient client, CancellationToken ct) try { var stream = client.GetStream(); - // Welcome info packet + // 欢迎信息数据包 var welcome = ProtocolCodec.EncodePacket(Encoding.UTF8.GetBytes("Welcome to mock server"), PacketType.Info); await stream.WriteAsync(welcome, 0, welcome.Length, ct).ConfigureAwait(false); @@ -193,7 +193,7 @@ private void HandleCommand(NetworkStream stream, string cmd) } else { - // Generic async handshake for any other command. + // 对其他任何命令进行通用异步握手。 reply = ProtocolCodec.EncodePacket(Array.Empty(), PacketType.CmdResp); } diff --git a/SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs b/SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs index 14d8e65..9dbfdb8 100644 --- a/SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs +++ b/SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs @@ -5,7 +5,7 @@ namespace CsmTcpRouter.Tests { - // Mirrors SDK/python/tests/test_protocol.py. + // 镜像 SDK/python/tests/test_protocol.py。 public class ProtocolTests { private const int HeaderSize = 8; @@ -173,7 +173,7 @@ public void Parse_AllKnownTypes() [Fact] public void Parse_UnknownTypeMappedToInfo() { - // Manually craft a packet with an unknown type byte (0xFF). + // 手动构造一个具有未知类型字节 (0xFF) 的数据包。 var header = new byte[] { 0, 0, 0, 4, ProtocolVersion, 0xFF, 0, 0 }; var body = Encoding.UTF8.GetBytes("data"); var pkt = ProtocolCodec.ParsePacket(header, body); @@ -262,7 +262,7 @@ public void ParseServerError_MalformedBracketNoCrash() } // ------------------------------------------------------------------- - // Model parsing helpers + // 模型解析辅助方法 // ------------------------------------------------------------------- [Fact] diff --git a/SDK/python/src/csm_tcp_router_client.py b/SDK/python/src/csm_tcp_router_client.py index 4058fab..f359f3a 100644 --- a/SDK/python/src/csm_tcp_router_client.py +++ b/SDK/python/src/csm_tcp_router_client.py @@ -1,10 +1,9 @@ -"""csm-tcp-router-client – single-file Python client SDK for the CSM-TCP-Router server. +"""csm-tcp-router-client – CSM-TCP-Router 服务器的单文件 Python 客户端 SDK。 -This module bundles the entire client implementation (sync and async) along -with the wire-protocol codec, exception hierarchy and public data models -into a single importable file. +本模块将完整的客户端实现(同步与异步)、线路协议编解码器、 +异常层次结构以及公共数据模型打包到一个可直接导入的文件中。 -Sync usage:: +同步用法:: from csm_tcp_router_client import TcpRouterClient @@ -12,7 +11,7 @@ client.connect("localhost", 30007) print(client.list_modules()) -Async usage:: +异步用法:: import asyncio from csm_tcp_router_client import AsyncTcpRouterClient @@ -24,12 +23,12 @@ async def main(): asyncio.run(main()) -Wire format (8-byte header, big-endian):: +线路格式(8 字节报头,大端序):: | Data Length (4B) | Version (1B=0x01) | Type (1B) | FLAG1 (1B) | FLAG2 (1B) | ╰────────────────────────── Header (8B) ──────────────────────────╯ -followed by exactly ``Data Length`` bytes of payload. +后跟恰好 ``Data Length`` 字节的有效载荷。 """ from __future__ import annotations @@ -46,22 +45,22 @@ async def main(): from typing import Any, Callable, Coroutine, Dict, Optional, Tuple, Union __all__ = [ - # Clients + # 客户端 "TcpRouterClient", "AsyncTcpRouterClient", - # Exceptions + # 异常 "TcpRouterError", "ConnectionError", "TimeoutError", "ProtocolError", "ServerError", - # Models + # 数据模型 "PacketType", "Packet", "CommandResponse", "AsyncResponse", "StatusNotification", - # Version + # 版本 "__version__", ] @@ -69,33 +68,32 @@ async def main(): # =========================================================================== -# Exceptions +# 异常 # =========================================================================== class TcpRouterError(Exception): - """Base exception for all CSM-TCP-Router client errors.""" + """所有 CSM-TCP-Router 客户端错误的基异常。""" class ConnectionError(TcpRouterError): - """Raised when a connection cannot be established or is lost.""" + """当连接无法建立或已断开时抛出。""" class TimeoutError(TcpRouterError): - """Raised when a synchronous operation exceeds its timeout.""" + """当同步操作超过其超时时间时抛出。""" class ProtocolError(TcpRouterError): - """Raised when an invalid or unexpected protocol frame is received.""" + """当接收到无效或意外的协议帧时抛出。""" class ServerError(TcpRouterError): - """Raised when the server returns an error packet. + """当服务器返回错误数据包时抛出。 Attributes: - message: Human-readable error text from the server. - code: Optional error code extracted from the CSM Error format - ``[Error: ] ``. + message: 来自服务器的可读错误文本。 + code: 从 CSM 错误格式 ``[Error: ] `` 中提取的可选错误代码。 """ def __init__(self, message: str, code: str = "") -> None: @@ -109,30 +107,29 @@ def __str__(self) -> str: return self.message -# Internal aliases used by the transport / receive code below to avoid -# ambiguity with the module-level shadowed builtins. +# 传输/接收代码中使用的内部别名,用于避免与模块级遮蔽的内置名称产生歧义。 _RouterConnectionError = ConnectionError _RouterTimeoutError = TimeoutError # =========================================================================== -# Public data models +# 公共数据模型 # =========================================================================== class PacketType(IntEnum): - """Packet type constants as defined in the CSM-TCP-Router protocol v0. + """CSM-TCP-Router 协议 v0 中定义的数据包类型常量。 - Wire values + 线路值 ----------- - ``INFO`` 0x00 – informational messages (welcome / goodbye) - ``ERROR`` 0x01 – error messages from the server - ``CMD`` 0x02 – command sent by the client - ``CMD_RESP`` 0x03 – server handshake for async / no-reply / subscribe - ``RESP`` 0x04 – synchronous response payload - ``ASYNC_RESP`` 0x05 – asynchronous response payload - ``STATUS`` 0x06 – status broadcast from a subscribed CSM module - ``INTERRUPT`` 0x07 – interrupt broadcast from a subscribed CSM module + ``INFO`` 0x00 – 信息消息(欢迎/再见) + ``ERROR`` 0x01 – 来自服务器的错误消息 + ``CMD`` 0x02 – 客户端发送的命令 + ``CMD_RESP`` 0x03 – 服务器对异步/无回复/订阅的握手确认 + ``RESP`` 0x04 – 同步响应有效载荷 + ``ASYNC_RESP`` 0x05 – 异步响应有效载荷 + ``STATUS`` 0x06 – 来自已订阅 CSM 模块的状态广播 + ``INTERRUPT`` 0x07 – 来自已订阅 CSM 模块的中断广播 """ INFO = 0x00 @@ -147,7 +144,7 @@ class PacketType(IntEnum): @dataclass(frozen=True) class Packet: - """A decoded packet received from the server (internal representation).""" + """从服务器接收到的已解码数据包(内部表示)。""" type: PacketType data: bytes @@ -158,13 +155,13 @@ class Packet: @dataclass(frozen=True) class CommandResponse: - """The result of a synchronous command (:meth:`TcpRouterClient.send_and_wait`).""" + """同步命令(:meth:`TcpRouterClient.send_and_wait`)的结果。""" raw: bytes @property def text(self) -> str: - """Decoded UTF-8 text of the response payload.""" + """响应有效载荷的 UTF-8 解码文本。""" return self.raw.decode("utf-8", errors="replace") def __repr__(self) -> str: @@ -173,12 +170,12 @@ def __repr__(self) -> str: @dataclass(frozen=True) class AsyncResponse: - """An asynchronous response payload delivered via an ``async-resp`` packet. + """通过 ``async-resp`` 数据包传递的异步响应有效载荷。 Attributes: - raw: Raw response bytes (the part *before* the `` <- `` separator). - original_command: The original command text echoed back by the server - (the part *after* the `` <- `` separator). + raw: 原始响应字节(`` <- `` 分隔符*之前*的部分)。 + original_command: 服务器回显的原始命令文本 + (`` <- `` 分隔符*之后*的部分)。 """ raw: bytes @@ -186,14 +183,14 @@ class AsyncResponse: @property def text(self) -> str: - """Decoded UTF-8 text of the response payload.""" + """响应有效载荷的 UTF-8 解码文本。""" return self.raw.decode("utf-8", errors="replace") @classmethod def from_packet(cls, packet: Packet) -> AsyncResponse: - """Parse an ``ASYNC_RESP`` packet. + """解析一个 ``ASYNC_RESP`` 数据包。 - Server format: ``" <- "``. + 服务器格式:``" <- "``。 """ text = packet.data.decode("utf-8", errors="replace") parts = text.split(" <- ", 1) @@ -207,15 +204,15 @@ def __repr__(self) -> str: @dataclass(frozen=True) class StatusNotification: - """A status broadcast delivered via a ``status`` or ``interrupt`` packet. + """通过 ``status`` 或 ``interrupt`` 数据包传递的状态广播。 Attributes: - raw: Full raw payload bytes. - packet_type: Either :attr:`PacketType.STATUS` or - :attr:`PacketType.INTERRUPT`. - status_name: The name of the broadcasted status (left of ``>>``). - data: The status payload (between ``>>`` and ``<-``). - module_name: The sending CSM module name (right of ``<-``). + raw: 完整的原始有效载荷字节。 + packet_type: :attr:`PacketType.STATUS` 或 + :attr:`PacketType.INTERRUPT` 之一。 + status_name: 广播的状态名称(``>>`` 左侧)。 + data: 状态有效载荷(``>>`` 与 ``<-`` 之间)。 + module_name: 发送该状态的 CSM 模块名称(``<-`` 右侧)。 """ raw: bytes @@ -226,9 +223,9 @@ class StatusNotification: @classmethod def from_packet(cls, packet: Packet) -> StatusNotification: - """Parse a ``STATUS`` or ``INTERRUPT`` packet. + """解析一个 ``STATUS`` 或 ``INTERRUPT`` 数据包。 - Server format: ``" >> <- "``. + 服务器格式:``" >> <- "``。 """ text = packet.data.decode("utf-8", errors="replace") module = "" @@ -258,16 +255,16 @@ def __repr__(self) -> str: # =========================================================================== -# Protocol codec (internal but importable for advanced use / testing) +# 协议编解码器(内部使用,但可导入供高级用途/测试) # =========================================================================== -# Header layout: big-endian uint32 data_len + 4 x uint8 (version, type, flag1, flag2) +# 报头布局:大端序 uint32 data_len + 4 x uint8(version, type, flag1, flag2) _HEADER_FORMAT = "!IBBBB" -#: Number of bytes in the fixed packet header. +#: 固定数据包报头的字节数。 HEADER_SIZE: int = struct.calcsize(_HEADER_FORMAT) # == 8 -#: Protocol version byte sent in every outgoing packet. +#: 每个出站数据包中发送的协议版本字节。 PROTOCOL_VERSION: int = 0x01 @@ -277,13 +274,13 @@ def encode_packet( flag1: int = 0, flag2: int = 0, ) -> bytes: - """Encode *data* into a complete wire-format packet (header + body). + """将 *data* 编码为完整的线路格式数据包(报头 + 正文)。 - :param data: Raw payload bytes. - :param packet_type: :class:`PacketType` for the header. - :param flag1: FLAG1 byte (currently unused; defaults to 0). - :param flag2: FLAG2 byte (currently unused; defaults to 0). - :returns: Concatenated header + payload bytes ready for ``sendall()``. + :param data: 原始有效载荷字节。 + :param packet_type: 报头中使用的 :class:`PacketType`。 + :param flag1: FLAG1 字节(当前未使用;默认为 0)。 + :param flag2: FLAG2 字节(当前未使用;默认为 0)。 + :returns: 已拼接的报头 + 有效载荷字节,可直接传递给 ``sendall()``。 """ header = struct.pack( _HEADER_FORMAT, @@ -297,10 +294,10 @@ def encode_packet( def decode_header(header_bytes: bytes) -> Tuple[int, int, int, int, int]: - """Decode an 8-byte header into its constituent fields. + """将 8 字节报头解码为其各组成字段。 :returns: ``(data_len, version, type_byte, flag1, flag2)`` - :raises ProtocolError: if *header_bytes* is not exactly :data:`HEADER_SIZE` bytes. + :raises ProtocolError: 若 *header_bytes* 不恰好为 :data:`HEADER_SIZE` 字节。 """ if len(header_bytes) != HEADER_SIZE: raise ProtocolError( @@ -310,13 +307,12 @@ def decode_header(header_bytes: bytes) -> Tuple[int, int, int, int, int]: def parse_packet(header_bytes: bytes, body: bytes) -> Packet: - """Build a :class:`Packet` from raw header + body. + """从原始报头 + 正文构建 :class:`Packet`。 - Unknown packet type bytes are mapped to :attr:`PacketType.INFO` for - forward compatibility (the server may introduce new types in future - protocol revisions). + 未知的数据包类型字节将映射到 :attr:`PacketType.INFO` 以保持 + 前向兼容性(服务器在未来的协议修订中可能引入新类型)。 - :raises ProtocolError: on header size mismatch or body length mismatch. + :raises ProtocolError: 当报头大小不匹配或正文长度不匹配时。 """ data_len, version, type_byte, flag1, flag2 = decode_header(header_bytes) if len(body) != data_len: @@ -327,18 +323,18 @@ def parse_packet(header_bytes: bytes, body: bytes) -> Packet: try: ptype = PacketType(type_byte) except ValueError: - # Forward-compatible: treat unknown type as INFO + # 前向兼容:将未知类型视为 INFO ptype = PacketType.INFO return Packet(type=ptype, data=body, version=version, flag1=flag1, flag2=flag2) # =========================================================================== -# Shared server-error parsing helper +# 共享的服务器错误解析辅助函数 # =========================================================================== def _parse_server_error(packet: Packet) -> ServerError: - """Extract code and message from a CSM Error format ``[Error: ] ``.""" + """从 CSM 错误格式 ``[Error: ] `` 中提取错误代码和消息。""" text = packet.data.decode("utf-8", errors="replace").strip() code = "" msg = text @@ -353,16 +349,15 @@ def _parse_server_error(packet: Packet) -> ServerError: # =========================================================================== -# Internal: thread-based TCP transport (used by the sync client) +# 内部:基于线程的 TCP 传输(由同步客户端使用) # =========================================================================== class _Transport: - """Thread-safe, blocking TCP transport. + """线程安全的阻塞式 TCP 传输。 - A background daemon thread continuously reads packets from the socket and - dispatches them via *on_packet*. Callers are responsible for keeping - callbacks fast and non-blocking, as they run in the receive thread. + 后台守护线程持续从套接字读取数据包,并通过 *on_packet* 进行分发。 + 调用方负责保持回调函数快速且无阻塞,因为它们在接收线程中运行。 """ def __init__( @@ -379,11 +374,11 @@ def __init__( @property def connected(self) -> bool: - """``True`` while the socket is open and the stop event has not fired.""" + """``True`` 表示套接字已打开且停止事件尚未触发。""" return self._sock is not None and not self._stop_event.is_set() def connect(self, host: str, port: int, timeout: float = 5.0) -> None: - """Open a TCP connection and start the receive thread.""" + """建立 TCP 连接并启动接收线程。""" if self.connected: raise _RouterConnectionError( "Already connected; call disconnect() first." @@ -393,7 +388,7 @@ def connect(self, host: str, port: int, timeout: float = 5.0) -> None: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) sock.connect((host, port)) - sock.settimeout(None) # switch to blocking for the recv loop + sock.settimeout(None) # 切换为阻塞模式以用于接收循环 except OSError as exc: if sock is not None: try: @@ -414,7 +409,7 @@ def connect(self, host: str, port: int, timeout: float = 5.0) -> None: self._recv_thread.start() def disconnect(self, join_timeout: float = 2.0) -> None: - """Close the connection and stop the receive thread.""" + """关闭连接并停止接收线程。""" self._stop_event.set() if self._sock is not None: try: @@ -430,7 +425,7 @@ def disconnect(self, join_timeout: float = 2.0) -> None: self._recv_thread.join(timeout=join_timeout) def send_raw(self, data: bytes) -> None: - """Send *data* atomically. Thread-safe.""" + """原子性地发送 *data*。线程安全。""" if not self.connected: raise _RouterConnectionError("Not connected.") with self._send_lock: @@ -441,12 +436,12 @@ def send_raw(self, data: bytes) -> None: raise _RouterConnectionError(f"Send failed: {exc}") from exc def _recv_all(self, size: int) -> bytes: - """Read exactly *size* bytes; returns empty bytes on clean EOF or disconnect.""" + """精确读取 *size* 字节;在干净的 EOF 或断开连接时返回空字节。""" buf = bytearray(size) view = memoryview(buf) received = 0 while received < size: - sock = self._sock # capture locally to avoid TOCTOU race with disconnect() + sock = self._sock # 本地捕获,避免与 disconnect() 产生 TOCTOU 竞争 if sock is None: return b"" try: @@ -459,14 +454,14 @@ def _recv_all(self, size: int) -> bytes: return bytes(buf) def _recv_loop(self) -> None: - """Background thread: read packets and dispatch via callback.""" + """后台线程:读取数据包并通过回调进行分发。""" try: while not self._stop_event.is_set(): header = self._recv_all(HEADER_SIZE) if not header: break - # Extract data_len from the first 4 bytes without full decode + # 从前 4 个字节提取 data_len,无需完整解码 (data_len,) = struct.unpack("!I", header[:4]) body = self._recv_all(data_len) if len(body) != data_len: @@ -475,7 +470,7 @@ def _recv_loop(self) -> None: try: packet = parse_packet(header, body) except ProtocolError: - # Corrupted frame – skip it and keep the loop alive + # 帧损坏 – 跳过并保持循环运行 continue self._on_packet(packet) @@ -489,29 +484,27 @@ def _recv_loop(self) -> None: # =========================================================================== -# TcpRouterClient – thread-based synchronous client +# TcpRouterClient – 基于线程的同步客户端 # =========================================================================== -# Type aliases +# 类型别名 _SubKey = Tuple[str, str] StatusCallback = Callable[[StatusNotification], None] AsyncCallback = Callable[[AsyncResponse], None] -# Items held in the internal queues are either Packet or Exception instances. +# 内部队列中存放的元素为 Packet 或 Exception 实例。 _QueueItem = object class TcpRouterClient: - """Python client for a CSM-TCP-Router server. + """CSM-TCP-Router 服务器的同步客户端。 - This class mirrors the LabVIEW ClientAPI VIs and speaks the - CSM-TCP-Router protocol v0. It is thread-safe in that its internal - state is protected by locks; however, the protocol allows at most one - in-flight *synchronous* command at a time and at most one in-flight - *async* command / subscription at a time. Concurrent callers are - serialised by ``_resp_lock`` and ``_cmd_resp_lock`` respectively. + 本类镜像了 LabVIEW ClientAPI VI,并实现了 CSM-TCP-Router 协议 v0。 + 其内部状态通过锁保护,因此是线程安全的;但协议同时只允许一个在途 + *同步* 命令和一个在途 *异步* 命令/订阅。并发调用者分别由 + ``_resp_lock`` 和 ``_cmd_resp_lock`` 串行化。 - **Quickstart**:: + **快速入门**:: from csm_tcp_router_client import TcpRouterClient @@ -519,33 +512,29 @@ class TcpRouterClient: client.connect("localhost", 30007) print(client.list_modules()) - **Protocol flows**: - - - *Synchronous* command (``-@``): :meth:`send_and_wait` – sends a ``CMD`` - packet and blocks until a ``RESP`` (or ``ERROR``) is received. - - *Asynchronous* command (``->``): :meth:`post` – sends a ``CMD`` packet - and blocks until the ``CMD_RESP`` handshake is received; the eventual - ``ASYNC_RESP`` is delivered asynchronously. - - *No-reply async* command (``->|``): :meth:`post_no_reply` – same as - :meth:`post` but no ``ASYNC_RESP`` will ever arrive. - - *Subscribe / unsubscribe*: :meth:`subscribe_status` / - :meth:`unsubscribe_status` – sends a ```` / ```` - command and waits for the ``CMD_RESP`` handshake. - - **Received-packet routing** (on the background receive thread): - - - ``RESP`` (0x04) – unblocks the caller of :meth:`send_and_wait`. - - ``CMD_RESP`` (0x03) – unblocks callers of :meth:`post`, - :meth:`post_no_reply`, :meth:`subscribe_status`, and - :meth:`unsubscribe_status`. - - ``ASYNC_RESP`` (0x05) – added to :attr:`async_response_queue` and - dispatched to any matching :meth:`register_async_callback`. - - ``STATUS`` / ``INTERRUPT`` (0x06 / 0x07) – added to - :attr:`status_queue` and dispatched to any matching - :meth:`subscribe_status` callback. - - ``ERROR`` (0x01) – unblocks any pending synchronous waiter with a - :exc:`ServerError`. - - ``INFO`` (0x00) – silently discarded (welcome / goodbye messages). + **协议流程**: + + - *同步* 命令 (``-@``)::meth:`send_and_wait` – 发送 ``CMD`` 包并阻塞 + 直到收到 ``RESP``(或 ``ERROR``)。 + - *异步* 命令 (``->``)::meth:`post` – 发送 ``CMD`` 包并阻塞直到收到 + ``CMD_RESP`` 握手;最终的 ``ASYNC_RESP`` 会异步投递。 + - *无回复异步* 命令 (``->|``)::meth:`post_no_reply` – 与 + :meth:`post` 相同,但不会有 ``ASYNC_RESP`` 到来。 + - *订阅 / 取消订阅*::meth:`subscribe_status` / + :meth:`unsubscribe_status` – 发送 ```` / ```` + 命令并等待 ``CMD_RESP`` 握手。 + + **接收包路由**(在后台接收线程上): + + - ``RESP`` (0x04) – 解除 :meth:`send_and_wait` 调用者的阻塞。 + - ``CMD_RESP`` (0x03) – 解除 :meth:`post`、:meth:`post_no_reply`、 + :meth:`subscribe_status` 和 :meth:`unsubscribe_status` 调用者的阻塞。 + - ``ASYNC_RESP`` (0x05) – 加入 :attr:`async_response_queue` 并 + 分发给匹配的 :meth:`register_async_callback`。 + - ``STATUS`` / ``INTERRUPT`` (0x06 / 0x07) – 加入 :attr:`status_queue` + 并分发给匹配的 :meth:`subscribe_status` 回调。 + - ``ERROR`` (0x01) – 以 :exc:`ServerError` 解除任何待处理的同步等待者。 + - ``INFO`` (0x00) – 静默丢弃(欢迎 / 再见消息)。 """ def __init__(self) -> None: @@ -554,26 +543,24 @@ def __init__(self) -> None: on_disconnect=self._on_disconnect, ) - # One-item-deep queues for synchronised waits. - # Items are either Packet or Exception instances. + # 用于同步等待的单项队列。 + # 队列元素为 Packet 或 Exception 实例。 self._resp_queue: queue.Queue[_QueueItem] = queue.Queue() self._cmd_resp_queue: queue.Queue[_QueueItem] = queue.Queue() - #: Polling queue for :class:`AsyncResponse` objects received from the server. + #: 用于轮询从服务器收到的 :class:`AsyncResponse` 对象的队列。 self.async_response_queue: queue.Queue[AsyncResponse] = queue.Queue() - #: Polling queue for :class:`StatusNotification` objects received - #: from the server. + #: 用于轮询从服务器收到的 :class:`StatusNotification` 对象的队列。 self.status_queue: queue.Queue[StatusNotification] = queue.Queue() - # Callback registries (protected by _lock) + # 回调注册表(由 _lock 保护) self._status_callbacks: Dict[_SubKey, Optional[StatusCallback]] = {} self._async_callbacks: Dict[str, AsyncCallback] = {} self._lock = threading.Lock() - # Serialisation locks – at most one in-flight RESP / CMD_RESP waiter - # at a time. This prevents concurrent callers from consuming each - # other's response packets. + # 串行化锁 – 同时最多只有一个在途的 RESP / CMD_RESP 等待者, + # 防止并发调用者消费彼此的响应包。 self._resp_lock = threading.Lock() self._cmd_resp_lock = threading.Lock() From 0c4fb0802227983c9a0a4ee43237262dc74847df Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:16:42 +0800 Subject: [PATCH 10/12] Add interactive ClientConsole example to Python, C# and C SDKs (#43) * Add interactive ClientConsole example to Python, C# and C SDKs Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/ca3628f9-8283-448f-8789-d860c873c1dc Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Validate port argument in Python and C# ClientConsole examples Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/1a565ac6-daf6-4291-8c0c-b3444c37d8ba Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- SDK/c/CMakeLists.txt | 2 + SDK/c/examples/client_console.c | 265 ++++++++++++++++++ SDK/csharp/CsmTcpRouter.sln | 15 + .../ClientConsole/ClientConsole.csproj | 17 ++ SDK/csharp/examples/ClientConsole/Program.cs | 236 ++++++++++++++++ SDK/python/examples/client_console.py | 222 +++++++++++++++ 6 files changed, 757 insertions(+) create mode 100644 SDK/c/examples/client_console.c create mode 100644 SDK/csharp/examples/ClientConsole/ClientConsole.csproj create mode 100644 SDK/csharp/examples/ClientConsole/Program.cs create mode 100644 SDK/python/examples/client_console.py diff --git a/SDK/c/CMakeLists.txt b/SDK/c/CMakeLists.txt index 1c42a1f..73033bb 100644 --- a/SDK/c/CMakeLists.txt +++ b/SDK/c/CMakeLists.txt @@ -86,8 +86,10 @@ endif() if(CSM_BUILD_EXAMPLES) add_executable(basic_usage examples/basic_usage.c) add_executable(subscribe_status examples/subscribe_status.c) + add_executable(client_console examples/client_console.c) target_link_libraries(basic_usage PRIVATE csm_tcp_router_client) target_link_libraries(subscribe_status PRIVATE csm_tcp_router_client) + target_link_libraries(client_console PRIVATE csm_tcp_router_client) endif() # ------------------------------------------------------------------------- diff --git a/SDK/c/examples/client_console.c b/SDK/c/examples/client_console.c new file mode 100644 index 0000000..d40ef4f --- /dev/null +++ b/SDK/c/examples/client_console.c @@ -0,0 +1,265 @@ +/* client_console.c - 交互式客户端控制台示例。 + * + * 连接到正在运行的 CSM-TCP-Router 服务器,从 stdin 读取用户输入的 + * 命令,并通过 SDK 转发。同样的命令集、提示符和输出格式也在 + * Python(examples/client_console.py)和 C#(examples/ClientConsole) + * SDK 示例中实现,因此三种语言的行为一致。 + * + * 用法: + * client_console [host] [port] + */ +#include "csm_tcp_router_client.h" + +#include +#include +#include + +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT 30007 +#define LINE_BUFFER_SIZE 4096 + +static const char *HELP_TEXT = + "Available commands:\n" + " help Show this help text\n" + " quit / exit Disconnect and exit\n" + " ping Measure round-trip latency\n" + " list List CSM modules loaded on the server\n" + " api List the API of a module\n" + " state List the states of a module\n" + " mhelp Server-side Help for a module\n" + " send Send a synchronous command and print the response\n" + " post Send an asynchronous command (-> suffix)\n" + " nopost Send a no-reply asynchronous command (->|)\n" + " sub @ Subscribe to a status broadcast\n" + " unsub @ Unsubscribe from a status broadcast"; + +static void on_status(const csm_status_notification_t *n, void *ud) { + (void)ud; + printf("\n[STATUS] %s@%s: %s\n", + n->status_name ? n->status_name : "", + n->module_name ? n->module_name : "", + n->data ? n->data : ""); +} + +static void on_async(const csm_async_response_t *r, void *ud) { + (void)ud; + printf("\n[ASYNC] %s (cmd=%s)\n", + r->raw ? r->raw : "", + r->original_command ? r->original_command : ""); +} + +/* 修剪 *s* 的前后空白字符(包括换行)。原地修改。返回 *s*。 */ +static char *trim(char *s) { + if (!s) return s; + char *end; + while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') s++; + end = s + strlen(s); + while (end > s && (end[-1] == ' ' || end[-1] == '\t' || + end[-1] == '\r' || end[-1] == '\n')) { + end--; + } + *end = '\0'; + return s; +} + +/* 不区分大小写地比较两个 NUL 终止字符串。 */ +static int ieq(const char *a, const char *b) { + while (*a && *b) { + char ca = *a, cb = *b; + if (ca >= 'A' && ca <= 'Z') ca = (char)(ca - 'A' + 'a'); + if (cb >= 'A' && cb <= 'Z') cb = (char)(cb - 'A' + 'a'); + if (ca != cb) return 0; + a++; b++; + } + return *a == '\0' && *b == '\0'; +} + +/* 在 *line* 上拆分 "@",将指针存入 out_status/out_module + * (指向 *line* 内的位置,*line* 会被原地修改)。失败时返回 0。 */ +static int split_status_module(char *line, char **out_status, char **out_module) { + char *at = strchr(line, '@'); + if (!at) return 0; + *at = '\0'; + char *status = trim(line); + char *module = trim(at + 1); + if (*status == '\0' || *module == '\0') return 0; + *out_status = status; + *out_module = module; + return 1; +} + +/* 打印来自服务器的最近一次错误(如果有)。 */ +static void print_error(csm_client_t *c, csm_result_t r) { + csm_server_error_t err; + if (r == CSM_ERR_SERVER && csm_client_last_server_error(c, &err) == CSM_OK) { + if (err.code[0]) { + printf("Error: [%s] %s\n", err.code, err.message); + } else { + printf("Error: %s\n", err.message); + } + } else { + printf("Error: %s\n", csm_result_str(r)); + } +} + +/* 调用一个返回字符串的 SDK 辅助函数并打印结果。 */ +static void print_string_command(csm_client_t *c, csm_result_t r, char *text) { + if (r == CSM_OK) { + printf("%s\n", text ? text : ""); + csm_string_free(text); + } else { + print_error(c, r); + } +} + +/* 处理一行输入。返回 0 表示退出,否则返回 1。 */ +static int dispatch(csm_client_t *c, char *line) { + line = trim(line); + if (*line == '\0') return 1; + + /* 将命令字与参数拆分(参数保留空格)。 */ + char *cmd = line; + char *arg = strpbrk(line, " \t"); + if (arg) { + *arg++ = '\0'; + arg = trim(arg); + } else { + arg = (char *)""; + } + + if (ieq(cmd, "quit") || ieq(cmd, "exit")) { + return 0; + } + if (ieq(cmd, "help")) { + printf("%s\n", HELP_TEXT); + return 1; + } + if (ieq(cmd, "ping")) { + double ms = 0; + csm_result_t r = csm_client_ping(c, 2000, &ms); + if (r == CSM_OK) printf("Ping OK latency=%.1f ms\n", ms); + else printf("Ping failed.\n"); + return 1; + } + if (ieq(cmd, "list")) { + char *text = NULL; + print_string_command(c, csm_client_list_modules(c, &text, 5000), text); + return 1; + } + if (ieq(cmd, "api")) { + if (*arg == '\0') { printf("Error: usage: api \n"); return 1; } + char *text = NULL; + print_string_command(c, csm_client_list_api(c, arg, &text, 5000), text); + return 1; + } + if (ieq(cmd, "state")) { + if (*arg == '\0') { printf("Error: usage: state \n"); return 1; } + char *text = NULL; + print_string_command(c, csm_client_list_states(c, arg, &text, 5000), text); + return 1; + } + if (ieq(cmd, "mhelp")) { + if (*arg == '\0') { printf("Error: usage: mhelp \n"); return 1; } + char *text = NULL; + print_string_command(c, csm_client_help(c, arg, &text, 5000), text); + return 1; + } + if (ieq(cmd, "send")) { + if (*arg == '\0') { printf("Error: usage: send \n"); return 1; } + csm_command_response_t resp = {0}; + csm_result_t r = csm_client_send_and_wait(c, arg, 5000, &resp); + if (r == CSM_OK) { + printf("Response: %s\n", resp.raw ? (const char *)resp.raw : ""); + csm_command_response_dispose(&resp); + } else { + print_error(c, r); + } + return 1; + } + if (ieq(cmd, "post")) { + if (*arg == '\0') { printf("Error: usage: post \n"); return 1; } + csm_client_register_async_callback(c, arg, on_async, NULL); + csm_result_t r = csm_client_post(c, arg, 5000); + if (r == CSM_OK) printf("Async command sent.\n"); + else print_error(c, r); + return 1; + } + if (ieq(cmd, "nopost")) { + if (*arg == '\0') { printf("Error: usage: nopost \n"); return 1; } + csm_result_t r = csm_client_post_no_reply(c, arg, 5000); + if (r == CSM_OK) printf("No-reply command sent.\n"); + else print_error(c, r); + return 1; + } + if (ieq(cmd, "sub")) { + char *status = NULL, *module = NULL; + if (!split_status_module(arg, &status, &module)) { + printf("Error: expected '@'\n"); + return 1; + } + csm_result_t r = csm_client_subscribe_status(c, status, module, + on_status, NULL, 5000); + if (r == CSM_OK) printf("Subscribed to %s@%s\n", status, module); + else print_error(c, r); + return 1; + } + if (ieq(cmd, "unsub")) { + char *status = NULL, *module = NULL; + if (!split_status_module(arg, &status, &module)) { + printf("Error: expected '@'\n"); + return 1; + } + csm_result_t r = csm_client_unsubscribe_status(c, status, module, 5000); + if (r == CSM_OK) printf("Unsubscribed from %s@%s\n", status, module); + else print_error(c, r); + return 1; + } + + printf("Error: unknown command '%s'. Type 'help' for the command list.\n", + cmd); + return 1; +} + +int main(int argc, char **argv) { + const char *host = (argc > 1) ? argv[1] : DEFAULT_HOST; + int port = (argc > 2) ? atoi(argv[2]) : DEFAULT_PORT; + if (port <= 0 || port > 65535) { + fprintf(stderr, "Error: invalid port '%s'\n", argv[2]); + return 1; + } + + printf("CSM-TCP-Router Client Console\n"); + printf("Connecting to %s:%d ...\n", host, port); + + csm_client_t *c = csm_client_create(); + if (!c) { + fprintf(stderr, "Out of memory\n"); + return 1; + } + + csm_result_t r = csm_client_connect(c, host, (uint16_t)port, 5000); + if (r != CSM_OK) { + printf("Error: %s\n", csm_result_str(r)); + csm_client_destroy(c); + return 1; + } + + printf("Connected to %s:%d. Type 'help' for commands, 'quit' to exit.\n", + host, port); + + char line[LINE_BUFFER_SIZE]; + for (;;) { + printf("csm> "); + fflush(stdout); + if (!fgets(line, (int)sizeof(line), stdin)) { + printf("\n"); + break; + } + if (!dispatch(c, line)) break; + } + + csm_client_disconnect(c); + csm_client_destroy(c); + printf("Disconnected.\n"); + return 0; +} diff --git a/SDK/csharp/CsmTcpRouter.sln b/SDK/csharp/CsmTcpRouter.sln index 337b5e9..0f53351 100644 --- a/SDK/csharp/CsmTcpRouter.sln +++ b/SDK/csharp/CsmTcpRouter.sln @@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{B3 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicUsage", "examples\BasicUsage\BasicUsage.csproj", "{4DD90760-BF7D-45EB-BAE9-E47F65B053C9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientConsole", "examples\ClientConsole\ClientConsole.csproj", "{7160908E-E970-4A92-95B4-462403BD0EE8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +63,18 @@ Global {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|x64.Build.0 = Release|Any CPU {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|x86.ActiveCfg = Release|Any CPU {4DD90760-BF7D-45EB-BAE9-E47F65B053C9}.Release|x86.Build.0 = Release|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Debug|x64.ActiveCfg = Debug|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Debug|x64.Build.0 = Debug|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Debug|x86.ActiveCfg = Debug|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Debug|x86.Build.0 = Debug|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Release|Any CPU.Build.0 = Release|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Release|x64.ActiveCfg = Release|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Release|x64.Build.0 = Release|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Release|x86.ActiveCfg = Release|Any CPU + {7160908E-E970-4A92-95B4-462403BD0EE8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -69,5 +83,6 @@ Global {AA5DEDA8-96DD-4A05-8029-57ED1E7DCE1C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {1EFBB3D5-6452-41A9-8501-897D421B33DB} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {4DD90760-BF7D-45EB-BAE9-E47F65B053C9} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} + {7160908E-E970-4A92-95B4-462403BD0EE8} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} EndGlobalSection EndGlobal diff --git a/SDK/csharp/examples/ClientConsole/ClientConsole.csproj b/SDK/csharp/examples/ClientConsole/ClientConsole.csproj new file mode 100644 index 0000000..8d84430 --- /dev/null +++ b/SDK/csharp/examples/ClientConsole/ClientConsole.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + latest + disable + CsmTcpRouter.Examples.ClientConsole + ClientConsole + false + + + + + + + diff --git a/SDK/csharp/examples/ClientConsole/Program.cs b/SDK/csharp/examples/ClientConsole/Program.cs new file mode 100644 index 0000000..29823ab --- /dev/null +++ b/SDK/csharp/examples/ClientConsole/Program.cs @@ -0,0 +1,236 @@ +using System; +using CsmTcpRouter; + +namespace CsmTcpRouter.Examples.ClientConsole +{ + /// + /// 交互式客户端控制台示例。连接到正在运行的 CSM-TCP-Router 服务器, + /// 接收用户从 stdin 输入的命令,并通过 SDK 转发。 + /// + /// 同样的命令集、提示符和输出格式也在 Python(examples/client_console.py) + /// 和 C(examples/client_console.c)SDK 示例中实现,因此三种语言的行为一致。 + /// + public static class Program + { + private const string DefaultHost = "localhost"; + private const int DefaultPort = 30007; + + private const string HelpText = + "Available commands:\n" + + " help Show this help text\n" + + " quit / exit Disconnect and exit\n" + + " ping Measure round-trip latency\n" + + " list List CSM modules loaded on the server\n" + + " api List the API of a module\n" + + " state List the states of a module\n" + + " mhelp Server-side Help for a module\n" + + " send Send a synchronous command and print the response\n" + + " post Send an asynchronous command (-> suffix)\n" + + " nopost Send a no-reply asynchronous command (->|)\n" + + " sub @ Subscribe to a status broadcast\n" + + " unsub @ Unsubscribe from a status broadcast"; + + public static int Main(string[] args) + { + string host = args.Length > 0 ? args[0] : DefaultHost; + int port = DefaultPort; + if (args.Length > 1) + { + if (!int.TryParse(args[1], out port) || port < 1 || port > 65535) + { + Console.WriteLine($"Error: invalid port '{args[1]}'"); + return 1; + } + } + + Console.WriteLine("CSM-TCP-Router Client Console"); + Console.WriteLine($"Connecting to {host}:{port} ..."); + + using var client = new TcpRouterClient(); + try + { + client.Connect(host, port); + } + catch (RouterConnectionException exc) + { + Console.WriteLine($"Error: {exc.Message}"); + return 1; + } + + Console.WriteLine($"Connected to {host}:{port}. Type 'help' for commands, 'quit' to exit."); + + while (true) + { + Console.Write("csm> "); + string line = Console.ReadLine(); + if (line == null) + { + Console.WriteLine(); + break; + } + + bool keepRunning; + try + { + keepRunning = Dispatch(client, line); + } + catch (CsmTcpRouterException exc) + { + Console.WriteLine($"Error: {exc.Message}"); + continue; + } + catch (ArgumentException exc) + { + Console.WriteLine($"Error: {exc.Message}"); + continue; + } + + if (!keepRunning) + { + break; + } + } + + client.Disconnect(); + Console.WriteLine("Disconnected."); + return 0; + } + + private static bool Dispatch(TcpRouterClient client, string line) + { + line = line.Trim(); + if (line.Length == 0) + { + return true; + } + + int spaceIndex = line.IndexOf(' '); + string cmd = (spaceIndex < 0 ? line : line.Substring(0, spaceIndex)).ToLowerInvariant(); + string arg = spaceIndex < 0 ? string.Empty : line.Substring(spaceIndex + 1).Trim(); + + switch (cmd) + { + case "quit": + case "exit": + return false; + + case "help": + Console.WriteLine(HelpText); + return true; + + case "ping": + { + var (ok, elapsed) = client.Ping(); + Console.WriteLine(ok + ? $"Ping OK latency={elapsed.TotalMilliseconds:F1} ms" + : "Ping failed."); + return true; + } + + case "list": + Console.WriteLine(client.ListModules()); + return true; + + case "api": + if (arg.Length == 0) Console.WriteLine("Error: usage: api "); + else Console.WriteLine(client.ListApi(arg)); + return true; + + case "state": + if (arg.Length == 0) Console.WriteLine("Error: usage: state "); + else Console.WriteLine(client.ListStates(arg)); + return true; + + case "mhelp": + if (arg.Length == 0) Console.WriteLine("Error: usage: mhelp "); + else Console.WriteLine(client.Help(arg)); + return true; + + case "send": + if (arg.Length == 0) + { + Console.WriteLine("Error: usage: send "); + } + else + { + var resp = client.SendAndWait(arg); + Console.WriteLine($"Response: {resp.Text}"); + } + return true; + + case "post": + if (arg.Length == 0) + { + Console.WriteLine("Error: usage: post "); + } + else + { + client.RegisterAsyncCallback(arg, OnAsync); + client.Post(arg); + Console.WriteLine("Async command sent."); + } + return true; + + case "nopost": + if (arg.Length == 0) + { + Console.WriteLine("Error: usage: nopost "); + } + else + { + client.PostNoReply(arg); + Console.WriteLine("No-reply command sent."); + } + return true; + + case "sub": + { + var (status, module) = SplitStatusModule(arg); + client.SubscribeStatus(status, module, OnStatus); + Console.WriteLine($"Subscribed to {status}@{module}"); + return true; + } + + case "unsub": + { + var (status, module) = SplitStatusModule(arg); + client.UnsubscribeStatus(status, module); + Console.WriteLine($"Unsubscribed from {status}@{module}"); + return true; + } + + default: + Console.WriteLine($"Error: unknown command '{cmd}'. Type 'help' for the command list."); + return true; + } + } + + private static (string Status, string Module) SplitStatusModule(string arg) + { + int at = arg.IndexOf('@'); + if (at < 0) + { + throw new ArgumentException("expected '@'"); + } + string status = arg.Substring(0, at).Trim(); + string module = arg.Substring(at + 1).Trim(); + if (status.Length == 0 || module.Length == 0) + { + throw new ArgumentException("expected '@'"); + } + return (status, module); + } + + private static void OnStatus(StatusNotification notification) + { + Console.WriteLine(); + Console.WriteLine($"[STATUS] {notification.StatusName}@{notification.ModuleName}: {notification.Data}"); + } + + private static void OnAsync(AsyncResponse response) + { + Console.WriteLine(); + Console.WriteLine($"[ASYNC] {response.Text} (cmd={response.OriginalCommand})"); + } + } +} diff --git a/SDK/python/examples/client_console.py b/SDK/python/examples/client_console.py new file mode 100644 index 0000000..77aa05d --- /dev/null +++ b/SDK/python/examples/client_console.py @@ -0,0 +1,222 @@ +"""Interactive client console for csm-tcp-router-client. + +A small REPL that connects to a running CSM-TCP-Router server, accepts +user-typed commands from stdin, and forwards them through the SDK. +The same command set, prompt and output format are implemented in the C +and C# SDK examples (``client_console.c`` and ``examples/ClientConsole``) +so behavior is identical across all three languages. + +Prerequisites +------------- +A running CSM-TCP-Router server (LabVIEW app) – the reference server +defaults to port 30007. Start it from ``CSM-TCP-Router(Server).vi``. + +Install the SDK:: + + pip install csm-tcp-router-client + +Run this example:: + + python client_console.py [host] [port] + +Available commands at the ``csm>`` prompt +----------------------------------------- + help Show this help text + quit / exit Disconnect and exit + ping Measure round-trip latency + list List CSM modules loaded on the server + api List the API of a module + state List the states of a module + mhelp Server-side Help for a module + send Send a synchronous command and print the response + post Send an asynchronous command (``->`` suffix) + nopost Send a no-reply asynchronous command (``->|``) + sub @ Subscribe to a status broadcast + unsub @ Unsubscribe from a status broadcast +""" + +from __future__ import annotations + +import sys + +from csm_tcp_router_client import ( + AsyncResponse, + ConnectionError, + ServerError, + StatusNotification, + TcpRouterClient, + TcpRouterError, +) + +DEFAULT_HOST = "localhost" +DEFAULT_PORT = 30007 + +HELP_TEXT = """\ +Available commands: + help Show this help text + quit / exit Disconnect and exit + ping Measure round-trip latency + list List CSM modules loaded on the server + api List the API of a module + state List the states of a module + mhelp Server-side Help for a module + send Send a synchronous command and print the response + post Send an asynchronous command (-> suffix) + nopost Send a no-reply asynchronous command (->|) + sub @ Subscribe to a status broadcast + unsub @ Unsubscribe from a status broadcast""" + + +def _on_status(notification: StatusNotification) -> None: + """Print every status broadcast received on a subscription.""" + print( + f"\n[STATUS] {notification.status_name}@{notification.module_name}" + f": {notification.data}" + ) + + +def _on_async(response: AsyncResponse) -> None: + """Print every async-resp packet that matches a registered command.""" + print(f"\n[ASYNC] {response.text} (cmd={response.original_command})") + + +def _split_status_module(arg: str) -> tuple[str, str]: + """Parse ``@`` into a ``(status, module)`` tuple.""" + if "@" not in arg: + raise ValueError("expected '@'") + status, module = arg.split("@", 1) + status, module = status.strip(), module.strip() + if not status or not module: + raise ValueError("expected '@'") + return status, module + + +def _dispatch(client: TcpRouterClient, line: str) -> bool: + """Execute one user line. Return False to exit the REPL.""" + line = line.strip() + if not line: + return True + + parts = line.split(None, 1) + cmd = parts[0].lower() + arg = parts[1].strip() if len(parts) == 2 else "" + + if cmd in ("quit", "exit"): + return False + if cmd == "help": + print(HELP_TEXT) + return True + if cmd == "ping": + ok, elapsed = client.ping() + if ok: + print(f"Ping OK latency={elapsed * 1000:.1f} ms") + else: + print("Ping failed.") + return True + if cmd == "list": + print(client.list_modules()) + return True + if cmd == "api": + if not arg: + print("Error: usage: api ") + else: + print(client.list_api(arg)) + return True + if cmd == "state": + if not arg: + print("Error: usage: state ") + else: + print(client.list_states(arg)) + return True + if cmd == "mhelp": + if not arg: + print("Error: usage: mhelp ") + else: + print(client.help(arg)) + return True + if cmd == "send": + if not arg: + print("Error: usage: send ") + else: + resp = client.send_and_wait(arg) + print(f"Response: {resp.text}") + return True + if cmd == "post": + if not arg: + print("Error: usage: post ") + else: + client.register_async_callback(arg, _on_async) + client.post(arg) + print("Async command sent.") + return True + if cmd == "nopost": + if not arg: + print("Error: usage: nopost ") + else: + client.post_no_reply(arg) + print("No-reply command sent.") + return True + if cmd == "sub": + status, module = _split_status_module(arg) + client.subscribe_status(status, module, callback=_on_status) + print(f"Subscribed to {status}@{module}") + return True + if cmd == "unsub": + status, module = _split_status_module(arg) + client.unsubscribe_status(status, module) + print(f"Unsubscribed from {status}@{module}") + return True + + print(f"Error: unknown command '{cmd}'. Type 'help' for the command list.") + return True + + +def main(argv: list[str]) -> int: + host = argv[1] if len(argv) > 1 else DEFAULT_HOST + if len(argv) > 2: + try: + port = int(argv[2]) + except ValueError: + print(f"Error: invalid port '{argv[2]}'") + return 1 + if port < 1 or port > 65535: + print(f"Error: invalid port '{argv[2]}'") + return 1 + else: + port = DEFAULT_PORT + + print("CSM-TCP-Router Client Console") + print(f"Connecting to {host}:{port} ...") + + client = TcpRouterClient() + try: + client.connect(host, port) + except ConnectionError as exc: + print(f"Error: {exc}") + return 1 + + print(f"Connected to {host}:{port}. Type 'help' for commands, 'quit' to exit.") + try: + while True: + try: + line = input("csm> ") + except (EOFError, KeyboardInterrupt): + print() + break + try: + if not _dispatch(client, line): + break + except ServerError as exc: + print(f"Error: {exc}") + except TcpRouterError as exc: + print(f"Error: {exc}") + except ValueError as exc: + print(f"Error: {exc}") + finally: + client.disconnect() + print("Disconnected.") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) From 90895fb0146fe2318c5250d2010c34bdae2d76ba Mon Sep 17 00:00:00 2001 From: nevstop Date: Wed, 10 Jun 2026 16:00:14 +0800 Subject: [PATCH 11/12] update deps --- src/Server/CSM-TCP-Router(Server).vi | Bin 459882 -> 470166 bytes .../ClientAPI/ASync-Response Queue.vi | Bin 27744 -> 27756 bytes .../TCP-Router/ClientAPI/ClientAPI Tree.vi | Bin 20922 -> 21094 bytes .../_support/AsyncResp-Background-Thread.vi | Bin 22883 -> 23479 bytes .../_support/Status-Background-Thread.vi | Bin 24136 -> 24028 bytes .../_support/Connection Handler Worker.vi | Bin 360917 -> 361617 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Server/CSM-TCP-Router(Server).vi b/src/Server/CSM-TCP-Router(Server).vi index 24b6bbce4e1ac64e0f354d3f5b4ac10cf0d3f72b..7bbed7d230decbf9d6783c4911f684832199734f 100644 GIT binary patch delta 104389 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 KV#$d%TmKI*)M4HL diff --git a/src/_addons/TCP-Router/ClientAPI/ASync-Response Queue.vi b/src/_addons/TCP-Router/ClientAPI/ASync-Response Queue.vi index a40475c47f64ea51bccd50c2f165404b02a6fea1..927565ecc10509e705e1d6f5ed3984834013877f 100644 GIT binary patch delta 7641 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 D@3UWG delta 7598 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 diff --git a/src/_addons/TCP-Router/ClientAPI/ClientAPI Tree.vi b/src/_addons/TCP-Router/ClientAPI/ClientAPI Tree.vi index bb107220dc992010dddf4a3a379ce48dc8ef9af3..0effc0f1b5be73b24f6ac21b49b03747a76d479b 100644 GIT binary patch delta 3721 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 delta 3522 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�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 BDCz(J delta 14298 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 V|K@c5Mfv?to+hw&0;L||{{_(U)rSB8 delta 14838 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 diff --git a/src/_addons/TCP-Router/_support/Connection Handler Worker.vi b/src/_addons/TCP-Router/_support/Connection Handler Worker.vi index 6b354c8fa81c29f5c64ee7bfa992571b0aba892b..60f92156ad74a83625ec98f57afc62bd2184973c 100644 GIT binary patch delta 126415 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⁡5zk6x@}M)3icS1~DJp8h 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

KCz9Qt1A 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

r>3pjl}WQHr&EU zkwi|S)<;6Yyd$1bcDa-4EVIlDm3Gkw&14PWj{q70jZ~iC;6#+&44Hn^WQoLy#<|eL z@&3J&eDI4%f`6}Gn$9hpPS4caEiAmUimVZml;mLC|%e!;L(5b z>DKiE+uVlW_)?(_J0E;k_3vuSScF!9i?i}v=j5B5-fhjUMQzL=O@}OIllJo)ZZ0)RE!fuW{d$!;>o*(KXLtGdWg1y( zI=|Z1$o5t98GASOu0kq=)^)mgj2-2JL$2zN#%$dffj%8mlViH1>P82-y^!j_a`ltv z8_onnwteLzFDw*kzq2}|%I;Do9GDk7CL6tJOr3Rf-sO(>)!0H@?R(bzq_^zieNSvb zUX|G*jn7U_cnzG?otNG+Hsi()Ak1Nnwto)CGt?YtBge>~e+N95%}+ZYjJ z2+7NM^B6sutwWs;G?7_sdqO1lSD#_4#rky0|mRWTf)SNvt4C98VszU(e> z`TGgQK{hoG&nH_fR}us7&(X<_)W)ftuWda%BD*Z%l~H8!Na8`!-W;Mk^rV%4_$XI} z=YuV4!3W>?&kh+m-=m&5o$Wdlv+*Re>GQ>hfg3$hN+w7%d+*S|R6x|t%z*T+v$x4g zi-HD-p97!?ZO(Nmt}8&lstS3c-g@p+Xb_ZH<+boCo!Dyav3j)ydC#Y1wr+;_gmycXumZq-b&1 zDHLdNcY?d8xKrHS-Q6WP{L;_!uJ8Ltl6}wJXWyHZmC4K;Nz{l~YkJYv{1-~;*osvi z>fY%z7vBRuhaVYRluZ;k1SN{zojU?T?_p@v*D$yBXH<~4?kH$-3xpkB)x%t#WPCZcV>mMw@GWekvJ{r**qCwO*wQ68l#*4Z~>JlzFph~orEPq0N zwfc9b;PrsXI{H}6?nhw7)^#ZuP3jb{Z#f`xYeecJ*5^Z>3e}$nzqI7=0ZuG#Ydo9j z9`jtUEUB2ne&$OU{rEisP6hkvb=ZhLHqwijcuujEU8)$nxC@Yq(Ez599=k+Q?PO4sB5rmv5hF(RCxgH<)o(P2YrBSUNHxNwK7F4$=iWvU@vLKu z_H{Qfe2Sb>#jy3D99!+F0dlp^haVZIi2kl}x>XfAp0N|s)2&A~Y(1ciBr*$d8-)wO zu;B0&SSRQ^T$wQLAvmOmRjr3Ogmc+jU2h*`{Gvql{_0xy6uFFD5Z^%RCXJ%MJs41Q zMe06di*^yiHd$&XsrrY2nU!&)F3t%_sjEcbTV&GvRf(z&ib%wkA3#ZmdRW{6B!vj@ z|LcUVlxf|V-0tz!-$I+|e7$6coYb{g${K@8)1QU7rCgwxv0ean*cd}T^TGcz{ZF?O z%d*f9CaXrCi&fFd$4;89*f3MQ>HW$c0=>XNb@MBduxIPW`EjJoWuubkYwBU5d7Y$*t0oY$<>O$yXBTOmHNw^8u>Z702hYt67RTj|0=x;s|Ne%QFi_NqS#6&cr?%?r^>E>1j{(IO0--=xHWiFBLhd- z?uu&z(KFTbel=0ajwU9I*1qvq$=O3q_o%(=Xe>KTuTFSc9o@W;td5efV7u10D8SmOPOd z7&XNW43Jryq|s54Y4268Ujv1TX>^(O9U_dD)XC_ClZhjok+8Bb2hxfk`NHbEcA>=> zOlrtIJEzH;Cd;2|!!hWYl^sIEUTiL(=TTG2?#W1`-kuGxe0qD<7yV*}%p2z{L2Zu- z*V3-^=sNw%ye{bZm?<~2pch+mMJuFfL1NtS27uno^#+VeMolG)`NE1d7mvpB4+RYI z&R5eg1WwQ7W_YDiORDcGKeR9a$|a+Y0GZ~V2lNy%S66X{R}|~~rqdE!e) zGluC}3$J6E@nJ!SDelR9+V67(ufK>Z!!k2WQZpvH;RCln-7g?tNCm*>{^uy$KhqI_ zmtA!#|MPx>v`fnMTe;}IWEpdmf~Ia_73};(t9mp(XAmqe#sq9Lt3BpuB5PE2fIXYo zMX_(muTAJ79FguK`wmlvy0NJz_tXVs<$kj0h5XX6+wXxCxS{t2?}gnpr0(N8-A{$JDOO3QtFgjO2)nBp6hHW;O1~MvQ{vN55u7{>t5o$b|t1jee&U6oJbCked@H z8-og`{Bx(+B9)lq(P}r3-?(JV?(2&j=#L7UG-h~v?G85-T8#Q*jN$|w`HynogU$|k z&P=z4r<^|D7xg=sA}?^W$4O$fY-jjgS`go$8wb^wAV0ge58Rd@S2s3{BLWz6cLgrLD|V} zc&0?MY=Yy($)NgPpSn?vEA6xpTl<$oF2EHU*GKnI;?NySv3XgXSO!q{k_WcOKIg4w zt=-xH9qrx$WY_7fTc=*UeQBE=3sJTt6m$CY!-<-o+=OXF(`mBk!+#-T4zcVFzz;Me zYMwe8U^?&f7WURf$MkmJdu`%$8cUBdx%juzE{O4L_up5v1#u14qoMFSS>w#gUor)D zMW*Y{PYhi$)kG|bWdi$p``Gt5i~b>i9JsQsCJ3Wb>%|Jm=Ch9UE;^+1*{Uii)r9)b zRY_n2+aBk#hkj{PpUq|m+aA6E-kkdS(PRf3O`GD`wB1xY*+)X>)lhJ~>Tw#>y7weMV%po{Z_ek-n&S@1E~( zs^mj5Uc3^&M*okVcBwSde4Fu)&Di_$%VaGHDwx?fk4p=Ifx0wAI!=Y>5!uFGKlEvzJ$#OEjDH*X+9%vV z6r3q%FTl&PSvM``I{;9(UTn7nmO2!-!3ig=dN@^dmNy2NxYs#s4R|&=w7q`h&IpbJ znMKCJ?s5{{9eC?o{Oh)nj)))gF}Fb3zO1ygq?>?3v1oiT2+_7cC3^&iz6S6V3xZr~ zVJ&*H1>vLvHz~4aWslzdf20Lj2r%DT~45#rYZf6A>*-EZWN_G7C}95KoeT=qG)g_rK#$2$1_C z)hCGfM_bAJMmaU;{l;@l3qe2yLtTcBzw<$$1_mM;MYY&)3Z@$NNy$&jjm{f3$6XIj zQIJbZWJ>GSM+3fNoYFXy?}9v^k2V;J`3^ch1d2CTS4o#B@l{={kB9%N%s?;`Sd*WT?MuP~+S5#c=#n@TAC;rK zR1+Y+BrejP4ZzF5@gIGYl?mn5;*1WXKK($bi9FFtnji76p3irM!f!k%F zN^ipjWx(EgDl|ljuNmI(L(o74zX&)TcClMX(C`>TeDEdG{)(ZQagX+qF>T2ytz9te z`bRF}1hC^9=IOcSsq%VQdx7rHV9FQ%_ai$B=Bj^#Ar{Sk18n9ZZ;eCdvrM(43+y=P5{ z*#|cnv9TTdi(Xv=Z%V<$C$Zt$f!b=!7{S^Z^gv7OWh-mn631+b^+E)6#527J{02Ik}b?D zQVTc=AnpKVTOblW6uL&$KHGTd#`FutC&q%H3G}8e!tCYoP^?_z6w3L|H%!?}Jqu10Y2LmDa&5}+7J2_n>**Uo4oaY zA}5F%yOvsC?Z}4Us6e}?{EM7(Zaiw8PejRyb`fFP3)$%{`Loh200a$!bvmC>0PE$b zY^f>1nzCa15{`xBtdj|(+;)k|4Vrz&1K6E<0nwq$!a1$pvC%@+KFq~qLaV^fc-6V% z3*cHV{#VJbFrZu-y;KSz9Eu*i*XR6Q{bLqU;4JjTFnrerN!Unv!XBcK%;Igc@gcJC z!pC-oh5=v4Zh|bAcB#wzfQe5NSjwIIt3mZiy>>O7o<&#_q`M}+KJz<`iSIWm_y-Ql zX*7tZ)(~$wEy{f177-t$J+VJ-XxGu|qkOhQHn@_c99hA4hn((|LOAwtu`5+_K?l=6 zGl-DduS5fW6&G7{t3NmVY2va0l_ zo_n-TQU=${r5_UNn9BU;)_~b1ZUcMk$?&&?y{VbLy#z9R%)Tu$oSAi%?54KyC)Mxs+8@^hWC8=a z@MBUhPV0yBWO=2~OKFk0r@%K(bD#QioI&||3nQDPPIJ6?KBpb*5VZ)_-^)S1`xRHT zS}`ADnyXM(T;KNS=Dpo!ewuDRA`exJ{Se*E+&eE%%cX-aa@vG!3a}K;V_CgvL|RLB zFpvZnPtH5e)y|)7=yp9};5?0!o!K~S5TI2M(BZV(_dnw6b31q2o9@BjJhhRXIXJ8g zXnrZw?l>dpm5yn~`JLO3TXPWIQA&jPv8dFpD8rDgaO5xYAa~yMWJb6-xT4Y$r#WA6 zE3m?_*W@&5UDSDF9zdTH@h#%$M){13LhSglKqPJ2e-}R#+(kRf?3&dm_4QaL)K!Ma zvSI>{KiCAM2A66>2)(g)v)Y)4@_9}|!3Se$5vLyN(|H?cKyz@-kq=dBV?It*023F( zjBsL`GSJR4K$Xs!jJ2tH9-v;hZB)mpvS1yk7SbAk;Wkn*|NS2o&U8=hI~){ghJmkW z>vEx@C0+Agp(9p*{dW&v&m6PLDJ*mh&2jnX{e~kXaX+28HbyE8Z>ut1o2-zwoII3_ z+y0rH?vGwFiyXJT{5fu$gXr31+?fWxg|F?@4tg*OR(WN>IshsR=ViC}LX~_Bhh+f{ z`oqJKQDUZ)#6CvRA^EHnG{sgY*z+NOFl#9o>=rtbME}!{rniZ+ny6e3z_4a%@Krn zk&bqsdg+nukcSZ8F#{j$^E*r$iSLAZ{Y1b9J6`M3c#mv7iTyVn%B*huJ&A)*z$LIoUg=e&97$x1Bo-EO-Ysu};qzJ%a; zZ89<5ko}}$5nKiKp}(S-;JL}h#e#k|@m-t>Dl+0t#|G47m`yxSISpegCRio24@XEn zoiA2?pmx^nLgx{>Qr<0z*f;@V;f;Ovzp*1r_h{q#rap_Eg=bTpB#3ELYd6BMcTSbjg5hBMCF2l^XoWzfs`*B1J|nG zf#S}%g}0{sYR7M&DCHLbrte9V$Icig#nDQ+Z({vKtLecG#)vDXWBZwhsBBIXexd;G zlZ6tx8iKJ75y#dOu`*D*5yzgm=mb(^Htt!lKo}+a;U=M@G#!S&y0M6E`(Y*f1V6A; zgCF)$uo?JHFm|o_r`4~0HK3|-| zT;^GCZn={E2JNq#N&C&7H5wC+MiCdKt`B|k*hIIkc#KNx26Kk75yBW;gTG(DePde7fy`0?4l;_+8Q$nnXaPp-LueyDG}!_PM*B|Wn6U^cDTOSU!vA` z0$V=a5bw^Q6!z*fg3Lb5L9cV=L>FtmvH*PN<-ymvI568@sDq_WZyk z5(j9}7=3)8q8L6IgERzi9u{eIuXFEJq8$-xWjGvCj2`AKCk*=|%u?(W3~}u=v7XJT$Me>WuKRo# z6D+&W$#s8j#2nH7k$X{7*``Kmoh2259?}E$+JZJ3Q@^HIq0J>>h&^J5Lok2x2t;FM zosaH+BB#p20%*nK-!ft^*$EcYz#?M3)%&dvM>WsXXm6dLS!I?f>Aeu`?!2E?bv*=| zmW$OaFUQG|qx;=H-o!|@c$XoCsg@gh*_x+um5@&VAH2r(bsmdZB+Q>rq?>u7a?ahl84vvyy)J23;D;(9tBsZp7 zT|#Qx%%tVPQn>N3Gy@OYH@$=AhCwZ@Z*O%oL+F;|*7`JszG~v+P|+(aXg#1;+<-Fx z-w`Li)cfvu+x*ORxupm>i!7?>k>}4>5p@;N5zgvg}$Xt ziZ-h9%6W37r)Dc7W&SCr#c#4pNr~f>e3jdWONXPr&h_+VgtcD^hKS$)Ow{Ohs)IZ` zUO%dDU3CPaYzok1(`N2bBERIbn*Broh+x&iKC5xi7V9#P$?|-hLMZH;a1=%Ogx6^$ z0*4V7mFGfz>EgpNc8v?cG;+A(@2O>*>M+`BLe60ht(_@;a2Vl82F}( zfQr7G@iFSR4mQ}6(})K3+0w1A1$q*a+TLW+9y~Q-B+e!%oc`^_EBxMug#vu(cj*uL zb+EOTbzF;Mp!qS1T~pH+BPvAxuNkST8;b8jpoAP#^VF?}Lh(oCP`cVOtpsM}t~09s zDOMp11pC}-w~8+r!Qg6^mY?sE0J$A2JR@qPv%L9lYM%JLsOu7cTs^%qG%}>l{f#A% z&~%v-3VY^>v;@*k#*zl7OpcN1iIu^)GCNjmw-ECSGlS3N(SuW)atiuLHH|9t%3+S7 z&zriTtupk=@{aQc2P0U`YEWq3b6GR}+pW3by(hlkYo?MvpMN;@)qnu7%X5dbeL%(L ziEo%ms@#Q2ae?UeB*nQZtW~pu zje6I-W{O-S!`^W|@!b}4TLv-cVHIsK=?A%629@ff6~%{T%3Am1o0Ir5^!wwtQId%( z<5{S~8;J@#*3KKEYyma%8=QPdUzlo>y0}eRc8OY~tXY29_szRvc{AArb{L{K^Xhfp z9zrf%%g6$d1&xo%k%dLTHsPXJX8Q<6kXKN)1?>Pu^#I$j=nK(t#S!MJHoS5Of{H1% z_+!w!X?m)txy2uX4Otp5Ib}$ED zNIY6{=8smiWP-L$JLmGSaYGq;Faztx?It;v{F%rhgTO~UpV$;(&V->%T`$~DzqdO_ zCd&2)t?>7hKs9goq4NO@e4{RdynVvBPrn2^1q)+{9HTGngio@L5(cH0ze4?Foy_7r zmRNuR-YTW`RVJ&NyUJsH{wb}mR(w+~?!dNv7LwDS$TC|SNaU-5(yZOYa1@4;IftW! z_O6=&+8U)$%E%Me&suDs>T5GDOljwD)_#F{c{J5208cD~Gk4D_yRvCeY|a7pX1|g)5N;CpVowV(h)-$Xzdno47&bS-{Z_D>qfhhCm3dHM zMA9?qpL#Pb2Pf{%%4FPvf{%G@agfUjg<#6f*Rwq7E2UM2{~tpAZoUHlnOUHCJy|~r zC+<-M3A!zkS8pYA+_-8+n-s);!bH?V5Fh)Y@$LJ#2%+6nzt;>^W#W@tn*ecQC^uUi~VA<>_^21AX)Ff>%1qu&XxNxWd8c!M^iVx7D8#b!gLMQ0@b-%@cDS zkKbg_DZd$f2hdU+7yXUHo$@YkT212>D7bC99rLd~d$N6jGW{JB?=^K&n}EeFQq5q`tb1>K#{w&8^6i}zWfdEz~J{+Xn_OP2 zF#Y6ev!}%3qbb`7=*lj#XMuo%MSXpXwoi3C#3^x0W`IU7K4V*&nMl&C+Ts1wELyE% zS}Y0xcGF)=^ofYs%kz|Y0&F$AA#!G0!g11+e`J#-Wo?@OM!@CzvDVc999$|RdE8t?eq$$)=PMGOE7&*EgQ?Oq( z2JF195HSmG>Fm719+Ry4QY86n#Igle2hp&Sw|VTOSk{_v@O>oV;YhX-IXC%rq09XE&g8a-l-qn zmMMI->=eA$T_`5<3vYT-K(SkjT&M4tVteu+HWXM3#O-3piQ(Lo28W@f^pjUTHv{=; zW2t;RC6aoo0{8+NhSvmk-6Y%&=TA(u)iUVAQ5Pi*89Lj8=2R&|j2L3}fOluFWA!Ju zxw10izm1>746DVs;e58xpnnTYSnlMPh!)z)uws%f8uo;nT>*dJ<~YcBX%y%Kf6oyg zdjwA$AK8Txans#CZA%&@LgCBL2m$P=K40h^w|6eK8Hr&Ur1hJ*E89q(p;zIFcF_-p zJg_J=AZJ4q9dwFdqD6}mkECv=Op=25;D1Ktc39*wBe}x&qU6t89sQOaTs0&%W7pBb?50x`)L)y0 z!!g%M0DUKb7iBNRY@WM^8L1Z8)SKg2ue^z|CNc#d^|ftWKRai*h*p0%fjW!xgM zz&^}{EMvdikh=%HBB&lWzj{KS+GV#Z&fCeHwC{v58yTL~LZOT`iBrT^(U@kj8|cV7 zwBI1xT!UI_EYs!O;smuwdl8HEM(%x7%p$;j`u_TfQ2j^Iw=5As|DY?+5jg}J#{ku9 z`el0+CZQjFq>5-5Vx}`}CZ0)|fzb}TG8$IDMIr#}}Dm4JI zyxjZy1Ll9l;9WTSaH1Z`7M~|AzK+ZjNAGi(qF*YBjK+})|>SLzv4W`u!tS6ujeJumUT$isLZ87qO(H^xzNm+M~NZcjw$Rli^ zg*&>kpFcE#?My#b_r>*6rW}1(DD9(!wp8m$lB#C!NMDNAh#qE=M5`M`_nyI)jTLN| zMENz_r5|n#Io&61Xs&IoYaL~MjdKyHGNu{$fIxYsTeRpXnU1< z)T3%I&4qAI9|iG}NG3dZ(%K@JwFj+z@)38GYIWGCefl``3r6{mPfcT*>-c^}77X&C zU5Z@NtNU(p1=VdF?f^6O!9j9>h(^uZ8@HNB$5Zxq_^s!ZKXC(ST8U|^%bEwd74g(w zLR|w_SM5~CI$@bsoT-KCc;$gP4q+UdgA7T$mgIN!vb8R#N~e1hv5&swGn0ZmjhWK& zr}YLU`Q8pr!cmFK_P;dbZ}orIx{OJBoe3pa9}uT+jG5}+KPmj9G-U5NfnN61m{xvr zJbTtC0Q~v7O>l7jo)y{i2LG&;v0gQ$c}6n-R)OE2z;GB&qusc z1Jq|ZzpA^>|6)zI{irw>dRiH{bdV#Q8W-hG0*O~Z_u+dtyQ^dW}n$=CkGW zm70RPO1K|a&%^zf=_`Cw>NR|V3HiZ0zqYb_y2rHVX@R}PP0Jlvs;^{TPw5svAVrFb z;$J(XJCaL)Pny?fQ+fh~0fr!XuDBUQ|1j8bhPZZxC@&N%IFUdOkrwD5Tq}opVJ|YN z%XlNi%c2p!5*;1#O@;{k_WX|VZ9)FQTJGnZkIz8b_f*dKGPm&ORyK{4?Sk>3bW)o> z|61o`lJIh@i4K`iK?`u~N@5=wp3xDBgZ}K7hCe&C0Q(ljY~pp%ZP6e#3D~kWQ%zv| zK^N8z^TbRE11+JtIxe@0-YsM`FMLYo_mcVoK&I z(c|^o9hshraQm&oq#bRhGV7uG$Ve-)3?qzMv@v@bq>KHzXFo>a3@X*2z|(HrMGnrI zRMU+|0Bo``VM6W4{?mDiQ=ss-2a3bp&tSjv>Q7li^x2+1idLj+^xB<*lwS&sXryU- zk4Nl8?IG){K3!f{VH$;(K7E`=-iR-?oS?xg3hg}_-MxAoml7jv$qQQ12EL;0Xtx$6 z*FP2No~vm48X5J`)LRQC;R}Xfr5#VW+%--Ypu`v17gqANZ~vC{!B@O+k!7}6fW-K^ zFIee@Uz|jAg7D--2D;23^#-bX;UUX(u>ij-=!HE&+G{_u{L(9{xM|A*kvreH4+Dy;R_Uv7KCGV@fTV{<-3b(qQ(;69T_}co72rwxF#k~sp+#qY z=};&0Q6T-rTq#*DsJfXqC0yv`B`NM|tC&*PrmF@M4#P9{S(Q@p`KUo3MU<1lC(!EfmC&V z)YW2QZ!cf*8U4QGUVNETi>KT9;9j|tOgJqqo2@fK9(P^MZ&WJ`T?_?^cwK$FIDRiOwJb@Irz08^73?}ORkgR^?t(1{+ z_mTIj;d77r@GTRI5RAr@?-b2N=|0DSU5| z{SMcyn=))Br8MR=#>$0qOW18Cdc+{y&YZKaK?%#G`U5b!1_KcR3F#8LfX=`fl5ZGu zwmn#5UyqO~{Lc~DIeBWOKC7$pT=Y7p!kD~@SONc-9WB{e0Nm(y>p6T7x9}-CGK(D6 zUJ$|G447Uzp4x!HIeK#xqq8?IQi255SiPGM33fX+kF^}&E;Iwny!v0Vx2$rw#xZ+S zFpW1Y19vXIw`|McYIni_;Aigi5(Wj~D)hOr9csH5Y=o_Y9v?%TBVV=U-Pi>QMeDGDsTgN%x}uzWbS+N_2- z`~Dft{q2ibW8eI^n^VqeBLdC(YBi>hjp370%`M?Bk(^q08|RrCGGHj>k5gRGZ2Y2`zlAZneJ&lEYp}1?_F!* zYb*5G!a;d!V%H4+{`Obj-WpIpgP{CTJ7iU|f1ajhx)+|Q_NEiFw@U(&c0!SDw~ll8 zVI{EXuVuE~cq!)!smcC_)oRy}% z@8=7l^g@|vfjdtcq?#x#p!8y+4#5(~Xw9GvIcFGNMRheijFBOKQu_nkIwM{~Iof!S zAA26QWA|yopA$@~p};*{Py)Q5Cc9m20(~5`9+4(U2;q{S4oy?3N*7aJda?=07iLl< zNVMsPqpE(d(7?FoGyP81vnxv#Q%TSZ$@$oj$FPB{f^M0ZtswS&p={(u z^6Lfzvg?K!Btg5!ARnLf=qO>z?X4g&p_h>8C=0}U4|g&WVZ;8uD7%QLq~5!`BR&%4 zN#0^qDulrwci)!8O-_%+D{&sykC(Mrc1PRd&_;mAH}(n6dvhks3*5ZDpg7mlW4+7{ zkT2?1?d$JRqfV%YH}X%C!j9kZ{ErSehHL6rOR|F?-~Ux=Eb071#>ZFCsK_XY+iBS| zZne2YKm z;7n{K>OypG-n}th6v*w1UP3;*qPg?KyA*mH?OEMp^tFsC5I-U$(c6d~?RHObZMiZX zBBSCy1Rz;~da{WmRcJsnjWM&H z9s7gX3|~F0^6yG;Ct8gv6?a2}deH*I?I+0ghYIp}jdiu0N?ZTGfDN8sVLqm8ePDF!6`nnP(A+wpJ}$`I*W;Ke_e3-S$?f{cU5`o_C?254hdrPi_?|0 z&4cKW$K4!0(ECO>#ZgN*KHrh_{ukJHBz#_3Ym)HCXk6(wti zkTA($3eB4W^)MPDcfF+Q+_w)v^)EEG0s;h#3M#-B-Jp<4~xhD9zsHMeF*h*^xo4IW|=;IbEu(Mby&i8C~AWU$NpsEUxs6rM@j1 zzDXvcB6HOEHlMzwQv`adzONof;~;X}kUK~G(U#rMI|wONNU^p%IozU@naA^w=&P(2 z0zlaQ#X9+_{@yQguI)PkYs=D?j0b!8womIHF{RzjFIJHz@{6~v1%fNLFJ}0j~j9NTBQef`whR5H21p}S1&YeRld9O2WqYI3}_5ED4Oxmpb93{fyx68inO)BhyjPn)J00M5takDkCbiyt|R1piV$ zCY92TR^U2<4;NIce-mX2!z4qSHiMLwry~vSu9W?$V+yPDt=ek~l~55ZuJBQSx%9gh zN#q@v?;^V(ATwH%OWqrCDj``D(#(a>0?!!>t&)#d|H!SPO0v)G_4@)(;+P2D8lWGs z<@{5xkRqX^fy1s>O!z(D7$Jkv$T^4RNHkmaJo0>WiY%L?B<B7KNw0*Y9xp7hFQr6a8+q2)qLr2xVb4W1V?2nI0h`CD9%$&hn{_6xD%(Ls8TpiD)W(`f58kr~-H$fO6$;9Y0vEQCWdyWtHGkDH?^%+%0lp&?&yXG35~t3s$>y!7xIrbyESNBJ8;{M+%v`W!tIx3|*0fvM0a~?f|Kt&= zU2xHI`I)V9d5PL^VWA&~aWgixOLi;Ab~8~vA!u7`2fvpI?rnW~bO%$lIhE7UufXL? z1G4(>I8H;SVs^Z${Y<#I~6gD+Jcq)8zYw^=J2C&+)B2onr?GdU__6DLg8Gz*b^j0?uyn1IF?)=a4{bzGIWUxoE;Y1 z$kAiccZov8T_N7c8Ny(0q6iAJYjKzWk?Xs3Vd4@2OrBNix=yoww4P|oBvpd+F%Goq z%Do>%nB&LJw4D8f!(D+*T6|0TV^LrJ!z2*&d--#?Tvz%Kho97L6;@!m?s5#MlCnaX^#qlu?=*-MUr>qeg&(=v6gP4 z3Ui+;qQ3o_xB-7pOyjh|QR9X-#g7v&y=WUa4KH<>V6mdoq=-PKcSJQYb9TS4=1lYE znoR!k{#v8w!#8?Q>3iYbHW;DMoM!+MtBNg6$NP@} zr3e;=LI4&znrKgO!26}roXS>X$sS^xV`!48_V+I;+v~!Q$Hyle?i{j)uP++URe>Y! znIMoaxuDZnv~SwC{H9+T<( z+P72~BGC19gbcT;C;8#49gV>N=C%?bD9ag4RV?E3&^9xr-d3sZ_VwI1v1HO5VM_fL z5qm{XOtN0JC^`{7AxW|bs}N~s?O7~gR@$-_fuPOh5((QtI6H@53~xA%R>ex6)n1`9 zG9c80#HIfVmwG|t#mQN?RAEIW;NfieF=c0KloWy-o7gzrymkp&c=MD{Fna}vZ`BP8 zAvn{4QstjLc@~*w>M@xQnNKTrrHPiFs;If0L*}b4w^r27-nU2+uX;0Ndp!SCw?2HL zP{XPusb{>3GA}W0`*QK%IL7`;hV_L-v&B!}WM@vU3_mY)E=s=aNjGHXUGe7476wDR zmWgYQ{)N&8)*1To0sc-X?kxjwrEC#eFI(Z^*v|es5$;#5f3cbI?ZuSNE&D%|g5@3i zAA*){P-?d{Sfl{VJof{a%-yf<+>q4fx+wd6bBY-tUF znRc}cr4K#ca-sGoBpD@`)Gw+yol z_YhXI2=f8SwNoQoiKj&n#JVt!@PaPO>FPHv_*s4KgI@P&B!Cb*I`yVegB+AZcHl#@ zUf7?1jDo=0>c{kr9;@AgE49Z5N^xNpEk{YOUep=dLp@g!cmQT@yA*SV{a+8;iGc&B zi*Q`f-C5Q=$M&WtNAId6*kZ7UBU0$$EPuWhI$28ltc$&)^M7dJ{}Av0p+?(~J){@G zO;3Ohdo1Z-^BxavY>%xGOsyl6nb>q^UZEnh#)2{V!r4GL2=IY&T#MrBUlrz8?9+ybz)B!eFuWwnr!sGxCckN%t10iiw6AeuZEK+ET57u!EX5;z!(x5wyP*u754Ac@Dt4#QFE-tkJ{HAz}zFiv#xC;+~~ z>)ZpiRbXlCnNF5MYv^63+H;!pjEDGx7$izvyfc=a*MN6(L$$1{MuU|>G+h0)=Stzv zi6$)ZHpVTr@vm^uP;*6)yQ6X+BtfqEOGVH{o7#&}3Qn5@6t1ehtbKZpaAFF-O}d1B zJn}pLQ-qeFC&|`Uh2AEq0ZdrljT%U$9snKZPm{`MYh&y)mi$FJh+Hv_*2RljNtnqi z#Z0F)b+22_d}*HUmz%HOae53_5^-Mk*K&iB)R~@043Ng&GWmSO) zqP2~i1~NK^Mi*D0v9F7ZLGBg%Y01cPk)fw@G2~K?-0)XTeYHt*f~wJTnz_F*(;a+d zIuUyIpM~D^t8aU*ppM)w8K5%RXcr|6*f)}EY6;W&3@rgc_6t-#Loh-1p26o#EVyku z3{Y5l&h$#=GvwJ>?~_&4aZ0nH9^wx#=m4%1t)FbIWtbQTdbF=spc#!kfURgqWi=^o@7Hs>ZqrI z&)&6^CgOa>cjh5=);L_zD6~}LYtTt(ioUvXAYe*Ccw}s~aijU`0O=WV;4Du;)Ic;X zfdQ+B^*L3>Ylv(bqE?~tduOoB4`E<(eINHzp@%u88G-L$&^6uE1|3IuVN0w~{zPco z?`35aQsugdM#EObzo5Jnj{!NWJ+B8})t&VV5nyqEGA&&y;{{ z0c|=Q$$PWRcGh;t{72@$EI5aaSS#He8)DdUXX&cj>go#W3iPKZ6EFGL_)SW*Q@b9O z6-&oVuTS$JFFX%-QTfT2_B4G#x`+b3XyIpXyE~R_S8wUv)&?8AoHT9R__T{kHfIkH z5g%UcS<}sM|N5ncK9-c0(wb}j2G(L=J1F%$O~&v2!+@e99enb9rM{T~g$Rp*b< zogmSlAc5uVKhqTy)KN&c@B}@@Duc?rFYAjvacKm9KYcI6pOMa>eD0^wAXnjbaF(l3 zFo&dXRbEIA)6@>Sjcmi)&&{7pYon=ERYOna+L!ZqLl4jV^^w zLJSe+LAT^AWR~QBCsvX<=JR#gA(xsJokO7XtoB&CwjQQk>DI2P(l06iLyKHJLHU;j zE&&7V6jz@6P2j=Wk>KJHsoROs;+pY8`=U^%4q+MrJN5uh$=}1 zGB`zPVYggzgGWfzI5x?w8?-%F>E0aI9M_Be;ptoT6T(#Zaq8(&55^s86~hMU95K$zGPEjof0`rY%Duf z#Rw4Ilno6BZgpYQBU3dkqrP=Yp=Z~e(Z@r? z12+y$B_`WU481)g@(~(e#GK~83&%EA9qH{-n4}O?kuJQVlWK=zV*4Xgg6e^HFEBAK ztJM~$38R@bdCnafY9m8*`8^SBWEA!FQndp@+Kzd#P+vuQ5R7Vg zgzE7T)YQSm3qytV7_7ku*JKK1m@3~EK=B~k1tK+QEmm&t&FTH8{hs}Hri4-dRlGH5 z30;ibo``Vxi8A<1E+24ybBp;AIcNShZ<3c4&-9ndF;}q*3Ub%{1{SnIBM7EJ*(xg( zT6PB&su)ELFPKM#5`TnU!RR`^L~`Xhlb%<`kCuw^0m;yfpK2WVgsSifGscgSAmm@o zh#mNpsPMVt$n9BYyZC9)g5Z$ytI6X>jk8^d>ElN^0w)1aQSV95S~3vKfNVquEVOzu zQ*1XE<3M0Q;=|xu&k_l~Ex!U(n6_qpKA^Zqv*~(vl`*UWmbug_f_7jc0aqqcGDcFn z6URj2ONq6GHcR^4vRxpRn}_aqIYS8g^{pqG-~!fx9Jw*ZffV_BN;R1;DnS`qHI~Ty zM6Aks_<6n-8-!j~!I+eC+x&%%_0}XnuN<9~e5Sk;VLZM9>F-AWt?IjoT zOLl_7r--jy_GB8W*K24nAb5rRX@%9~8;n~niJkx-KgKARl0{mfGx-MLmP@c_lRV{e z4-d#HV>BYr`F-U)0fKkLl-*Sg3kKx>vGk46l{9VFlL;oaIkBCIIk6|UC$^n4v2EM7 zZ96%!Z993-{e0h#sL)*>6#({PTtDQqN>tdf#;++Qjmd=)kgK9<9cLhC};@tmvs26@~;WMORO48 z)$p{@#Gj2N{2yESmg$YhGv{qwaCkVVA0{&1jsR;TKJto83kAN>!2y`-ztpJ-X@=bB z*e1{0r>|-5jgjRlmG~Bo+(DoI{PiWgt)kAo5r_d*4)-s>?rXvGL$4O3YMTt<9|I=& zaBsy;JL>&?1Lhnyq{kWT7EMP8I*;wS-5LtM9b)R=d(3~BpI1DS(WUy!)c~Rslb)P6(!m8-*@MQ6fT`y zbyF>QeU*tKJ_!nlaMFU%;%r>N)!DigQnJYKMZFsd_6-7 zZA8{)8aejK%CPzEkalNy4lI%%aIr8?p)O8Y^z1u+%=L5qzmOt2ckC87=-O7P{ehKa zleK!SHD5l1eHvlCs7~p4KZAI9cOn1~(v79gdDb~^@pvP>e;qvuxGFuw<;t+MU@u~5 zW`6pG%5hMv0xc~$zs&e1W?OSueYUwBoeoCx)8&d{xJgf5m!qfThVJsq*(mwHO%AR@ zUf%^DVtbn3j`kND7l1#0wROzx4Q#_;a`Y?-WRZJXlvu)<5U6L>KpC^ z0+0M==QaFuAK35jtz3^?qegnd?D65Ncs!@WtEo|2R{tI-hY64C23%nT9>L7ckrhr0 zzB=ad-+K0IyG5#qiQbWnmJc`o94U`Z+dS9rO{cALc>VgCL=f8x&U|7a3eI*--6M+Q z{UiV>KYe_0e%i}A(oifiP+0(AEr)>F_}xY(PrG<8FXEZtI_diR%PBl(vio6YekJDm zl+WDS91;WArJ*$ewTS){VPL~of3Pf^bECPsTQRn9>nlQnR7!8@kkCJUd`o09e>7#p41`-d1QkHD&Stw_Z zhJpBg3yTdw8kQM;Ee8>0syxg`n+|O*WX?=I(`AVDX1_9|5QxXT{J_gL#Q)F``b$0a zCcv)#&3pU98TE}O+Y&u#C5hV7$`U#74+f#Q8I?d3vO0DyP0lp{67PqGkluRgIw!ss zGsZ9+&;CRG44Sdw%G1^D3P*J+? zQ2@rJCJ`I?3++ zvtlV!o!>igNOoy?w@_ZAfCuR=W{W@PL*(YM(suaWw4x7Ni_ch21i%QwfJB?({Y9M9HfWvX+~@@aZQuWlgjBzdhu|VCi^ZxKfmz) zY?LN-#wK?LkUb&9Ipb%V#vKbHIZEq2yA#a3NalkwR#Hdjxwhz7ik&v4M$j`6LFQG; zz0*hFnc2U%otLbo@*kAwG+uXa0z9twU{rc;zHvT6a(64MXoV0PHALoM;m!$d9qoxo z1KCBZ5xM2RbXDp`P-|qFy47xB@r6+1oBz2)FTlD098feJcxmMrO3f!F%81C5cc+X+ z%GW|XAeYulkX&8o7&wd^|b^_$m zEw`b879rZ139qDUC}-Nm<65<{@-AfMcn+mEjTC988j_lJuL~bZ*npGaV+u9wxjrYc zIVOM$`^#N@K(3K#AnsBx^_z-1z=+!J%uY4w`bV8@&8Qm%_-f<&r|!l#5n2b;hN%e=^dcS zc2zN_XfY))p5%-o=!`dhmu_Te*OIBO#9l-d7ZOsV`B)V1#C)V<7mm5^lBLF#$%?zK z?D=;@#T)1D)A8Wq3C>8_ZaH%uVTHLY2P3eLp+;4uIA7;Tk-e}65haN$hCB^%7nI76wV1eB=z_mCYFOhs- zBUyGCA`Suh;17#%^Ft%ntMhAX^7%4^1e!!ORYW^U$eLpH@#=xet0U0IAXyv;!3M|! z<7BM1nDoOc%n$+eUv!*yz(*y6mww+6&=hHlPmHkWBjr+27&#j&RVa_|>XLSf{+nn> z%t$>h6FqfH^DKNzodk@MBDn0 zItpj%6esCerdemJ`nlAsT1uI+TUqr3^yaoBMSrNJob)9VVj^ zSa-cDtS^_W)0inIZ#C{slziiJPX~vV-7>V?IEbOIkwMZpo9#kx`R!bI2pq$Z#7vYT z)x3OhOBn8dOs(Y$Kdr;!Nu~gnvrlW^o;M8(${D5NqFF_Q9bp{9Gc>F1$z`3XK3XP% z%Q;0SB9rJC!q}jGZ2?CForG|p86_})CLIDFhKLkkBZB+<6|(FF7|_y0-!@xkTWMU_ zLMKU<&?>`$>TQD;FF7bq!((G68)BK`*HXeIt79d6QGp=eLIG=Z9aHQEy{-(#paY`Bs(9bmCY>V6qG>ucr23VmOJ z`k=9r6)gHL@i&25iRJYt>f=NJH(k`)<^e?82Gp=j3!#gk02is$)W~stQ9{gzXa3+x zmR1BzbMcyO@83?7gz6B$Cn`!{K$;F9yGG@86#4bmOhnS7fwOYlhWpsnzJs;TSn)QV zg*u1oH_A`o_{_g>L6K>h7;v;g<`UZyvUV`P@DCWG(a~SL}kWOw=oYH7C+IZK5kxaF8DD z1G80)-L0n1l@jplo_}oLL+|f6Q}^&X(x*kOI(v;xwFsR15MycB*I;P z{MqTzMEM2tG*zOlI66QGlbYQjw5q2HN@1Nbx3hyh^u!rQer3z8r|W*_HGEmbGZ-fa z44qA|jc}DGD7_A_V>JdPwJhw3#99;lXZ`01x7n2wxGU!%0hJE;1>aB)fuy~EVO2r% zY7fEqvoZA16v)I`%TnKz`TWn6%21ZCYn5-NtbIRcb8`9&8oV|^K39h0Vy)Tj5+2YU z-V@m&BL&aAZX3)Hv!h6u4m+(wUss3qkSl+^iL|5HUt1dR7*FXeEqBCcG$^dGZcisA zkJA*t=aoTaSHX^vzemTJ1t&hTQ_$~IK7WF25swBL#X*&tDE4nFHX+NQ14s9tFoWW#gbu=#>g|rPl9z+l`Egwv;6-|N^hkIKbM{%1~H2pMnD>AFKSQf2RO!m%q1WJHI5A4Z_qtU*oH8ydw?(K zJgPJ)d7$Lrf1osCBkkEh^;NO-lm-Gu1Se4}qg_gca|rVsS`U^XhFoCioS>Q$X_!7UR144NZx{U27^zNZtl=oqQLm;%7B3-3SaIp-b5#qAI8thp^qrCEHnV% zH|bX5wI46$-zM+DsmIDpohWnzd-v>7Ib;FgOh;8&fD4e@}w4maFx zBWe`H!Jo(oz#bSb&D*#UB}W&CtoHg}3j^36^_YVtb3{u7b;Dd{W*cvN0-1R4NBS}r zEVSOFOFE*-624wgo=BN{yTgD0NMN$eP=WC^bR(JSN0H_(p3}lF#6uWO%13jRwI>|r zf6sRg4|L|}{XG~O7*-!;7|qOYqE`3Ee)qyA;{lzgzrl{TL(ra~XDjedB#DqKi8Yf6 zoa-iwW6C6qZ;EX*qS6j+E5_05mgAmvb_ut8GuO7{pKW?xwVJt&1p0ytU_U)g@KzEW zCvO;9b346EZslvD7b}Nr3gfhCIk3(8NAViGZacK=d{|ZfJR586Kt2WG;%k9u!%kuh z@%@*mHb>>LSY?V`2fMke6W;*c2_TmL(UYE+{fWuHThT82!R&VRvGu0e5=|9 z!nO@flU3%icZo^2U?{26jf|AmOYgWmAM?Uzrzuqg8H=l^?9a`OrLAr52J!iF zS{BLJ+YlrG795WYaq4kPrzJB}ciHCX*;`!C(yk5)Z`E@r=_L;RG@tl?VvNEw6Ql5& zXgrzf%PW+_hp9rUo4a0fA^lvry0owY;?Yv8#QYx3=Kf-getW<3L(4MF4uCkJOd9tx z0hRtnqIneBqkF$CrsiX<;iiHdeOpp`ThRxfqQJ@k<#xfj6>|6n!<1&oFKzulDbC&S z5Wq4L%&rQYIg8voXG+|1yW(_jc3adNPG1D$gp(Rql%_@p+}z!NB=KI!mw9SzI2S(R z1T-2IXlWJ*3N11YF{+u7F8QP;X@Wb7_GLI!AN0LlI*+{CWmn^IH&S)zJtRVE5nVn2 zU+ruGImathUDdg*-@bzGo%b*AdF~@Z5A%Jz3y>sE;fkQZSAzGdM3vJ}yU2!}bxC@j zB-Wjqpk|;r#PQ=CGL45WsNUg2%#@zB(FOXh=b3Nj*>+FRbxhNA@jWoas%K13+|Y0P zfJ!aCbd~0Oh)@oGu`&zvLeX5}(j#^bIHgMfm50TaZ!?X`jNhCpNBA-bLC*FFd|6Az zxO`ca(>awfFK_#l`&W3g6t{4XPl>ADK8H(yqUp)7pjs zgx}LTeJS8m&Y{vCkQe02RiKI&k9=fyZ1u;wcjCkjA`50unPJr%lQ)p4f_isH{d>{pa4}ieK=3lpf!n zIgEqzeBP7<(NK9e^k@A(xN>09U6zU#<6av)@IUOg?YW@^%b=!1Rd!!?7ztZ zf%QpI^;vkPhks>Q9kJzE(G>rr?)e@P3*O;#vf`NaLFK?vPafb;PDMNJWn{7erc5q? zIG6?|;M1;JKTHXKq}3>x2}n7r&)o(!yii-J3u((<#1zk%i#RJ)E^RvQR2&9ITBo&d zV?2+RkL8~T=c~5kkc?B>(jpZg{X@n%m(J;c7*^YPhUyShzcAE;ABNk8hWkMPQG@^- zX4Zl$)%aPEG-asxSC53Ekcq1h090!>!EqQn#w_FH;wrR7JpcEm2RBS;7kpqBAS2E& z!iZNjQ{xFq|T-BtE7yWA|d%r?5mf-wOkY{gHU(?wRK$o(=^Jh+R z%Z~1Nne#GI3V&MvLjgR42^RnCy>aEhj3S=?H% zPkGac(|pNi_+E_54xhSJtUE2JQRO?jTy~7d(`No*PG>YNSPkp0ncz=;7beX!YLA?N z>hu;*oh&Ctp#Ow3IexV-!1HBW_s@-uf3taDn4VbYr&~~r?KpVcEV$+pczcV7Nsm1RzG?rB zTMHJMIA4vi(5qCx&1n)HAJH>Up{d%Ol14VW1jjp28Tg#i*Fw*^!nQB^ zA2x;gJ+FBrlbWLcY4L7Uaob!9o35m_KBRZIT}6{1w=fhW>$U+Swr z+*-w9AQjbFY;{rJ@`75gt6c2z#K{bb$OF%t&$`W0x`Jj+-0`QKrJ2oUYL`g1r0T4H zHJ?K0o^5PNL@6QdmFD)dtOd32Jx*1fn>%!LK1CqD2POp2!;IdpiaU<9oe2d}IrmHg z(Jh2VjSjcp55Y{`|#}>RnGg-8@NO6}R7C6jw^cqdA1uZ`K3uPF{J*lde`n z@&7EVwOKmrrq_BcBR$92K1eS$;*7g~SoFA_T;HvQAGALjiZ4I3qfB(?OzL-~Z|4q1 zhA%ba0A^hsA1ZC&I`xIqQM{YM#qT#X%^w4;Mep~L_@%Op!)zX`QAO?v0%2+mJFteq zLYN6UPv{v?#qSBcnQMsN-Wij`?g_w|e0~nnIlB+ z8AS=b=V+q*Tj0cMrcYQLPx&Orxx{v7Bl3XbfJqGw2$|h^$9tL2dlv=$pgHD6w&R>Y z=R0*Z@qF*Eytczu6Q#(eSDvQ3$gTo>*)FA|6Azg$(6_xGX3Y7z0&jOrL)%B3YdE7E zZg;E|%1F-~)0QVR-K|#0LM-VR;|NdfeK>`MqnEC-V)z)LhAum=dTv%XY>2m5J75+I zfY)H)FUujR8&zsGa4e0mx_oSgBCvwxl$W`So$KHf`tW#jx=AppW5|cRpe5Xxtt!u(Kt`hJZmFk zWQ2-!vfedSy&kLZY+^uT+2QUSFj=1rNS`}&sh=)d;hFE^v*&m#KU~>DQ-6~`#IEYs zAU^1JlByQdJtf_r0WayDswA&Y5a9YX#VRSZk*#T{;26`iI4l{gu;*s*Z7MPsnToq?s6VWo zm0C2{H2(THdn35O4UsPic_2z=AYArDpm=3(-gi^!U}xACz}c!05=Yavw>lYI_kq;; zvDcqX=K^0e2&AwMkKPeZ`ra7;0P8lUtv5gx!^G3q+I-+kuRu1#1kwj+ijSH?+qH*6 z8&gRkoI>6;lu(}CSBrqvDiNPT%cul8jRyj5z_NC>zb$`AA)F3fG3NWiL@8wMA4aAP z2`tT!Kxr%}e%9K}@*c{m?Z@vp6XcHxZ?Y|fgANyoWRE^#p?qc*x!M2*8}ovk3kh@y zPYA-~pR;mguykFjQ<6?ac^ygYL6+``{NED8&kPbTLhNwI)YyspkkRb$2TXdaCi{F2 zVz9Hf*Z*OX3IE{ct7%gINK~+hX=^dB|6t$|Z@&8)OLb=({v|eISc96LYsbyCmOo6Yv&M z+%&3zMX^9ln7zum84+Rz1G)_1+Ga^%w7>_@XNYl=0YMghH*VlJy@y>e8;!z9A{iz- zbM}#(xmg}^G8v`?d1!iJZfIXT*RFI5z$uxHU!et`5t>nZhyl>jo(Z}0__B*F`tVir zQ~S)K!uj+pd!L_wp9%RlVv2Nze@mm|2YmCP4~U6D;^GY6^(2tE(R$wQ736o@=1plIxAW)) z_qx{qNyPid2cURM$JLhZ$OnPMZuzqaKSwKCF>%2__++Y`nJ>UbMm`tM@!JudS#0o+ z+WMJfF1YCVFMx8@L1y45$zABWG||Q0V+N#Fnh{XA>3*%Sjo-nA zG6!*b8_Wx!F`enpHpM;LVKD*+vDPjH@4$M%R6>-c6F^v4*6lEE^;Tx8oruBVcOuQD zY;-|G6UFBJehZ6-bNLZx%~vAbxJzNWF_!5Utx-VJtqZ;G%;vbuzfvr@27j@~PKOYi zh`^aOx%@BFIDDh95TS*R<3GJqsf0%Ou~k?71y$-oada&WUQFZX{Orj{O7#=0gX%9i zf?~S9K>+_Th&S|U_Hll4boueZ@2unTye-kFH@K#}lcrO$RCVnA*oufJOG6741sR%5 zwQXHZAA~DWZfV)L9pj`tVaEjUjK4iiitXWyvM#&cZ0<-@x~ZW0Pq)6O^P!=VWwBSm z%vh?5xFq!clKa3#>ZG`#dSmaop9_y;EEH=kmIQbvTVmlUZL?j97VE3eg%>cAiRgtq zvRG_Nt0gQxU7g)&H7`~V$iEr8kJmnWS9)fxRbRe9TM_CkDEP7o#dn_@pTD_J-@Ord zZXOiQ8T>9S_)uHg++5g3t=h!7w<0KQ|LN91 zJGH1Yxob3Y593vrWzCcE%U}FFjCOv_gXfDwXAi?7ulz%CQ@Qx@eW4-cvq-)B`Ch!y z=jN4>@(;0TM-Imc-0Rw2dQ$kZ6t9f9vzvh=U*Y z-(8Fu{Dg1hcN}i?6VZ@S$IM}|q&iML0s=HG3vsQrKFFFQruC=v*A=p_%jQ$m2;SHS z%cCxS`)Hs4I0W)DJXRRB0PPV`t7CC>Osrdi&l9 zwGDM$6L}e$Xn!ONW5Yi7mEoG_?r2ja{}7iFpXq9}%@?t!o8(Y`#~;z1ZKxLuLSPZz zF|$4@W0O;r*k4S8T_$#BupB%@gTP^I(oP#YT~4m~bFc4v*#$rj&Z0f;OWwiCzn*C* zfcQP$iKQukDf7({_9uPRP0Kf7T33H8?7hxOW&*vWgASd$KZ>cBPl(JqwYAp>?5*4# zU@oJET-PG6>9;%YE;u(jKw&sxDo1R9h)lU!B8fqeihTrMnxQKQIYJE) zN`rK;2g~Zy_KH&@zy#vLR7?>lbjPoWq(#de5r?LYpXCeU7TtM=vIT3in~48p<_~^< zSU|jSsp@KQw9-*>bki1|>~n9FC5SDlj+-8dmI@-QU%+{(Us%kD%usfw!-CwvhZ7az z7zT{yeTy`zok7*1ZxE?Q(aht3qn~vRNIJI3CFic>lfDlMAjkD5MQ0uEGJY({Of_QQ z(UgZ;tPl;LViDf)|9$f;mVt8$j{v4>hl(b-YP8ar}NMk8|Xn1bL9#UOU z*95VgpJX;Z+nqGFikfjxohn|tC1Gqm8j4*`?(HIFY%R$;#IAVWtf-+?2R*(T7YLBS z^r1{_B$sBs8AN~URGv!8=tB`m#eou#fV3W!vg%8sGsSubM!{$~EC?9%b^VqF65Tt8 zbNKdbB-Y@s4;R|i%fvP_E}Rr$hl+?6+5gz3H7>-UW@()Ud@NV+<|#j%D0a{tuRbvt z(hQY8^UVLT~_65Tuy$QW}?vyackcHz*N5v<*$@hMY+T#T9b2#zF>LC{=IZ z!zL4044G}hRkUSmGYe$YBYp|nBMiDE!LArqV270&j$BIwS~2U!D#~C8izO-dVD2TU z58hF;Ytd+h<1sU0FzQbvlO|RU!ZF`agk6v%mJfz=?NTy}O(p_hLFvL+ zgYoePr;Oj$7iw+1Jc5Im>r+iJ3Abh{tY6j_m_wx=l|ollqJJGnoJ%v+9R;I@_txX_ zr5pWx?KT1(Pj%`QtYA?3dLJm}8A-@24nG1>Pe{!??Y zA`87^d5DkMFD1gMOsD!NVg&1X&wlxieS5>TA@~Xly?`AwQ38DTyb}NXbgI*BDpWB7 z6S66(JVXv0g&)&MtDvvg)wxwk7Oxyn68usWCwy=O~JeV`U#(3mT(aNZg@lOsM4Ko2NZ)*1>J?*M=4E9)! zne-m|6)qlHA_TBqp25ht0@BrHEj{duV~QwRZe9Iwdf7g}8lps2qnjMo1>@lDhM z2o__*=CWL_C-+Sq99n@tUnM|L z7Fx|T?1J{d1Zzs7oy{>nJ0V?16gz!5rK&q2U+1HxlclX54oYHW^;b%}{?B&(oPpV>s7qJ)*r68;ZIwd7~_VyUBs` z0V3@I7&f&lj7oPYEgxF>Y-J|rLkt+)kzVxj`sS8k@0|>Qdab7|oNRPS_4B7U(!m{X|ehrN%4Mt?VK|ys(`py@C z5c^P9KeT)@i{qsyc6a1XbOMXhL|q@WI3LP`+&pP^f2Qk}yk!^B&w0P|+{3Y5T6*E`a% zYF9qphGcZ*F&!T*c~hL{4^ygre#WrM{Y~=!Dq2uwgEFYV_lGuA| zen=bA9_9Byu@O5y>>1LE-}M8$VG+P^?+-3f#bvyvN%lPj-8vvv!eVAlD(wQd&e={qiv>>e_ z?Zg{ZDS~8-#nynP6?*>u!;m(q7PNJiMX1$^;1pKturt{5_OV!*)lkqldD*g3c~jW( z_LR27v^h5V0r-Lc)W=-aXgKlDq(A?Jlj0V^l_2Ghi5P0G%rQ+GjE)y;uA?oK6LwWj z!0TOrmx-Q?6qPt>0K5O#L|{Hv6#q{M>%eJJqIl^Mj+8$Er08-K5Cmzgx#gl1R=vq! zodL;8zfG?LJRG7`litm0EDcQ{@jbui26F2uqyE4tVFd&}x-8My7>CqpA;GN|xkmF! zk19jacK?2oFvC0u8W1enEW$S2NS%RZt*3MDr+GSpi7m|Lro8kp{`H|=_mX_>vG(`n zZnug9*mfHc5C7;KBZKd%7cKcmzOV1l}7q0``z0n8SxEtB*y8!uYfb2Rpq3z$UbRNp*5qu{70ZH`Ih!W zyX%+UD6*ogh5i%&g5w9;QU}~Q0@l5NODCMT+1Tl-!B<1sK?t+T^vZCm?>N0m<%(qE zzTS4(u6$|&zi0(b#+LqdGJ1=48BGR|!R4|5h@q&mGqCt;O2-PB_P}@i=aA(hC!lx? zJoyFs3|8Ts#(kS5tX3ygL_n6Xg>L6JfZ3(j!hO5$hpP~EyM83E-3nfGiQkTV@(+eL zBQK&bK4HQZ`~d`$Z%R0(O&Bfj>JEJmS;RonRbvLmlQF~ z3mCbuM%H~W%pP1V-G?eeL`C4&?a2)95a{3W;F3k&A=toTlilATSipWJSG+@z0i@|> z);pQh>z}5QVA-AoBS6kuM-q@}c&>=p1RCJx9{pw(*7*kn zSzCJX$01flt)bamU26p~)8~nOm$+g>OD$3!x<{ETv^Nlot_G8hKOmR@@F_jx#Yo87 z93q|Ax)X326T}h`{{~O9$dcl18l>ZFn%Yf6AfxaGAybN(MB8*usH_FZo8sd<)nqNn zNZlz5L&<_GEFCGy#ohjTKTNnc{<^R!K!>4?EgdWFuMdw*d^g9NYxn58zk0~3(&w@O z8E+!4?R+!_+hGnL2BE3~vOiuEw6i`i?^RZ83yDU_4Z+O23Ba!m{jBnpC04HnpWmn- z6-#3p1OjT2SXYxJ0}BWkIU1Y)oFB}Arr|_*Cxxcc7PRudg=2rA4(ftVsaEuQ4TnB) zNk56q8KyreC_XX!^~gc=){)IMSiF&1bL@Ei)-&Vifd)6NC?E-zFS+>bahVL8Ij)Uf z5Q6fM6!a@-6(>?w#-t9HqfKi(Fw%?76J}T)&teE+xe!w1{Y7is2W_)OG~00XpeRew zyA~;im=DA&NaOY^47irOX&h&4y|j8_&CkLv9}=L%!qnpA`&=sgfcsqA^j zEOm&Q*bPx*1&DR3l;x7PjPd5Fq;4&#+DX;X_iQOqevr5adzByEI*E1h^^PvkqPrC*#9CKkzkMg|&={L&N@o7V2Sq zrbp|rPVuoB_6(&4>wKVRA-wh5HzU_S=iZ7zP2K^3S|V1TF1@koCVA#h*02qd15-T2 zAYjAu5V)dhlXs;1)+55+$B(sb(>1g&JBBO6vAL&{q2j4Iq{97MD>eHdKkIU;WYc!l zgzoNg zI$I)%$dKf8!V~bi30DoC7=3+N^0NkY@VXF@i%YQ4tkN01;W>~8u?Av-I-E<23BBpo zi3X6QfY=ItJ^z@RicObnPX8nAaEfnXQ`XDUW?BAbzis!A-OQnE)k(q(esRe9UAqrb z6e*8cDkeyARSnT+_y2uStyQERrmY-nOjJbztr)p<%~h%@8$3kH{*2idZ{0##AxBGL zfM_$7D*6Twng8hgt=o92NBh#>)Wen)V}QnAs`3&TQZEh>I%2i*{|#=IYR;&fn>35H zNO0(oYWBhy+%P+s(QvqUp3>LRUV4Oc)Iwgyxb+0s+jnlUm3i5l8_62_I(L}zw>Lp82c z3D!W3m*4VgNO3gs{4bb$xALbAqr4S)F`A$E(s}2<891n1$b=cx-D_=$YU+qJPw8t$ zm)6uzZJTbUCO^kIaYtAD+BOvc@j9P)pFP{mKSGuHlMj=$Cm!W?zmY08oJ(%MP3KmS zu1;Rl?XKf!Q}lhQ8hJQ15Pj!bPEB zThedEh?qPY)ToBt6e+Ls%%LpB3f0)}T2lqLo{E6jtCIc4(bIkgAVuO^6!AJ_{|e+ifM%Zq%P4lt^7P2Ik2>2C32`VPINaL#cn z6VZqA6={Dr#on^wd?m19^_SZ{hUVbODYlyRxtYSh_!4Kg4`E%qF`~IEo{95v^Ds@? zcWWF6pJN_i4>Iu<82Jy{*P3wS2#n$2GiKR=Hq6@h;iH!2S{fSB1V(Z2wT`L=Bhr16 zSf6pDgw&AN6-u$X!Cni`mon$V{wO75E*;ODXSjn3B6jmu;68fmNn6{4Ii=h1Rq4Rx?zFj{2}wSG`TRZ-}QQ%B^>_>3(V*Ek3;J@l;p2 zLfl!9Lm^Eaa2T#VU1Z^a&d~fkno)$D0VfCVPiDjSJ9HMO`a-PDKimzrk-5b3UH1AX zB?`Pre$LB2EjZ(7Ln3zVL1S8;?~=LDj{YFzs0yo{Sub>-L`i$w+vYU z_B%1+4<%l$X@PM;?4FcK624B~FI`uHKq|QKLMUP zcmu3E&?ICdrpw83T-Q0>W&i*E$>^5JY0690%#U~=Go?!Evu2=AtxqwYxN7l2THWrH#}X0FCi$fIaY-qxaassC7>Kw;lw!#f)mF8-ylMRNCyEo*>y*m$Gd>inrY3kdW-_$4#U zlWJ4ivbjKyk^apAYEDg}TD5{bc4_b*uXIPQmW>-VUwyrh}G3legjiTMYC2hQIF zH0Ji8snw=$#MFUwvg1=s)8H&yycQ%T@VM7e1|S}MEj(SF{#9_#Xnd-iSZ4V}o$~Auh9IHR zm@WP9Rqdn}-ttdb`G@I3Ls2?bg)vsZ>Zro&+b5cLMwH5J0!G_Z7m<^TE-0SeCS!g@ zSG1tM^MoYxq7kG=x=cQ|vN;8604_-8R&-TBUGAV3J^G22t$IUZCxGwK<^yYXIVswy z>FYLNO0|tvCt);W>QRq$VTI>}mg@7&x#^3$bc(^kR9K1jq-FDQ>-y=~`U~VAuCty6 zYEjIt>7;HhQ5DW>WRfauJmHnCZBdqnZAGgtdiLTxNk0Z$Z<&&O0BSH=zm#jGo&>_o z{GyKqge&Ggrb*MlzhyJD@~pCKr2i0CDH}wb*Is1kY_#=Nxu%t0ZV*c-fb}U%*{8B? z*66iG!lElF&sLhT0Rvb`nLMI+n5Wb+S}PUMh6YF$;Dw^cNaW@@2DoU>(tIfWm!%tS z&)v_Pi3wATQ%rvdIJuYL+~CYK`CpbUgqbCo6-GvrXdgQmiYJ*Bhg@-bF+psNu-z&S z3{T`d`&|PqstZik-2W1DRohQ_Nj%fojeng^kT;eut`0g+mM^w8-eFd#9w~HJh&EaR z%vbRD#7`R0mS^9d2JH_wo27TtX54vdjo>P z46afa7b9!5@GPF<$g~T0Qc+5(X$Nj0AwF46v^iz>S>Cre_eTsRrIb1?_lrF}`c?Vosec>eF3V#0HXW-q9@()1TkNWWAw>>El$3o>C+Z+?h5x zq%D{fXqDceLR>eYj{QE1Sms*TQFW0^caad;5C4vWK4nbV9vgDTU zx1Ul`OTj^KZGidJh?Hie9uCs91oLS>rxuP$ARu_D23Uf$=Vbg00eepI>$JaRP_xC? z5UxPX`$@CGS>MMC*YSJc?7v$j|F8^9 z$Om^54(d@rUsD>J1Un~$0%plCNl}acu=~!R(;DAdfKSkaQQ>+#m zgzPM=AZQ6`*XL``iB4z)2P8XSX-~9NS0{9XbJ$~Pk6zBmk8SySw!%dqZjg zljwtbQU3+#xrxQC>|SHYz3boEItP$RV6H?HT>VOawbgVJ#a~OB>?C znt&6P;*x>ao*b~p*{50zgCy*QT4yZ1{AUkBH*7%Gh!Jom^b6(X|p4DKin8Ct$P0e3Arn4-}M^jc>V< zS{M4Zrd%clU1&aeGx zO3aRhRP>@H5#UOn=8T^GATgX}W#KH-X^tBw9WDD`v#xhUo$n3`aDlb#J=+Ox_>yM4BjiOaBqu1-&en}gI%?|Mwj3}8*m)yFEAYp1X zjz-(eM*(t6S7(0^NqnQ(C4FT{FaCJ}_t6wx;K-Q{%pY90Sh6-zD<`}eSyQR3kkO|6 zBts7VrK%eu_2cI&vTujiUp(aC!}WA}(h>ip+LYVs!x-uVolpzU%v(o69N4Hl299}y3sG=4K)y7Pp!kHJY{ zNv!Ty#ZAcnBpa&+Gfw$?MVJHs<|sluhjIa%5NwvfSlGaEV{{ojjDL|R@*$4q?rw&9;QH_OZw~wi(f8{~v7Mu1=p1;0ZlvObXkx_r znol-BorZ>K=-JpgSZ9q7eXE{^8UyJG$IEPBLKC}1?(=Q8QYf5eSD;XMignp_|Y^o|WVb<3*tVJhX-n_9cc=(Ld9 zO)qslrUPcP+JkX^sSH6|LueW6hJ&sDA4k^!oJq4qV{B|=V{>C;V{B~O{Nm(`ZQHhO z+uT?i+kW}qR880E(|2ZSrlzK*y3f5wUWgPB8x>8Pz+u&;8hlr*ut7zTBI&@@$#n+6 z~Hw)#X;G=i!1`7|>dE%g%G=(EKHHG!5SRCH-_3{NrJ zk0@*ya7RxkJz6?|^V$XY@nr8&oN&GZkN;L&1Uf_@Xp?$2B|I>fr)y!XZKbi6Isq~8 z7F}IObEg*&PN^TFnw(D4OSj;O7FYl1uvWAi(Y}r(Xi4x{EUhr|!l>TwhS||Ri-)gZ z_v1xFu0feBQybPW2jUe3|Jp%O{6;NbFhxq|%4H+i{T)$3*4D@ig}O#CSDZjhN5ymJ z|7f1*AeL{F#s6T1moQ;CpVTRR@U^W`9)38V<|$P$*eWfYJ5B)+DF9uhDAkQ{Xgc;a zG;a(4#_@5Ci1q@ApV1hCQ$IqKiG>k_)&<~PxGPSf4ggKCnXBN9x|JIS?P)t! zVZy6YzVk8|)b?cKg5@TLA%^Gbk6x6`c@~C0Zz$cBM)=3VmZD^v z)E8<&E9lpWRl3_h(e5s#?k*XUPd9^WnV8y4`dmTNo@g}K$MJUP@Y>bJ@jOXe zf0$yYy)3R)=FxpBv(UQ_ffBn%QLmng&+bM+)bII^-=IW7@p+0fNKfQJRWyMQ1b>|EUL!{d|@C15TvBuf88iIK4LcTk6!1hpZ%Pe zw)zp>{!ZQYWb%BwG=4y<;Am*b+JdBqnC27qcJ41{LR|DYBal7LKga<72VqHlD*fd? zf^mbcQb^dld?fT0Ty%N1I48{|+nooiOl zz-pGv9eqN_jDUmuGq`8J(!nL`*7n*P>kvaq$E+vUUJt9MWzgck8w0Fyypq)Q z;7AAp(e}5UhmPg*v%Q({HumG|jQPOz4L>crSM^ouZb8Rt!e?QCHzS~Sdy(Kgr1zSO z^@gonmKw3S9i1<;J(O=lvo4@&&k#g~Rb)DzdzD6RwZe+}(Yh?4%64$|!5aBUg1{Pp zHmoOCcO*q%{ooPV6>T_^z`c5EgY8KQ9IF0?G`98S-=!aqq`xC+KPwmCuxa&(0kumb z%s1jxE?tX#syTb10(}tuP~T_e5BWX|j!R$A1?Q^eoxrhvOabDf*Jj^x+AGN><^<5c z&Seg$gnypDelv)UGxjfHD7@I5HKaQrk1@_NBj0nDhh`pIzd(5%eKV#G_@sDVq-diQ zsO5rgV?Z=u@{RAAIIcrhdl^%{qere02n{$|h07hnvj?9?{wrlzPwjzgq)~x%1}ywh za?BM83^uHZKTXfaVtRBJd$TO-rtX*JjL< zV*P!kf3Ah@nWa2~4zw9qbzQsrXhGmpa&CA|=*$<%DJiZP0^FaDH6jJSF9GJ6qg{xy=xr-`V*{r3KM%-zxVpc(n)Lll8?4H3)6Sw8T~QXIpj`i>veXUI(Q5AAIw^}W+m$GIJrab9Uu!0_qsK*!4OGubE288$4N;G7<5$C`<@2{6iZ*Rc?X z3i+_j_3F~DqIPBZfe3<|`~{gpJGz~gar~S7By&aLxGH{1m%)hHkBIDG_bsdT6|Wl! z6w6u-2rJ-8m))rLKN968t$S>JV7wuta6!<4+uViWQ@c5-uyB3H@h_!R>O8Zchvwa}&c1_RUPJN`O`Bt`4$&#sflru6qg_*U(^^B#F-VN#Pc|jJ>xHH< zcF@B<5_K)oQ-nlYQp-=&Lt8)DJiYV`&pBk?HmSeiwv&8o1mCV{+1maa!uAeQc$c*0 z?Q`3*#;3EX(k6YA=X84A~;`Pmb6y}Ex^&@}T-2R9K5fBAf z;NA00JDuPNtZ$6B|F-;izOm)|1phAAR`#$@Fg#$(c%f&=Z4wJC{thmh5J^)j@UEOUaNT$(9cOdp;HUNm z<7`W+p6f+=B90E8&Olyv{lK3+fRKh>3)vJ1gGUU6;uQp<;@$aD2bU9N@LFg&!>QiTFB z@?wdoS?cmv`UQ1=CbsI~(&dLjwTs!|m=aGI-m)pTdGNp~wBvwFVBH0D)v0O{A@z6B zXqeLESuARrU9dXkK=_XlY@0T>{eH+P3ZnZ`%PAh>dXYIG(OlK$wisO@ngh<2$x})%bl3}P z4SmU-x8QgP`ylr7Q~wzi)&m4XeMfV0KJmqrR?8o3M7Ems1`J)Dq^&j$qfp*z+d$_R~Bt&lxQu$?itU! z1FJ2ts3!WF01~U}XixPpsH<&a5rqnu4I2vb5N?yWo2B5id{^}jvvjd<8>Gcec;0^x z=hvh3{cno+VN^zv^bqjRpBj-9UoA$H}Q!$mG)66)RHyBP~93iyy%KtGSjGZBYK+MXZKRkz}J zLKQKC%+>EV_+I{?U+UQV<-}%x`Tzlh`ySXMt+zkt(*=ZL91f8d1Aeq)u17o>PKe^zhR9PqFnCk%r|R~IxJ`Ea)PA@ zO*VQVjttBk&c2o|14re>-4?t{?~dmt?z4=3mif2oMgJh|-@4@SM=Sn1Y7q{W-C`M+t!fbiwz_YX73bor@KWL$)s`ux9!F^1APu+bQal`Z0{Mm?nA|ADIx5E9h}niQR!0fx?CCvg zbB36!doeq&KYspr|Bbum!e}`oEm}e_RZQR8MBp6DNhq@^2;a#!ZgE|g~Kj724oLF*(8;x z3TFB7Wuvr6s>pzF6u~YkRqB-M=zxhu()Jo^vxg{mKwuiQM^GS~HXx z!dCV=H@Vw?ukz|+3}=hgr73!&_R2bdnJHRRmhr=82GyA%wAeT<|5i#0^5zjDoP{E(;#7TiC zhQtMiI&m%oAS&e$@^$|yx@7JO2H$& z(?N&z?jcBD9R#iB8522xt(Qfz=j+FC5Sjswd12A+dC=}kL$fM+oe(on{`i~bfQ9_F z&?f`G9E_Rxx*&w~Ut#Rn#T>+c--WU9M6(x55IvZ`g^d5Cy-#z)>-fK&ejdsKTHmR$ zb60D>2VI6%dA}99-`tS4I#vGLVl!Bqe3V)-bcd34-bTsg2#SF3iwa7c{8SSk5T?Ev zy4l>*2C4EZSDr88b+P0GG+(^8GKM_0U0?8r5|(n_2`%>UbD6~CBBWcBd!P9ax?~nW zpJ*h3^tTGNn4uSC-hT{Ti^k*_tn);$k2zAHT{^8j7izzPy7~MI!%^L zVQS5<52y%*srN+`Gj%XC<|l%`6#3}B$;e8xTOOfHFg{zZ@;p;6q1mj3kI-Y-eO>WZ z&hk7JD3Qw|#WwrTEN- zpREcX_s6YLW3~l8UV*DIpE$yA4k|2fbHI*4pLeJxBJXEzk;=fFD!0LcC_&T#qR>pZ zUBEN zh(8cKV(Kd4EV`aE*?-M8DHXQ3M=UJ&z+OFd$DS7}s^`I;R&eE)x;*D(uAbdBwLo z;ji>A5^8K@E4u~&xu4R4Ah5O+5$sj%KrJ1*D!{$~Gu@g}xXiAgjk=`x50-&~rlbxo zbyO?`O%w>)6oB# z|B!lS7TMHgMe;3-fH+2QM!4lQkOWOd55q4L1*q6}2O?=G_$^QnJ|Oh1BpZb_EF{uR zP;U7(ZywIC1mqBm&yp6Sk&mTX8Pj_f2jTbYTu#}uTlyjsoF*23)+8>f{!+RANNB9+ z)+*F{FkHt+nBJLEAdUOV_X~SZQJgNI&ntB8Oue^M1rxfEgHIVW!@Bp=>k41n{cac1LwLco0zOB>0a5KB|YDh#9k_wT&Ra?_@2_TnCNwQ zl{hwzvY^N#L{+ImS<$Hm)MFRa2bhl@H{J%GvDeL>+Q-RrKy`@Gv~t7Ct*jV+&%#}w z)=C9^f(%c`{-{ButvW$6Jikx`U2YLPXa9ByP!0uPk5+%Xxu+=#ExQWei2(zD9MhGF zo^|XD5tTgi6oOS<{jJKtP=3nT(Ta_KCH+=It5)ZU_}>_DIt$@l+SW zoF}ClfB7#LZ08ZBQ>0PP+@Y(?>p_iod&|)q@Aoqlq<^Y zYF12tHB}Dz*K?N@dIdjHGdK@BkEF-n+9c4Qyuc27+GCc02RMlqvZ&LNB=rT6DB@_- zeS@O^0L3@3MJa=K36n6f4+&LFWtc!!)YysJ1u)sk5-o?*qeEmr3$2gNo(c& z{w_|B=z+iC)weyL22V&fQ+9>94pr0u>PRhgnLvC5dSK|!OC zEQ#ISoluozb6E3A;7gA3`D)lo@!~9uwE?-qGg>EY?U1+}P>x>+=ij6lQLt29vMTZc zW7%Gsy|+!$a~nMwlgkx%B5iM(S6AkSc#01kwAn#!L+}NHv?KU({Oq$4`~Z(m@2pe8 zyP5ee431HECBSmHUZfCop8sQw%|8Oh(L$Y%S|xbi5#z7agi7R$ExFky+S*$EaCU6j zj&{-cIl=k4j0=18O#12Kl4TB{;~H?X_O!NUE%H0PoMVbV!^FC~-m2Ej8x455U55cQ zv$0!l9VRTnnYh*|EZ{RoRhwnAif_z4i%kSmnm3w?ngfjOv+-X}y+WzX<7CFG*2Z`L z)VLnlUVK)JsvkX{!WBqO{|7EIPml5bFeO6(3l?QcGL&3$*Zy^o#24dfX6w;R=2uM+hYNl$w-!# z`C=qv-(>FYy3T`*Wb`0&qOi3e1{>29n|Y{5|Q zmk^hmVpxs;vES|x)}6Y5l3z_zJZHtzq|5t$NKHX}8A%dI|M8$sIj42BOCZhwVUdbu zW}b(f|8W%0iF6-?o!0&RF3A98E@j5pj#vsK0@rhEKyT4GY%p*ZmLGW=-S14VKsR_J z**Gk!{{-1p=HZz@B^FSLxZ_1(hdqq>o$q6FqgFT>5hZ|K4?)OFA)L$cL?RYvrSt#j z+U3f=X!v>M>D6r`CZROUj_U5W0cSlh`;u#etx2{G3+|MKv|F)F@8SsLbQRo@gYOhS zJ+SA8uZ#HuUZZ<@Y~L@FHa)`hs9RPru!d0A!PG4DO@4wH5{;~ieRHs^_T0d4bE*HJ zQzECU(YM%?DC7<{)Em_(Mfw9zPF-W+<=1d^Y&(0VXCA`ohBzMvbD7luY(hFGlK%O) zP$%y6^q2HpA2VaI+WxXzqfe6RLVdGF#=_hpp_NVeyyEC{ja#L=J56=8?LFb%S`E?T*M)3VL{dr0cM!%R|=@axN#W>+lQk;V$`dRUMs@_(hl=kIAD!Z$ue~ zACRsof_!6S-M|NN=RrC!z&GgDgY%MDMkV^XEktcz=&Jyn#TOGQ+2M)A{^J}2_)rb- z-dZ&0G&Y<Ja`YL7u*zIPiAVLa@u#`W(M z`?40x*Was|_V)qe-M@uzNuZqf{AV^S)&6Hi$NS8~HJ(askmO1D^b1>nBH^q#HSu**64L?=uvopY}4*iVA{Je-=B_X7yyn;_vg`Uhs^Mdv=Q4xaF*LRlQ$ zCTJnE-B;X_d6@fn}LD$_o{imk43 zYKy&%Ni)9uzmV?qj%kj14Cmfm1=7CSYySboV$VFHR|EFl$LgnumAR zG}{XarROLGXT!Lqb``!nksXHKf!tF2dx;vR_Q1pn{2Ub@Qu1%dD&1r72jUP{wD|-3 zG{7r`nxZsZpBLCGJ2*`b%Zc0<4HKcOgaESsY^5&9UKs{_pS>7Lk8R7~R>;gev!)Yi z$HqU^7o36cp4Xcc2UG*Mr#RME8^C$|})pnzo$mDat+# zbMq(q!WrD6R>BrpQ|lzyLo3`bZ4?_z1e3-ZyC};% z&lPczVH3P`&A<7YBGOJyjd^^_^Oc)eb{euZks6*xjgc<`wX@wrfy-grO5SMR`MQ7R z>GM7`@@F3(1&q`$17I#N(`JECkM?i8IlbNu!jY`b^>K+>Yl^!R0 z$;zIA`8eqr-b~Mk7uZB2e}{4fNOkZ$8N&81Xz|CIL4RIB9kg67=h$@dS~bZNbLgId z4*bRn+punqB-FA6d)t-k-c@#9HY^y{GMgd-ji2Ya=H}`?sJ`7~!tKW2yAqwJim*2%LX{YSC*v@*COX3P>m=POCAjJRAbDdwAOzkM1`wC*WR% zQ=aZs;%kKxMyCe?Oe?Dw`r0}xYo2e%yMIheAic{N7bZaCrNwKFTR?3TH`YC!v;7(b zw~K(k0SqFGTTo?zS=Eik2eDM3sqV?GZm3PmO8KGnOO8Dk6wLx*v+{zIhtZAi_qTfS zR>sININd*}-FI?oqT`{zebrw)0^&1bU(|O6xwZmBYC9PFk#;BMf8r_z%w3|zRgdd8efEH<+-b*jwb zo)-`Lx5zQ5MKji>?yY1k@+(nT4(c7fC-H}vkz=0(J@km@iX;;Q3?yl~GLsKEN@P@G z=s#>efn1;g@s3>{{&CKJbB2EJ>Teq8Q_%ae*LIee1n>`_K%oIr-QumoWR)HP0S1Y2 zGq}=PbhNsWlu^xBiSxOd_Qani`D&%oza{-QG2{p;Z!#>ONd0`yorj=MJ?HrN@AZTm z_N>_N^;lKreLC5;hrUOow$~Gb&4<`tBN}Bxov~K}NplQ5^aK}aEB1{O)7m+644z7y ze;eYXT-LOJ@pD_k;+|C9augqbOKKvT28xP8cK(Pr@2P5CAL=A z>w`8G0=s7&)wkv;)142xZL5}k_grl9QKR&dhPw60Y`|=oZ2aQn}K-|gb9>)@9x;uq_~0Shf_g2 zJd4foKje1La2{_8=Fdm-{9jh@H}grsqXIZY)Kcdxr2x_z*rsZ1+7xUqnzx(A=Yh%`AAW(1<-N#}6bM=u&qOH zE1qrkXmYLDlI^EM>>7e;5ge{=?Xwl_ZTqv8-S@veZH}^Ek0?B^TJacJeRu)f?tSt^)c|y89$KZ+EVQt{>JAn3VxPt`( zhc_jc6{EwjaK4uVV=WH2gby@wh~*#QH7M&GIS)Nz*H1(dd5w^&PSxa|Uv;jsA6}j@ z>|)}VTGWQpDAJjXwYFuTC^NgNruR`VP6+Pk8y zN(>w_11B5%QIlh+M!#2G-UANxM(^(p&LG5SEKw_3m@upAjRe=%8HQ)!4c;4{3ZsL{ zfeaG13ho&y)99PTRttp%;(!SnPeZ3vzeId}1>&BKzY?zGR;E8tFC|4_dL^&U^=KHO z5&jmjfVFXqG>&ir$M?I$tm6&tHJK}Ea@#dq{3@5DUx3DO`?&`P>ah>x{~H7d2&Hq2 z7aJr36<#2W{5SW_u>Lnl^}U1rzY7BD8mB;%GN1z)2t)t@QHOBLoYIDM(^doUui%cw zTWi!?Peku?%12*FgdOtH#?foU6AWb-uE|OGGNrr6QxHzUH>|3HX^cWa$iR3Kb3)CN z6`5H0f*}+(=F!p9&(1=c|7>iWEv(Msj0=#S|H|~d^0Z^856f`bdNRH2IBPTOILh?g zMlO?A047EYxU9rrpvYRa8(P&gbX3foxGubH-j6?jO7LcmRbDq_RvVM-iq+Q{gK?$7 ze?7YJ=-g;iL_oz^XJR@Rmew=ut(;vH^Bi-Y%rv&?e5|j}8mw0vGte3h8i6CMNtD}= zu^t$UD^=e+s*LcKBMzYkj7f21$raO*kj`0d0BtNd5`NsM*>JL^hmJa_P~c7fV_~a< z@Q^uD)F9a;C5A_8v)Q1X10{lyF)}7g3H{vnPHa`&@fZtte58qTVS@# z{c&GWBUPS%vX#&#Tv%?Q$%f%gRue3hi~)ovb7H(|!&R3VK^2jkIBk|zb#AtSItLIW z{qh#K9h4m*SzMeG3a3@FnaYmHy0mBa0$$)*fcjhD{KCseojUF{ zCK;oWSj`=&7$?IT_Uggc#?3ZBfFhSiJH1PeDjQTT<4Evm5&4zN~$8HnEc!$;qe)El_i` zu^gwFU>8Nk!f(3lX?M|S6G3JM!ieeR$USm4(379Ol$aaF1JYbq$^UN?lnTP@<5*1{ zfH;}Jg?mAS*o`FPhB)oe1SdJW=#G|68w-rVbS4OFCap0vEuosh-ycUYru?M<__}4! zjVWh*;&}guwhGJhu6#~L4(XZT7x2#wrCo9qe>JTqb{C<`1$?NgQ6g$x^Hnmbt=c~}%v`J>gt%4=CXYlY-5*NpjN4Zf74{9{II#^S={?Z)p#J+!& z+=T6pE^SB=VRrJIBX`RmF6AU8M3M|Z5~$nA8{MgSc8+9Q+^RlZ{>xI>AaK+EZ@F#1^uImf&%} zc|;A9X!>JBR1_?<>9jfHwz@n%a(8S9VOcD*!ttaZxeO)Xten76ZnOrdR2_Zilf|(q z68)_zRLQG0*8HV-cfKOR!v)NUmz*Ts70o*qRIP4GU;dSzqLFG?Z`4kjF0g01iHN@1M-^}>HzFpsQOa>Ogx%rK&Adzz=^QN2fn%W{4or~G^Wq29V?USR z?idi6b{WNy7^H3N*#TB^G$JZSxtD8|E{kMLC{57>@Rn;_UQD%<_Bk-*=x1)JaZW#X z*=9M`d@R+HDYjKM@LyTzbT_7NG?6>49GlQ2&u%`S08DRE{!-Z4gmtoseXCdq8qp?( zlNuvlEnG(zyeq@c%I`DFslTQhqC9xlVS<&?iChS;R+nSwCaWy~Sf(N<&O%YzFY$zQNa1uNEPqOb4a7;icCw zje822&ID$f3LJD8t{v`%*VP~pb?8zUr^8=>1hOB}MdfS;A(E_si>~f(bDinMqhPVl zLrY;oTVmOf5<7$!#K|kBl-i+!sy>bO>j%<6VUg2VU?L}Yv853^``+Fd4yaXim;S@) zlKwasRVfe;+oT0w)#@*TcwPfV#FE@NRBd{}hchuz1>3%UQiYp(liZJ(zgW@daC?$r zRmh!U6~#25nmw5%;uh0_*L${F__lQk03_3)Gowk-9sY;}LxTAJbcmMy)Lwhi%NAi& zEidd&hjZ(AY7TNL>mnLlA{Z5%(VwY#4EdGtF>8gJN> z?3eq`+Fx7rTIh!r;P|wGWET0DXJnRj0V+0LVqa9h216TIQHZLJBS-wa%Z?k5Z&k;Y z;9G#6)&S^DNmq?KeHI6Z8JS%qylRUXd$u{`3$8FMJSOcS>#B=mq~G9|@}r@N!G~ES zidXp^A4BEU#lL<*4(G6s)(A|MyD-xR4R`vMI=s_0Tsg9q8Iyc_az637TRtJr(JFV~ zO9!;18@Mi&>*FyxYPLh7etVT2dxMtWKD`6SXelOQ#S$~l;9JsmAE-Rxlu;+8JbJKs zAsc8P>I&Qpn|fX8P4!jJS6FkDq(?NT`S9*>A`ygGg+27`YGrq=*NMg=IWvqLT9Q;P zBk3BVd;C%>ge&YE&?-Z;DJPAshUz!8X^YDpa{cdLbG?9}{O}&$h1hnOu6}6}kww5} zdsUYcGh6^AsS8B}IIF=i6mwjwjPV}|VOr-7r&}{a3YG&|(SOVp1#Mz80VODf6W+HU z%&(lL4quC=3Iv%!Sgn{#l>xO@wT9VtX`5>{%Ztg)P1A(B^qKTG2ZEf4T;rxUsTSX2 zFHM`#_R*8MUHOVHS!J~uPQgTxRwUrQ4~Qt2aL&QCAu}s95PTtysRYZp@n+w**~P0z zC+5ntAgi9+UAHSt6`B+le>J--hn8E3`Nq%MS~FybQ>J(T5OKU9oCtmLbo}$3Kk^Ml z-pfaCK8-4U$+C`B%4*;o#SzzT zF`qzeQKQM2yY#Kq+1jUH+REE8(#?m)M$Vlw3c7btmd#el zbWYbpdog5^r>>5wuF^o|(O4JBB*RLBE?L7RfpapQ#_m09JCkPs&g5tFLOYhjSgR+B z)5T*VOv2;m@XuZ%SWrR))Lh^n*_9U_+aaco=hC?nIPP@38Wzk$ z2F?if?cD21ULh>%L)MOOEB&N-*5-W4^wbD(q}~kFWUAZ{V^H@FPWD_-M30<6WQtb zL=iftvWul?1C#wD^ep`xCo{)08QcYP*m!@`Nj%U3Q1N=v=4gDkHBB`fBCwK@vWI3* zdWU5R?F(Ea++4et&klf_(4=S+$5!{0_t^B%4?S|*xg#g?C$O)WA3@gx263i zOXg|UiGo!xRgXZGKg?nI$T5x?8Q6wezdh4B?`C@#llOw`)EN4FO4r8gjL%)59Yv;_ z50KSwoDF@7VqO~9t?kE^0qoY-Ek({@qJU)AUmQ`By@tBKIk+U*X;I0y`TPGM4fH2(s;J*aa{e@=~x{jwxP|jfhT+5zY-T zkr#0cG>?p+Q%azD_!J0>8>>*J!&!uYMXGAc-D*9F|IK|qfB4?LdBEJ3@yw9})F`pWFW-a3@*Gp`SZf@@ymw7Nepd_J8?5b7BY#b;AuE$iyos%4h?KKShQ*hzrKK%u(><3rKGhTsv_ZmG(qH_O-ym zd)Zq!OFM8)F_nlj&KITXfqN_U=j+hoFc~dONsBxm5}IL#jWX7H+y^^Z^(MMjev*`t zI52BABSmhFx?z3zD;|1@`&G^BtK4n96<+yeeHcG!ZrwifmOV5>${KkB%v5Fb$Htc} z@pIG8KaNEW{Ej+2*T;q90hA58%NnFqSg`5WXmlWb@Gw2v|yK~^ordZsv~-|q}Et8NHhF`g29_nspu1BR8tqo#uHTd@f8Q{nLTcX6C(-PLk51? z*^Y#fC=iGsEB?sHwXwcF_{Bfr-q5KEaRz&=H0`LWoc623EJ7Q1Am;mpAWL0B8ndR2 z4!XdH1J3BfdH{t3onOu18p#-kSp@Eb(vI_@R2J))`OFU;PeqiZG~<=DF5psQHW?s0 z7a09gnpD+BIH*K(aK&$fKT%j|C4|08Y+Y&rN(ea8q5~)9Ntu!1syc)NAYthjb)wc} zE&+H|YlQrzO2-~f+fq*d>c_rap^i049Vwt1FYPFXv@FvMu(S=>y%d`3tZ|^x*+=Gg zdaCVtyKfZDp30|0r2%$kmi$YDH8wyT%s(&KEC0HEkIOMm z8;5I7iXgjIM>AoXRPDLc&)c-={#uUMTo3EeQ2(1ZazypNV@TGi^C>?8dAkC(rGD$8 zY*Nf3igPncGgP75JyjXw7Av?7#5YjG+XK8+$+A^-bUQ^dj#37)WsAiDZKpzQ)i05S ztyhR3MV|0w6QCkDyUv@BKWc`42r4ZwXVF0Iizsh8NdIG%WJM6ur&M4 zz-_MiG{>qNZY}lT<}RPn?S2NdHyxp`ui8$Lb!rc9qq7{+y--o}+r{3pI6ZYYY#Q3U z_Ibo#rA28Nk1zOp#%8Rz=|5H)I^aeu+&RXRiJ0etuno{UB2~!OfCku~Wjdd01mUa72S3L_Qw8I8JI=!8 zNbkP_QpH9eLQb8>Es9c1m_>H+PhvKds0EX?F9ttcOPSpzcTpstj!NPMi@Df%7Nvx4 zbxvj#d#|(YU#>4p{GTrZey&sJrd;f|==-W99PIxRq(- z>Q&D|18RiL#=uk3i_AQr;dd0=Nx|yxOyltDWS4}Ei4LXP^79`6i`qk6x?$~pjPe5; z=87t%T47r!Qrw?WAt@@iihnzuq_ zxNSK1dZE$F14qpd@&SLJzx1o6R4Y!ZF0W>bzZ**=;nDCm8e^Kw?&Wd5Q_c3bBa)~a zQ*x|GMbH(&!As9+wc7cJ>wJYL7nPgyksx>SR6FceRW^g(y%{>w@SD*Mm^T8Rj-409^IyY56Al+6LCI{{a`lCjW`JDl&1MnFJqgtx+ zEB7+Dsz#mel;a7x5>sSs^vE%viLJ(P;w-&Y*mVsuF4F4F!=8V#3`97 zC#=&cZCZ5u$*^c@nrRSgCPWf>1p}gKfI!vRz%44RV)%1VNWD&&*}66wp+x#XU4%Ek zg}&ZoC8DRNYg>R%Z_E0W=zLwG=p&e8DPjaX|8==MO{VO=4a@SEM5`4pNS378tVU@! z1f~^SmOK`4zR13nyELMC7NarharW#a>d&YFZlQN#A$)I}-(ml>AueV>fgkE0Fe9KA5eapSktLH1_D;MAIoKCJ6g}kwO70?N12jy3pEhSk>{uX(`dtV1HC{Y`lwSl|M8)N z_vS6)for;#ZOK~~G#>%mObOVbrN9!BHim=`+{UsM>3~@d33OVJ=-%Qo;7*zT?^G-o zAuAh^u&i89C7nX#V6x~Z+iFCbo2?rV5f>RXU1*!>=B@1S=)PQ2m!f7y| zv5{{X0rlo9WFp&1GnDZ9QEJ{NcN4bT7Y6UxQ6QR66b}2W-E4W;;OOhvfqPffiGr+m z@I|2wnl;h!Pmq`?6*Fnt8Xu5pC4VEGTd-Q6a&$2-?Au{YYHyx$E+VA9xg}HvrK8fK zNbfoNWbh`Rx%7YJ=Ge6I1?CRI!?abv{ZsOuE^~f&=R0FIqyi|%9y$;!g*A13Irs%9 z_rEK{{JKmJ%dnIDgF=zZM@uWL?vlf9V*~vYy}o%_0!T=P$FcS+y}Qv@GFPmZ|17(c)tlQ8eLh(+x!;R61HlRl(^D z0JQI$e^mX;Xjiz@GcRa5(rl6gR*84zl*1iU=BgyvLtCbLjF>YxJ_AmSPWycePgDq> zm9Y%Y3;ef_>R$PCAKJ~A6PTQ%MH=|U&4iD1=~j6Y>ZDj>+adnwlovQj5JAg#Mr#X3 zY3T&N(KUf`g@7J$h<^4&FQ#6#jIghVoJ5&elCwHfx zJ)GjcbYV8|R9E{IY)Wh0I5TgLt_ASMh+i)rm-v4$5!r8kv2l)Mfa<5L2{ey4Tw9O*xiT8*XnjwvU&OsEcPopclM1(~9Ar;4;eQT`J^+1GZML5bqMsC(W(c`@d}Zm@ z+xDlZ_LFxB|GviVz2^5C$?Rl`qSe%VGO)!?Dr2c#^Y()0hXJRXLRnXq*#V228 zzxh(4T3^~c-8|(xMHaJByBebLim_ih$jeE!y)cD>1SX4^iM&-pAXN_25HR+Y9GWFT zLqdL@2S1gV3{&=6OauvJ3vypSaWxj^ekvlB#{*?Wfb>DFe>5u75;EAFEcu*;b=r)A z?(Zb;e^GFkw0ej72+7Old$%?BWQwjE^1I>xTIpocc+CWXaA?y*_)S?uRM=g4kgzYm zZVO*=g$juFAlfk(Jk@>plx#Fb)RnB@oiP*`(OQHqL=yM@;|;^rSxuBmRzz9NW`F=_ z&OF?;0yd4V(mWVEpqLNEcmO899%lcy))RaAzaQ85P;XiCa>Dno8kIY);S=)5OTt@TKL05gt%hfB7?B~BYt!Vw6%htzUt~}Gd^UFn{ z%ROfber|yZRoF_iB~T@ezNz9XMBz5hFmPxBxe5%dl|9=#YFMW+1zmdr z6aWF1|3Cm{G1^@L(+mu(2(ca+s2CeU>=V#HFkpv^F{SqaN5ELi>e)b(x?lj8V`eje z3Na$YI9s4%Oh{sLpkmBOVq2hM2zN4bo`H%X+{w%d@lQQa21)ZDs6r$$@gtaGst2HA zNDjQ(02M=W;I#=*F(e0GhXoqgfgt-C7?^J=fNA8AWWEV=HIh$m^+44jLX!E`0;m`V z!XvjfK*czb#9$ubLK3ToxdzF{w_ZRsBZ=LH2_cIqzyuKn-o_H0w>>b_u`u{R#DM;X zYi41_41N}26->>dF<8V3u!t?e6ca;rAUOP4By=zp%D@sKk^^Nzu&Bf07`YTobqZfF z#S}Rp@eh%$CtJVNhh%isB15+%b1u9mLNUbc9FyA3kD@!EI zCx}GI5?KP(jL1$bkuU=h*`6hG3Z})8OHjo?{%18hf+`3WJA)|}bp?yq9V}u`FvX$; Xu!v1z5Qd00z=MmmV#V}=7FI<7zcv2N From b235ca6e4710ea36d65abcb5ec4d2242825dcb2f Mon Sep 17 00:00:00 2001 From: nevstop Date: Wed, 10 Jun 2026 16:07:15 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8Dexample?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Async-Message with Queue.vi | Bin 0 -> 41108 bytes ... 4.vi => Async-Message with User Event.vi} | Bin 64645 -> 64837 bytes .../MassData Support Prototype.vi | Bin 0 -> 52442 bytes .../Register Broadcast(2x parallel).vi | Bin 0 -> 135839 bytes ...gister Broadcast(status and interrupt).vi} | Bin 106001 -> 106280 bytes src/ClientAPI Example/Register Broadcast.vi | Bin 0 -> 40594 bytes .../Sync-Message with MassData.vi | Bin 0 -> 50598 bytes src/ClientAPI Example/Sync-Message.vi | Bin 0 -> 9340 bytes .../TCP Router MassData Support Prototype.vi | Bin 50913 -> 0 bytes .../TCPRouter ClientAPI Example 1.1.vi | Bin 9187 -> 0 bytes .../TCPRouter ClientAPI Example 1.2.vi | Bin 49999 -> 0 bytes .../TCPRouter ClientAPI Example 2.vi | Bin 40201 -> 0 bytes .../TCPRouter ClientAPI Example 3.vi | Bin 40913 -> 0 bytes .../TCPRouter ClientAPI Example 5.vi | Bin 135409 -> 0 bytes .../TCPRouter ClientAPI Examples.lvlib | 16 ++++++++++++++++ src/Server/CSM-TCP-Router(Server).vi | Bin 470166 -> 470166 bytes .../ClientAPI/ASync-Response Queue.vi | Bin 27756 -> 27756 bytes src/_addons/TCP-Router/ClientAPI/Obtain.vi | Bin 28518 -> 28566 bytes .../ClientAPI/_support/ASync-Resp TCP.vi | Bin 30718 -> 30738 bytes 19 files changed, 16 insertions(+) create mode 100644 src/ClientAPI Example/Async-Message with Queue.vi rename src/ClientAPI Example/{TCPRouter ClientAPI Example 4.vi => Async-Message with User Event.vi} (80%) create mode 100644 src/ClientAPI Example/MassData Support Prototype.vi create mode 100644 src/ClientAPI Example/Register Broadcast(2x parallel).vi rename src/ClientAPI Example/{TCPRouter ClientAPI Example 6.vi => Register Broadcast(status and interrupt).vi} (80%) create mode 100644 src/ClientAPI Example/Register Broadcast.vi create mode 100644 src/ClientAPI Example/Sync-Message with MassData.vi create mode 100644 src/ClientAPI Example/Sync-Message.vi delete mode 100644 src/ClientAPI Example/TCP Router MassData Support Prototype.vi delete mode 100644 src/ClientAPI Example/TCPRouter ClientAPI Example 1.1.vi delete mode 100644 src/ClientAPI Example/TCPRouter ClientAPI Example 1.2.vi delete mode 100644 src/ClientAPI Example/TCPRouter ClientAPI Example 2.vi delete mode 100644 src/ClientAPI Example/TCPRouter ClientAPI Example 3.vi delete mode 100644 src/ClientAPI Example/TCPRouter ClientAPI Example 5.vi create mode 100644 src/ClientAPI Example/TCPRouter ClientAPI Examples.lvlib diff --git a/src/ClientAPI Example/Async-Message with Queue.vi b/src/ClientAPI Example/Async-Message with Queue.vi new file mode 100644 index 0000000000000000000000000000000000000000..40b67020534e6c0a2a73916c87b3e3e7cd5be99a GIT binary patch literal 41108 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 literal 0 HcmV?d00001 diff --git a/src/ClientAPI Example/TCPRouter ClientAPI Example 4.vi b/src/ClientAPI Example/Async-Message with User Event.vi similarity index 80% rename from src/ClientAPI Example/TCPRouter ClientAPI Example 4.vi rename to src/ClientAPI Example/Async-Message with User Event.vi index 9140fdebaf9e54f4c928c6c2fb9bd0046c11d7ec..2b45ffd600887d9fbbba78cf2d56a514da524c3b 100644 GIT binary patch delta 10642 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 (WIXLO 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 N{->G{>7Q!C{|EREwL|~_ diff --git a/src/ClientAPI Example/MassData Support Prototype.vi b/src/ClientAPI Example/MassData Support Prototype.vi new file mode 100644 index 0000000000000000000000000000000000000000..c88ec7774dafa7b61edbab0c32b282019321f9fa GIT binary patch literal 52442 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 literal 0 HcmV?d00001 diff --git a/src/ClientAPI Example/Register Broadcast(2x parallel).vi b/src/ClientAPI Example/Register Broadcast(2x parallel).vi new file mode 100644 index 0000000000000000000000000000000000000000..e3bd40b04dc3b4006d14a3f215c85573f69b81d7 GIT binary patch literal 135839 zcmeEv2S60b((sZql9VW{s03jNOOBFrP6A3;mL=$ta}WVR5D`I;3`$TikOT=LN)RL| zK|!J*h)9+s2nhcSpz==6^X}cf@B80JLC@4wS65e8_k`-5sn*fektHF7V5u1>YpBT> zoPb~w3PT|H)({8;3g9#l2sSo=W3!3~SqdQpgpe=`VM{?6zX&f1W?{+01LGC3Lh$lh zB2WT+JY4R!0PlY#unM90ge|OuMPP7#K{$+G$O-|o5VR7630Uw83c*ECNInbre|7r> zMJ)LRd3a%PAt4luAC9mB`kxPNPJ+g3nVONJy|Us z2RB!gGgKCBi?Vl>)>4Mbdm`){(I^)#v^(0?f>{S;ZR-MZ$T&M7ERhHoS2nmO)Dhu~ zK%-G;cEFPO%Jwd<2zw+75DB>fh4_PeXe1^sxCd&1hyzHw8PxFP^U+HKbp1jxlD5O;GyIRiZ)iEpMMZ=kEE zsl_DgU}xuMZ;M1&piw}b2v-zT9f7p5wMVhZ>Z-HjA}lQ(>|GduYG7L$!K9tttT#1+ zZEN()x`2&?V8{TChhU(PC=p%(VM`bvzaSqBxU2%V6r>O@48_Nb;79TxEQI*r5X`S8 z`6Zk1{EAK3+!4;U2zys>lx^GQ8lV;4KV%zxvuz{&Y2`MJg$Wo71T1iaxwV6w0Pu&N z$w(dxb6fM$O2|{Su6!&qIDeI)xi;yM4o)dSIBr7$56_MILxllz<}Q)X$%G-6Oy+2F z@5AF^An?Wrg``S^R22r>ONI172o-;nGlWoukdV$>PF^}e zZfvl7_$WtLZTrCN3#M}$v!QDyW}MYgp`F^6eeYG)MqsWGt3XpKTyBD9OQ$Wm&1KP;EC*isfNPMP?k4st=kK2$4%=fDsOT#MZX*~# z-5)pc%~1lubtQQ_g9C7*g%|YC8Jf52Ne54=^2?Q@f{rdC63MX+?znM5GH|fPDhDRa!yJA+d)S zFXXCSwUp1xV5z(lA5_qu>F-rs+OMb{By32WE*}?iLzfYUIhv93-6XFP*9OM4gXaAg zA|jY3LcYc3wAPi5VK#RQadJA~q=5+s_dE67I%@1GaWb-ymG|aJJiak}<)dNO2I1^S z%%NfAt>Lv}Zv$k?0^Ogu`Cquxupa-AwE)N5A^kA1PYhY8)KKmSOvHj%WR<}62J;|w z%8|47yraw29T zCC{9adq>rDn=9}IUFyh>=UwWooRc3=KF29_NYi^(YkS!-6N~s5_jekx*)1D}n4N!fX@% zYkquh%s-I^Tv;>d@P(JkGDU^@8WL!-Pq198Prh&97=B4S(S#F8rO;;aY zh={SDpluu1Qb*Frck-g?LfAqR^~enrpD62R&Kx{)7|TGhnm@O7nvB*&K)?wrg+anZ4xoReL(A&nCWt=W!GE(mtMBpRpRr8a_ZG3neb6RFkcFe)FU)-7pnXy6}YJ2J>1{qu@zpy|$*e zqG9{vt6q8rhM5I2WSy}op19a)d2Bepzhk4QU;1oYp2<@Q>XB1)w*rRXhoMu?FzWf* zitmf02E|nc63V@(Yb#Z~|H^xQjp@k;(j@R|0SO_;W)t zab}ZOm2(oF2}-1o)RIH`!s8x0n4fw@q`e=VIhR<}(RsvxTPUxgQ{}D#clPN%FJtGd zxY4ERq+^lF19%~CPkQqo^*h)?J8=H|Rb2UA{Etf_y)^hbcH|s%JYx{u5L@D*3hN`K zx9!4Xv~C5lsIrBYWS!40ej2QW>b00E*4}3$F+zu-%+H_Z9o6Wa7G8MLbso*W@SN-J zzDI#r*5@ea@z@bVPs-HsyHjLz4>l@N42@qM=gQOk%!YeUMa)>05@jaf&R>;C-bsw) zyL|3!;k)B&5!|O%2n9^W-lnfrHPJn}+sCT>@u0MMmE;_qS}6f0?V{s4VO<4xOc{H1 z=>x{{+xx@MEI7))U}w}=5S>u7jh=1IUuAF4`@rD`T-M*|!LuJv$WV7oq7Gu5nt}1FKZ#XpIntweY7J#SbAI zG`W}z?>-6mbGtmw4H~1y_lo2Re;``J`z3afFFa7Pig)D<8LdLaxo1Y40fZf;)BU(6#0$xNW7rJk3$)zx< zk}HIoSyu>ESud$jTjME(-h6$Y{7C!rjCT6^F81{HsmiXgdi*FvKqEhs<=8uIf;F?s zIZSjHc|eWlWHoN*J7ZXvnDY0(0_K06fX3~oOti*s8@0_tezT&19V43gCR&Mr4S6c^r1lrcs3+iCC$@2Ry4|LP#;o4d8c6Ba*P!|`lVW9uu z;O6qZfi6&6d#EEC0c>4So~}?U2WLBkD_|o_P)8R>6w=lT@UPHlKmkWrTRU5CKx=>r zYKO3QL!g1uj&80{l&2%g8Ss~pfS0_peoF_G3)J4h6>5pH0-Q3?^oVVjaLc?IE2h>0r)C*Re1iEtY>S70;VMF9JlI~2Xx)Sq@A4+UR$Uv+6|o6*gDVzZ0Ee$ zMJ`Y_XOtt*MSvR*Sjla37y&&4Q$l&VxH^ONf<+X-A>jZR)Di{sBHD!$=tUP>S5c^q ztE;1nC^xsYt*ecj1sBr6j(gjChp9=+aD$Tm#XaAiF!<`811F#))X2dN=s;lXpj{k* z(P9OvwsqQ~zRt}+rP1geT?n;7xPR3J>|2xz(%BXmzrbw3=BW8rZtMJp!N3z6xI(x( zJD^>_lNbtvkq~ZDzn{k-T@J1JV_$hOj`Pp|Uo> z!~Jn6n;!d?lJz(Nx^RkC>c6M<7sva;HQO=vP!4qpstM4@MYydcc zrfjxF8MrPY5v~r-zcCItQL|Na`|`SdA^~0;0N~Ax^YG!r(Cy5fw)#Fbv}3+!bJ}S+ z;3u#QSq^af2?YE++s!TDr^UhiP1S#Q^Uvoq{wr*10#4ik0N?=uj4C^UaV!7Zb}Jv? zVFVNK4{-zXHa7pBJ=-qN2;^-2f%yQ#=IrA$j&J!u;qT=EhOgzHZPmYBc3b|}@|#kA z)bCsUVD(`6e}!+FL$>SRB>ZNx+4^T&`u`?t>u?2tXB~NQI9H zY%wRoz(RJ)a?*O9$rbGr1eH)?g|_BY47frpTd+o(+ltJ|VE*USY1Ci5Lc{n;CaAm zQmyK0s0@3BsN#q(oXYt?RvBC}0X}I%l;t(#a%Ic|D>eb7Kn6Fr-&@hT*}g`gF?zqr z`Q`SxY_;z}dliX4lvt|m#WW;jWSOJ!3Vj^Y7@;LPzUkgw;Miqsz%jO>w+7ei{Qx@Di~qf2V$0ilbOqPY#*?WDdjc>5C`u z-~pp_x*XiBvqBnXO=1p)9*Q;C1M*2947=4Rq{O){9qq|)r>964KYOwV?Z%UG(<$G_ zVublP{GdSklu&}oOFw4{JC0)*`hryj$Y<>`*Y0Z_b{Y1IImFsFfOxqs4%cU+e5+{1 zM@t_qEmm{q;1#}EhQ$;@RG7g$Z;V_4tTF6%_OSyS43y=rV-#U;vqN!)v}&$!7K`4a z{xYi4$P}?ofghQsnA4Eaz@5aBAbH1^=fMMgPqK9y-T)uT0*}EL=h4-O$K^wCadlsp`{Md;DN=>?p-q99Qgt(l$Vfh0 zjsIKoap%y7A8Z`7K7Cg0#65k;=a~M7bmZIH9=Q)*C78Y!a!QC^jxrWlnW>pd#+rA$ zy!vUXMqnr&dHF#pB}1~X6Q=rLJe<7wlR~P>{)Zn7J;zhdbmY#8oEjGQvrKuXV zKvX1s9htE7*2)nbdntmQBNZJ&oYfxAY5zWlql0VC%PZ=Pz@>Zgv*Ggh_fu5huxju+ zv)te#4~?#f@0fNQG~Jh)bXUdJulMz^*_+VlJB{9VJnCnIuA238KJn#$VqB_-DHD3K z;UMlxXD=P38+J|c21s#sw&;Uiw>4+)0cj(^TLyJ#gHj$Lyo zw~2l-!tqTcliR`t`HPY+fovCQW81wayqA0_4+I2ow-4sKe`LJt1G7D)86yDgA;Yn2 zCbknD%5P!ng&ZIZTda(hbqtb@kjGh%V}HkqiquWKz(Baxj{|*{DWxGWA5o((5L|u# zZi3@^sa2$AGw&5M^uCbLCnJItP_+j|tFjP@{>R3f?`1*zETRQTKr?Rco2|C+@xzfp|~-4v3oI? ziaB2(BhsqzEqO(fD05k|F@98&t*daEgjdjk4#8{<(_jxHZ2`WrkXgf4MmEK$KH{7P zO2?Rk@)y0Sgq(3dHDdc z#axe+u(k1e;%L`>_+`|+>l z7nSVK-O;KJaefsaUuW>%R^V76pwp|s!@b4sJxjXT(~88txU{) z()>+Kwvkt%KuVg>s7>lg{+8#7oa-5NEbsfC+;`3;7Hf5*6$!E8bD16xMe9*crzbjI zuM5hMu5E|W@V?*_J;cFee2tjqwXiweU~yU-#);z!I>_^Z|8TZXCN@ zEak1pOVcu}t!G;=MPisnaEr|g)ynW%iUjw7z`JzaSMOiN91Ik(O1zZnbxBmo~KPcQBrc<%hRiD5E7iE-D(9=J{#T$cQ&}g%o#g85e2ftm)&5Ox!0j!XigB zFVHYinRY6EwuwdbJ#HtOFp=_oA-fGrb2M%@ePJCvV})(SSqJB8-(sNz9?e^0bVa&j z2V3l0rw=q!GTIrOF;Bhv(qLKdZRa?Xai`HORLZr}xFApJ35jsILe98Nh zke_D4n|ksj1wS~~+-tgwI4XobChJR1%dF_I7J8oes_d=(-q{=OFBDeRnQ1LtnKV@YMuGa`<~ec;XP%=>r2c#Dtn)mn(~x|iwbHM6|l?sI*l zwP^Z`Xy0WFZgKQeq9taOkI24fs|ZrlQlTr&PE;{Hd$0df?4r<7KhW?*)?% zOusPGq*AHBqr4JoaUteoceT+%1md-mLhU^cZ^;kT%#yraMpR7bf^GvGA>2$WGmqAi zi|W#-aREwQMeg>ejzZM2Gwl!M)|wYBQeFArwB!$QuFKu*DPC}YNnav(?75uw#rIzh z-&g)b{i?m@l{_Xwdu~zr)D^Pv;e2ap+S4ZqIYIDu3vF>>X^KJ z;?i~RyT%WwmZ>lBe$f@U6>Yf!yE<52<#GHm0ldKGjk;@}g_tu@*ZWkns!|+5OEZ)! z{pyX8j}ecLPFp__=HWa?6t;H1x7Y?H_PT1?wXnRvuDid>GU(ly#{+sJ<~H2U8&d0;M;<_ZSMuhay!O0@0YR6+8!MaieSt^Tk)>u>7vA1%a24xL3 zB=ers70{Xvw)?spD(T;F&VoOeKGZ@ z{^;b?hltB7>baLOZ#%VLzuG{Q#@#$wdu`zo#V6)SKi<^GP`Nj9&X>=M-uQyu>-1%+ zQ*c$9s`5(oJ-nRPA#YT_5Zl?vHx_>2@oE;2+K7EANm+q8p?2oW28P#=_(R}f9rUx( zw)emeBOQ28MsCv&1Kul8*57!W7AHW%)!a?(QP4@%-?@F&2P>p&xFBH;^7l2O99~=d~toE-<$K*K90tdi&qVlW z-C!dFP-TvnYa+4`C&P8STZB*#-qrh3G#y}?bGe zi|28C%Ta9lVX=V;drNH>SK@UNx&eUBLdR({@gB z_STn>F}rxF`Pa*ek)ExO{L9hZ^!Sg5sqdUwFqd-Ex>tbc!{NNucIg%A6^)#n-0R1# z`~bn6n(_9WS?0y6f%v*F zIS0>bEv$ZAbX0^~ZfaHSUzs^*q=msfRa--mWOsB}!A6`r{IDg%zRPAa0-dR?o+;t` zQIA-{k;{B9hA+p?mPjbj6`LCR-J9h=go`(Ovhu$Ke zrjm0cy+46Gl^k-v^fBbzRoxfW!{i4Z@@Zg}c%+j@e3Z#62_AcIihs_yhUDVtk@b;? zs@cpEH?9GzG)^P=l&CWXolu;DsgzW%K@(i6dl^nw7lHydE>IL_lHQ0tB9n#0^Eqyb zP*bqMS@bzsXOV=OvGE#x6~)p$cCb!Nb;G2|TWzjeF>=5rQ?U}=gV$+W2!wwS*n?L9 z5)EZDW#C5xc}rVhY99jO>wrT10XlF&AeG_)=8873!3qAYiv)zPkXML@N0^64VDoob z4C?)AzzeWY7O1{G@SBt+@MK_0=?04bKR#t!ezm?Ut=6}27ZvwZ>^6l_%M>gdHR{Dov zOZ|UHI3RXU{?Bc@XP-U${M-UR&)pk8d-nOc1%95pHGca0`+v^%*Kz@|fc&jn@ZVm= zH{pP8|LF6{Uu*kbsXYt)cUa)u#>V>2miPny#J`&=0e5E*m?8pYL;e^**9c=Di5kKS z<@{T(1`qICo&fqS0XzWTFL*OM8?%S+SzylsdlvXp7TEQI^`|8KoEeb+8-^b#zhU@O zhg;JAWoy+8v1EY;d>U?v%vq91-@UkegWGz z2>b>M1wM<*2lkG@zI6K-qyxwMJ_0_>G9U=RAyPp4B_L5pIa>ptWo)`LE|$P=y#V~w zy#W~jg8?=DihBcoD)`I)f1w{B8@D!Y{mMV6#$SLP@&O536>YWX8~zRX3*vXm1mtWT z{4~pn_|Be-_m^Ra3}ta zVGr4}z@7#6EU;&RJq!HDEbz;&3nmnJ8;l1?z%Mr79z6bg0B<`I&i3y%e!-Bri_7^)rK>k1V znc|-dyAA~3(NN}6GvETEym9F|xH%(Hn)YZfuwmcy$A%mNb>DXFAlQ&2n{oDm&yFxp z0*t^%M?jn)fD8aDxcPm+bO7Q-`T*ZT>h(HPNRvtA6HRg*F{(bA&TI4t=TY6N3Cn4t z)1Req-t0$QWY;?YX)HAt<0A!&e{%LMMFcCu z75{rY=I-ZH_mx^Ku_}5~g@>3-H>DF`5gb8utvk{XSC}(Ylf&lSSSKO!ZN~jetLv>y zu4nVUe2MpX1jLVZ9d#V+zzxU}bVG-YD{wF{y35YUjuxJKSFtkW@kH>Eak<91V~-18 z`;Ip7oyQu4X$+Fx4@zOqYM?dDCmFV$;1hi{?rn%Nmr1B~jP`DJR!4h1IN{yYbc zrPW?|0%&R9<>njB`QaTJ%$!_VFQt)MLWO1YhG~+a1u% zx7NohI}%Mbh1N+eQdCz_&RQcfT*b*)j?4y!;(1`?f-&;J0jdNc-yEIfkXgtGfSvr! zvHfq31Nt;(F@B zz||8xhcUI}6y(6j^Z;ObswrCn-xY4+;RedGasUSM+yJ8;0ys8+*XzoDtrv?^PHi)4 z7m$S|rJ$t;@`E_Ax`(>sHf|%MAg#8I`zivQ{toK#Uf~=M-e2b2aucmCZT^BZkf|k7JHl0UKS6Lmv0sXP%fM{hF z+jO*omahCZ?r)$aw`~*b7&T=J%WZnLjNCUlkAN@izm{#4k^5Hmoq@96w%xFo<@C0r zp+Rtn)%BIP?SKPSQvTW(IQ#~(dfRp3C@JVk%WTz!1Fku;`Ge@z@vd9Luhh*O$JW8x zk2DOjU1&5v(y+{Tp@BCk5HP;if%SBYv{OHGZnQGT`Gjw++dgM^wvA<%@$&sq;kz!!-u)==*yN8iv({Z`UAxc*f25fcZ`1t0*K6Sq z(tdqCGgY1+ujkAFX4cQ5+S1dT}DbGwzh zapxd-EduSZdHoQxj*Sf+p;QSvrz17CdLjR}aliq-O|W~Oaa#BNvU*~!2+I>6iyN2# z6|~_l{ABd|9FC9QWx(p|*^ne4AX&#A9-+8V&n*Q?&CA87kIO|oQQ>L$Itludk*C>_^4SNM(g zz3fFMYhQo#?$c_@z~{#UODQhX9@Wk;nXG9EU#2^<^;L)7$+GjWG6lznNP@iFLGs0+qO8UGCds1i}mWqaf@TvV z=}*+}u#(F>u^#CvbH6fc9IohLl>FR<%aXZ(;ye;^S-I+iT_^Rvnisa#ST+j0-n|R8 z2>7HFL0sC|D-v-$87{XiDRmUX<`~uqFU<{?_%}3$S#az3j@(?BXLy5c7pKd_MQ>zB zbjzZ?`7MKrTj490y2>EN@gvWIjy+q*woSz8zLGBQbzCO6Rs$cCuP*xL^4oZeVj4N^ zGKI)_xbrFbT*f9N<|8Yl9CUdsb-isPqKQ>d4xt{n*f;^aVgEuOi}Fq4#Fv!(l_ZKO zesMO{C2^@GhZhB}-n`Z@lwUY_yEIQ+%g~pABX3H|?&j=(Q@~80t5@Fu>`ERdw^X{W zxPjk3^6+%4_k_pbu?I9xQF|^@0hlbi&v^Ud8W-k0o~Z$fg7Ce3T$?m z51i?>&q$w^iW`;Ec+}8#*WT`R+M7yiZlwgypeokb#DJM)e26WqOW*C;Nl!ZQ`Vi)u ziuo?rZP?UT7R`!_jP_lP$cd}2G@x5V_PNe7pwGwp*F7>MHZlprbC8~5^D|YI%1eDo z!I;SvrO-iAYn^-`A~nF-3SlCj;Hsrfio{Y6yvNeEnz#^2PsMAZQ>8nHOMXf+Fm~Uu zTjSJsQFpoK;pGzaZH;FIY_k1E&4Y#V@=f1g)3OKI`jx^{CI$$*;|S6y3lxPl3qpt{ zYgyTxqrwuS`yMxcQ60b3eN*^yk41jeq*<#SZ5wxY6@-uTRid zcEV3ZKE}W9X0mwp;Ixa{?8gw7FND^`*ut<=sk&a!qd_( zk9K}eyhJzhNt@JK!VuXpfm(Y*#`(m!Ui6^ zNo=3*c6Z`)#JC6C_&VR}{YHJKh}#_6y4^{ub~#MhGl#6s%HNqWcP?)2GN@~jFDs{= zwlglTeCnsX7}(q`_F=tPi*%S(${}b-JFGwnYlI6@0yA7bTj>>c9P^RdtbV1+<>1#< z4{ayiCS^ZsqMM`oG?|HpE7oEx);82*3VEh4_=N-)q+TiEnND!Xeu6jILM0S1mA)=n z8J8Ww^zqR6%jQ8y*Ahb!UaQ#@7M8@bF2Cvxjyb9U!kG{6KIlyp_6O*`t(+Y-U&AA0 zxum>x9o<`lWy=nKeGQha8!sXF00K?~01hGuqCmn068J1c0N(DwhgnQ8(;Y~}Amuuc zh(M$SNQ58)zT<$iCE%CVP_f~t+4r0oYLF^dRi*%F z-JV>e0dPzLaT^rc(bCpglE4~`u(#$y+SmbfFpmmYuL6xW1FJCutKqV7wL=4R?5$>f zeM|!=|3?9Qru`}4{}eVhc4l*ck~nr!{w1Gd142$t{x1ywk`KHFv;i{g5b&q-H#UA+ z9_;1cl@I6#X76mkztoSvYx{sD19m990|hgH^5B{RzX#j-I|}VUfkI&ZPvMXAf7-dg zMExFMZqMZZPTYS2z$;>Q_Dr5KwnNj89Nf4*b)hgiH;&)d;&wCj41+h;(-ao{F^<6mKfGo(-@^#q7J*sKthU6G9-!>;vZNzah3 zbGZJtA3dDnT1x|nGDagwRiDcE%*O>Wvx--o>ME%ZE2LuX!?3C=F$#V3jIrLD#y8eX zy6!!r6>hwzH8k?dn^djLM^dS{K3zO+w66j#Q6IP~GmEx&)FIIQ$q71&;+&TS#AP0A)QNw*E3zqOy0F+oJhnN`8`<;_{l zkXtq-FTF=>ISj+nh@A}NVT962t&ZWbrHdlsvR>b22L&!89!7)+k$@fED{1JN$8 zm>J{4inB)aR9>hwgfq`2Cdcae`7mFb?;IsL7!evQ^|4TfS8kZ5qKy>8kN~R_GbV8i z^HhcRerj`X?;iY^nV<>`GgGK$poAB!v-kMvhj#66+dL0FQej)ke(jfdR&|Wfmu?E~ zxUyio5QKEWHtT*CoIrd0_|?6CE&MPG_prO* zfYAL}vHKS-1S=M;`QRI9g;U-9;~(Rro>OGpZ|UV=tVX{uD-G>_#}d(oJ`hFrv}yH= zg0Of0Xz8@usim&hGZjOj7h!%>$YP>e=ZkR`)fGI3br)=q=UqelhmTh>UU~8)Eowoe z1n2q}wU9UONt-Vx5IQ_WIo?pn>TeekuDp9SR`(ib-eu*rm8bP46Bh($yG@MLRbJj+ zi{MpGJ07NUtl$#Uwf!PuEGVDz2Pb2@j+wm<%DWVj`$FdgRTtvW^d&psD`_(AEHdqU zq2fk|L*t&0^KW6`vS$to<=Agh$$&W(n2F_Ol9tOn@D(WU2WEsRXt>opZ zv=h3LFHhxIBN|j&UGXL6sOz#|mD4~ul>-Km!|g=Ra<5cRqV}AimUoY!H60u5x$C11 zX?94?@crlT8J(^M-5LGjl3M*BZ6f4tK^ajTjQWL9k>p*Q>ow`u_;R}-m(47Lxm&L8 zhc}#B!@bn_ncYcD@!7nqZxf3XH}l0a?;mkbGCOgLC?)%JPgpx~A1>>eLO40%^Lttx zQ269hQ(ra2U*ruTeQq&NRMd^k7v}IF!ICh?duZ{VF}*AV{Zi)WrKV~R3CCEPy2kN8D%pCWC_^`8C!{VLCJ{fnjRn3LCOZ(TFA)F0X> zfRXP^3c*%`@LTeU2#mq-r#;TOPI++7a+>okoammx9QiVVdS?3R>hQ|F;T8F3j3Id+ zS;Nn>8f9Ri4$@eYkv6{|9Zt!?xJ2J`bfpl_GiTZ0%v&)RtQl_ofTd!bM^BhD!>^@h zQ{F`DTvKfR!kf`}lH9HI_*F4weZEqoJEceOgxy0=kpv6YpL|x7YN?`OpP}2Dz}r;G z+RxXa7S=*@3Z+-;865$w^d(bRPz z!!yMe39F~>b%(7z`ZVOrpFn=MK4gHogTNW7DWD5bR(Vy-nH@iDr&h_xRHNDpT}Yx1Lst z5U~|#Q+CXe+pw}C^aJ#={GmhEcbQ{YDJ38C zprZ8DKl(W7vxqLmsKgl4HqGxBA8DYK;mE!o0IW9Ak3FuB*~6{VT3jYgpR~^g$Jx(@ z02a46=w3kjq^#;Y@n#<*CY7S8DVtn9y=rP@sL zPB@*&VR12qUXXN_LvWzJDWfag;1X-Ji2gOXcE2-E^8IM6k37qKSL`a&?|%5u#C_(* zh9H*Ni|_-E$BuqdsT+PbOcV*F7`W(DKQ{~?c>h82(_&%ST!;p<;u@C-+-7_u@~GnU zA?G9JlOdQ4A*1~^J;w9+lSx?&2w0D)a^$n|6NjBEPFuQlNKO{4Xf1pozJiXq=EQk{ z7a>P?^uhfnPg`>vKh1eie6eX(I}+V499AE6zlkiOC)cTg6OnPUyifBP-k4`=C)Zfn z163=Gq9#v?o{=+dHq;ONrqVlZo47a2mdBU|^L>mtS&A!9`&gv=w!LC!`q^! zf{m;0pWi;Pjhj0mtXtmd;OKra_1=l{wd_+FU1ud(b@rW2A5o1~eJsv)z&E*N8T~q9 zLiBAc_v&4Dvk}?$0pJi5`!ps=qqAf?wc$~4>*F&IYq%_^7 z?}&OlUSEHX7sWkrhs7+`T+oh))$FlJK)*r^cS6}nQ;q(Wt_ICWUnB7(-?Hlrhl>Jl zo~xv@@u1AD02v=U&W+G-Ow1iMdywU^VfOeHUZm!8-N#x}IzE^}L2g|cd{N6eFzetF z8vB$EWb^Tphu@tO3lB{4$U5b5rF^;=Y4W;2wof~=-?KbiqR}+LJ7Q&}YCOpzVMrbvs= z{8;kB&~@$W@>ksL@;OXL|k z9k`xyzpK>pBKA=#s{=`qPVlb%mk+MCHnid1pp;G>d}|Tl`joRxSI(O8;UT1PtA2>2 z(gK3}Y=>-%L8}Wqm+&FW2dDIAX?3zrr zzY|%JTF~YD)UwU6!iGr8R={qB%Tz9v{mtwFn@6li8|O0{{B1bjMM@pfzxo*Nkx8a8 z$Y9}ogvobB^IS%KTFd!ygWE|5F7>ksRm-*O`D@7#PYRlooffufly~7ubZc`fsWwid zUoB5L@WjTy!QRtw+9B4#Hu>qnZXtPs-=m z*S+mVA07#!3uWK)WBXA1kx3Q5~ znV=AVkaO4A$ShxY1;N`$?6G4HJ67Zv+Ow*QR$Z?yo( zFDmwnY+I6dZhx`hFS6~FyoLW_!9QdJ7=DCbFV&pj@)iErq<c>&BN7=K$o(29s3w-w!ejhsr#6&)~%(Q-Pwq^a? z$C>TK4ea-AB`^=<`AzzLiT{#=tze5r10Sz|FR1N#H5+@dXMsHn{E`KLb-#CBNr1y& zD)wtkTf#RszCboGxBnrKzp)BHb3iyKAH=>_@oU=sG1q@oKG=rMR)CHAdjOQTX`h|j zP2%4p|6BillMe|08~Yc~a;pkJ>=yjJ!v7vC>`B?Pz@7#6Eb!lDfv;EGKiuODZpBOn z<0k($fq8!&_Pm<^t|7J8n*VVN{PmOZmrGV{Jo$(Iw^aCtgacytr{}l~?&ht&fo$`NP!_CfRAs@*ok1=2Hwcy^-OltY0#;bB0Jj< z6p4XBeaeYodR2$9;@!K1cBV+?DwX|YO-H;(qCaDu<)b8Pg6CPy%{|O2&k}=7Ywb&| zxH1jBvgV0h6bMhO@#G18nH?8i{i=mxqmbUZl2m=dh2{9Akhb!?mOE_ia>{eWA@P+M z=2aKSju9RBT-K~#Sh`#%)9U{6UeXA=772E;DTTrUk^Ph9QroLfmX<3HxI(o8FU_4w za2dZ|l&Zn8p?s2AOS*oDCAGKg3=6I>?{^!Vi3{8wu?Huf);P+G;#j^NyHWg8&Z3qX7VG;d^{7Z2TQD zwHS89)Z*C@Q)_1&EyEpgw9q@^X!-AmqZP9wjusG8>suTxAg0#0I9jbc;%L459!Cp% zc}E;A;vI3cpgZDd@$ZPEr3AJJym*8A-+}FR{|n#RSKjYse>IL4#?xKOHgD5*#J$1< zzq8(i?{#4E{73^|hu(tkd{|`Lq#g3`H`@x(cf`@c{~A+iM;t8z{Oz(k;%E_k)n!K< zEdtJMz8!J22zH6@M6f-ccG{1?X1nYT+Y^NDLW|smc4HUX;4ZYUZQD@?A={69gpRw= zVs@eB??Nm6kw*Gx7uqg;PWoyWzP=x6)bDnoP3}V5rM)zpH&KxH``Dl%{;@3m*FN9D zM^E-6pUf`0$n2tv%r3gfLbv7lf7eB>6{N8Wq4n@-o|NysRc>yTrf(SRs&wMrWKy%~dibop1L~ zn=|KvZ|i_I+=ZVkg1sL`{wCb3F_}jcHvFL)VBH=An6lquW?}tO%q&c1X)R@_uB)@H zy){(Y+1kwx1;nj|>bf~PIyk!mORod*vVJ*G?Eku;wcByLP)L*ruYj;6jE`TC4+acB z6wCrC#0x|5@gn$yLe>|NP` zalLJuYk+(_0&zDNlrtEK4Bt#c-auDRQ;SK~!OqUj-WG|lK%;>EK)9ly>IkHbtv!lO zR#%-J7h!4XVDG{JR0rGYEts^koAqXI!M1w~-)tNC)5>i%TV`{&`SlC%Y`%5N0poB! z1Y(WxSeV;x#WU;5$0CFCR~edXlOE~dloEvFHWcvi+^9cP7%*q<68W4=7-Gp}jyCtM zY^}tx+x%;AG{+77b#`@{l+p?b9^}Z6kD`Y zPNtA9)6R{l>naRZlir;h&ZQvVXKC`3_ib(!=BZ+fv>DvzHreH<6Se0x*f=kjiaGAH z=cIN${XBP6e~CZ_hiE3clQZ!JyNybVm~hcr*igu)uFRIW7KypV+tZxy)9TB-bOj_f zuxSF#)_XJD0(F<_?HT3==O3uwUJmlX&CB6FI498fcpN^+Zajvg;b*F2NmPQt{#Z4H zMvLg6sN0m^=o5?^25|5kzx*vu8t^ioobG09)_-!Wf_=0Nf!li^@(aetO?+#3-~#bv zh&N-knV_lksU8=K!BoZIy@nGOVZDPEbhdX$mMK-8N?_4-{K99HXG;W&hwcj+J00lJ ziS6i4aThg;!KPq-x=cxF#=|SehC5-Q9Ku3#`KpaCw4O5QYCTcGz7L)+Dm>h?|Rgdz<9(fW7~#{?;N|r zf}7(lYcZ|v3G&TH`)dnM@by!PqiZJ?3#6}}zTdkVm6nn(*{P&+cZ3#&Jmd3n-JGRg zO-JARVRU`>0j5K9YWLJHFPX|Ot%$Inh;+dVuutH&N-KytB=+#)gUSzd*1;NMe^*u+IHl~ah8+slLR7km7u<4`8O zfxDPtL?I)0h9L&;45k8o@ZC`!W;zpSFkxqC{mhuJRH>iab621151PNmJYy_CGqF!S ziF@S^L6F4wqbYV_Q(WOMST4!5qh$B!yzICyr1-rSwJ3IN3bjpz6Ax3^Km_?%-*&a* zDphA;oG3Vzi{b*~e#$kZi2O-L*jTu@(J^6gT3M^D0#m@bf$(%*NMF^m?1+Ll^N6yD zvZiajD}hCaP;F>!ew6_zvtzP-+%^<}OhIwlgu3$26?D>< zj?L*w+LrnD~+jG-fV(oozoVrA*t z_6$R7iU|^$RG~TfZQ9NF76C&A?XK1_k>H~-)jjTh!Nz`6Sr@II&fVxUXPL+!?%ggL zmh|e%GpZ9Kn>t64={wGI61vcc)WqBM^s#W_wdiU;Y?+RRt`eokFE4-Gq;$d7 zu%Qh~Uq$ZohIm0&Y~2SpYZf+3kDHz4cN6!UyOkb3o8|zc>64e1xf1=RFEc|x??rI# z3rP_(=yAgoj27Kz1-`t}{juC-v+a5WqO?XYtCq#wW6$81D<=dFg+^D}o1A-zqjA(G zeL1eEw~tPjQ!uZ&PccW1^Ztb)4+E#H=;^hZc$Nr-5sbhw!^?d1SC79SAMy9UjV?Qg z`F>4!@EE41Eg>rf*9=4}&>DBV!ji5e+cxx$dge6-C8nU_EdTpY>jKo_gQg2lHI7(` zO;I2z@bRTwzSVL$CA84c<)aPrr*@8ZF&r4M>0jzhe<1O+;#4uC0NMhD-K_=8!9;OlrqL*^!y>XF%T&}{qaY4HC{CElVK&|+7N(jxU^GP4?X@RiL#T=833|GBAH__hG`YY7$ zwLc40q9po``3|>V8H{&x6I_}1|2oo(&2SBI>o~-z+qs+meVjTFkq!bO#RgUgz>BTh zC_n^P3#bsZZ=a0#f3D1Rg*^)&UX}VDSUt+!ZOiX+T_-BJKuS^b1DcyXsj> zUamA&YFEc3i8Vz@egs(OIRVjbcYf^*h1u<$V*@j55c>|k$a4z61eKzJ@5 z4sZ@UZ17JUYIs6bOcL2fENA+$@j<8dMILE&#|}S-RhhH zq0Y`=!@%`}y{q$&20BBn?Vt`eFktHfcXt87{%m0`fQ`&S9i1KEX4V$K8p6f~P{6^( z+Sd9qpf!jHXbZD*h1me59b8?YaCZ2fm;CB@bUl~0X{aU4nWcYNje}&M}Tj)3;;t7a{;tx zcd)m%bAd9+NGWq_$t&;X0;9vh2JZfQvNizJ2w*STI_%Ud3+TF&l5}!{dF&*mZCss! zk-T%D2H4IC(M8TsCMUQ9&_%#99}@%BSE%gDl2V+Yq~9z-cOMvhTY>^7pg2_D z-WBLTVC>j9+XJJ;0#t41w1v9^jj}c^v@-DevhwmRam)4K03!a8K#*Yy{{_t)93uD-u44(20N|HI9{pHKbUfY1bda0dh+ z?dwaexHnMm&kFv-1b$0}O~?G8$RG=L3a*lm{5T zm2cdsf4A(e{BPwEQhwI&d;MVbVENyM@0vq)>qiiNw?VYNaYz5(W$he-n4XQSKm&f! zKffvuN&(gTRe3NQ$OjOW{y3}x1Mn9NKsGRde-YOISoHs$99EWtTrhU#JD^pNA*T?= z`2);Q$N(aj1b9BU^NUmqcWD!S*es-U=XU2Ta-JaSP?0jgcD8&ZY=eC=c@WLq9V!F+f!jpsGF zovV3cf?VA3vL1ND9#pT(3_elcY&vlcP7-f+`u&!`TfHO2RaBIUJI*A z)&0#?!7pLetTBZPx$g+70*dFrnKnd0R#oPal!;#jB7hXgC}R7geVvH*wE&IL{$0+2 z+vmK~z9-$)cs@{EiLQ=CNO(1oN~CJFh=h;7HZ%Km8nK9VTI~~?m@QuRvNFewJ7PSl zx~OQWYt`e~`OhUPtz3{`%PsMptf|47X;8C{Tm5o>dgLLEUTv#J{QLQndBwS#=QFKY zy-YswnXJCzUWh^(EP0QHasqo0)cIR0{rrHBuz=S9aH4@1NLkz|Fw-faDc#Ltd+usFu}OokfXhazIW|hh$?WMj!*|^>di?`qv1*6(bC*1Z zuDtl%@%a%dmo4op6NRcMA%aB;akiWOUM2PDv(GanM|C5&t4jg&Io~i1k%3GPk`D<&nBkE`$yyTxXOVEQDcD(3rwQ;qRq$1UoI^{W1wm zKHH)QJzN zZ7vZ!{?rA*7{yj`CuG|k79<^k+O9PD9x0t*bzi!oqSx!IU+yh~rbNY~&)(^|&)&Pp?wlF@S>@bYI#`iv$k#6prz~sFott=5 zam6uA=&hdF@kP`ljW=oJiG?ziQ41?HGBpD@L`V}aPaCXU^dx;HboQ80&MC!bZ8vK@ zy*j7$-g=dgi^fl-nzaNo4xZ@A<~tb@?=h#i65^?bJR5Il~}<)m8kwPCvYnI=G(KZ+g1{|A98db z*(KFulz9k4mv8s8E1EVjE?>=?pW1?P*96w^Ea3#2+jG^u~ z{KW|0P%fv1SLW08Sf0O=p8wLI&%|&#(XyJBYu$r_>ZxkAnRDI=mARm2=rWY4sVZ2~ z@f||*D~)~rcl}IJFejl@77=E>KHK!z!b-;`?_`_^MD|ugYAU!Np;cQx;i%J|l0SZd zVF=p!wV&9zE#r(*J5zQ)M7JMHZPs^~B+c6|b*08i@e!$xHwwDe=%N~qW{&lptNND` za|GUX5Lad;Wc8-ZXX~&&q<&bXS#pdNnNo+<5lN?Eq9#)RFQtK`R5 z>%^bZr-jx)n(|MnDR^oTDn@H09C=TIJ9+D~SPL!_T>GPuoV1$t_2u5K)9$7T1rbuH zPt8fXm4!u9FKNoB-_c?{p(q>{PFGBSq$hO%pTC*)JwEFLdgVnzQPLueSq(ZtNyB9! zDww>+gX8rp3BF=!5+;VdRn%lyYf6=E#LPEq@o<|wYOFF+U(5019K*9UX_T$Jv?|K+ z*!z7wUw=1y$=4`k&-u?P!ym1OKQbSu9wYi1;+8KHZ8_OS+yBtk=5esC4Nt60ywL2R zufav_o)nyZ8~TiiIs*W|P? zTo?p%y70qzD3O}vgQ0^gQAZs zO?KW7)N3#lU+Tp)2)wn%%kd*tZ|&^le(85IL^Y&9UA2l?G=4nQ?1B2m4c@i%$Afp@sTi?17u%0? zlr#4}UY0Wyk+yH^6DyugG<4D5l)tCaR_YC(Iw$Owe391)zun^P&~v>0`}9L->|^3L zu;l%NGYq**6ZKuBbj6FZ$+;d`ck(pZ%cl81LJCdKlxyFp)k9^j>GpIdFo+~_p%}cr zrW}hcd-f9dXQ;Z%-3ljfx|8%Q;|_f^l+&14X^}1ua#C=I)NQSfwkbsD5Ku^JQP>82 z3OD6StWCS$cgxoxUy@f@<6}#@@S^|&T(%2MvRPK`+4QGSo!i)$FTZ722f8DTvh!l4lhM>fhIH(k-<4%8w)b8*<@YP)_{bK8AFk@MDWjwc=JkIaORxGlPP z&l+44sF^4rRCTR(h;^&36MNC{%;ehIb2D{Er6k8I>dPPR_x9Rn8|*l&kLem8GG(U2eBKldR|*Ql#0TS*V;HsY@CKSc zgL!$;tv3zu56HKk{&!^M>&VsmV-3OiWX{aML){g}+KHN>+S3Zh|HtUqOcxgE|TXn=ugCCxWr%w92%O*<9J`WX*ITVCw|bzpoG<1 zOoRDE4#UjJ;8@KXOKi)o!Azq@YrH}Z%FoHhq3h`;XEi+Yd+m(LhX^7JJ;fwPOJpBphtIYlex@1p%>5H3nN3Xr0s_WviXQ*>7T9LWMO!1 ztgghVMTT{a{`D))j}@l(lNuAzteIEYs|mzcFT*dZO)p3ZDP75vT1G|5^1Lf>oSu(_ zlITjLQm?wQW6OlF^sVP7RX<I5ZnYD-kY9I7|Hkv_^5tul zW9lCCmw7*~R+JK-_vj8iYtTjcrT;_AD;1(k0_!**TLhJf*UlP@CvwJ}y=vZ5L`(DG zcq;!jhTM}~_s+%iThCt)4?q4vOu(&fZCW@eZ?Uy~=u@<+W0ywA_O@qCNREYCh34i< z!`=s3RnNJ?W|A&&-=piXyM;P}SNWw&CE|wcM?2F|(!mJBD&4hm#Rm5L6JM~?2KzDC z{foocKR~|J`&xgFH=c|hUJYOsSyy$-iXRTMZC`9)I7z2gmNR^8;PV4h%Sil_fSamK28aa$~)cYA!stH|TRzwm(*;lsrsR*?Y;`xc2;G@sKCfEJIteD^#<0J}4p8 zSbMzcC!DQ)uemrpvvPH#&Yd2;*Jr|N;@X(D1!k5Hg{sZ~f5+tk4R!^ii7JfzP|rU1 zFf+l5h7`dSU)tIW<1H_Xi*u-#TJ7@BWXy!A(kIqsEhov$$_3t&dK;#yvRW^eJ0n`g z@_brQqKE{$^yi*lBlePOD?9Nw_8Ap+;&cU2=rbuk6q z?{R$lu&)GEWCSFQJV};j=7M42=)6L%%#c-xm^J0RqNtBPg>L)kxDk0!Sm-3hRv(%BPv_Cw(cG+h3_!+FA8&dwl=p*i#V!+y{953aB0Y^#uX} z-+KdpX8|J02yoZ}_{Y90X-_Fj;xiKOI;4QzZBRg2mSS$?ra>DSp{d@LDueYpLWJ&# z0?8e0d_5-}O=x!LjS?TW3-|rBvx%VU5pmL=ai~g*@S`iVWfz+7n4tze5c{MpuozE1 zcuLoRMT17}Jb)Ix_V)7vK4iOB`LWTuD8%AT8s`?$vBcAcH||w^shVKMb_W9ST z4u-hG15S(h+-_Jqe9W+e$yQj71=@83j5L7^^4(G)RSoonAJ1W z%tS;U@vL1OES|d_uJ&#{eL_4fIJrEcXpZpsy>5*cjz_W|A~O^z$&Yk)kvDqj!s*3g z+{7NF@p;?|v32YFm@;01a^6>Z&R9UUO={1lfa&pG+iN)gKnXhS1nDyFy;#-&D=beU!vteCh2>JETqWn3a!GVsrBBYW>KIPGqkbD*cb}$Ov@*c*9#dqR z9ZhleW8$|u6tEQwA(ATb7fBG|UX9N^NO9}5Phhl(X=C2T={KrJVj8w`k9(%1Kk zVNoB7#pEcSp4Yqj@B=F>RJ6^Vk?##@1B@p;prP!DVEF02_4>3>tD>@CrqCsgvoD^O z1rzc#@R^;o@?@osWaP5D-avEhgAjSN^u*08VM$wRX`a#2A17oq@ZCQiPb6fGe|y&K zTteXEl1hm8ZLN-)2}06mysF5>mr@DC-%I5c2h6-ZkLhhti+5w1?(0-|^@sFgSB?>j z6gGX?d$%s?_Ce7K7Vaf-j2fYnJWO-E{pp(T_I0AC>G;V}bW&Mn7*|f4!<6N$&{nP( zHkii4m#jRdU*2MPJ#)O_l+w0Q>t*HT*YXh~R_P)X(IONzax?a0y^>aZ6mD3bD+W~X zPeSGuCp~S$YXif`qqyWAyc#82fBA-Lv>D?a1A7K|zJu>{0vBw)QD6^V0e+8%&D#rw z_yBa^f`Bi<1w1R-Mg<@6?_4AxyoKC?ARaas|3UGvd3}Gz!#)7M^@9fwEdGT6hy_}- zE4!H)lT!jAg?j%-lsq5NOl_Rv0u_W891ew}+b zeh%&PYYY53cW3+z4-fyE?eFCRVgdO(x8Q%gitoY!-FENtJB|70wt(2b*YLwqhZgu> zV*zgflX`Dsfb|H{e;TWR<<2PZhzOJo`SSoSjedn|{T$G54d4Oz4zQZp+nB@jp#=^t zaA<+QWPyDzSbs^vubBb)zhn5B@;ipVbhsn!H(QG~h(+6967mb?-)Rd-2Esos|7ZRG zsVaV_KhV;jDZgm`pJx3=bKu|~HU7`q{u}lOwEhtg`u|hL-?84MJz+=xe|8A%?V}z0 z|Fio4PX6A*!}OsA4lVHCWq}`8tplWvi~_&GLV?fX@_{|@y&X1y6N5Mg>A*3FcZ~tQ zAP%G<5OeZ0wVB>4Y&u7|CxZXi-fcLyUm{o#nG|_u=)TrHUM)Sf`o~HTLjDep%+Cfku zbO@Y%;F~98Lx7P9hyZ>Ma0md&fSW%K$o2qUI{g~@RBrup zwDN{8MhqA9FMO6jdvg?agIUW4JBr0?f_UrBrf*{GJv0(cgDIC4WwkZswVA|6NbiXl zd=lW|JC6bZiNX|4I%qSSH^jW zY?s0C+Ly1LG%j9wTU#-g$^raXmuZL5UUc6q0au&gSvgiJYB%X6>FGl6$%>8fOV0($ z4a!u#St<)(c}_R;`lF1ptBw*pzIKl`tC?IcA8!=nrCzg4&T)3VS2@Iv-8wDgkM8)T zE(dWDYk6QXU|AbP9#e~Vcls3F>pt1UtjwxMpJ`7KxpnHPJ6nC!QG6E#B@(g=BE~2l zC$`rpN?OO$=}a-ippJgX!z%Zi&gewCGN(|H>u2Cm;J#*8vFUlbv zIXNYA*W%Xn+sdp@4GbPz@oUsyS>)I}o5(F+K;&GY%pU8kUt-{N_feL?{p5_YP>R#i zvR`f&|0_O@cF?pSC~ZG{8KI17D`+bNHUI+3r#*g+F!%U1qT1uvh)YIGK?Pt2 zu2ZOceH_{B@p0s{$H&o~JwA>Azeb?XDKMY{evQ8SIO^Qv<7je^kE8V;K910E_xL!1 z?(uQNx5vlPnLR#^&VjXncUo}&2SM)}eGlK}&$NU3I6^V`xh#s+E^UubB9yv)`1bLE zgZk-bKD3~p<>8d?()Rc`!u?k79v?@zp1XW|d>rA1?$Y-7IKoZdrS0)?gqy!h+hbGQ zecFM$Pdjioe=duMx(^My5ADuAG{gl5r2ptQynX5=-W~6I`G~*SZ%!26ihbFTRZTFnr+ct)M#>9}moe-4r0O^9!27 zg-yBnxZu2Sgx@12zMrq>vYx;-8rez>s?Ai`FDpW5VCG4BbM;@YzHO#ZxB7#XBb<(1U|c zB2bIMg>T|u0)af-q-8*e+T%+{-4x+V2WcE~8pvt}sv`k?>{Ov{`$EnXKt3G7Ku5=q zIVP3x0VTqsF7+bz|_-v%630I$(7Z}_)HqAK6=Z+Al(OZ!=9SseY zP*G-P6Q42_wXl=*KcDBl_->em7q2eL*nZ3)6q^?_|BWG zBi|W>)ESysaF6ofP;;7C%N?VE$iHF*L0EtQV20`{$be@7&_}?z!4153hwwjRtZb-r z?DX0yvg^4k7pq>Iw+R%6$WWl)ih=w0(@qfOI>eJVZPs#`!0~R_l%fTfklN#%&%?7C z^5ZP2%{5YmAeU6<4Q+dIrrBG?L0ESHIjF`CK`& zcF%e7QjHV4RMw)HC(Q%trs@-u(#(7tZT(t3m<>&r{jPLwuQI(CVP5*^eUaiDK5`lw3ckym)Mgz^i#=|0SzB`bBUT9{D&FX7+t<85NwEmGD#Qv?r*v~{ z9$S{-?!S>0`s`S+@O5SQxj_;oDx3R+%kNHfVG6IPo6Qc8RY6yR+}M>R$_TByEgIF! zOZ&zA$a(s++6B_q7rNty9E(mxkZ3jQlH~Q?7CYm0#sJ5a`PK3aS ztNMvmE7aqPXUAyOJC3bKP245qc(hqWNW|l@GQJsV6^E%}+@u<)b%P^)(fWnG63w&m zyeN-hG}l|Mt~k#`Q{_E3I#KK37$jSgm$c&vZ@H+hRx;6eO0JSkq9l~Mqm*kYaK1Ed znWz|`7XRFj&XE2I&PEp)rcnLPwvX&cZHKibij^Gq<-t$&FrIk{tmqW4n_|&iY0lZ^ z#G3NK%T!;nLzX9seVSW>O*wT2r!qc$qe&M-%OSDI4u=WuZoGx+Yp(tJ{Dv8^q7oBsa(WI9;b7%kEmPqY3Vlb zSt;@u8V0+jMB=KUtbzkPr)IHunvZ@OVo=Dyjq4%itHP7NcQx9orZ_sW_~eSf?Tow4 zUy47ta%F(wizEr9DSFDTs($G*l*^saZ03WiHcc1xhhNdUX|o@!8onM z>EN|!X$RevPY==ChgdWOojnrW2-B@b3MgiBMv~dwZ5j`!TTHf51 zV`uv+ycllSCoA%*aVhIUBumS65R z2~iIirE`u_y}k3EaZFLH9&}qZ43Vw{%C@ik|jgesz|zs*mSfL?vdDtI>+r@kM9l z5AOq=x3DdrqT;HR3beq=>kLb%72MkH)Q$^XA2c*`#8B5Mi*#zoU89&<)xftD(=+Rx zgKxeeV0&)Rq>+8`wMOn7(Y@0z9!|-NjG%=|l;T%XIA5O8d9Uh znW)&jW4gJma;K1M@%q)kfP%!E#axTA_V=G-%)cNJ^j%2(DqaslXl0W0g7l#B(6yL;F%eslJ#2BJTW{Gho zDC3nsAzgQSS~q1=KXYpLLRJ4v6Xq?LZwnud`OKsS)~0dQGO|rSp>M7Gd<}YEWo0qW zI~#O4I&KVT2Xe4aU5rmZanSbW_Vx%8%hLSPc<|!s2!r!bRgN=sa8;TpYj!9}KuV^A%K(1|I)ip1<#dW#`6=4NgG72LZqW5eygEmtRCQSZ{Yg<)JOs=HtVV#sV6_r5a2uEh;B5nRtO3q*EP#2=H^(HG ztO4&2pl1-+H@6|cOXr|l5PNe33*dqR^a=poof91c(#Tk%R&W~!b89DYEK3`hoh65v zl`TLA^GJZHq>YU+SdB4Q4TqJBtqnj&-D%dhX&ONJKN`R}?Joo7FT?is-fUJ-66;>d zZ}M5UA%uj4zhU@I{x&eh1{wAU_{;g*+rKOi_VORf2lNB8_cq`+^<(baKEP$b9)~0=u_a2!fyVaLVJ=xAu#`!;m`Ab*}1=bE(03l|3X0CeUkqNxc^va5D2Z4d%}W& zy$$6Qxp#+K&1;1jdX+dmmXGwO0=pwln{^w&nHK!CUS^whY7VTRjIMLEXRivMN-}(A zZA-6w$4#4Jz(6CJHRJjA%1ewVDxkn?L5Ipx6~#Vp^JlKqqVTq$%Co1=)eIXbgRXn3Ez)VXT^}P&I!s zo0w;~vsLxWO4=z7te5LLIklp{>c&)eW-3WUv%)T1Xc4%;6Z*`G;$l!c?z4(n`+K#S z!couZPmf|SodWL6^S9!*bXfd&|+{jZRCf-%i z#7&VVYN{ctt2g89C|%uIqK+KFM59?5P%E_OIPcGyVnm^uBp#ONU=uvb`lW=6cWyfC zWCM1>9n)EudE1l$C%Mx`Y$wnFz{lt}Z4gFiZW}2CT z{T+5U@zrI zo>)1ja|S+ron#3GMVCE1y1rQPW7))G-SF&bdjc(?%Ul6!E!?g|7lqbit6y@8YsFk1 z)2e!@of~tRko)KszD%({nSQZhljcwp3qNImj>S2}rnJZA5T)i!IRe)ijW@Pjq^}$z zcsEff?=!FXKDT~+`!4Rw`X@rCKkjUO6hdUzKMSEQ9)u^)V8zbf3b~p{kTBx*j zs98HvZ&+0`IQGEZuu!5LZAP~ytMGip*zD<$oab>=0|E^-dLJ6c6c`omyT92U(kjt) z**fWN@wEo77~o8r`+{ZQRlZe5Xsdw41eyMrQlWW(G7s7FH>pln6^E`$H{!T>zG85E zE=BaJ&cnE0cDzIAOv07d7cCtvZ=G9M$rlpBrP#E=ms@=dn>2BxFc3ozoX+<^vt1=; z>u52mmALuZ_fc$Vpw;4u!B4$Tq|v@f?s^Hul==7bN`qO><8p;oPdVk0dFy)~AwSOc z>Sk^1>m?1j=`6Q3m<|`UnGS7clD~gpycPZIL&eQ(7Km;;ijNPL&xW!{4nsLw_15Bx zM_b|aiD)+0zhZweN-pxQc-!B4-t}D4rE|~6`zPKX{p#oVp6;SU^26g3xYa48uVD|< z=uI4@%VfM)AbDsb7sN!lPrv`FOX$9&Uv-7%1pFb1>^&wp(F0M_#hcbTZ+!39ZrvJE zco5gnu368np<@o0w`VF+c*C8!LU-HAl<}TlN2or!_)KDUW~Ar9C`%g4n~xJgk#scj z-I2F$Yf7Eet-Tq_IC1PkjGmU3%eZ@GFQ$UGBq#GM2XB<_6B*qJt=qS!Ndae ziOw!zFnhU- z;d6FJS6ZV3OL%X#YPEu^`*m~TYMW;lUu3fMGQY?Soh>($=vHvWq$@k#nUs*Q`9kwv z>rtOUj8u%0V|_S7tVeyXbRjR&%V1cuVPgqv5vGX7zW*G+9Fl&)Z{%hUPgg;nUi!-x zy+r#7X0;9i1Ievd`Kr%n8Px3wI^U>B4$h+6x|~Qp+VnZ4{)tV4{~*0c+o&6%?zI}H zz&Q&{DUvv&PuWARV!pXh7r!aKS2%9XgzWaFd3-nlx@Bke^srbHlGNv&n-{l9t*XB= z0#_remF?5p=kf1e&^oQR{*rRV02yWV(-O<-C;Qw)4xZxM6G0s1y|32n2YA}@hTJGQ zZ<_KNuqU1|*U=$T_>#Nb(9kY)lAD3vEpvRfV-Qn6`lyC#9siA+(fK}F%mrt=sTq@u zeNb<$tBRh9qKW=YEx%S^fA2+ePy zFcnVmr1dOWvQe;o4Y_+qt`T*;yLvZA6CA(hnqiqTN1~hXx^Xwh>2mG`%?)hJ3uGI3 z9UNT~PcB4DXHdvKUvj49Ukixh4CDBGo8+T-ho$G!i?y!z99pA41P}N74EJx{m~6eo z+7hix*K=xmhS0FGQ{1tYtoNO0v}bh_VfuQKpJ!L7&ADZ?g|dq;-bOY>58s_^)ojaj z{V>^jq$PjQXPI8?x#w%c4?!wkzLTxDOD9_|IE8j%lWrr;CAX`Tpp&XqdU^rO{@ZiQ zaQXVg^B=uGYf?(solo{m8OYc`wL3PKuVzVGP!{(BXNhMBJ*P3|h#~Ey_o-07_)A&m zE;SlmD$9RF*$-Ph)@@gA)In{Qr?Or7iIb*>Qc2P|uU5M4{8%<-F?JeKS+MQ4plJ~+ z?aoXPTrk}L;MiP6k~hg49wq{iC2sdO~s?n#vE0!>$I z^R8oku{x0(PZgI0Wq|hC6{Lh_^VD}f!+dzl1CKObIgV@mg5TGBkNZo^Z=lkXSdhj? zIP&x#4Lkm&v$+dBnOHJ$bj;M(rH-vjOU9D=*$Fd)PMtvUGoN6bUcJ&UbUU4S9$`OY zc;}e97TqJ*GGLrzk)^SCwy8<(pQ4m z6~2~)GLskbJVLwjvTOXp3~Im5uyz89Lvjyk{N0H8jQ;rqy9W^!i3R>}q6;?Rv z*8H{`9Oq>cncsXMwJK+%Z~2(s>|@0?86iQZbGwq~QaXX^D3z%b9gXLPns-`L$_xKl z-RyYMkYOgl8kufwA9X3*c>xoG3qn>cvd$cFu3fIhH3lh^U&`*0KDY8|wsY58w2!j4 zPN?&`Jz!pNHV~g(KCrK9<0{iR<7cO}2~yfZ8J#=0V;LiRpT+hBdX}r-BA<-K?|*u{ zR3+>~7Y1y8ex6{S?21jb(NMJ;AqiSO+eBul7SHTaL)l!rhOyT+C#Qlc>gvyTva%Rg zqlJz=dv+#ij_nhDJ43GEvPv(_gNV$c=gEnp+_m_32rGIow^;HNzZ<WLk7u5mpw##v z+TOp^vA+5~^yXLVhP0#6!{pIa^pe=vnO55RN468MF!1K5M-#P9Ku8EJMN5&_R<_Q6MH*y0ud z@sYWC5zkzZ$c`!lKa>GuSjP_V*=f#U<7Q?9xMBVFiV_y^-bCxq_{a!Fz{-F1iT+ar z7h-dePxSv}C3%>AXn{iu{0A0ji{1X0{&$o*i144)KX|D><@_!@0kXaGe?Z0l659^* z-YO2L*a6x8Yvg~g1xOrFu>-R0NZz|WV8H{j?UlTfK48H=Wdj&~4hJvQ7VG>i{JBa0 zOzbyF&Y-Ao&HPUM2NV0s|CjXt&&BR9;hTN-&;2t~|9PpON5vuK&;o}R_~$I}W2NxN zDQ^H5*?XPltM`W&U%lTi?M6;ue`F_uc_7d4;vY-=O%AG@wT%t%0Sow&+Tp5Z`!F0@ z;LrjGvH;-wd+(J5I6P3XgE8$0-`?JWY$NafLm>ZP6@cb|a8N#&`ccKfwEJ_e|5Ev2 z8xXAk8}-iwP#(fQd$$PUKO_Hp|9+Pb2>%EB7tnI23P9{m_-BRxGgdg1a%h1=3mjVD zf0qTmU2*?(k1@J~n+#$n|2~3we?J_qYW{Z(sl(R%w_D)vpNt1CS-t)IU;5us;a?IC zh&`15Yug^$=YNQOlD~yt>vNFYZ<3M$=I{Oco%jzX_LYAS4S&t^UBkWde_iBn=YF@( z-87pS^l$nv>Z=h>v9L{-*p1w&eO$)<6J``up3l1vr232D+xadP*p2wdAQ zntxP!OrCn$J%B7!cKHtZ3B`?a9~Rpij=D0`tFbw?PH}8GM4|V%W-qB*QTH`nW^1u% z)_TrdXyw|OIP=&!r!@_np!GUBHRH8f)FYu{9 z#MBhn`A9ygGM;L5N`(=L2(iZ~g=`Qi$KuK5MP`#Lh|#ARj|b0(1qaNHXw}&SUuZTR z^QAs|-tktgUcJZ~o{cF^F7jZ1%Ht5RH`!rca;Hp0n}-|78$Bi8Jr7)>9GWFH7?9R< zw*H7&%O=L6=HZE=#iUWO(}Dj(v9x}|UimiMpg@8T_+mE#*#BR_(%O0R9Kg~#qpYsI zV+fQ{Ag#BX{`3Q53l(z@t`^lETrI9WxLSL$wDk61Y1!<-((>7ZrFCZymKK1k^&LwK zz}3={-O&NHa}So*yFn7xWAIg5S3)VMX_F!pYvF-Bh!P3G4afA?IPubn^wAX%ER{QV;?L&*$hnBn# zZFC>nx3=x+Uu>qI`LG@Kq21YsmcI|JLm9h@;-S)6cPE_CQ?8&XYf5;g44ukXaa)F({ zt1BB=!GQ~b>L7yv9S|gbn2@=k5D3X8004IJ!Fc)EVSL_TSz{HFXoyl@MCA<)hU z75C^T2Fw7< zT)>dH1H3Puy%(&T21%V!eb93{<;R0MdJTUlv`W^J-{*8VIEwsme7QiqPG>4nBoAU9( z0R8xY5yitV%+AHn%L^9*REL>zf7kCXo<~?}rYv>+CtmWz|Moh4M#$(waHtB*%*xsh&IDW;nbCKyU{pYL>^qvVOFFq)A~a*) z)eO^kH}RL1L)Zr$ak>Ia)Nfa_zc`klKf^U5z-2J2cV^CYKIwCB_5apx=}FY(8yHr~DTFIL`i zOus~FY($bb;1s?6wkt7hBkN?|+ocHZ7J=mrs@F=K74Kvno!*!%Jxa=ZjUsuEbE#m) z`K32aPy0vnz;;7|ig~B;wL2^CDBjA+O`Lut@JQ_av!i5{XE;UDZ-kkVg&W}+_YDV@ zhOJCV-7v#v^`}OfIievz(}`%t9{kGn#ww)ixDc#FBuXF)oNr=+_?4#SrlTe%z>zov zf0vF6(qZN>1l}d&4TKgj%QP^sX#jiNRk9-1Ai1vgMnP-gWb8}roDW+DL9{{8+ZiFW zQfK}Ap6EUcO6MbXAV)p>%wa-`gpiP#76_Ud;vY0I^T2B$z;5~Dg*9|86ViM(_a-?##r|VUQKz?Y4q-7(Y5g8FP)4?!~WuOwx7@* zT>L8fzWmkF=hClTSj0K1`l{Vu;ij%pUD)sNBLkmrOn0Iiy%?@sdj4STUP(ilNarG7 zY4}$JOmGWr%d< zWat)@EnEmFivT9J(ZgaUWFqa(*&JTYwsgbM0vcnda5`>k$)13nf(trdI{KzY-%;`0 zC8?_s1*y7L4yOc4H^e5$(muK~)$s}&MO}H1-x0#g**GU+#$SFxGH+C$%QZkO@8M-5 z|Fz_q;91CdHd)<@>;C6&y)LIsjLtY;Y=X;yUfxfnt8&T2DDR#2IRj+DQ50PCw?d|F z4w-tF!nqxt?XzDUU&oBDd$AQGjK%ZhS?SFy=k=Y~S{O^%Itr%THKbp|$}^{O9G{jD zX*pV}3|(_9A$q@2t*4>p^X$9=r*TvRAFA(6h4zt$ib(4gwK8vNz8)*s!-{zmyLSDPOWtoS3Y%4+63`o{de&7z zb?6~#He?hO7qpD{kT(cEqFPgEWh^WQ1>dZ*LCmRSxYc^YXP%bQiI(XR=pXHs6C&>-McrH@$kYU7UpZ1uH+vbG5cD?I;g%^%@mt78+V^ z&{hd~*p^iVwJIx7k*R8k#~pDb!6DNg+#)%eEIi^OSn9pA)`EUp+IkB$EVk*%I#~LF z%tac00^w`S9w@d&$tna3iboAflLKVR2(fcDI~lMCm;&5xBPltr zY#3bWX$VRRicrf4BN0Jf&^*Qd_T+t3j}qJtZ1Hx9kZI>Q`A)@BHl3@hTPD<5pHXeE zMewT4^l(~51}sitvm19_kSW7XjZWq~vEU?`LvmVzm7wUB6zM&$7oEbn>YZ{}6hYe$ zl8@pTkZ08+OWommHM}rIo4P1*ifWYaW^&kDkvi-|RE-4elHx3dqD6 zI9kJDcGN(k^MY&{O8_ZJ5vy#OBX;hY&RJAjKPg_+1(q($Gbwj52cl6IG`mQxY8N(A zj;N6oX=2!xcM@8iTqx3ufY@EU2@7bqNI&WXG@_+`oxAAiqo5v7Kiw0PY?>RNa0Xmb zzL1d^R3>jlTAgKo$8C2p-RPuMWSv^*7xHeF05;sdSv*x%k|HHkyz!SBDbfYWVN-k( zuo1%QQ^t`=TwMuCL7I6l#oS}xeh`-Tc+8#u(2+($w?|RPj3UYC>J@VI4*l>Ct3noD z^--dc7`MFg)oholGv;~gk5M)*w+pr>McZKO(4D=mm^Zin@N)efz7n0s+}cq%TLc&C zbi;Z>*RKk^n}L#Yc7=9`qFW?cl$c8!GKiwg(tk8~(pR;4E#do6mpWi+l;Mh}@b;e$Trof#CGAn&Bs zptI9XrJ<#|6W5jF;$NS&v@d#?R-qk&FRgO!WjlT!3%rIR*6ROJ_ZCof^xXb1?(Xg_ z?i6<`ZpGc*og&5E3&pKKDNvl^uEmNw6qn-e@}JW_&wF3FuY7miweI=?$^Iof$?WXC zXENtZa>jCyqZZ3J3a`f3J?7w~9{01@jEllyWgxHXo3JR&QaLd!^{#0A_49aj)}N=Z}0wlJV6NDkSSzid{=e5P^Z+uBH^{A|W%n;;BU($ZaXUqeIZK zKZZgs7i#0aY9v;YU|iwq?_#OHE%$}5(?ndTDEw{>Gs`cY5j5R9Lr~u6{K)7$p?pnXH~^0 z(%^{dXUcPJ>g~4+X#yb(y$a$iR-yC(k3abEVDq0;ckuJsAsIg0MD(imQA|Ig)2~cv zTR3Fxc?INcwC>B@aIR+&Bt_rg5OrQZ`dFI}Z$J#T6(o>cv#4IF89E=Es{7zY`X*YO zdkt+$K1ixZ77C3i4a^HeZ0G{NV?~Kb@zp~3Of=pnBM$NO1WmL@J*0MPWZsVjhhpAx zx^70I!I}$xd2miMY{o6mFDxa_DgckGeXEpVQ&L-(!FDA7Eo3yrCvRjTc zSw$u}H_A43$DvDqBP&n5(XL?)C;GHbsc&d}v)HWQbxz>gSxd@kukVJN5=#10JPK@B z=$wR*6AQ08tgBF6jYA*svTzX6LPIKti|oT|o8nO;8O4;K#XtF?r5(A5oSb*14)dlA zmD!_iJAa{fA)ueS%4YL(63ZXg8bM%yG2YGGlCRVfs;6UZkNIkXgfWZFd}xVk-G@rp zS8mhbYA5!&!l;GbqKyK=`J!)*4oAz}j6l5$B|;j;yHr(-iUQKu_+7u22Z!e3qSFj^ zRy?$86I_NyHQ3WD$AZ$icfQEvc2=MAFg}T!a(uqyg<1N>ProWTl*m>4!u-21y%m-M zsn{s@tI8|5!aCV0K`)KIOnp(o8K%72V8c(%)8qKyFyRD=v`8ZZN`t}qNT`y~n!b>T zlG34CiJ?{eKg$pGT5NgPh68M?=HUicDut$sx~!!cb^|qFKGc>z=Gvl$7@HK5%(9vdosyp7(>LZ z`U*;(ltZ_SaaA#^{1-nn)N*1`Y$WjcXQl@B+4Ct2BxGOR-*Ew}BeNk6yrJ>xvMp8pDrJmMSkD8q*wg7Jic~j*P_yLxeHUAm zAw}^+GuTi^+oIg>=j#|A#9&ar`_Rg zUY*FRsVI@w;jkuTM5FqMW^mkLx}R)lt-8$tseVCjW>ciQ{I*laqp^@%9RuZ?CVd(x zIkx=`F=}0L8G&k7L=BTxHpWRj!^(M<1Z4O*Jg@Jf-BnR(rx8ED%~VrNJ4BOcHf&9o z-N)rIs-pbxR;p!RbI8p+`m3%@c1O&B^}!ajs*}BGxaL9ORe+1(5vNcj7j5y2OYvFO zXn~5`TLtvxL#{85)x3{5HI`TPIRW$s)i zWF=Z^4um=-a_*kJS#p}e6o9dbpWSVF|H?FBsiZTMLvV}XON4FZ{D+Y7_v}IQHuGFr zikhY|p)zY>a)v_fRQqDQd|x~aU4O7+LA= zD0(#CB-7Y?`f`?tw8WR08cD3nq%xz<@=raB4;K1t1QT0l$=Z-Zup^um%jS(1 zDYS)C>8Y*O?PYu9g;RsR%JvD`jTHlIic0lj1_H!X&ZZq80dCI!Fe|}RFEG}DL z`)hD1F3bDSODGJL?fXi?^H7YJM6mNts#6MXVs|gbTQRGexjFl|Qs8Syt6sNYIiVe=lQggHzf)X+K3h(~1O6mrh=*Kf10-)E=yc%4fr3dC6~vYA_D>y0Wt zI_qI}S90cH?S7>P3r^A^^eIH{%xw1*;t>CiWO#%3@eW9$j&Tl!GstMwJut`jLn%;p8 zhI303Mzq4n+Fy(4t3i3O3J2QOB0>ZwdBOP#k+#+-BHR+UVDlYN{ zTcgjXIy~u)Ht2`g(mR~_{-0|UtF`6d&>K*URwI#d8tzyfM_8pUvrs(5J2?pp?r<0! zUP)a7=ZRH(V)IybGg`)nxqIbYf$+oR64I`wFtkNxwK%*931mUzVF~1)ACY9%Ex+QS z;JzA@K(^sGNMK5@qlXR_zjh_c!RLq+Kxg2>P9SrT?&sAH=d!4*NQ`w06hWmpa?R6= zpB=A?mFXWWJDF%}ypmhTy6-|F&xR$O_o@`!Jk-QTupXlR;nqNL37NzlK=G<^knOUZ zjH&OkoZy>LVIY$i$two!C6jAhlVz1dDZMI+hXb+1}VQ~?+HC`?(s_+9f3r> z@$qP9ZeG^390qCrAEEn`)jkQSbI0bR-%yn%Bh^!P z|DgJi(x?m}F#Z+eK6Z;<8dbBV#_q(>aK*HNlYL!~tN1`ejVbD@fdWsKvC8eDW0WNm z{s=y4a`Y!7sb=0>mcTOOtMEa~_fdFT^LTExvzYl9#s#;Q-RAQX;X=qr*o$GamU2|Z-h~rm;W>}|Df^ZN1J*ugjSV~mBE(DZxOM_33X}oQ zVpkhZ_2G8qZQ_dFFWRBLU8*L1p5>6_Usn!RnCDLa#yZF?C`=?9v0+eVKCaW)03Mc& zC_Sn?Wm(a`P~mx+UK`!s=Xe_P#=Vt7ZU-EhUA>eYO;h|%Szi(&sx@cR8JAbkObxfj z-J2XYt+f}@!*ZUGwdt$uiY_hrSxZ49lHN<+56weS4Lm+t8cWdbG1SZht6UBbAA>(r z96~a>1%5`6YdjF-YAL2HEJtiCW==nU!dMUjquw{5T5Sj5-U8gafpVL%lU8{36~pjX>!qK zHHo5J{V3Vzxu4!;vXfiH&Jo+jAVRnNJmz>7u*@XoGVD+O!}~*w!Ieo z>p2@#!>IQ*KK>gS@NL$;<~Z;|`{n^o_OJ0*$!qI`89bdu$8?9?pX z)V5M~BP7Y#6m(nt<{BZ>ZQZ7|&5*e^b)+cfGaFSuSnD!F4;4)!MqImPUaIL0Jcd=l zE#TOAK`%o)_a_gqh>cC@QRie&v*hkO5)2DtIgY@hZ-z7RqFH;;C*Z2%ZatC5^+k0! z_BO!7^?xe-fMr@8d)=s8n+s}H|QOUwR-3VF1Xv!eVe<1 zvJ({h8kT>z0pjQ=&J&yI8AYqgelBo%YGK=F65K+>a}s=Iyz%shC5MS0)u<~qRMO!L zmc$P*oEl$GhfWy8Y}ZW4sbs;o8CA%tb|)nc>@e=u-WlLh+sxxLb)75bxtS-NkSHI- zT1G4hAM9YV!>HIdg!*IzKp@?6nxvfTHz+rn5Gwjrk^Im-gv^@j zs5l?X%M?l==*#4thIaWp9kK-DmB)&TTfHq0m7Frs*NwJqB}hbH_mS6R?byA z?B3CKIBa#IsiBD3svs##htl-T2UJV6C$Tk-#XKAcf7oe4eKIwCWvlwuFc#&`w8IS7 zz5vTUQ>=k8(lI68apl^7?dn{am+W=%sGyAQ4~Y1bI1OBw*nX42Zg98Yp6&o7!tPEI z6k!whN_RojIkBJ%k2fmI_lN}#v`IonSL!8AR8`S2-oz%Tv=!teDE^Oqdi9yV4zGyRv4@qy6FgwsTJRi1QBP7q&K=ksqspT$S@YOeEs@lQ(c@WJSfJ zaDCLxICMJZLUw70PGMRW@|p47g5Fq8rBX{PgL^5wCA0L~M6ajoKcd=paDk!eg?UOF zzGsQc)?RM?z7)<3|Dz{!4Rda2s8d8AV{T!8$`66h0P8bQ-U)WEuQY!n;(TuEc1KlH z>1lV#*C^9UNp39~A!~E59KDG8t;Jm93E7z+I8*b^sDEM%+6_KP6Oim%*`6cymE|S0 z6276Sdl>j0wW-J{Qdd?w|3OIFM8RD#&pA4*&OlH)cEa;HGXRhqTagF4Wq=`Q1P-~T(VTm#?taP*sgB&rg>5t3N8HPISDqp z9oGD6RdZFGv*^K*2J&WuPktKG;XVnBM0JyvLf-=^Ln5z*c?lKr!wM#^Wy9%I0vA-M z!}mI-M;6> zR{#74{K%yGx$rc4kTeo_0d$X1$XZ@5!kcSyF?eXlOYr-pozABH!XS;I(+~LAM;xSD z3h!AvDa};O5(SE@!xHIsBviIYiWjb5)QZwI-Wp3MBY4d#y&#`hUzbiU{_K>Mr-@MR zmQ*mfAo^OxEXaA(6uFr&%(o_4)hnD@jsJlL1@k0cs^o$%k#2FSK<2rlKqh9&sw7y- z*h0k&W)D;SG60d*apPz8DH6Q0M3Zs##py>uo!)>aWuQCUm5KsbqOEpt>Ew(1CHOWu zuVmUJ4W8V`l{uDX^?K?5g8M(i7)YF}8K!ldn>0Ag{q)LPt1Vhagj!_ff_?G);yuyuTUj6iz zyIi|dK>52UAt8nIP*Q&laj@+*qQ-sonTA z#@C~tAqywL}DaMgNzlDS|FoR4;Vht1}dnq?Vn{V?c>*bw;j1nw?J59blR9{QQ8 zW6l5LikoPPMeqo?M-9cZT<0^q#P^1&0fJ@&cwL!{NbcRk80VuMx}!p8L0JAsBo9P` zMvW>b@6L{=AM`utW-Thm2YC9&Hjz(7ErZPp#ubOv(o<3=0NP z%osf7IzOM~3!j^lZ*M~@FV!OT6zSX05(TXX*kseZS6D5V81IH6zw8Hp(V(frd$FKf z_U+Dp!a^qCxu9bsl80a7r%ijVH};d8OvWO#Pt3$8vAIIo^@oK^`}8-VJ-(|oJ|7&_ zx?tOKSC3qETrwQ(+h36c4@K6gBTawR5TGh8f91t^7p5I}C?b)yrNaIh!HnN0xu*4=%Wd?9NhX{?`G7_$?A0 zC*HfH2>9uE{QPmCwRS!V-gG$uRU7z;U<_z595n+vaaX_69F*SbcQQZQBOVCd ztX!;)lAFWhx$2V}j@{CN2Ypv<*9uXKvTA??%eY?9~pP~bxxq4g(y!}pB*9uJD>GFlAgR<9$AA% zU_N=i?;zg$=HB$M_^NVt{YfSFMizX*zA38Puo(wD=&%kB&-Oad|Ma-4fd{EBgVC^3 zPp(4^!S+@*V4WE{vK=e;&^_wTu(=(m$FKj-;ID&cOM#-)7QAM^^)uZ)VdI`gc-JKBw=nqsSZ*#r=~rMzNuYg z`^ZxkJUDo8)QoX5ft|@S9s&Wky=T8MB*KG1qeG8e!E1}v%oXSg_ zhJ*@s+1pFnXF`jid4KnVDYph6FQJFXpBs?|0>cEt7@q9u{Q3DE?B|wWl0OSu-}-Ym zk~PXz@M;!u=u%G>eFy*tGm~Nkt-t=CzGD#S_nPy6`L4qM@p}lt;Q-0NzzC3jttJPp zDF6HLiw{~y1w817aQjVRO#li!Vu!EfQSNGqb&!-WDrmTA`$?=LF)p@fV$wH z0bl}@{8}jA2LdEO$tVc80)P_KUl9OeKqWN*hy#Ip0MG&~NNfOrh6zap0BjIo2LJ{L zlmNgB1VF!(?*jsz0Qd$1qW~BH0F)*Gh(RC@08}6VS`YdX1b~wmj1&YwI|Zph;2Qu^ zKwuC66d>>e0Kku0f-DgX?Fz*hk1fxrj=v_Rkv05Sl80agQp$$QPzwMnKrmR47zO}T>!iVDkB&GARGXQpq^DhKo|gA z06+!|59S2>2eADUO#caHKmfF%!4U*Nmqk|qpwa<=?N7k*Cm8z^jDr9NP=^TwK<~X| z1przf00IEP&|d*S3IvV;Fa?MK_63NbPC)^v3Pul@7ogC92SdpK@&Y~z9RduS2B2#I zj|ZTwKuiTj#|+fp0DQ0wD31a#uxJEI9f;XL>;YmJPzG2H3a}-r$8X`G(15=9fG7(@ zH=v&ifIk8BkqG1g=^%l3J3tNrBUb#a8A#w*5Y|AQ4uF~h^)`V%J%RW~fCu^kR%(L5 zgRBA2KlY1&L2~}iLpuFINB^Lmzv(ZV1NDD70YHCw4v2RD=KpdsP#%yU)bFoz5o9uu zasm45!~qlui2&pQ3XKR*D1d_86`+Wc00le^Ivk*YK4F*v3g{Gi7ohM!6OcmyZGZVE z&=$G}$O9C?44}ZfFCdZt3OFc)KR{7|`hdQF?QH>U0fr77LJ^>VSNuA=Fai1%$b;x0 zKmnWmIuEe{3OYvs`s=EI3*;GrJU~(X0g4AukdNR4@`1_;0Sb}}L`?xo1W+b`5(AVB zpupImE&)mk(6it4*YyYB!RP>aa)7G+roSAF5|H}<$O9Bv6QEQ81!M+$0muW=D>cv$ z=*$Pv?*IjS48k6uv_J%%LZEBFp8?R}uWWXS^MkX1LV8i1vtAvb0EE@f1v+|_J1;g zD%THi|JeVZodbvXZ}}kke~bA1-}VpU{}F!4_jmGN|8-zs2nzZ;K4?gP6@s9Hg8m%f zukkTF{NnyfDJdy~prQX#;}`$o1jKrvd|;rY1OUK){=a8{1^Yv@;@lZ?g0F!d=T$n^8Zc$|E~SNJ^sJ%A2`Inl>av$ zWRZUnfJzX*e?J1891L7i>DN2Sfc*=q;$mU|@BicaqDGBCCN%T18~bfYC`vIYe>AcE#~5cwRTr0^OUfdDj@BTGw(gRbkS zKtzNC4g;3^dKNSpfxD=@76mK+P6V2MZmzS;t(H_$>l+7iR}Uj+GcYA!Tbe7Af`lrWJ3G@WrdQID534}0c+%n`sx)t666B)- zmGDB2D-Oe??wu)>5iv2G^TyiY&?9ptUec%-UlqdDv2b^Lg@qc03hH|*eHR$LbXHTw zGHTSNfD4S%V!(mxMzj8^Z6ugu!h)B(cqlqqTjZD}uyQS;xOwV{G|5r5cz9y{eTUt4 zG1N?JN(!pFT1YiR$k0}xLQ&BE z<=y*fH%0-ySrNt4E~M`G6Hk7Gux^naSP5mR?~7zH{Mx#jpf?(RmhZWgf(29$57_YK#rMd_bJv_5>RK3JiR zgmBXnQ&S6}l4ePh%iZUN3F=%t5o+X+2W)lg6FYAkI$|EgKf3Y~4uT;?R`bIptwDtb zUMTtcma_;^fB45;jX9T(J6>IrZ|WD3yVsOHg&2iT{-oj|LK)*g?%y~q<;xlEijkxo zd(%>R>dCO>*PdcquY+zdf9O8pV&}a1v$)RADE(8CZp1B9Tig778}pJYFSNk4Tb3@4 zY`@c6u~>cqxAv7PO$KrOGWAyS*V)-``AI76?nkNbZheSE6$Nq>wcJ|uugf2JKkPT< zUW0RqKBtLGOMve3{>oZ_y^8-2?S=o_Udf3frGdg&Av?2II@(lIqPP=%?$8lT1$I%6 zl#hX36LQeKR7y#K=iJxh-aXz*dfZEmE~6e6c#_Fx@QZa-x=b(HDQDe86!e&Ik;24$ zFfQJ|ncc@u{{%1(WyiS)VuTf5$p-;?cGR0?mRK=m1Lv7`BX-rpZJiPkaw6L-$2RN^N5Hc9eM(D9G>{~(*u&V( zYtl>ucFuXK8Q$aaXDM(Kq0Aj$&mFoa^3X(5c%i<s8kB_5Q5!rwZY*VE zK-_ZHqhHNXG+t>4WRyTX2ApHL$-ZYLtxv`+(ov686Zdz~I2 zC}EfFxvsKCEf%h`pnUtcoY$ywU)Sr+Zb1k&$E}}eeSkhdS*(+v-A|7ENEQul;zAU5 zuBl7|2XO?;|4qQim1n4Wr~Zk6u%o2pvsF4@iydLihLqjn*}f+aQg_PnI?@_R2&;+X z$7Amw57eu$*U$&eI9$W5N$`JbB5>XR#y-I3JutLia}^Gr0r24si~%(Bh&h`Xxmr2c zlc`$SnSn_hmeK-}C#kj~$`eJT5S{XmCMb(Y|*VRxo&dHio*5$M1*@PM?_)whbW@kVT6koQ*^k zdfx58)jXr4qeCG>t(o-4Cg6nLAc?c3l7Z3ld!CR%P9uq5;Q+}@I3k+a>h~9E$K;__#6F_A^d#&9RHu{ci^w|+cV$&T=Zr4)_|81UM}c{S~1(y--t|j zjWWUnmQ2h!PWy?u(cw|Zw(iy37`w%He`E+FH2*5a9KjK=Zf=poK!!>q_~+i|*I5&{ zD{Y@3ddXbVX>dcoSKpC(sob|~gw8%+!5@15)-^gx_ouDiXPtZPPNVQqVX7c_h{~=Y zSX4Hp5n)pYWgv-Z08@rJT55c6DHg%zxIa^7C>On-yLS>KpTQZx$_gVojlLu5Qy$zH zOM{C7eAi%s7h@sHy!82RwGR4R{=cVpR(4)C?%#R`!;bC$8@*$YkZ90k{)c)8@BFRz zviHr_5F%Ki&-LGA3lvDmtmIqa$>G6|aj|*}#^^!x-&&wRbMS%8f)vq+H zUo>i8p${s4K<@2cL!&cDd5MD`*DqBx>DauiD}|ld^K|-|zkP+aq8#=mHa!{nvehYh zt3K+0DTYE3=0T@&xR8)Wf*c07`5uNuSJQe*cZ@C zBvQRRxV~2+&%=vSEmafGSJo_3XVNWGZxTo{!>6OdGD%P=4wj;VsF_DnKXY#EF|HD> zUz{HM>5517MmZ;9j1(oTn}r}oSBh;x@X%eSMsrh_DQA=t3*iyY>diz^y*8s>Pw(4X zU8a6Ijx@QJFsg#>i4|Vxfhni_@v-<3b1hUx97NoS(|rA@{Yy&guLMECd#PwUN+DI6 zNSqll$6^>08T846(o&vu8VooIQdFys+zs&xV23YS5vJ*S5iYk2RA?J?nfxwbg1`s2 z&a|XFnQ{h9S5G|~SkT~z(2-zX?T+yD?3}M*R*Pprh&fL#|AW7!{4e?&4=)GjZ+{E0 zu$}rFf5T=x>RXZjPyOxax4&g2X_XL&;DwAbRfn5G4lH;2I;od`hVo@mSjEbefUx5m z4LpY;Agzu9lw2wD^bvtRW_bpkW?tHN5SM_4`(s0TAP{%6G+{vH0)lo9@-lsC2@7mDs zQd%@CW?Ax-%HUUGFWw56vf*=QDTo2Wn1z-1@||96eD!=dp}7cPitVSAqfxa<25y}~ zs=I+3U-KHT`EJD=cF}z+XVkPd_z>$dHaw(wxtB(OD;njG3peUY7OHf*@+J?IDw#Wb z$FT&7Q-GIFBLE*6@O|@?go{<1(!X@oT(x^I+vA>k5&`!}yYZrZ1`Ie`=xkhz;#tFQ zoDkU!!%{TNFXnC4Wf}>Z+d`_nH-bfv@__kW#XO8Cw|3fkz?bU%z$Qe9wfyjo=K=S% zGYV~VuQ6~YvgM9jUZ7(AAf%Q{!v{QYBd1FPI2lHdln!gvOFK7Ch5dLMV2f2{%0ndh<4Rx9*mXGqg>F9J3^g}T*cwfH1`m9ZRvdn=@Nz@OHdB;VN z^q^l?DfHZNTAQFk4khNO1&0TG3|yMcwOh1W`|e_N^u0rixiCIGFZb9RUPkU`!O2O# zQ7>)>PfAx)d6}rG3b%WANlDU46?X?+L6TW9P2~DdKL@UYO)o*P5YJN9yvxueh9SAl zM5bvoZWHd)`#sVMw-@A^maqx1m{=SaTEVb|djjiw*Ub*JZ z{)e^#C;!t{tTMgASP~YvOrl)5?edr2&}BU`Q1r=jb8tBYXjPSHb_cbU(o$prXc&Q= zsZPj8WI7FZq0e2Wa@&iVo@?>JEglX0>xP)efr6tPHLW>Nk82j%x-RO%g)@^h}^;5oB>?gST!L{0&8I+L#e9VQGmiuYi zeR$7!z`37F`h9KL^Yi`!9o5SapYEX|OonpYy-+o^%dV|;u+sQ9AIub$v4oDU_d_%J zM`7dR?O(mD{Sp7(EGlI-qX|LA91P{hg9@n38-1{918Z)G%4|K427Sx(S9gCv1_}5B2Umq4 z0oVNX%;e9!G`Kp1^e;Xnz^4YM0wU-+0uu0r5;)i+_)8%E&I9eik{|@ZQi1qKe?VRl z>;n-0tgix252gan{Fl54I3t+Iulxc~26}>n1$sIIgh9~L46q*H%RlX3`TyboMPOk6 z#QU&--y{QWs`ev6jqHATLuTv5K70omlH~YsHaMH)2bvItako2Boe+)@+&tVzukc6? zWbuA|6;_u{_SA4!PvoMKcSa?hH{y$5l^(v4>zZL@ml+o`}n;N?}Vz+*B}FtGO}o|%Vwh=y3x(`_Er z1H<+ZY0GHe22DJ=ZT-Q>At+QrfJd2mWVtk&td-!D@rERUH~yp=NOT{+_I_N~>A4ez zhGIaloSKo4wR@hi*eIQDpLtT1Ku^tjUgG298}((eH zBe3$0u6-yiaLbX@K2n+BeeTN|ZSNO%LPg&Sp?IO|wWaMEKCB#y=Et}X^F*0S7Iqzo zz=XaPM!BnSO_i67TX%&vV@^MaMMm0=@;VI>dc|X@$&q+BqRUr$pCmjVP6LK0*73Bq zWMQvTxNV>4o7c}&1@zP81cggc8&W*BI=jVB2#scW7$tp< z@tq^N$9-S=sMtv2tx z2=d7*;u}W@kgK1m`#4O4PJt`IV-uWZF~=D!f6TGtrz!=8n;BULj>Kg|pfNPxxm_EXwW+(#R!`yzp*5O-r|N5wjH2RR{F++=KfBwx;7ms?N4|PrWI7 zpVz>&w@zv=whj9eutKb$Cpvjpmhcz|TgW(Nv%e7+M|-}8D?`c(9!lVH!Wa~*FU75c5tXm?gp<=U>h<}}oMJPS6mFa5%+DL;@>Hap&--&H3ceaeiqqTP zS{V!_F+rX*dq@kJ#s(S3le632S)7d&9;!1hNo&z(+$jb(JKS+7ILn8<%ShjtRSzId zQEZ*rUR;um?6ZyR2kh-1`&Hze;mPr+ShlLv-TJXn2P2(L@ECEPX9gZuvgy8M-k9W( zIO3L@mA8z!$Gvqy;MF;;*my2@g1)D{*WfSbL0{+fL`V28nfzM4O^$+}JCIhZyXUPO zZWawz+V(ctTXUR_6WmE=#bn2^=2HapuUmsV{J2P_kR z=~4H3@E?SJmR%cZ{MoN=pI)_PYGzl>v9S5ZWpSpDq7K8#B$|=Bs1GOAIp<)PpZo1B zCjwK^u4{UW0OY$WVJ+fpoph+wzvE*u+;_7i~G@yoh26h4x|Y zkWwcWwCC6N%k5YCc;{HoMRY@np+Yfg{`!*x}?cN?`m&6uJWg4#8f`URFej~ON_(CPRm6|XEj zUJ15#v-Ni10UX$X7LG>*Z8Dm6q{}BJO%_Y*vh`F(Gus0`I;>q(_iMa^?69+rBS(v% z%(W`#}({n%mk);*P_mxcrO4uUpggl>> z4VI>!nr?Dq2@)2?A7-p%ViL6g@jk1s%JE4@^ zp&QNpQ?qL~CF0zEvU_$}7Hib{7o}K%xfec&L^-9ap0K?~`)e+x>;35{b}sg0P3{2)cJjB}^Wj!5o69!=nj}+g z#OAvQz@FKVJgA-btk3bXEt(WXCHhWtn>kiX*@T3O=x469UFk|t-o&gcp?a+vO%hAx z5n2uE8Cy1&5Rua72^f$JWip{uDpUUI0nwouuL#Y$edlf1l<^k0H4SXrw71GlJHV_i z^}b#|XnVE0g3X9AdAa3dfN4$)DSAWG5GW>^QSDhLG@DwhG6uIwdyloz>Xg4nGBQG% z_MOg@Lv3xV4>~kJ&x@i8TF2D64$scKR8d z?_I0=4(cG^%DnMTipb1vyoWE}!QKAd=39xqth?(UpKKTs_kBh<(_Fsn_OA~rjB*(J?Uz$o_L*;i?Y*D5U(L+HRf{2M zQicc8o{sBje|Bnbp>H9v=2z6Yt6jLovaA2BWbz)xnr3e=x6d~%jNn>F1ex@KQ4xZW zgatrS$)(ns-}o&930!f^vQ3pX$l0e(XkA_e5uj2B5GeoT^(Pp=?@=CyTsKCZ@;~t- zVUk36^)W8WB!4t&^76E`|6_O}YGd9yb{GnAbrZ_D)g28kU#={FOB>tkk4wp>I!!xF z%5T1IK$ndQklknM#ERpI2~^hEsB=K7b11fd+~NHm>29rYk=sHM*BhBlK~G`fctP&u ze4*u3=O>}4li4XW5t%(+zDw>U0gXeyfMGDssW~p&hy7Tx@nzTPFp}hh#*{=2MHE2> z+3BWi$jtrL&hq=IP0;(j>N#?v-XNb0Z6#NS3cp zoc25w9zB_dHsohw>q+{&X{wx=+$wc_tv*4+lwxW_rt=NO5so`P7@yH^v5x~+;%a{! zJm#?^NYntw7jN>sL{Z+iz!cfEwjITXxaa@ey`*{-ypp8O8-JHT;4_Ja-$`xW+J1y}HpIk;+Un1bw@$dziSNK<6%r{W zBG$qZNDn*IB1FwvcOOi+*PF4#zIf6!tPe%ZIdi}XgyUL8;<7yCbnuan9MRhzmSZeX zCB-Nc5iP&GIbUUovAM9&GR;+oO8LmJWI#Vb%LKuC=}wcl5gOkjj=mc@ldtmi%;M3L zR*5z^%3_A0Sm6;1aoaPDe;2HEU500D_A}x4uOG#FNZG*zv6I4(dKPuI2kvW0m=UrQ zUbK8e0GIZ=IT60l`A`i{juN4c3R zQOHAUai`^UmO^>CD~=2X@q#CdG)8!63U7*}XT|TGD)wUo+xpapxaJOV)D6kjrIWR8 zvm#a0bwv(Xry}aDpayy{;lx-O)#zr!)XUDD0239sTu0W0=I7(JAGP9?WaQ!6GTi)_ z_a|<{M0u#(!OpDya^T4W)kz{Nxh9?{N3YFRo!#WGmhcRELo4G11+PrVJK%byb`Q~l z5zw$?lw@<}AVS)-)>NiWPQV*FzI}s?XF&@Y9Amsj45G>-f-kxY>1;6xXW@9wja=)z zyleI8e)>S`^>h1o7y8HNvL&QE6$JfolfJX?rNSf$T`UvynsYTACsbFsL-xw>`60~e z@SVa_+xD7l3EOqFny85_v>If)4s!DfTT~R*<}Z{^;q#@u7-6Y;<3~isSLyBmpWe~aHzF|pm`<}0@E04<$_0a{vF1Uh$kL<0nZ z1&uHc=i5?+*tP>mGskl~K(YXg>NpD+mG26m;KvRUAc=ww{+n_i(*hO|2F^1u}U+QxJ zTG+1U#S2Sy$$XGgXIcytV@OT=a-fVHhBrKmv_0t(KEII$*nfSX{oHmcP>iT{?zX~& z$awDZuyP;h-}{CNka|U%YW4+S;fWyNhGZYsDV)axP+WP3U|8zDFDaqb28VM(!Tpkq1@#cykGMLkU0>59@xdaYIBDC!r|p%QtxS6FyGW zst$%r&X0UZ8y^S(Ph|S4seF7BNnj{Z8bgoZo{62T3CKka*qiRxt$>Ajyx4G7vK9&} z7h4dKv3(LCj79=rEJBkYvEdj2Cm@>!cs1d(7AR*C2lPIp~tdX{>x zfJBjzrU#NXq~Xh%-S9KtoAB{bC-}FgP_#W_p)p=)7qjGk7CAH=!2u%I&Lbs~h5j+7sf- zJHq{k^O8q-H3^X@$<-~k{nQ%3V_;2c^bP)%LOo|#QRFWx>^<64vmI@S+r!`Sd%Wemqrti1X zEoeP*PhDzxGSPgrdxo8oI=tVlJt&?bEKoKErjlqgfBh1KqLpeEw8=KXJ~_;(MXK(S zv`siN-t!8~U5MMsG_D9z8XMv#lFs$#5McuZExX=;MbBOOWHha=ghnoq7wv6zd@6R1 z3{m3Kd3;!Mle6O7XxFsn9WgU0#D|#C{UP0+s6TUdLaxr)Pl<6QXC^17>4La|m(>Fm zUX%HzXY;H2WV0jY5*Q$?BYxzZ{{TNq$sDVsv3?*R6nj$GRSt_aTnUf!eOq``7e9h4 z9m-P{#VP0BLWzcF+&gjZb2T$|1iy{r{!QZe&*B=C+-roM4``%uN~I$s_L5~+9w z(6R1Fu2VF}5)Qis2;H1Xw@Kb>NBB8T^GnO#Yk{=;i@KDgR*qAIGh8A0eu z%J9X_&CRGW>jiSmDo%+Fz8<3&1?=5=MEx_T)Kxzv=q|#HN0*C=b>Wco(Ml?8%QO|x zBAa7StOKQ|n5=J04Lq#DXvrmMsMV&pF)Fk-=$23jLioalMn9ClMD;*(;qB8{ha3;* z?Xb16D6_J$VuH}81inbc{Tr|5$krL;di8iMM<_r)lnmc}HI=9%XaGhUm$+kfwtswW zWDi;89xx5*TJ44xAJCdo0+))D_EIEOY!1Y0G47?Epag zk|k@?vR?{S)AHfvi(Uq~aZ{{YvpKu`{mQjjNI$o}y?~{6S9<|%(_%nfw^bn4cDEPc z_eBrT&Y#TE^hI5~iIqHLFW|v-Ppm^;0C1d0(zL8^QvEmdpU{Z6>Z6 zM_zIvdq&9|P0OEXW+C-(bi=Y(Ot5ZiY!h^k=2TpG~2 zpXDO+-fe(EPX4X#QCeb+x2g;FqERoyWzQTPaO{(2KIGj(baMie(-2zP^^O4cOwwuQ6uf%W{kE(dA8R0o2;C>2Ccb8n47G- zEDw0%Dca=*_k=EJo}|ns=2K|~_p-#UoO1~xn;dAWrxeLQ>`XJssIoqPjXJSc+Kl*c zi-^a8t-x190;_tO?2_)iusc1*I_8T$n&Vx-4q%m#&ik3)G|G6cc4^J4AcwulN^QV* znu4?G)s7Z`6MV%q{NsdI-3mmKWI$Q!hd&O>J2@u5&=#!?b~5zuTQW^_OXRrFsSc!U zJOk8_r}-o--!zp9UJ0-E-X?Ld=*N?uQqo{T*Bp|Ysq^uDM=T&RW%H3X8ZQ)pWUkRB z0b}af-8Z@5ocp#1&1wf&^LjPTag#+i;9D>RjJO}J%=Bp z@XO7RX{`fh(lv@D)9LkL?c`fJD<|N8c6%l+@8Yo~%PZ~#)9rYNzz5qF90PWxH|!B$ zWoq#$1QAS5QY$9!S8Gz1w%>Yeu6Ek7v1Z68r!A@I%Y&#H55BcR>$_Gk@r7Fpjn;h4 z+%7+ozD_aXbBWF_ENyIeG+;0r2d~?{L2BNOhOmjcOV^gXIe8s>#pKw1O2=>d^<{rj z(_8r00&_u{e5}*y8(4uHg>%#0FZ<>5qWSuvL(8ijW)v;#;6gKKSWYE`lA*WUL%Nya zM)EB{8r6EH%}eMPl~?kKs&bW*=&N}d?}E_A$OW`aVRO5z&|J%<+II7|-7>6u&ys^v zw`jcuV_M<3?~c3qjUcb-N2^mf`|iYq`D4+<4#rpeF}0zV>ZFcHlg8H~d?NcReN*GS zbs3b&*5)n9VsqkoWjm7sMK595)B_}jOj}1qS@hz$-hQE;;US~B2Sy=y`b;3uL zB$_a1(dO=3ZoKcSKlAVPhE$4?IJFmDzA7!cXxv2lzk5-JZLN69(&z)?Xhvjj~)#+O++YdFh zaX8x3@o_j!wWK7h7jS94QRVVjDOJ&w+$_dvbaAr~tSrV!+QcvXigO z@e-AHCV$}WOomH>-*Uz2Q|ET{Rb?(6qZv}3HiR#5`J(A;Yl!H6mR(qR&+nMc+fixy zeT0Ra0f#TFep$Cem#_6*D_-k6+G~CiQ##tmAB`b^KhOIEO95bs1kAwwJ>bB-IN*c8 zJPXXg9cz%b!0Z9c6~OpLU??yYa7O@CFffM!b38C70dwk~Z2_+!&_EJdAPb`cP=o~m zlT`hKAM1Y@`}3Ex>Hin}N&h6E=g&Cf|42X&aPj_kbNw6MgZ~Y;%s-lL0MMn}|KaKW zPvg4&8{UKeR}P^6b{&9^{A0NIW2pFJNIB%ne|kNhal!plTC%4h{K-}KPmO?KEg&F5 zkFOjs0Y!)}PtE*^{C*&T_5b1f_g`>-{I5B}{#&Pj0UZI{vjPe8A9ltwU;I;wz5mM> z|J3N;eE}u?AM?dO<|_Hm@qhgjcRe1v0FVA3b{EKVfBpaFDSZC!AcFnZ{(3g1|L@$B z&&TwF=HJEy{^yJRxWDg@c${DF`F~v!b%21lX1S+N>cD!dOis4h57Hs9!h)i{H-TXd z+q+>26G|rL@&5=%;}08#2vIGeohX8UUZN%rcTTY z-%%5o^O6I0fw2z`Ux}oaKByi(q~N0-EVeK^%%K0sf`J9iF2;*kZ-3!eL4Y?S%?1{@ zyNPly9jFk6z>#su(ufdb&C?b{E3lYpi!I~a79OV`$Hc5saC$DD%d<$u-0 zQ@_l&p3Nr55GNJbh!6Ws0yZkOt8j*=g!^b}Lc_iIb30g_SrkUizDF^8=*(36NU)He zRU9gAL8s^+f)+zsd)K3ciCfTxPr}G90Z)&OfC2AOEGT7>jOrZRJkcoh+WLEP<`39! z#pfTFy;uNZL3&_hMb!<} zB^~_5C3z)e@tIQcmZ2<)p4>&H@o_?19+K&qvJV7drC9aYRC)%k(ALw8JS^v%tl+&Go6!mJ*0jB`n;#Lu}l;YtjzpU&$M^YshuX z=tvOT_uW3#yqRY@Vu6zTdb3u=GMxcmKmMH$`p=50%ggdTS7Gbz19Svkxok@pTuZf!#vE{0zo9QHqBv<@OMOTw*w5 zbK7H2Udm&#?S)LjiP;bVV>13Vp?lT}1|c}(#mXhd-qErx#he|~@mJz|otU=Nm18K> zL@#1rh|U*1Gg={C^Fl6*_(tMFHpyG~YwFdX&C%v@xz_8_+1#XW5-vmn5ez6Ath zB$993-=0*j_BI=NA8aNi-iW_rpwrZ`blxoJpFF9~KFBs_;DsxX;E5pp7PE@Y#@cgb zbx(YRPDy-8i=aafz<78wlYreMwny9QKV#2)5o=_AOwgIxF=O4ixC$M1hhkib(G;0Q zd|@t^vfFw8(u%pE@Y~fFFhA5+2n2$lx~rr*jAz)5MkoqbFxJ5g!qe@jXKZ|55QVaG zc<8|!E02fPqa9(R`r!n&MAnO(qpQ%%txE66Yl{5;xo%T!nB%=#1%_iC#r;BbAKYE&yBDeRXafv+8wrgMQO7Xaxd_X@&NKzPh%&{A?=l0L;woUoPAo{f z50J+&zQa|eF#!tWDz-R!QEP+8&k0F%NB2LT+G00JOQ^e0b}j~2{e;_&sMn^b4jeyk zMRyTZ&w_2tm2xF)3?G>!g5{2j+4l5X$kj`*QRM7k^?)zj0tbtPtD!Sxh2o>CFVZFk z(022Y)fdeZUm!*KXxJzo2Fx@uU!YWp23sapqZzwJ((f>YwIs1_D^{ttEE#1JF5~*i z*k}<|!|)D>yX)9!xoJZXZjkbl)K|uSCFgC2c2}}7&Z37W zV`r}WH$Z7H_|gOW1`B9I<3DqybLuA>uhe+Wt}7Kz_>NH!dSg=Xbw8J{E8W8A{75~w zWnGfR4U;Wnp)HQC-lwA4!ulb~V_}+8_I~M?u3x8+K13d%Bua1d(}>71eAqXdV{im? zX&=MQMr6863CEGn;Gk+(w12VI4L!N^ zLNs~dVLS;I(CS>!*98w`xC5W;xE<$MxTVsE+u49An%I?}fA8RETIFYAImyWmGBZ^;}9Tm2{_!w0atr42iNQ2}ReEc@?UFtS^RG1Wwr_5yW?Sl6Wj@$%&MN9OVm!sQ6yh)LxUKm);rQnN+^;OLnsSZ2L zS(~MSpenVe3$XA$X}Shl)8xhF4!Ln=R^TLSX`>F;a81>vXv$r0(KZXFK2c(l*bdls z3wS6z^9L~NYvwytPF^?`45wOQ|DbUc{K+f$zWyqG<|178#f^77vf-|&H0CnKk%@A& zZWEhR$nS3?@wT!dPVxlAJ9{x7daPJppTtUKe^~DY$o3}-0!G=5*S(j_3fw4Nd4{UG zZGY5!`;_6eLcYXjcd>}M0BA*Mhh{n=IJW(vPg0L$yo9j?Xoah)JeF*xY(4fSSbYeo zI*hdET?(4{#B>3E9Q}g_`G%p}m#TH&d&z133e-&^v~#0M7}G!2z4{18$Bbgw9lkNYq-)#NDo*LL)Z`eFC2jGtnV8V4v5%oV9p6*>r0g6A0}n1qr))QZAT&0*bKuZhAF7 z>4eaBDSsc@B_=EW3bFGPFCM#-TEC}hn@}|-4c=7OM|2*o_AV80?lv}=-%twoQDtw~ zUKB4MvGy!pE-9kenMaGVJepCJI}^E+dN@sb66OnYlHPho-UBb=P6~GMIC6E_9BZOO znyj1M1S_KaR#AJg%sM#y>A;tDR)1D-*Y!LJAHaiAmCQ5_Go?!9oj>V52se;*|c-h86Yn#VH%`c2&;iJ^|kUr*b1e>%qDW1yOS2-q*ue5&9JY^@2PN%`BGyTg!?a9&M75&GhV#M zAukb0*wHK?n#^_x{fL{@_IY-j(h!NbC5rP}I}SY0&v+ALf7Didr`;!S@MeuF&l4tZ)?d zB&qvra(h$@;STF-G!L7ylNpUGiqT>B0;lQ(7>^|^21IU`JoiR(xf(LK6!6u|nS^$H zXqXc6r0{ook)-4DO5SR``Mq7#qWHU?3C~^y)`O0eF2INE5DdN(^62EqSIDe;wdlRLBkCCJ{p@fHU4hBr0U%UZ$dD#?{0O%%{AJ z0?|W^#ClfqJKfuWZX&)ItF^^z#WPkt7}E}$!iVK@DV>( zGqcrIe4t~U;N1O>9JgVbi?5IeqE3~*a*7JS#Un$OfoL>7CquuA2)~6mL8Aa`slKL1zoJCD!pY;8 zfp}$hj*fnXg?0rAK`hgW(lA?X%P&-Xn4*Fn#4Q77MzS-ku)ZX-&K8!N3?m~pF|M$V zMS%*gh>PY3ZKt*>QYtdLMZuQrX(Ck{gGVJ!H9A(YE;4?T25OiZ&mR>cGTwt8L}o%h zr+A5Lf}fnmM;J(se2eU`Y>d6VjYiTMhbH^BT!hDpJe3az4r3HC4!zVVhJso=3ogHa znR>D${e#-Z7Hc+4WHb5TpOu>IUK@&B?L=obP3~X~Zs~wVJE|X;jg-JN#xg{iRGnR_}f|58b2`LlKK^^OL5{i zEoLpYwFqii5}dDLENE{plnWexSK^ak}De^ad zZ&wK*+HkGyNwu^`a9$rSBxDL2heSnTOn_Hr-h*`TtGn)?v+N9!)w}?HyFNP$E=D&}csUFUD zoX#7rA-Nnyo5&h(NL;?d@>Y(9GBmJ0zNtj+L~5U*JHBDOG($b+Oi-%7M8x*y2oEu~ z!8^udy0k$JX09)P#M<8s^~?ZPISqi}f-kFNydLicfJXng?L*-Q4XnggAnD$W$a*6i zGV?O(JQ66>@q(sxd(4weqJ88uU#_t&lEMw0N*Jy;9T~1SEZIDk1Vrv+*p{4nV!2n9 zxRC*dRZ1sVK_!an)-Z31|)bzyk}sjep7H z#lUZG4wOA1R;9UGog;Rd`Es^PP6*@cWQ9oZ$dwFAwsxWMpiiPg-Q^1_+i z-u0aoy&uaaU=4-dH%6m5y9PsUV}WPw;v1V{gzU|!ZBbCknZ^RO2};2?+-NHI$S7c} zYLOc+mGMn8s}7}ayP2wa!B_nPR@Ku7Cz-aU!YA0-)?TO8uz!GI>1^r2uW0Pls_<2J z!CY}n;G~>ubKP9AT;4v{4tZmENyZ6lb>2eGg8aB626d-$GgTpVS|Wj?d`^sf5uhX? zp(LYd0BBerD5p3W;l)x%Z@cZxRY#Cv?8edt>h60LE~mH;TAE>nWnHJ7zo*_bS`WQn z0=>NhwlK-7u|FV;H-eU|72+#oh$D~#E_rkSuP^hvR(Xlun%<9IyFiHWQNokXE_DOkGg;>7t zM(3epC+7LWE;_>Itwk1mg8XZ-puX#&y8Og|8q>pe@d`a^y@3U{WJG9g={!EiQsZg) zYVdA_VCX{>Xa}$bu|+*pj~D1bnCavqbC`5p@DFrpkr|&~130Fb8W~^SEN?2&y7Bqb zhwQmotmuyRP4Y9lNm1}Ml3OPsY2YfAzh&5;s4qBIuNkmr7G%<#^u0B&oRn&e?#M+#4KZrqzhY7NElChvSXakKAs(2(i;#_;32S>IP7( z7oJTc08OE~8q@g(;q#E>yHAW-eRp5d+VLZZFk`Eo=tQLtTe7Ed=VDk7`SR)1>en^u z+;R&~^}fZXozrk^!IDG0E*}JuwKc=bF6Jw3vP!;m2$y#zCTjEx7%o*8|yHh%#o0XQkc*X81eWKtOy@qJrqC961 zRYU%H(W5DUQPNImSX%M08Eq&+Cm2au_izE}m?w&5;%MT0$SZq#N>tJ&69hJfTph% z*10KIuKA1HlyciW3!4Xs=ZN0!WbEL=kKTv%;F4Cjl}vYh)(vepR-WRS+$HfX7p!%p z-p$x~Mdd;||K|97l4tTYhC#LxD$NLmi)hXfV_t58Z1^5bwDwdA2 z8S%ifFnAXZO{fsZtVKBkCu@|hX0doE<^hnad8pEp7Ne$z`J#qKvxPNGFCH%H=3!|r zR5LNU(oG6)042Z9q!Y{W@)C-LrpzhFr{%PwyYWI}cdPB2{!U_gh_(8Iz&w5JcWEt^ z_rQMqz~lgE3NlR)l=Fpw!`IuP_5vL(i@gH2{FKM91o%351)3LU`;-nvIEk;}q? z8aiUnJFVGlZAC1(Y|NyAQXP$Yt`&v}oWxdyX>LcA??@&vaQ6wZYS=zKW6=tf46b0( zbrNA+@KM_kPW(ny?qeAr&M?Bd2+BR^`0~eT*3+y;vX>46}h}! zk8ADZCS-mAy;x+wiZ{vdQH+&h3T&fbk8TqPlHY)QgCz7d%c-QX5C|ztGOdgBRUE&Wft*|XuXfqYX%R)~=eC9!a?7llj zOE@?fb?bP?QQ7(q;L4jX+we)eF!g?s&?mMBKl$A-pzLsw@XjDNaJJnSb< z6ZEdR{9c?Wf3;a@R*nY$B;xq+me9kIL|$`V?n^=b24{lZp78Uv`YrMHYwkk6yE2l@ zx7(Dy<8kj#3j7^$nFotD<%mF)<$LW~9;|>+QQ9%adadn%I;9bia)VP_uHUFO z;^XBw@8aW)>ctBdm6D?NVg_71O+Mbl%$xLt-jw2R1}lu-E1hgW4vkfNbmb?#Q&mwz z6p`SD%IbRYCR2Ai)GNc#^FmTFUpYIb@!V^@X_W9PNOVPtQ~T2*Y%Eu`pq(x$k)-sQ zM#IuU2b#tRa$+T{IYow0zW8{$P=2AT&gj_8i(_gPPguDX87H_Z!n8GC9rw0A+Pk&B zzV`wc40S~x5#V(}RJr0yk6wE~Uu)e@lypGWpYM>meA4&p;{bG z!-!JCG7%?KS5AY)5l_Qdr@9g6sd|OyPh;+=+UUq{87UP(Thb?;O{o8I>zJF48E@xU zbd8GUW`vM^u%xG@UL3`|fa<}|75yjdIcM|e_56~^wb!hg-_`lf;Tr)zET_A8#xzGY zmG|_=NfhawftZM`5vB|QxZWAoN46WLXZbYR?3dj*eBt(){t1_AXqG4YVws(85UI^v z=aMcl!JaNL-KfrQ2s7?J6T0;cj@FzQL=t9vwLQP&CM>oNJln!nWLxCVaaXK{%eWH4 z02K4H$f{*Ii;NcyY2;AVEANl=%!)7FrgP@eMrC! zoe|fhS@&r|%?s^FU=S~_B|W#gDO^ursGs{rB)066AWln6=WO!6Lio@q|i=$6N?x+UZv~!9?Bj56Yn{X{U=KN z?!b#D+2ZvoAJx@h&Kd>WiHYiq&OoDDAWF_qD#lVyzMrfH)hc?e4&+78DYyB~72SOf zXZz{0HYT=@TJ})){#No3-8&5g%hI|987A+h>4bNO_hspk*c50 zTRg8*xFbEgEB!FfF=ampysmLHSG1>ASbmwzi5cI_yh62OONUElzm6%4Siga*Q@)pE zuS}qZSkGT0t~h*m9-F(E6V8@0-SDBv4c(%eZbQ)q$SH z`v6yR^65MC3GLuYjLExk=v2~zpOpAGc@+ub#j!^y$L>xm8XIuUVXUjEL-pb?x#;qDH3d*HQr(>ksl?M zF2u4iwKqs`O$_K5J9*5+mKV&l%>95rU}4j>pErHkOIwG$_MLZ|LAND(@p#>+uE0FC zFV^>@o9#D;F0*_n9NrK5>qJ|&mz~|qp74XJ^BFu*l>mOZHXhnlzjtZrzm;(nb%Dthf$>8NvVC) zvWMqES#{8711rDh7_BxMz3r!Qz)KEFLqe9yA-GcmDb%0X9;#Pw@?{lJ;`@A)zntKU z_NK<3rWL*eyu94uC^+YqHrfq#(-Bdif`bp;vdOK~e{)NnVpmQuix%{)Pgk{3icJNE z9%6w2|8R~>sMj8@QAW>(qaH22B~NIC68M^(xL?6=WNlH{^o#VS{`b@J;GPOiL3V+x z-#!kBzy^wUbB+SS?-oD9y~)@X{{f(4Km78O*`jic9sy<%zXg&)P@j?Z#jL#06--%- zk9E891)Rzvw8RBHbW;vkQ(a9*DM-36O2930(5LDhZCKDt5I8?xL&D=E`L}Bmi*EQu z(0z)0V|+_ILnyS-=W3PXSQD)b-1!R2Q##^P4dKRYl3xB7sf z%Skc-LxBWsY#5a7U7d_gMd-Omsh(X=}3!n}JkOu*%`KbJqAVBRwAJ3KswuSL{ zJm=#+0^q)z$Nf+s+>e+4Izcx9X&p}7(_1uP_3?Tz{3O5#49c)I@#T(Im1o)o93mXo zh)H)KdXOP4*7T?&**fOS0Ew?*Eq(=^0xPDe=LlYGUrmC_$(zEx1S@@XcWrl zKqab^3`?)!$1Pc$svU|i>c1%M^RAa=*!eZMiGSP&bV^AV)aB(@)=aV7>}EEL_J)b0 zdhBHCnlXw_I>Ig#+uJ9dM`FvGbzd5c?O5lF7L4mSv7*N|$uROLoH$Eqp?VwvVR{8G zVDPD%@&`JWBm z;WA|AoP&U(EKjzkw<$nPvbA0W zwUQ~u@>W4f7#4BRdP@_r1%yfzjZJScuhqx}=?ary+1x&DXs=^HHCGX0azrz5R;CeN zZt{~zMS=)R>O$n3lZ~#JJ!11~Hn|s4imNVH8>=Y3pRrH-84bph_%P22%51-x4sny* zRPDWVWBTxur97uLGlO$O@*y@Wy-Ux&@Uo8nj2 zDw6S^rL)u{D+63e0P#vtithSgi%c+^Jg~UKqCtxwLjHK3*_fV${6?2`FH^sx+HlpD z;pl;9M2(Su zg)p9C66Q%JGjzT^JDR8tuA^>*M1peKazk-y_Leu*)+t7jPZTbuYy58 z+~*{U_tr{&+kp@y52;&aeElcj^Snpz%i_y8N1S(fBa*mcjiu}-%GWsA?z6u5#Ga02UX`b6?xcF?k47*w@4H1s z^H;hZaIpJu9 zO=#WNRAxQXksnhi;Y*wWv%V2SnMV;5QLO36p3!@J{Aw5@^4&~ebB_a3?6M7S;E~t+ zf-N}IOJ@n&>8)2jxRlGT?;zd3A>wv}a!i2!us~^nJnMO*8sfLmIfJ@Mb2*qp5WLS% zS__aa8yJ;#=QLX>=X8K=jrklIaSz9!@CgN_a)f{#wNAR~vnq47Sf$Y&#Jc;mGX8U$;2L+X^1}n-a!cfApW3B7 z`xTCRf}9`y4Jw(1KS!J_kQi-RzpgQF8AP?vx6XxDFfP2E&WBNv1P!_FD zhF@TvNtdMwpWQ55sV%3J=N zB0omt${xj-#bay-aYfIqxCb-jLuV=sgF75jznr!Xt^TdM=zF9+=4)uNo{!gATY~{( zYc3AS?2Y+%?xwh6)NqxF%*@5}r!SQj$M^lO9zf5@wALU0trr461i*l21U@oJSUfTq z0TT+yF~|n6^!m4Y%)ixh-T=QT-u%}8@Nf0}$2#~H(CQB41Xb43*vZ}*NH;;OX((Z8 zNvtaQ&md4HP-ajj5G7?LVMI6(a2Yj8IT;}}br3K(E+9>YIdGiBK>5$p2!WM9>VNQH z!H585I-t5Tu!I19(h$o?3dubx0OcSVHRVShjYm0INkv)wNiGu>R{ry(KM=4lGLitx zCwxL!UhXk+0hqwHRAjlBfC6yaCD^Tqn8;)Mk5w5NNfRK;#vihQnxyb!|3DpZ7l5hL zBMuw`c&D=PuYMt@L}dQp@_4+(E2g0GsQ)M{2rI}sJjo{FDzcB~Isy}fyO5Zm%#$1? zrz-hNKU?{?Tq`8^FT7V=Sm9}o5DP*g3hGa`N0PwF{3%01SQ^^^*YPnyVhbw>vpmUk zGLmLbV}KM8QxJ83!c}DD#2?24+-C}Dp&JkBAbWIdqxqzt#czi-hFA`kGJ^R{7P}mY9m5&{JQxk0tOJN8mY?|5pP2 zjX}UX-2wZJ0mEMYOyIw<2DT>*;$Ijj$>aI-f27}-&C@)8-2s-MHgUgdzx8Z?wfB2` zptcf!qxUqgXY&Ac)%dHPtHBfYdv2hB$M5{d^m}fg0D(Uc;Dh>mEP%gh58&{m_iTI~ zk2ExYq~EsQ0#DGhev`AGuxI_IJU!RX`%MML6#bE&t!e7O69)aekJRNS4CWbj^n}6w z#?m03Ft}$J`V;nS9%;YU(6%( zv<&|VgZhn?|FV7N^U7a(&wT#z-IMJzpV#~v-!q@rWk2b`|L)_{uXREAjeY*r2jVk~ z?#UME8OHmBy?BQGY7hAtrt_qS@(la62B^<4?;28MF+1~D+ZC?A!A$;wfq~Nf4aW92n8V*-?tg>5`5Ub4348Oe ubpeC5dBk2SnVMTVyO=r=3pv>vniv~8yHK&X6FV3>8QR#G+E6n9EdLL@j?j1j literal 0 HcmV?d00001 diff --git a/src/ClientAPI Example/TCPRouter ClientAPI Example 6.vi b/src/ClientAPI Example/Register Broadcast(status and interrupt).vi similarity index 80% rename from src/ClientAPI Example/TCPRouter ClientAPI Example 6.vi rename to src/ClientAPI Example/Register Broadcast(status and interrupt).vi index 10aa96adbb94b4d65acf9c4ff5f35443bc1b0a6d..2a9d21164f1d3480b1ed2e469c916ad743747e4f 100644 GIT binary patch delta 15940 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 IJ4dts2S&CGwg3PC delta 15683 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 diff --git a/src/ClientAPI Example/Register Broadcast.vi b/src/ClientAPI Example/Register Broadcast.vi new file mode 100644 index 0000000000000000000000000000000000000000..18b5580b58b22275cbb00966aed5f32435536d5c GIT binary patch literal 40594 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 literal 0 HcmV?d00001 diff --git a/src/ClientAPI Example/Sync-Message with MassData.vi b/src/ClientAPI Example/Sync-Message with MassData.vi new file mode 100644 index 0000000000000000000000000000000000000000..c7539d352567396c42fdcfeca029f90f58d0f883 GIT binary patch literal 50598 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%0Dlc(DqdZhF6x_{+3~`Qg&Mdg*DQ0&O>bh-jBUwX! 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 An!Aqc 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 literal 0 HcmV?d00001 diff --git a/src/ClientAPI Example/Sync-Message.vi b/src/ClientAPI Example/Sync-Message.vi new file mode 100644 index 0000000000000000000000000000000000000000..e74af1f36c4e23c5e72130b6efa6832aaf0afbea GIT binary patch literal 9340 zcmc&)2|QF?|DUl_w(M(TPu2=WO15F_%%HMFgF$1PktJnUmO|OePPTfeWZxr-WXYDL z2qpVocK-LGCvWvWPw(>oygzf!`Q6`kzUMc0j(hGodPqG*N(u;}h5E zKnSfN5C{zTECQ_nP@vo>MIAkd%g&ZKm?GB3(%xBK2LV%dGq-cZTH-~qE?Aq3fEgqi z2Jr@4z?lSWh#-UzS)e_5#+!f!XraI#!4^QE@*A~m1@`By5GsepZG{L)T}uPx`+)-5 zL*6cMpVf-)S54=#P%J4wnAX8@vnLGllxWPG{O;tkf+uJLQc^Z-4LT%vCJfazkZ@W2 zEfkP}{rtk7#^#JL7tQKvYKdTx`uA6LtN>Fg7}OhRNREnⅈirIDiNMtN;;KLZD!F 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�f_KK5!dAVO1gxU58ZVhAmp2S7VB~D#oSdy(_ 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 LXDcIS2f%*=iL=z_ diff --git a/src/ClientAPI Example/TCPRouter ClientAPI Example 1.1.vi b/src/ClientAPI Example/TCPRouter ClientAPI Example 1.1.vi deleted file mode 100644 index c4f23f20e5f30db3c6e2b77ac70dd831b1cffa49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9187 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 diff --git a/src/ClientAPI Example/TCPRouter ClientAPI Example 5.vi b/src/ClientAPI Example/TCPRouter ClientAPI Example 5.vi deleted file mode 100644 index 4ef0b43c84890bf55d04cbff9b62f495c533057e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135409 zcmeEv2_RKl_xLr>Ga2Jj88Xj=%=4Hz^Tjpfny)DoG9^T1$W&x1LQyoxP-bN)B}0S~ zp@>N2zYi+!<@NgB_ulXS`+c9qK4+b^*Is+Awf8w^?{m*tTi-xmk%SP2rDd$HqorVM z0>i{FhQaWaVK5jR;IS|mHa5VqIc$Y(#e~HKM0o`T1jTqoY>^_o2yqDsUO^$Gu#l*j zt$?7Q9iOi=;QfyT4skI_dl4}KL0%zoaU`#Z5W<1i77%zv#ch$2wt^x8NMYoEwEak7 z30nsVNnRllF(FB_ie@Q_k zXnz3#UO`C*grJ~30x2pi@E>eH45qQ9|NCnNOj!;F!>Ix%JRAm71_{Oo5+>YGQBU99 z2aWWCE25l{ZfJQub-1!W!qo$Xgo^?v;j6oOqY-X)NWcw)U4+9zpglYR6BpWJ!?0k| zV9x@`As_%0fBwX#Whm1V^_zNsf~vX3|73t z#=b5JMh$kvK$ZKjLVkF|85uiFJ6w-Rw=J6?eJh8=LFAm`+SBq`r%ZgG_awDYVf%0A%dc5Kx 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%0DXj9d<@sdj8f)Mj`o;qHkyE0b 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 diff --git a/src/ClientAPI Example/TCPRouter ClientAPI Examples.lvlib b/src/ClientAPI Example/TCPRouter ClientAPI Examples.lvlib new file mode 100644 index 0000000..f4aea29 --- /dev/null +++ b/src/ClientAPI Example/TCPRouter ClientAPI Examples.lvlib @@ -0,0 +1,16 @@ + + + )!#!!!!!!!)!"1!&!!!-!%!!!@````]!!!!"!!%!!!(]!!!*Q(C=\>7R=2MR%!81N=?"5X<A91M</W-,<'&<9+K1,7Q,<)%N<!NMA3X)DW?-RJ(JQ"I\%%Z,(@`BA#==ZB3RN;]28_,V7@P_W`:R`>HV*SU_WE@\N_XF[3:^^TX\+2YP)D7K6;G-RV3P)R`ZS%=_]J'XP/5N<XH,7V\SEJ?]Z#5P?=J4HP+5JTTFWS%0?=B$DD1G(R/.1==!IT.+D)`B':\B'2Z@9XC':XC':XBUC?%:HO%:HO&R7QT0]!T0]!S0I4<*<)?=:XA-(]X40-X40-VDSGC?"GC4N9(<)"D2,L;4ZGG?ZH%;T>-]T>-]T?.S.%`T.%`T.)^<NF8J4@-YZ$S'C?)JHO)JHO)R>"20]220]230[;*YCK=ASI2F=)1I.Z5/Z5PR&)^@54T&5TT&5TQO<5_INJ6Z;"[(H#>ZEC>ZEC>Z$"(*ETT*ETT*9^B)HO2*HO2*(F.&]C20]C2)GN4UE1:,.[:/+5A?0^NOS?UJ^3<*\9B9GT@7JISVW7*NIFC<)^:$D`5Q9TWE7)M@;V&D,6;M29DVR]6#R],%GC47T9_/=@>Z5V>V57>V5E>V5(OV?^T[FTP?\`?YX7ZRP6\D=LH%_8S/U_E5R_-R$I>$\0@\W/VW<[_"<Y[X&],0^^+,]T_J>`J@_B_]'_.T`$KO.@I"O[^NF!!!!!! + 536903680 + 1.0.0.0 + true + 3 + + + + + + + + + diff --git a/src/Server/CSM-TCP-Router(Server).vi b/src/Server/CSM-TCP-Router(Server).vi index 7bbed7d230decbf9d6783c4911f684832199734f..85e5e0613bc30c2bbfff1600ee840377598b061e 100644 GIT binary patch delta 18846 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 delta 18846 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 delta 4482 zcmZwLXHe5yw+CRV7<#Xo&{awR=}nqcA@qO{q!XG72-5k(0f9&tDN+Rty@w)Ax}gN= z9YFypQlu+2FZbMc=FFXY)`vB}d1lS*J^RD{uu_)@*A@vmjf{=dsK^P3b>Z4DT~)Xl z0fCz{0Riz90RaK$b=FGagcEYt_^Oa_DvgTj6kWWdnTfujF}<9G7;Kh-m?o35|SsYfEkh(IXnnelaaTcU-w_PLJ6t=*6`cmgjC67U=vbi z0s;-rWE=23pid*Okn_}V{;d)f5IM>|ps(^^pLEX~m zsp)GYXUCTTOPTMorR6b7@tG2`=U-|Ae+B;j`s>rLPl3`D9T)elQH)V~N43?Wn|~PK>Mh4l8@_>P?-saQ(MiEI(kRYOi#mkRRkiUJkLGnmXZm z-27&tULj?On7h-rD4E1%l?F^*y-p>A>+iQ|pRs@Cz7*O>;o$-mSAl#tZ7rh|gA{r- zj0UhyO+ZOCo%K2DQy-&xGi|(1htUj1X}xkA66s-I-`@N$SCrTew2!1J75Ymn-wmepKf}Fg@xM z$s5FTn=d!*>JgYGfNwW*Z~oLRJ$4(pm}ub*0TQjGAc<+D=Y4`fnrN3`Woi_==0}ak z7LY{y{Li3ZCHt|n&{nQLp{*rbv3l#_!%638D+k|^Ezi9$Y|(irxDMM1qAOrsGW0)_ zk-6vE+9@e8hV8Ugk89sNFVTgNUE1Bcu$nH>Sr31=5zd4odr~CB+JEOsMPheSF`2|2 zz>g{lOPLdD$#lT}4Am~Yzrwh(vUMuiIU@7)l*2}BL16hffLCC0ngy!fG>tP$;JA~i z7a+k(&iXmwFa9J$ITG0!T1wJ)3wPg^MHY5k<{N)YzuPR$TI!TPc~>qOwT8<7h(3$h zK-oAeFl3|hW73PKy)Ds4>Mq&d{2RA3fQQ1XcZ*V~f))m`4D9vQRtG?P*N@hVQ|brL z2CbLp(>!)qNu>&IXEZuq8Ho+NZwYOoC=~Ou1gRfUr|$+bE`dusPPDuw-f={qcF6~a z{wgayGi61M9@C;8O-Bu;RQ1ywS0X1Cv^epjR=3Me!RcEN#!wBnF5*22Eqh) zwFpS0512N898u5Nm46iX@AZ{|iQliB$#hj;mQ?#|Id)b0$m?Le=e0xh;cG=UvxVhW zZlWvl-vXLJZ;8ml^FZI>3O+;WkD*2#9P#k#SAF0aH9OkFY~4fxxo^_WuudOmn2ml& z7q^c{rD574w?tuem8;3%Px;I#U%_;a!*f8bmwD?;~P)^zK~2FEA%6;8*iCoOZ+i-5(OR*E2h zpFlU{9abgOp=oT;o11q1*my&x*?U$RIU|Ir8hpg%#qFvgxLZB<2v7bz3{_+yQ)8eC z<;oxNB_1Wf6r>usfY{tR(?UA-th&l)jYF~}S6@1i%#53vx}TM*YRW9kO}cic=cvbS z5!D3x-Rg89mSUr6uc7pqSp$Z1tK1|7_jVSGy(#><#GJB9OhM==(aTEXbg$71%8&|P zTmARs+bX9YnQa{Z>SP44swlAni;iyk$9)rLY_4%#)w&IEn$_t)%%%py0j%$aZzkPK znBvCV;2Cec)u=5K27Z%v9{>HFubCU#8Ff&dQCX%R7}d<+W?qkEc@A{Mg;@Iw#MCKxI8nE%AIq7SE! z_ezgQ*q(br{f8xi{p4K5u%vNFcE{A+RoyO-96ITD_PwWU5|3Z1I^5!O7qXOT&oj$-mqeDYFMIM>ZRg1 zCPR@j=_w#C1?d^Dvv z;DJ#SrT)y9B|5zu0Vg5UT?P|KQR`j~Z_O*A(uFSr|bSL+yE$I&n6W)bQ>723$ zt5;s*p(vSIQ@Bol_Y%9gO&%lmW2MRp@`hc=xSxT(iY&C81OH5b^^S|^Vt%JgieXHu z;0M4$K@h_VEwYu-jS_G>f@x<9EqJ-!ezweG?bc78N*vU1Ya~?6z5kiui;xPwGFq`H zUQrf}LT#c|jR_qX5!D(K)Rdl6S{C6ZB5p+ZL-8-YpzSsLKyI!?cq%P<$d&{b`Fdg%Hs=nqm2#Erik6UToCWB++z~~i=6Cc|IUDC0y4>w6BtT*1p3Tdp zWRq8m#6KaE(qh8J$cgdo&T*n20fV$$h+!Lz6DXyjY5c>Su%6Vnp6s}v^lLk-6X_&m zuvSTW;S+1-Xex%T6dNJNuHMM!!x@LQJFbT(@Gcl!5MxGvNM+pD#){2Tc8_o>0&s3DKKfx}C`rBt`@ox|kMex<1sG*+qsckXmfNoay<)qNH$bn3n;+VG-X~A9 zG;n4yysO4o$X`$m7;L~H>?rv4 z&;G5%;f7sA-Pq9D6$Wat$vj`A$QA={HubfHzJ>=>o#&@4t~WTNa~l~e%-*RBe$8X4 zSa^Ngz$I?!+=`{*9xC<0-e?(op)-8B2jkBm@h|CuQ2#0ClnL}5$cS3Dnl!eM{$j@`Iy7Je$K)?23n=?|WJzvQ9A z(OXUTxd=vhl-W>MLst)uT$Ye)ad)xjsXVN%fo0UGv^(l^a9n>RAE?fX?X}N^k0It` z5AshiZ%!nS6$1C_fbXMKdq=lxJ~+}N6GeIj6%-Yf`Ghaco?R>M8{+%s`EKRjQPv^T z6@#6&dV-}T<+`obeQ*_xvhAZKoEUS_4Ny!Y!fwAooY~B>+HZEwL59Y$-@H{$`G>IL zTYCj+8hWrMVtJnhso<39XA^J<;|++Jc`idE{a#+^o#Zi)t7PIts`bQD6MIG-`14QB zi;y;Q4fJ5ulJDNTQy;%WrQ*)I3H!?R zyW@LU+}YFPU|f02 z#BXya;A@${HW#DABRa06{&zi(M6-BQsCb{;Pr5r`<Z}KXBB?$8Rv8Rh@0(gYj5VP}4SqR_E&2E-4F@9EGyWP(U z-9GDEy3#qh+zPou3RO&q;E{oxbIDX~yJ3=*cB~rX_V4{A6m46nibP{Ti$h^8ve!ld zHFLoMt+hLh3Da?G$9W2zPjaGbty^>*1G9~1TTQa(3VuF5@d(myuXPiTU{JJNNj}2^ z(ZUI2CJ6_FA5x>y@SSPDY~o4$#(<~YHuyu2U)o@gU*iiq zgiT2k4>x$22OO2W|7Sw^NNKlEjCm(KqPNJ(uI39{jGK_T5Oa-8toemFTMU3E_Hl8? ziGii5=)ux0JuA#eh06LPC4ru+s5AXz1zp`;z=X;GZ=Y3mrP<+#=h;ZKuELxsvHy{Y zNf^iBEB7#e&}(%`(Zt9L=bW1Tw$hPZ{!t zImQ?tE4?g3&UWuBUJg_aRr3xlm6zPf4=mrM--;=bgvDB&P8#Hd}%yyg*oirlSGcE6aiwFri)gj4gW8nkwS%(HzA#vc8*tRK9Ug{P60>6R2t9uml7cZ>XRwA#1Xs zG6d6(+$N^_Kl=#GM>lq2;P&eEe&X-KU)TR8@Xh}c!9ND&{TCDZ|ArL(7nAuHQ~ul3 iulqlTAl?6EaPPks`p=we$Nv~e-_G0C>bmVyKmvuYidJ)!Z6Y2QsW!h-~ zus8DT1t-7};aFjrHX1@}1d9q3a^bo`F?sSFg$PPIeJNh}=*B%TSA6f6UcUW{nQ{#=vd(oIUai?0J2F1x# zeH6Nx1B@Ye*hvt037Fh(aheGQPjy3Vq!E2PT6L!9}-$5idQge_-5~yfb9Ui z?T})lzJz7=iwQ>`ns58&XY_^I`33B${7Dz)Qag2F)=Fly`;IJ}lhm5G?R?{_Ps&Y1 zCjz3czNe@GgJo_-yKpSSjqme-#vmIO(BpTys%fDMQh53`8=tHYd}22m!%!Z1FCxA_!oYdp3|NYcH=hnfQ8vI~nvON~MC{O{PS)VJP>+Jz zHJZE&$}(l3>{Sg)hlB%dj(7VOledrO2iz;w{?;^?6iZ&G&^$TT5QO4t6IvV1%b+JQ_&#}WZq$km{F#>~D&S`n^ksLGfchfs`I4!QEx+-wau565h3 zq&i36U+(?0Qa$<-@B#Pb_#1C??8_4xQ9Uftiku#L6`S)a4i~^BA+(Pe92A)$0ZXr% zGM;86$3WN4BX1JN44Kk%VHsI=s>5ae&*`^JiPF7GUD2el3S>vxG-*!>|4NfYXA8 z*CmXR!&BU4(8oV9xk-^O*Y~NPZ9|5+LdA@#CTx21(Dhi&;nD1opda?v_7=nN(rHT) z%j5?eN$LP*FqHL`$n8g035J!>O|m7v;j}%^3dvNGhVU~BlCd@~CDm;6tj?bPerS90 z;jwGFCy8Ss0KC)Yt6O(BzeP2Biw{W%UCy;1AZZ(yH;dL4` zV&$Lb514vsR~GuK{tz3jE#(!t?31Wgbatry4BQ%2JhEp$87L|pw?i=78!>KDuPDPf z{)!G(-(se<-_jM_5?A}UmrN4l8fl*m6snYh_nxh|EYgs9XwB z^%Io~4VPUye}8#OcuuMh_gtlGyVO6zqCi`1Q_tb@+`2`(g0UsGo${}%`=m0$v_zk%e{?LXXMM{BQb;&*fRdxg zZ=rDbcVWZAz{#s|JpZ+Td~Jk$1`elfE&+q`2&b(ud4zU&&*8IDLeVs!K2^6)?sdZbWG(pxHfN9o-)xAr+jpVKo6#CtK~`SPTQ{?#G;@J$Mm)*Jr4S_s#D9g`Ok-H zQ{<)WwucnsaUri@`RS?*=)yo>3#Y9ZG{|7;!!~?<#;0p-EX6uvHs~_k=Ae&No z7*sjEaw==ofxN+nwU5b69}po2L8n(u;);kjNeW>&>wb%bf{lF^{iX#a;e2Z!J@z;f zyo)X}fLZ|~|NQB@B&6a|2kh~-*Jd4^ZK!)F7r7+c;ZONV#m|28>{mk73i%z~&X&fX zC*}U9Xi|_Pz)Unr<;tBXJJyhT?aFO(Jdve6AaKm_P9%wdjn-WH^YUU+q^V`#m|F&K(H4Kpwbd){N$@-`tz_X(ZN%vL<#L4(FU;dVvM6It zV23u)NkeT@ZN%gY4nV%-79xd%h5X1O1HTE9`iPfxer{3Nh1c-4P0rRq3#jWIBV%7> zpSgCtH;~-HNS{w3PuSZ`eKz*Ov7>aIS`vvyoG)luAYP}acFC7G)26s6RR#?n`X8{? zK`kLb>YfjIlE!He$-+aFEbvDh<<>mby$ur8a}OVU4EXsVwR{7b7^>AS2he@Q;%D(V zC&~?_Jw8RUP((c&Eyfp}e7H^iD(6i4wWX!y7lH4peoqBoL{hg?Dq`Xoj4P}~LG%e; zU};*hIg=DEoM%Kk$gZ&J@Af3cspLykfzKv@_F>#l0cT9LnJHZ=4oNeGQInWvY0x?m zI~@vxs(QLk@!C$gEwKcfGrW3GQK6_1)vx#N`!1W=TTM-V&k!{HkZks$<>O~dD-*eS z>0HR35d4l5vWVg}0v*l|r$3+Tuf~7}rN>?>Hm;RCo*34k_^+u>&dq zB~e{y&c_eg8bmy2u(#~$<5<7rS`;`O09DjchBkpkauI2+k|0K}*-k>iT~>MFLE)L< zoN0g9+0bJ0bs~5jZTlu`N2H0+@MFNKrvX|QW}ns6{S_~B{lmz-ykFx+v0eN->b>Bs z;vWw7!6qagROn1)aHtPy;R`AKv<~Lni_W7xg9SZK{ z#XY~F%k*Q!SbPsPeEX6o2$|=ltYc}^V`(-bz4(*`Civzq%3pQm<~U77T*0b)K6mUm z;_}^Vn}+#87b`iLUX4tOiAD1&KoP!P*O?het4RLtpn)%Vvs9ml$~p3mB!3i0ZZIFL z;L~->j@cK%ZST^YaQRcNp_P;Ou+vAl=ZzH3(U&_NH!fXyQ;Px zJXO{uNmsH(&@7MD%V!81$S^d=T6((`G~ziU29q>^*0blI`JX^a zN$s06Io;+kJ=rW1?p{pUzyWi!sQI*SqtzOC4%Wz_nDi5Q0}YGS-^TDJ?}IrD_szag z%1utOO&H1$!M8-`qoK5<ew5B@$_vRYl;hTu!GgPDZrCyX+3|<7H-r~Q6o2A9cQ4EWQSu|bc(380 zmgyLNm+E+TZ_xJZ&9uyC>6}W!G5t@iW|rguRY9__sW9!Bb0Bh&LD8b|@g1k^=@zNM zv}hw4M;ljsE3;|5@W8jAgbsQJdzZmQPRC#?+nY>=$)TSH;W%U1+Eo_Rv{qG6rOU(` zcS()i&bvF8KIrj>Yk?G_-R3*CrDh&=#Rf;h`{t=$BDv!)qY|AmRC#N8mF7(F3N@BQ z&m6Tz{9g=@9)M_O?K9XUM`t;p1FK1nB1;iK6zQ1}KG{?p)Vj^b(FsQx*(cYAD_~EG z^W(KDyMt#cVxgdOIt-s>a?4~FHcJvHuNw|4Ki8wcU9rxOI&cVB`!ScpkfU$W(+VM>g@DMBbTdg1b%I7IuF*+mB;?Zey9XpUtruP zkanlu?*3`L6DccL?!a5{&g&`sg(cVL*6r1oTFoCD}MbO+W@Chlw zKAUka9@V>>f*kGw9M2~?>tE_Uqu8DdrnwHYlQ}poUd@v^f(*CyqFflkq3p!D5m961 zOi2lY0og#kq7&GhTa(*+wsz2JfI_d__iAyxl5tq{ApX+nI*X_hmqMe{3T`=`wwP(Q z#2C3tdI{Q?N}ZYHH4;J-$_1q{5yf?ir!8TXckr+Ed0XC7Fq}W09Q%3Xd^p1K=lH~? zA>^Iw1|A zYZMPsqtqhmbe?duO9PKP%5imw6ewww3#e{n*<5FNf-l&h-!PT)WZ@{PB2D2DF|HhX}7~>N*ztipw5%4i4ess~&uWpDQgM z8Xbh~jiP&Ghn#2Tas@9lhzeY(c0PG>@pG-$^NYP9)0c#&Gs0|pjG3J1{cTk5yiBR} zI4^$a{Iy{0LQ5e0?MKHO6m*_pliaVT$3C{!{m{S1dhuJOj^g3*E%+ke=&`4N)15>|LKQE=#X&JpO}YwK z@iZ0Vk#oYV?kzIYqsHRk31V}&N@eae*7t*t3m!N7=n`A!amn>AHLO|egIhzcF*T7t z94ycWvZ-&sNF4>=7G5R(MOV=|9|BI8E_M?nea7^P6oVkN(dPNgYCfT4?WB@R0s&?~B zv>;D1F=Q}J*#sfD;SDgcjFu`XU_EnpX7kMBn!qvmKAIn`ACVf`^+|Q zRv0FafGE6eE2h{{hVqIz|M#8T6I zVOALvPqKl(N!Fx0zR4x-@jxl;M2=H93~6I)!U4JZX!Q+6|MNLd)5%0&v;ZUkew&Zl zJ2>dfudH16#8z;!lB5wJ$| z7JcFJUcdm%RtwM;bpRYaAMItN-U?jCXO zI6?mchrA8XrjASQ)9hD5AmnZ!-`KS=KaMYLNXga#{Ogtt)Xo^hWU{hgp5PRZ+4fF5UI&6i%vQ zYjbc7Saxk)`v6$9T{BEr<)kXZx|ihDT%>jf^GL86W|srtZ2;F9Hr@jm)2?o?nI{E@ z;0)s>m6&C23a0l;qs~3v!)-Pq-b4Qlx3QlC_9EVc6w|I6{T|5rUyJfE&L(WH@}|8s zl_tI*-=7z&D`Xeg^Y;CSA=w%)iay_Kjy|`x?z^jHHh=oWAs{ld)4<1M<2B;)C;XSz z0M{h&8q>7Av`=0qf?pY!+d!|;hoSQ^M6cf%Vpiv49KO98f_nwOO85Z(N^^y9Xn0K| z88~kDNR{d!LAKk>)Y(}w&iaYgi|mhMNOC$|@owtzE=s)NI(vA#o}CYl2Ek7O&v*WH zxl%V}2NBO-Y$E$nBB-~kcXiuXIT+8Ah^8iiRKeK~qpQ1h14sYLnJ;Qm8~beK{Sq{u z^0_>IB&f#?$>=5A;@D~Sx%NHq+br^!hVOv>($y){#}in1>f2 ze7L@{1NWUZZpiWuaXMHqQ3WQO-(K`@gn**v&e=Leh_#I`DxYG<_6~?X-(z2JQdfA4 zc3ZGUZ!4{_W3C=8K*Q|wCDlt}M$F=+ZCV88uzc>P3~X))N&LFqxyJ#t%L#Hy{Qmg7 z0~<`=`n!+eR;o90>4B&(G8UY=0xEzwmb|7{e3KRmGh33zE_J-6)>zyy2)_(?>Fh_0 zjY-RSRK?evehGEZ(e+!k3j40&*0Fy@5?IJ=s&f`$<(wA8BR3Tqf%fyl={pPp!Jv#~i{-<6lJds^)I_g7+3847lUmZ-~3EqCenx zCp>fcM9mxOot?_`4MxAO^!XfPH;5E7{-%>xY-_b4`O$=pDiHFx4tD~HSw>2Ar~2?a z(53Sfa_W(jZ@dn%TZhzs-ns?ov(JAzidzfZyq#-VeD@N60_mfSXYnhy4~0rM&GxYc zK|-+G&^enV1_ord%Qp4>;ab$>Zky0%5*Dy>1-H|B;QjVuvJp6t|`dKT8#6Q=rcjr^lg=up_OX?Ey|!2-w~>n3W&DSaMEYlo4|OH zliIshWluXEZ#uJd!fiO1-~2E350gmG7=;q;bfJ@TB@;%{-W+-I{@iNo zb|y0>a3s?-G}P&i9suf}q6$5+x&`&u?Lr$a@m|d^k|>!R1JpEdmX%#rj_$YWB)$?x zsNKYJ6is}9=a5AEWqihn&_VULyR(L{sETT|3> zMJVGpfNVC~gc_-s1|f$2h~<63W3t^<^eqefV!RT;rrQe(yA)uU`7LK3NlZ1m07*UH znDz%Sd-zFJXIT#NuVvxO?3v%gYY%yb=j_hxW`FN1yBy)6T zHFH>bcb+UDcJqFr>BUZ9v-l0!x%dQu_+-ubr}CLs2Yq9<)QYd&=V=3_4dKIfW}Z- z?r(DuTiwOn!qi^WAt?=Rd;f{mu3l~cQxDPp%6|??Y*N58B`Mz>j7*N+CD7HMSCLus zx|3eR*D(7aLdGfmzw77u3auq_kjGO0?%sQsj`!~^nQcV!6C3Y3CTbV$9j86CX7Bkt z6LY4oI4gP_XT=GRL|=#w;6t-Ppjvtsdj$Xjf&;;W-u&-DjvNT*KQo$CP>lc5;J88muP?V+Vh^b|@XSw% za^8&a&cTT;-Xh{{|GDaavVae60fGm&;F3VF;O_2rAh;9U;l-Wc79dz~IUL+IxVyVc@ZfUu?QYdp z-Ftg#TArDnAMGRZUe49Tfy8uPLJ_FRG~p0y*D-K=6nl5QyThzW}8e zfqiR9EUtvGQ>e|NH9s43A3Im`1pA5#EQ3I#>mHfAIySbr^LT>pZg#PTMsrXofrESN z2oYahOi=RAMqJfTg>1ROUot=%*mHa~{1J^greEl#YizLX~|rxBPp2>z|#04zT~t zuqbf@sK!${*v13Og!$v%fNZ2=?DN~cL4|X!M;S82g$H`Q50lB6yZs@iO~&WKujkWclcVnBp*(LiPt?8w^qP_p`?> z;m^#P4~~v$&<=`wjz{5dgH0xZXz{bTn@hjF8ZOM&xSHQ63=IKI!Q5$b_QZsp3x#sR zsbGhfr~AA~*PddWn4i1EC*yXMC=fJg+eT@f&1!NnRabT$l z`O9T>W<{IQ5cId;^&A`vfMB}rF5$+g)E=ZF<`r0 zr~HR)x;3`_k5v+-HKL{SJsY(K$*{HU$FY)Y6FPXHOQJ1Hc%>~&!n>Tgr7^VFx}xG$ z_;A;sKOc9*)?>KZtJ`u?UAubpeT%c1_r+Jx!=fAhX8ebF1W&pE7u{-xs$_1liil4h zijA>FCp6q+Zb&Q$@TT9sn%gom+7mA64)_fBaU;uUNcb=Q zV&pE*c$aVsK!0d^m=1q5LYK5lU!uap@eci)Dp+-$EYBQU3pdu9Pi2X^yjTnsV!mW+ z-D^ZiPGxV#`TvlJV_b`n3NG=Ty^xW!2;71VJKy2<1sT46uH%NRSAz|!z#W5+HgM)7 zKEXje$6PW^4+%FN0Xe;r4PW zJXXFVLei%@g`-?e*^_ffA>xVg!Mp*!(!`2##bHn}=;WL(3S29~t{>fGN70k8~Q4S?_H4CW= zULY+WmCpo)_2FEN#rUSCewH2@f1m9Q@uG(|7ZqPhoe9@!!;jHlmo@1Qra`MM#5IfR z2W#27;JU84wj#wWv{)NoJwFSu;podvv)dx4Izj(h4-_btn(sebTobK-?jMa%+TI>@&75#;`(!DwFLsw# zXYN_|LUcI!;j@y?vemwCfU7}N%~jy=A%Dc!r8-ZtVhV?$37H$?qV+3|s(sz4E800Q zfg;#XXahaAcKMzBlR{hZldp0)>C}q|vsYr04Fi4~;%cRY-{v&II)(%)x7a7uG{mDE zy*H?GF!pSV!zb6w-uqJ5Iveb&sxAdP7*Ppl+7LKI9+S!7LItTFHkDqr+@Udd6Yu`8 z-pc1yswt^oO7Bd(=q22J{O0NXaML3J$shdKiu_i#5mpk|VP8MQ*jxnj+`f}QGg`FU zMh5Oho6lEagpT6y;M6sREvv>7@LcJcI)_aW?qryJ`ei%}7qv!BhBdxO-rRM9@jX_f z5G2(;2RK&eb-vX{=$mIGr)xDs=p)4sbE#gWWSdD7GZK$u+mwD!q!cl?-}@i{6woTm z$kWzRt4oAmh+h|%79>u8vg8toF~jY>FvUfSwyj;xeWQ|xm6@3r+5Mi2TCvbXelx6S zb|yM4b82cDRf7@dll&$skeZ0O!u=?X)7H+y#{I&kEQ6JI z@8^|G*%PXdUEM~Z$+vuY4G<6kxc>n7Y*_Y1*$g&?u1S~ZzZzuu#lOUmSbrcnrEf*!?dgURKl-~lj=0vcI$KU30j z97p+#qRg}j$uemIVt)NL^p#8cy8$(E*iVtiQ@!!MZRziS>=VrK@>Z*z63lz@Zpx%& zoO7ywS~&;^g}xBeC~T`Hn4^;(hyJ|hI}9-2M}gSwzE}9N`#$ClMTY3CdRJ<;+TBk) z+fAmYMb+u|Yt+vslXM_KGQqRZdgu%*6ssA>ljr@{uMY(u1J5}pBIC+NVIHZ z(H-|GCYVjuXp|?g&vFN-xvk*(?|TRY|91xb$AUnKhxnC1Gzi3G?~&Q7>+6CikpemL zgG{fUNf?NVB9rf7HB+Oa7*Z`!^z`E(+=ssxnL&};=z@=iM@IJOgUN#LBDDAuPTmrz z#6Qf+4!ivkNlMamBIme08H=>5%W{L6dT1+>0@j*N&$X?sT1@!>{i zjhdOQPxW-`7RQPdO`hDJAO2Ec2sdAz;fZ7^(C{r_h2d<5JQ@~^h{7Qy><54Ov0xC6 zC~pGaG@Qc@d$gaB<2OZYplv?KeG;M(5}^l40oy}E%CSe;Ql%{#q>hv2kY3s@t9X08 z7|?j3e)_U@?tknXM;`dJ@XlVL51PJ{ifTDE-TMkh=O4`g%`7fju^b*%NSyE{OJ|(8S$#Zw^JJV;HbXas z+G12%lv?digcim&pmAD1wcyq~MS7gGc(SWN zRvvobcxs7_#8LXMX$Ds|xOJsY>_6q8r|`0wuAAGO37_s|0s#^}i4Vnoj5i?wi^GUU zf=hyHPLZz`gfocgIAt!DJa9aI`3hmZE7|hnQuxak8_I>Q1rFY91H=NwY^qs^%%xxHCnG@Te?&}3=vRc2eYuX%-8+!OD(`m^dqF2^C;?m!yG*xIeO5cJ(rhGf@1gcf z3D2&wmQ>CyOLx$9F7Hfww=-Nc9nQQnZ88<`ZDxc z1CcUnvi}E72RGQvlDz_N&AP51``9?VeDNMnguz4V1ZyRCL^`Qa=^&Pa+xP+ZPd+<@ z#GNAZBnT&WZc1jmlr~i>wu~9bo@D4TZDv|IXW%EtR%AHeDy49+>ZLJ5Dh=5iSalrFp3I{ziFY4rX0FNO9hSoTmcPRm z>NO;JdN;22l6JYq+;f=>mf@Y1Lz?9~ks}O9Z(t>b5Ze>{AEP!%B6NXU*uL3Rly`;F zq!+M#oNs(+VB}_+U_6S;^hL_46eqHck@D7d*q{=&^1t?U4kznB`HMoRCISZPm#V@~ z8S~IUV8uxqcwamTIfyVf78w2jHIkL;06Q{erCwY77IZCKC_|~z+%SueJ=rGi@>Lkq zcqK&Ly-Wd>iiV08;{fQ2=1X-T(6v()r{9$l+wjG)x|Uid>|Ss#5!B4U#j=fWi$3Ab zB_F!slRt2-;9|hP#@W;gVxMO>>o!Fck&Dmi2qyy_7}*Z@sQZ>FM8e@wc~Rs*Od`Vu z-voY>_KW08Jj`p22+a0FA$Sbv_+1py7|L(RQmU)lU}{C;Qw8|RY!9R65FCs-pG04IHvCN&ioQZ!7c#%r`SE1J`);dmUawlw?05`=6K&ZzB% zM`d31Q~oNkdRjzvujB6S%iPbCH-ETN4BT8Vbe5Y0Rix@GBA@gRx18U*b(!b%Phwr4 z9oU{|3(M4>v~~bya~RYMbz|eNAPTrk~%(yP2 zGg?s%@8b~V>1r069?d=vo5JBcM`!X{-uQLfNv!-<&k``3Wk^4f<3+Fd_XIbiqoX8s znyxmqsSIKim7-J^JB+h^g;N7T(nrx)P)K4?c+w$%)s0FX-|wXQ=7 zodCcp(60ljGjH2lxgl%4>pH(pe)Glo(#h%%-20m{_uKA=R&z!g)D;G7XhoTI=avut zkfl|V)PAyxK?G=Vyf<)jMg|pKJ zi#8qZbVTA$K#na%dV6D2X+M}9f$s# zw^@ol!Vcm%b7M+hHh;)l{!KmwZ(r5G@ zR%|`Q`(8f8a|_q22F45fzh#Zr!y3MS@BpB#g7&aYnTj3pr5)f!e}j}cExmgeR|r3N z6$x9DT%rh&C2M_8X;2Gfj3@cS-L10k!I%LTn9t>n6q<(jQMu3EEvdGK+w|lFIWE804iL(# zN2j3UlNVgf>K?Qo=2;}AaGyHNKfJCPdVAf*E}Gdcr3<+CJwO6i#8CP`FgmZ^_ojNm ziQr_%YD|CI-0A+Q!w!mLNa~!HI||*SYTw`~d{dMnxl|E3#WZHk;mPh{l;kEk6Vs-t z29Hj{NKEgiB(o<|YHt4(L?>=~pcd7`aEO2L+lDC~Fj^}DUGP8|W9dhi}5W4x-an054_6jE(1_XOVZL}`fWAp(^n`?U`#{9ZtY67SC zd`kyWunFcL_^F$yEMcXHgBoEXa+*ojLTK!Fa%Mo^o%rIsQl z$iJl2OT$r}u)%68YLQI@wA~6Lk(CdWvLg zD$Be83nuMjoP0n*yAM3l89dRcB<^y_b&w9UTxFmkVXOPgu3dX_v#r@T@U1iGlId62BhiN_72NX9Hb{&htl-Ba)c z28U3PMAdE}a_U{KvilI8?N`9oqIt!fl%YXqbeigk36kj6 zvcgHq;I1%QV@xpfwB%+!0G^I_nxA=+^l2Ems&@|i&vF#@YeT(r(FzwSL!AOrj;AHI zE*xf-Bss4gT`#I$1o94Zx$nTRo$1pYuqmew&in2 z07zp?Dj(vKG45R-$is)*Vid*3#V)*%kDA4o8po_Z;6~juDMxFbm&L6JD+h*lE{i&E zbOi4Sx0MN!;NF*azB(=v)tVQ2KRsS~Y3KwkIH_|ZX`07oem88$=ofYitRcBJF4>fj z)+k<@**%#IWMBMH(tnMT$3VvLJ}|ETJ7D2yr7OC{04dD>;8^aN&^AgKKQDCzdkIhP zsZuVB#wAB7@bFcX{gEJr@0?6nx=)aWA2+U89K#Ht&+EXAizAe%Di$9%Mj;y{NE(0{ z?TB|5-CkgY#s7hGl^2Unsq2J^ZJ5jI2bNC|(j<^NqjqH*D~~#(9q$r)%|9|82cSJa zOfn*8^rNHw3kBM~#}nTzR|Q2^qY*FmNg3`gtUftRia8RZe*f)&ml>^e9Wdb%~MxgpQ2q7t|aJ`ep5@JIFn>s-UJ@*2LbQFX=&xB zUNs~Jejhw;=0*PO)D=|5*GAQ%Y0rr*#tRsTyP2iTD8rhG-3&0lz=Dq+<1;PPf6dcGZ?mPgL@SN< zayJl>%H4VR2Hq~Xv&y&iOjL8(hHr5AV4czWk5{1yaRtTZS;C${A5hsK9{Qa6u@&JG zWR{k?s$YO0kYh{R8}#My_{*U|m5b1o`*w!B5+HGY4KCM7U7f!06|J7=ZVp_;@gJ~B z^-6_9Wa%8!>0BU8$xRT@Fsug}L+P7j#L51|iI81@?D)-#`Yh_Z&38 zRu(=gv@YJU{4qDd1g^}rjlK~Jjv2DN8e6Rfe*9+Mq8tDF=~sugZ$ZBdF_5e_DT2kb&9 zqC{B;jZiCOe@aCG6mv0OD3FRM5zuLl8Mt#FlZk34AL;Qc0GYz5bL|%uX3?y19Dzwp zpW=5?n0eya;xMnBvo$};8&eS3>Wx{-i+7ngm)-)suBqkHD|DEvH$hBX(|BTwn*7~% z{PMRhEV?1tk_T%R|j^fh(L( z#_fY)E+tq@`ZqN1Q|r`0u5h^8iTmPx~jdqR?-7kWJ)qj#iwklxhk`(UJPbNq2 z5~%A`s)&nz#o~DzM=TT*Z1i^N9pE%D?|D}TP-A_B*S>3|^)?Rp<6IHua*4lA;HBwZ z;hOM`k=L)Za{lW3dT(;$?87{E+v6)7)_y2U4GDvdEMNwL0l|Xc{#DSC24VhF+xe>( zK{J1?%KwUW|5t3{5Bg{Pdq1+8nZ;K#3Jsb6>%e^cd)WWqT|@nEd##4qMHDVE&>a_+ z;Z0(iJD*zG->iSh>tFe|_x0C*5a_?mLHa+;|G&)H_8*2NqULFD!lG*C>_DL|rp)SQ F^*;yXq2K@j