这一篇算是有时间就补一下以前做的事。之前有提过Go有Encode/Decode SML/HSMS消息的库,该库对SECS2基协议解析基础完备的支持,易于使用的话需要对其进行一些细微的修改。

这个库的学习可以通过各个文件的Test案例进行了解,基本上使用起来不会有太大的问题。

库简单讲解

库中对Go语言Duck Type的运用是很优秀的,而且也是很常用的。

例如,通过对SECS-II数据节点的抽象;对HSMS消息的抽象。这些在看源码时,都是可以有心学习的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type ItemNode interface {
// Size returns the array size of the data item.
Size() int

// Variables returns the variable names in the node, in the insertion order.
Variables() []string

FillVariables(map[string]interface{}) ItemNode

// ToBytes returns the byte representation of the data item.
ToBytes() []byte
}

type HSMSMessage interface {
// Type returns HSMS message type.
// Return will be one of "data message", "select.req", "select.rsp", "deselect.req", "deselect.rsp",
// "linktest.req", "linktest.rsp", "reject.req", "separate.req", "undefined".
Type() string

// ToBytes returns byte representation of the HSMS message.
ToBytes() []byte
}

其中,经过我对比其他库,这个库的优点是对消息中变量的支持,可以事先插入变量在SML消息中进行占位,在后面对这些变量占位进行填充。

1
2
3
NewIntNode(2, "var1").FillVariables(map[string]interface{}{"var1": 1})

// <I2[1] 1>

但是,这里存在一个问题,这个库对数据获取的没有提供一个好的方式,例如我想获取List嵌套下的某一个Int中的第二个元素,这个就没办法了,需要我们自己进行拓展。例如Java中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// github.com/kenta-shimizu/secs4java8

Secs2 ss = Secs2.list(
Secs2.binary((byte)1), /* 0 */
Secs2.ascii("MESSAGE-1"), /* 1 */
Secs2.bool(true), /* 2 */
Secs2.list( /* 3 */
Secs2.list( /* 3,0 */
Secs2.ascii("KEY-1"), /* 3,0,0 */
Secs2.int4(100, 101, 102) /* 3,0,1 */
),
Secs2.list( /* 3,1 */
Secs2.ascii("KEY-2"), /* 3,1,0 */
Secs2.int4(200, 201, 202) /* 3,1,1 */
),
Secs2.list( /* 3,2 */
Secs2.ascii("KEY-3"), /* 3,2,0 */
Secs2.int4(300, 301, 302) /* 3,2,1 */
)
),
Secs2.float4(400.0F),
);

System.out.println("# Get value by index");
System.out.println("getByte(0, 0):\t" + ss.getByte(0, 0)); /* 1 */
System.out.println("getAscii(1):\t" + ss.getAscii(1)); /* "MESSAGE-1" */
System.out.println("getBoolean(2, 0):\t" + ss.getBoolean(2, 0)); /* true */
System.out.println("getAscii(3, 0, 0):\t" + ss.getAscii(3, 0, 0)); /* "KEY-1" */
System.out.println("getInt(3, 0, 1, 0):\t" + ss.getInt(3, 0 , 1, 0)); /* 100 */
System.out.println("getInt(3, 0, 1, 1):\t" + ss.getInt(3, 0 , 1, 1)); /* 101 */
System.out.println("getInt(3, 0, 1, 2):\t" + ss.getInt(3, 0 , 1, 2)); /* 102 */
System.out.println("getInt(3, 1, 1, 0):\t" + ss.getInt(3, 1 , 1, 0)); /* 200 */
System.out.println("getInt(3, 2, 1, 0):\t" + ss.getInt(3, 2 , 1, 0)); /* 300 */
System.out.println("getFloat(4, 0):\t" + ss.getFloat(4, 0)); /* 400.0F */

修改

库中对消息节点的解析已经将数据解析到节点的values中,但是没有获取数据的method。

1
2
3
4
5
type IntNode struct {
byteSize int // Byte size of the integers; should be either 1, 2, 4, or 8
values []int64 // Array of integers
variables map[string]int // Variable name and its position in the data array
}

这里就是要将其暴露出去,给到外部进行获取。

ItemNode增加三个方法,Values、Get、Type.

Values:获取节点上的数据;

