:Namespace MODBUS ⍝ Driver for serial Modbus communication in RTU Mode ⍝ ⍝ The following command are supported ⍝ FORCE_COIL_ON = Force ON a Single Coil ⍝ FORCE_COIL_OFF = Force OFF a Single Coil ⍝ READ_REG_FLOAT = Read One FLOAT Register ⍝ READ_BYTE = Read Multiple Registers as Bytes ⍝ PRESET_REG_FLOAT = Write a Float Value to a Register ⍝ PRESET_REG_INT = Write an Integer Value to a Register ⍝ ⍝ See the comments of the individual methods for more information (⎕IO ⎕ML ⎕WX)←1 3 3 ⍝ System variables I2H2←{'0123456789ABCDEF'[⎕IO+16 16⊤⍵]} ⍝ Convert one Integer (0-255) to 2 Bytes Hexadecimal I2H4←{,⍉'0123456789ABCDEF'[⎕IO+16 16⊤255 255⊤⍵]} ⍝ Convert one Integer (0-65535) to 4 Bytes Hexadecimal H22B←{16⊥¨16|(-⎕IO)+(⊂'0123456789ABCDEF0123456789abcdef')⍳¨((⍴⍵)⍴2 1)⊂⍵} ⍝ Convert a pair of HEX characters to Bytes (0-255) ∇ r←sendObj FORCE_COIL_ON(modbusAddr coilNumber) :Access Public ⍝ To Force ON a Single Coil ⍝ ⍝ sendObj = SerialPort or TCP object ⍝ modbusAddr = Address of MODBUS device ⍝ coilNumber = Coil Number ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Value if Success or Literal Error if failure r←I2H2 modbusAddr ⍝ Address of MODBUS device r,←'05' ⍝ Force Single Coil Command (5 decimal) r,←I2H4 coilNumber-1 ⍝ Coil Number (coil #1 is 0 in the program) r,←'FF00' ⍝ Coil ON hex command r,←CRC_H r ⍝ Checksum Calculation r←H22B r ⍝ To Bytes r←sendObj.SendReceive r ⍝ Communication ⍝ To Exit on Communication Error :If 0=1⊃r ⋄ r←0(2⊃r) ⋄ →0 ⋄ :Else ⋄ r←2⊃r ⋄ :End ⍝ To Check if Byte Count is Correct :If 7=⍴,r ⋄ r←0 'Byte Count Incorrect Error' ⋄ →0 ⋄ :End ⍝ To Check if the CRC of the Answer is Correct :If ~(CRC_D ¯2↓r)≡¯2↑r r←0 'CRC Error on Answer' ⋄ →0 :End ⍝ To Check if the Meter Address is Present :If modbusAddr≠r[1] ⋄ r←0 'Modbus Address Not Present Error' ⋄ →0 ⋄ :End ⍝ To Check if Command Number is Present :If 131=r[2] ⋄ r←0 'Command Number Error' ⋄ →0 ⋄ :End :If 5≠r[2] ⋄ r←0 'Command Number Not Present' ⋄ →0 ⋄ :End ⍝ To Check if Coil ON is in the answer :If 255 0≢r[5 6] ⋄ r←0 'Error in answer' ⋄ →0 ⋄ :End ⍝ Success r←1 ∇ ∇ r←sendObj FORCE_COIL_OFF(modbusAddr coilNumber) :Access Public ⍝ To Force OFF a Single Coil ⍝ ⍝ sendObj = SerialPort or TCP object ⍝ modbusAddr = Address of MODBUS device ⍝ coilNumber = Coil Number ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Value if Success or Literal Error if failure r←I2H2 modbusAddr ⍝ Address of MODBUS device r,←'05' ⍝ Force Single Coil Command (5 decimal) r,←I2H4 coilNumber-1 ⍝ Coil Number (coil #1 is 0 in the program) r,←'0000' ⍝ Coil OFF hex command r,←CRC_H r ⍝ Checksum Calculation r←H22B r ⍝ To Bytes r←sendObj.SendReceive r ⍝ Communication ⍝ To Exit on Communication Error :If 0=1⊃r ⋄ r←0(2⊃r) ⋄ →0 ⋄ :Else ⋄ r←2⊃r ⋄ :End ⍝ To Check if Byte Count is Correct :If 7=⍴,r ⋄ r←0 'Byte Count Incorrect Error' ⋄ →0 ⋄ :End ⍝ To Check if the CRC of the Answer is Correct :If ~(CRC_D ¯2↓r)≡¯2↑r r←0 'CRC Error on Answer' ⋄ →0 :End ⍝ To Check if the Meter Address is Present :If modbusAddr≠r[1] ⋄ r←0 'Modbus Address Not Present Error' ⋄ →0 ⋄ :End ⍝ To Check if Command Number is Present :If 131=r[2] ⋄ r←0 'Command Number Error' ⋄ →0 ⋄ :End :If 5≠r[2] ⋄ r←0 'Command Number Not Present' ⋄ →0 ⋄ :End ⍝ To Check if Coil OFF is in the answer :If 0 0≢r[5 6] ⋄ r←0 'Error in answer' ⋄ →0 ⋄ :End ⍝ Success r←1 ∇ ∇ r←sendObj READ_BYTE(modbusAddr regAddr regQty) :Access Public ⍝ Read Register(s) as Bytes ⍝ ⍝ sendObj = SerialPort or TCP object ⍝ modbusAddr = Address of MODBUS device ⍝ regAddr = Starting register ⍝ regQty = Number of registers to read (2 bytes per registers) ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Bytes in register(s) if Success or Literal Error if failure r←I2H2 modbusAddr ⍝ Address of MODBUS device r,←'03' ⍝ Read Holding Register Command (3 decimal) r,←I2H4 regAddr-40001 ⍝ Starting Address (address 0 is 40001) r,←I2H4 regQty ⍝ Number of Registers To Read r,←CRC_H r ⍝ Checksum Calculation r←H22B r ⍝ Hex To Bytes r←sendObj.SendReceive r ⍝ Communication ⍝ To Exit on Communication Error :If 0=1⊃r ⋄ r←0(2⊃r) ⋄ →0 ⋄ :Else ⋄ r←2⊃r ⋄ :End ⍝ To Check if Byte Count is Correct :If 3>⍴,r :OrIf r[3]≠¯5+⍴,r r←0 'Byte Count Incorrect Error' ⋄ →0 :End ⍝ To Check if the CRC of the Answer is Correct :If ~(CRC_D ¯2↓r)≡¯2↑r r←0 'CRC Error on Answer' ⋄ →0 :End ⍝ To Check if the Modbus Address is Present :If modbusAddr≠r[1] ⋄ r←0 'Modbus Address Not Present Error' ⋄ →0 ⋄ :End ⍝ To Check if Command Number is Present :If 131=r[2] ⋄ r←0 'Command Number Error' ⋄ →0 ⋄ :End :If 3≠r[2] ⋄ r←0 'Command Number Not Present' ⋄ →0 ⋄ :End ⍝ Success r←1(3↓¯2↓r) ∇ ∇ r←sendObj READ_REG_FLOAT(modbusAddr regAddr) :Access Public ⍝ Read One FLOAT Register of a MODBUS device ⍝ ⍝ sendObj = SerialPort or TCP object ⍝ modbusAddr = Address of MODBUS device ⍝ regAddr = Address of Register ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Value if Success or Literal Error if failure r←I2H2 modbusAddr ⍝ Address of MODBUS device r,←'03' ⍝ Read Holding Register Command (3 decimal) r,←I2H4 regAddr-40001 ⍝ Starting Address r,←'0002' ⍝ 2 Registers To Read r,←CRC_H r ⍝ Checksum Calculation r←H22B r ⍝ To Bytes r←sendObj.SendReceive r ⍝ Communication ⍝ To Exit on Communication Error :If 0=1⊃r ⋄ →0 ⋄ :Else ⋄ r←2⊃r ⋄ :End ⍝ To Check if Byte Count is Correct :If 3>⍴,r :OrIf r[3]≠¯5+⍴,r r←0 'Byte Count Incorrect Error' ⋄ →0 :End ⍝ To Check if the CRC of the Answer is Correct :If ~(CRC_D ¯2↓r)≡¯2↑r r←0 'CRC Error on Answer' ⋄ →0 :End ⍝ To Check if the Address of MODBUS device is Present :If modbusAddr≠r[1] ⋄ r←0 'Serial Address of MODBUS device Not Present Error' ⋄ →0 ⋄ :End ⍝ To Check if Command Number is Present :If 131=r[2] ⋄ r←0 'Command Number Error' ⋄ →0 ⋄ :End :If 3≠r[2] ⋄ r←0 'Command Number Not Present' ⋄ →0 ⋄ :End ⍝ Success r←3↓¯2↓r r←1,(FLOAT_TO_DEC r[⍳4]) ∇ ∇ r←sendObj PRESET_REG_FLOAT(modbusAddr regAddr value) :Access Public ⍝ Write a Float Value to a single Register ⍝ ⍝ sendObj = SerialPort or TCP object ⍝ modbusAddr = Address of MODBUS device ⍝ regAddr = Address of Register ⍝ value = Value ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Literal Error if Failure r←I2H2 modbusAddr ⍝ Serial Address of MODBUS device r,←'10' ⍝ Preset Register Command (16 decimal) r,←I2H4 regAddr-40001 ⍝ Starting Address r,←'0002' ⍝ 2 Registers To Write r,←'02' ⍝ Byte Count r,←DEC_TO_FLOAT value ⍝ Decimal to Hex r,←CRC_H r ⍝ Checksum Calculation r←H22B r ⍝ Hex To Bytes r←sendObj.SendReceive r ⍝ Communication ⍝ To Exit on Communication Error :If 0=1⊃r ⋄ →0 ⋄ :Else ⋄ r←2⊃r ⋄ :End ⍝ To Check if the CRC of the Answer is Correct :If ~(CRC_D ¯2↓r)≡¯2↑r r←0 'CRC Error on Answer' ⋄ →0 :End ⍝ To Check if the Address of MODBUS device is Present :If modbusAddr≠r[1] ⋄ r←0 'Serial Address of MODBUS device Not Present Error' ⋄ →0 ⋄ :End ⍝ To Check if Command Number is Present :If 131=r[2] ⋄ r←0 'Command Number Error' ⋄ →0 ⋄ :End :If 16≠r[2] ⋄ r←0 'Command Number Not Present' ⋄ →0 ⋄ :End ⍝ Success r←1 ∇ ∇ r←sendObj PRESET_REG_INT(modbusAddr regAddr value) :Access Public ⍝ Write an Integer Value to a single Register ⍝ ⍝ sendObj = SerialPort or TCP object ⍝ modbusAddr = Address of MODBUS device ⍝ regAddr = Address of Register ⍝ value = Value (0 - 65535) ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Literal Error if Failure r←I2H2 modbusAddr ⍝ Serial Address of MODBUS device r,←'10' ⍝ Preset Register Command r,←I2H4 regAddr-40001 ⍝ Starting Address r,←'0001' ⍝ 1 Registers To Write r,←'02' ⍝ Byte Count r,←I2H4 value ⍝ Integer to Hex r,←CRC_H r ⍝ Checksum Calculation r←H22B r ⍝ Hex To Bytes r←sendObj.SendReceive r ⍝ Communication ⍝ To Exit on Communication Error :If 0=1⊃r ⋄ →0 ⋄ :Else ⋄ r←2⊃r ⋄ :End ⍝ To Check if the CRC of the Answer is Correct :If ~(CRC_D ¯2↓r)≡¯2↑r r←0 'CRC Error on Answer' ⋄ →0 :End ⍝ To Check if the Address of MODBUS device is Present :If modbusAddr≠r[1] ⋄ r←0 'Address of MODBUS device Not Present Error' ⋄ →0 ⋄ :End ⍝ To Check if Command Number is Present :If 131=r[2] ⋄ r←0 'Command Number Error' ⋄ →0 ⋄ :End :If 16≠r[2] ⋄ r←0 'Command Number Not Present' ⋄ →0 ⋄ :End ⍝ No error r←1 ∇ ∇ r←CRC_D v;i;ii;lsb ⍝ Calculate the CRC of a Decimal String r←16⍴1 :For i :In ⍳⍴v r[⎕IO+7+⍳8]←(¯8↑r)≠,⍉(8⍴2)⊤v[i] ⍝ XOR est ≠ :For ii :In ⍳8 lsb←¯1↑r ⋄ r←0,15↑r :If 1=lsb r←r≠1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 :End :End :End r←⌽2⊥⍉2 8⍴r ∇ ∇ r←CRC_H v;hex;i;ii;lsb ⍝ Calculate the CRC of a Hexadecimal String hex←'0123456789ABCDEF' ⋄ v←(-⎕IO)+hex⍳,v~' ' ⋄ r←16⍴1 :For i :In ¯1+2×⍳0.5×⍴v r[⎕IO+7+⍳8]←(¯8↑r)≠,⍉2 2 2 2⊤v[i,i+1] ⍝ XOR est ≠ :For ii :In ⍳8 lsb←¯1↑r ⋄ r←0,15↑r :If 1=lsb r←r≠1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 :End :End :End r←2⌽hex[⎕IO+2⊥⍉4 4⍴r] ∇ ∇ r←FLOAT_TO_DEC v;exponent;sign;significand ⍝ Convert a Float Quadlet (32 bits) to Decimal Number v←,(⍉(8⍴2)⊤v)[3 4 1 2;] sign←,1↑[1]⍉v exponent←2⊥8↑[1]1↓[1]⍉v significand←2⊥¯23↑[1]⍉v r←(¯1*sign)×(1+significand÷2*23)×2*(exponent-127) r←(0≠+/[1]⍉v)×r ∇ ∇ r←DEC_TO_FLOAT v;exponent;sign;significand ⍝ Convert a Decimal Number to a Float Quadlet ⍝ THE 2*¯127 IS TO TAKE CARE OF ZERO (ERROR IF ⍟0) sign←¯1=×v exponent←⍉(8⍴2)⊤127+⌊2⍟|v⍝+2*¯127 significand←⍉(23⍴2)⊤⌊(2*23)ׯ1+2*1|2⍟|v⍝+2*¯127 r←sign,exponent,significand r←4⌽'0123456789ABCDEF'[⎕IO+2⊥⍉8 4⍴r] ∇ :EndNamespace