---
# System prepended metadata

title: CommDispatcher2
tags: [tirc-library]

---

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