Get:获取节点,主要是List节点;

Type:标识数据节点;

1
2
3
4
5
6
7
8
9
10
11
12
type ItemNode interface {
...

// Get returns list itemNode
Get(indices ...int) (ItemNode, error)

// Values returns data list
Values() interface{}

// Type returns item type
Type() string
}

List节点Get函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (node *ListNode) Get(indices ...int) (ItemNode, error) {
itemNode := ItemNode(node)
if len(indices) == 0 {
return node, nil
}

for _, index := range indices {
if itemNode.Type() != "list" {
return nil, fmt.Errorf("not list")
}

listNode := itemNode.(*ListNode)

if index < 0 || index >= len(listNode.values) {
return nil, fmt.Errorf("index out of bounds error, size : %d", len(listNode.values))
}
itemNode = listNode.values[index]
}

return itemNode, nil
}

其他节点Get函数:

1
2
3
4
5
6
7
func (node *IntNode) Get(indices ...int) (ItemNode, error) {
if len(indices) == 0 {
return node, nil
} else {
return nil, fmt.Errorf("not list, node is %s, indices is %v", node, indices)
}
}

DataMessage添加获取节点数据的函数:

1
2
3
4
5
6
GetAscii(indices ...int) (string, error)
GetByte(indices ...int) (byte, error)
GetBoolean(indices ...int) (bool, error)
GetFloat(indices ...int) (float64, error)
GetInt(indices ...int) (int64, error)
Get(indices ...int) (ItemNode, error)

这样,即可获取到指定数据节点的信息,效果和Java库是一致的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
func TestMessageNode_ProducedByFactoryMethod_HSMS11(t *testing.T) {
var tests = []struct {
description string // Test case description
inputMessageName string // Input to the factory method
inputStreamCode int // Input to the factory method
inputFunctionCode int // Input to the factory method
inputWaitBit int // Input to the factory method
inputDirection string // Input to the factory method
inputItemNode ItemNode // Input to the factory method
inputSessionID int // Input to the factory method
inputSystemBytes []byte // Input to the factory method
}{
{
description: "S127F255 W H<->E 消息名称, upper boundary, nested list node",
inputMessageName: "消息名称",
inputStreamCode: 127,
inputFunctionCode: 255,
inputWaitBit: 1,
inputDirection: "H<->E",
inputItemNode: NewListNode(
NewListNode(NewListNode()),
NewListNode(
NewIntNode(1, 33, 55),
NewASCIINode("test"),
NewBinaryNode(1, 2, 255),
NewBooleanNode(false, true, true),
NewFloatNode(4, -1.1, 0.0, 1.0),
),
),
inputSessionID: 0xFFFF,
inputSystemBytes: []byte{0xFF, 0xFF, 0xFF, 0xFF},
},
}
for i, test := range tests {
t.Logf("Test #%d: %s", i, test.description)
msg := NewHSMSDataMessage(
test.inputMessageName,
test.inputStreamCode,
test.inputFunctionCode,
test.inputWaitBit,
test.inputDirection,
test.inputItemNode,
test.inputSessionID,
test.inputSystemBytes,
)
fmt.Println(msg.dataItem)

fmt.Println("-------------------")
node, err := msg.Get(1, 0)
fmt.Println(node)
fmt.Println(err)
getInt, err := msg.GetInt(1, 0, 1)
fmt.Println(getInt, err)
getAscii, err := msg.GetAscii(1, 1)
fmt.Println(getAscii, err)
getByte, err := msg.GetByte(1, 2, 2)
fmt.Println(getByte, err)
getBoolean, err := msg.GetBoolean(1, 3, 1)
fmt.Println(getBoolean, err)
getFloat, err := msg.GetFloat(1, 4, 0)
fmt.Println(getFloat, err)
}
}


Test #0: S127F255 W H<->E 消息名称, upper boundary, nested list node
<L[2]
<L[1]
<L[0]>
>
<L[5]
<I1[2] 33 55>
<A "test">
<B[3] 0b1 0b10 0b11111111>
<BOOLEAN[3] F T T>
<F4[3] -1.1 0 1>
>
>
-------------------
<I1[2] 33 55> <nil>
55 <nil>
test <nil>
255 <nil>
true <nil>
-1.1 <nil>