---
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
* 具備無損傳輸層的傳輸函式庫