--- tags: tirc-library --- # CommDispatcher2 專案內包含: * 基礎資料傳輸架構 * 命令執行架構 * 任務執行架構 * Console命令列架構 ### maven ```xml= <dependency> <groupId>tirc.lib</groupId> <artifactId>comm_dispatcher</artifactId> <version>2.13</version> </dependency> ``` 相依專案 ```xml= <dependency> <groupId>tirc.lib</groupId> <artifactId>OOError</artifactId> <version>1.0.0</version> </dependency> ``` # 基礎資料傳輸架構 ## 主要目的 讓上層的操作者在使用時可直觀的控制物件,將物件轉換至位元陣列的運算封裝在傳輸層中。 ```mermaid graph LR A([object])-->C[CommDispatcher2 encode]-->B([byte array]) ``` ```mermaid graph RL A([byte array])-->C[CommDispatcher2 decode]-->B([object]) ``` ## 主要元件 ### ICommNode 被各個元件實作,可將各種元件進行串接 ```mermaid classDiagram class ICommNode{ +open() BaseErr +close() BaseErr +send(byte[] payload, int offset, int size) : BaseErr +onReceive(byte[] packet, int offset, int size) +send(ICmd command) BaseErr +onReceive(ICmd command) } ICommNode <|-- CmdCodec ICommNode <|-- DataLink ICommNode <|-- Physical ICommNode <|-- KeepAlive ``` ### ICmd, Command介面 被傳輸資料的基礎介面 ```mermaid classDiagram class ICmd{ +getCmdID() String } class Command{ +encode() byte[] +decode(byte[] buf, int offset, int size) } ICmd <|-- Command ``` ### Command實作 Command是一個抽象類別, 需要實作encode與decode兩個方法, 將屬於命令內的資料進行byte轉換。 並且在類別的宣告時, 需使用CmdDefinition進行CmdID的設定 ```java= @CmdDefinition( cmdID="20" ) public class Query_horizontal extends Command { @Override public void decode(byte[] buf, int offset, int size) { //將byte轉換成需要的資料 } @Override public byte[] encode() { //將想傳送出去的資料轉換成byte } } ``` ### Physical 實作各種與實體層的讀寫 例如:Uart, Socket, ZMQ ```java= // 以Push, Sub為接口的zmq為例 PushSubZMQClient zmqPhysical = new PushSubZMQClient(); // 設定sub的url zmqPhysical.setSubUrl("tcp://localhost:6666"); // 設定push的url zmqPhysical.setPushUrl("tcp://localhost:6667"); // 串接至傳輸層 cmdReceiverAdapter.connectNext(keepAliveNode) .connectNext(localTaskExecutor) .connectNext(pcCmdCodec) .connectNext(zmqPhysical); // <-- here // 啟動傳輸層 cmdReceiverAdapter.open(); ... // 停止傳輸層 cmdReceiverAdapter.close(); ``` ### DataLink 可實作與連結物之間的特殊邏輯 例如:無損傳輸LoselessDataLink ```java= // 建立 LosslesDataLink instance LosslessDataLink dataLink = new LosslessDataLink(); // 串接至傳輸層 cmdReceiverAdapter.connectNext(exeManager) .connectNext(ptCmdCodec) .connectNext(dataLink) // <-- here .connectNext(physical); // 啟動傳輸層 cmdReceiverAdapter.open(); ... // 停止傳輸層 cmdReceiverAdapter.close(); ``` ### CmdCodec 實作ICmd與位元資料之間的轉換 * encode: 從ICmd上取得該Cmd的位元資料後,加入可識別該ICmd的符號後傳出 * decode: 收到byte後, 識別出此資料應屬ICmd,產生instance後將其餘的位元資料餵給該instance ```java= // 實作CmdCodec的encode與decode method public class ROSCmdCodec extends CmdCodec { private static final Logger logger = LoggerFactory.getLogger( ROSCmdCodec.class ) ; private int headerLen = 7; public ROSCmdCodec(String prefix) { super(prefix); } // 將收到的byte資料轉換為Command物件 @Override public Command decode(byte[] packet, int offset, int size) { if(size < headerLen) { logger.error("receive packet don't have header data! size: {}", size); return null; } String cmdID = String.format("%d", packet[offset]); // 依據cmdID來決定要產生哪一個Command的instance ROSCommand cmd = (ROSCommand)newCommand( cmdID ); if(cmd != null) { cmd.decodeHeader(packet, offset, size); cmd.decode( packet, ROSCommand.HEADER_LEN_INCLUDE_ID, size-ROSCommand.HEADER_LEN_INCLUDE_ID ); logger.trace("<< decode : {}" ,cmd.getDumpStr()); } return cmd; } // 將要傳出去的Command物件轉換為byte @Override public byte[] encode(Command cmd) { byte[] payload = cmd.encode(); byte[] output_buffer = new byte[ payload.length + 1]; output_buffer[0] = (byte) Integer.parseInt( cmd.getCmdID() ) ; System.arraycopy(payload, 0, output_buffer, 1, payload.length); logger.trace(">> encode : {}; {}", cmd.getDumpStr(), Arrays.toString(output_buffer)); return output_buffer; } } ``` 串接至傳輸層 ```java= // 建立 instance, 傳入的字串為存放command物件的package名稱 ROSCmdCodec rosCmdCodec = new ROSCmdCodec("tirc.comm.ros.cmd"); // 串接至傳輸層 cmdReceiverAdapter.connectNext(exeMgrAdapter) .connectNext(keepAliveNode) .connectNext(rosCmdCodec) // <-- here .connectNext(zmqPhysical); // 啟動傳輸層 cmdReceiverAdapter.open(); ... // 停止傳輸層 cmdReceiverAdapter.close(); ``` ### KeepAlive 包含下列兩種節點 * KeepAliveSenderNode: * 負責定時在空閒時傳輸keepalive訊號 ```java= // 建立 instance, 預設在沒發送資料時傳送KeepAlive命令的時間是3秒 KeepAliveSenderNode keepAliveNode = new KeepAliveSenderNode("YOUR_KEEP_ALIVE_SENDER_NAME"); // 設定timout監聽者 keepAliveNode.setListener(new IKeepAliveSenderListener(){ @Override public void onNeedSend() { //進行命令傳送 } }); // 串接至傳輸層 cmdReceiverAdapter.connectNext(keepAliveNode) // <-- here .connectNext(localTaskExecutor) .connectNext(pcCmdCodec) .connectNext(zmqPhysical); // 啟動傳輸層 cmdReceiverAdapter.open(); ... // 停止傳輸層 cmdReceiverAdapter.close(); ``` * keepAliveReceiverNode: * 若是過久沒有收到訊號,則傳出斷線訊號 * 斷線狀態下若收到訊號,則傳出連線訊號 * 本身也可查詢目前的連線狀態 ```java= // 建立 instance, 預設的檢查時間是1秒, timout時間是6秒 KeepAliveReceiverNode keepAliveNode = new KeepAliveReceiverNode("YOUR_KEEP_ALIVE_RECEIVER_NAME"); // 設定狀態監聽者 keepAliveNode.setListener(new IOnlineListener(){ @Override public void online() { // 上線事件被觸發 } @Override public void offline() { // 離線事件被觸發 } }); // 串接至傳輸層 cmdReceiverAdapter.connectNext(exeMgrAdapter) .connectNext(keepAliveNode) // <-- here .connectNext(rosCmdCodec) .connectNext(zmqPhysical); // 啟動傳輸層 cmdReceiverAdapter.open(); // 可直接詢問狀態 keepAliveNode.isOnline(); ... // 停止傳輸層 cmdReceiverAdapter.close(); ``` # 命令執行架構 ## 主要目的 協助上層的使用者接收需要回傳的命令, 如果超過設定的timeout時間, 則輸出錯誤, 若是沒有設定timeout, 則直接使用下一層的命令傳輸, 不做任何處理。 使用者使用時, 會block在send方法上, 直到收到回應或錯誤, 並回傳給使用者。 ## 主要元件 ### ExecutorManager 實作ICommNode, 輔助需要確定有回傳訊號的命令, 以sn和命令編號來對應命令, 預設每個命令接預期接收與本身同號碼的回應, 若要修改為接收不同的命令, 需覆寫Command上的getAckCmdID與getFinishCmdID。 ExecutorManager使用方法範例 ```java= // 建立 instance ExecutorManager exeMgr = new ExecutorManager(); // 串接至傳輸層 cmdReceiverAdapter.connectNext(exeMgr) // <-- here .connectNext(keepAliveNode) .connectNext(rosCmdCodec) .connectNext(zmqPhysical); // 設定命令平行傳輸分類 // 不同的執行緒可用不同的CmdExecutor平行傳輸 // 呼叫iniExecutor時會進行CmdExecutor的ICommNode註冊 // 務必在呼叫前就先進行傳輸層的串接 CmdExecutor executor = exeMgr.initExecutor(); exeMgr.bindCmdToExecutor(HelloWorld.class, executor); CmdExecutor longCmdExecutor = exeMgr.initExecutor(); exeMgr.bindCmdToExecutor(MoveToPosition.class, longCmdExecutor); exeMgr.bindCmdToExecutor(MoveToPositionACK.class, longCmdExecutor); exeMgr.bindCmdToExecutor(MoveToPositionFINISH.class, longCmdExecutor); exeMgr.bindCmdToExecutor(MoveToPositionABORT.class, longCmdExecutor); // 啟動傳輸層 cmdReceiverAdapter.open(); // 傳輸需要檢查timeout的命令 Result<ICmd> ret = commDispatcher.send(cmd, timeout); if(!ret.isSuccess()) // 發現有錯誤,進行錯誤處理 ; else // 成功,取出回傳命令 ICmd cmd = ret.geData(); ... // 停止傳輸層 cmdReceiverAdapter.close(); ``` 回應命令覆寫範例 ```java= @CmdDefinition( cmdID="13" ) public class MoveToPosition extends ROSCommand { @Override public String getAckCmdID() { return "14"; } @Override public String getFinishCmdID() { return "15"; } } ``` ### IConditionChecker 可設定傳輸前檢查, 有異常則回傳錯誤, 無異常則回傳空值。 ```java= IConditionChecker beforeChecker = new IConditionChecker() { public BaseErr check() { if(keepAliveNode.isOnline()) return null; else return new OfflineError(); } }; exeMgr.setBeforeChecker(beforeChecker); ``` ### CmdExecutor 命令等待核心邏輯物件, 封裝在CmdExecutorManager中 ### ISNGenerator 可自行建構序號產生器, 在ExecutorManager建立時輸入。 ```java= public ExecutorManager(ISNGenerator snGenerator) { if(snGenerator == null) this.snGenerator = buildDefaultSNGenerator(); else this.snGenerator = snGenerator; } ``` 未輸入則會使用預設的sn產生器, 會產生0~100的序號, 到達100號之後從0重新計數。 ```java= new ISNGenerator(){ private Integer global_sn = 0; public synchronized Object genSN() { if(global_sn >= 100) global_sn = 0; return global_sn++; } }; ``` # 任務執行架構 待完成 # Console命令列架構 待完成 # Release Note ## ### v2.13 release data: 2021.2.8 ### * 修復ConsoleMenu lib 變數未清理問題 * ICommNode未實作方法輸出類別名稱 * CmdReceiverAdapter加入event傳遞 * ExecutorManager繼承ICommNode,並移除ExecuteManagerAdapter * 列印命令logger架構修改 * CmdExecutor的回應命令比對,除了sn外加上cmdID * fix: LosslessDataLink收到Keepalive命令的sn加入記數 ### v2.12 release date: 2020.1.10 ### * ConsoleMenu的命令預留空間加長 * ConsoleMenu提供命名功能 * ConsoleMenu加入Submenu的單元測試示範程式 ### v2.11 release date: 2020.1.7 ### * 修正StatusChecker重覆加入相同的regex問題 * 在QueueTaskExecutor與SingleTaskExecutor中補上ICommNode.onException的呼叫 * CmdCodec增加其他排序方法 ### v2.10 release date: 2019.12.27 ### * 加入CmdCodec可以讀取多資料夾的Command的功能 ### v2.9 release date: 2019.12.9 ### * 增加命令執行中的重覆執行busy通知機制 * ExeManager加入後令壓前令機制並整合先前機制 ### v2.8 release date: 2019.10.9 ### * 增加RequestCommand與ResponseCommand Annotation ### v2.7 release date: 2019.10.1 ### * 增加Socket元件 ### v2.6 release date: 2019.8.14 ### * 增加Task平行處理平台 ### v2.5 release date: 2018.11.12 ### * 移除IDataLink與IPhysical,全部皆繼承ICommNode * 增加KeepaliveNode來確保通訊的對面端還存活著 ### v2.4 release date: 2018.10.23 ### * 加入Release note * 具備無損傳輸層的傳輸函式庫