在前面的ModbusBase里我们还留了个坑:ILinkLayer连接层,以及其实现ModbusRTUFrameLinkLayer与ModbusTCPFrameLinkLayer。
首先我们先考虑,ILinkLayer是最外层包裹数据的,其数据部分无论RTU的CRC校验,还是TCP的6各byte头,都与其内容无关(最多看一下长度)。因此,总数组最适合在这里定义。
prop BaseData : Array<UInt8>
基础数据的属性。也就是ModbusBase里使用的BaseData。它从完整数据里切片。完整数据由不同链路层决定。
func initLen(len:UInt8):Unit
用于ModbusBase初始化时,将自己的长度传给链路层,让链路层知道要初始化一个多大的buffer空间。
func setBuffer(buffer:Array<Byte>):Unit
用于ModbusBase将一整个数组丢过来~~~不同的链路层自行去掉链路层的数据后,剥离出BaseData给ModbusBase。以及考虑是否需要进行响应的校验
func getFrameData():Array<UInt8>
获取包含链路层的全部数组。
可以看到,我们链路层作为最外的一层壳,功能与Modbus分离的很清晰,但又依赖于Modbus组件好之后作为外壳发送出去。因此我将ILinkLayer作为Modbus的一个属性对ModbusBase进行包装,帮助ModbusBase功能完整。
ModbusRTUFrameLinkLayer的功能是对ModbusBase增加CRCL与CRCH
class ModbusRTUFrameLinkLayer <: ILinkLayer{
var buffer = Array<Byte>()
public func setBuffer(buffer:Array<Byte>){
if(buffer.size < 4){
throw IndexOutOfBoundsException("数组长度不足")
}
this.buffer = buffer
}
public func initLen(len:UInt8):Unit{
if(len < 2){
throw IndexOutOfBoundsException("数组长度不足")
}
this.buffer = Array<Byte>(Int64(len+2),item:0)
}
public prop BaseData : Array<UInt8>{
get(){
if(buffer.size > 2){
return buffer[..(buffer.size - 2)]
}
else{
throw NoneValueException("数组未初始化")
}
}
}
public func getFrameData():Array<UInt8>{
var crc = crcCal(buffer,(buffer.size - 2))
buffer[buffer.size - 1] = UInt8((crc & 0xFF00) >> 8)
buffer[buffer.size - 2] = UInt8(crc & 0x00FF)
this.buffer
}
public func checkCRC():Bool{
var crc = crcCal(buffer, (buffer.size - 2))
crc == ((UInt16(buffer[buffer.size - 1]) << 8) | UInt16(buffer[buffer.size - 2]))
}
static func crcCal(buffer:Array<UInt8>,len:Int64) : UInt16{
var crc = 0xFFFFu16
for (i in 0..len){
crc = crc ^ UInt16(buffer[i])
for (_ in 0..8){
if((crc & 0x0001u16)!= 0){
crc = (crc >> 1) ^ 0xA001
}
else{
crc = crc >> 1
}
}
}
crc
}
}
ModbusTCPFrameLinkLayer则是对ModbusBase增加六个字节头:
内容 | 解释 |
事务处理标识 | 可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。 |
协议标识符 | 00 00表示ModbusTCP协议。 |
长度 | 表示接下来的数据长度,单位为字节。 |
代码如下:
class ModbusTCPFrameLinkLayer <: ILinkLayer{
var buffer = Array<Byte>()
var transaction_id_bak = 0u16
init(transactionID!:UInt16 = 0){
transaction_id_bak = transactionID
}
public func setBuffer(buffer:Array<Byte>){
if(buffer.size < 8){
throw IndexOutOfBoundsException("数组长度不足")
}
this.buffer = buffer
}
public func initLen(len:UInt8):Unit{
if(len < 2){
throw IndexOutOfBoundsException("数组长度不足")
}
buffer = Array<Byte>(Int64(len+6),item:0)
TransactionId = transaction_id_bak
}
public prop BaseData : Array<UInt8>{
get(){
if(buffer.size > 6 && buffer.size < 262){
return buffer[6..]
}
else{
throw NoneValueException("数组未初始化")
}
}
}
//ModbusTCP报文帧的事务序号
public mut prop TransactionId: UInt16 {
get() {
if(buffer.size > 6){
return buffer.getUInt16AB(0)
} else{
return UInt16(transaction_id_bak & 0x0000ffff)
}
}
set(value) {
if(buffer.size > 6){
buffer.setUInt16AB(0,value)
transaction_id_bak = 0
} else{
transaction_id_bak = value
}
}
}
var protocolId : UInt16 = 0
//Modbus报文帧的协议字
public prop ProtocolId: UInt16 {
get() {
protocolId
}
}
//ProtocolID一般不用修改。为兼容特殊应用,单独保留设置选项
public func setProtocolId(protocolID:UInt16){
this.protocolId = protocolID
}
public prop TotLen : UInt16 {
get() {
UInt16(BaseData.size)
}
}
public mut prop UnitId : UInt8 {
get() {
buffer.getUInt8(6)
}
set(value) {
buffer.setUInt8(6,value)
}
}
public func getFrameData():Array<UInt8>{
//设置协议标识
buffer.setUInt16AB(2,ProtocolId)
//设置长度
buffer.setUInt16AB(4,TotLen)
buffer
}
}
再补上接口的代码:
package modbus
interface ILinkLayer{
func setBuffer(buffer:Array<Byte>):Unit
func initLen(len:UInt8):Unit
prop BaseData : Array<UInt8>
func getFrameData():Array<UInt8>
}
我这些放在了一个文件里:src/modbus/modbus_linklayer.cj