Until a few days ago the communication between my PLC and the Satel Integra 64 alarm installation had been done through a relay outputs of Integra (CA-64 O-R), which closed circuits connected to digital inputs (DI) of the PLC. All had worked well but due to a limitation in the number of wires available between the alarm and the PLC, any further extension of the sensor/event number was impossible.
A week ago I have installed an alternative solution based on RS232. On the hardware side a WAGO 750-650/003-000 module was needed and also Satel INT-RS. I have additionally downloaded Serial_Interface_01.lib library (available at Wago site) and programming instruction published by Satel. I have also received na example of a program from Wago. For the help in making it all work - great thanks to the Wago guys!
As to configuration: on the side of INT-RS – functioning mode controled by microswitches (integration with other systems): switch 5 ON. On the side of 750-650/003 – Baudrate: 19200, Data frame: 8 databits, Stopbits: 1, Data bytes: 5, RTS/CTS: Disable…
Please be warned that the presented code is a work of an amatour and can be against any programming principles. It has, however, been tested and works so far fine:
In the variable definitions:
VAR (*interface definitions*) CommContext : SERIAL_INTERFACE; Init : BOOL; Error : BYTE; IsOpen : BOOL; Send : BOOL:=TRUE; (*send launch, reset on send end*) RMessage : typRING_BUFFER; (*received message*) Message : POINTER TO BYTE; MessageLEN : BYTE; (*default message to be sent*) Message7F : ARRAY [0..6] OF BYTE:=254, 254, 127, 216, 97, 254, 13; (*variables supervising communication flow*) SendEnds : F_TRIG; SendStarts : R_TRIG; WaitForData : BOOL; StopWaiting : TON; FrameComplete : BOOL; LastRecord : INT; (*stores last reviewed byte*) SyncSignal : BOOL:=FALSE; EndSignal : BOOL:=FALSE; (*variables for RMessage analysis*) mStart, mEnd : INT; crcHigh, crcLow : BYTE; RMessageCRC : CRC_Calculate; Command : BYTE; (*variables for preparing message on demand*) FoundNewState : BOOL; MessageState : typRing_BUFFER; MessageStCrc : CRC_Calculate; CrcLenExtension : BYTE; (*variables storing data received from Integra*) Sensors : ARRAY[0..55] OF BOOL; NewStates : ARRAY[0..39] OF BOOL; i : BYTE; END_VAR
The program continuously sends to the alarm system the command 0x7F meaning „list of new states”. The answers are place in the NewStates array. At the beginning of each cycle, the array is analyzed. If any of the states = TRUE, the next commands, which are sent, ask for those states. After all of them are red (asked for), the program returns to sending 0x7F command. Here is the code:
SendStarts(CLK:=Send); (*trigger sensing the begin of sending*) IF SendStarts.Q THEN (*if sending starts*) (*prepare the variables*) FoundNewState:=FALSE; i:=0; (*checking if there is an unread Integra64 state stored in NewStates ARRAY*) WHILE (i<40) AND (NOT FoundNewState) DO IF NewStates[i] THEN (*new event found*) FoundNewState:=TRUE; (*marker to finish searching*) NewStates[i]:=FALSE; (*reset the state in NewState ARRAY*) CrcLenExtension:=0; (*preparing new message to be sent*) MessageState.Data[0]:=254; MessageState.Data[1]:=254; MessageState.Data[2]:=i; (*command number=index in the ARRAY*) MessageStCrc(m:=ADR(MessageState), start:=2, end:=2); (*calculate CRC*) MessageState.Data[3]:=WORD_TO_BYTE(MessageStCrc.hi); IF MessageStCrc.hi=254 THEN (*if CRC.hi=254 add 240 after it*) MessageState.Data[4]:=240; CrcLenExtension:=1; END_IF; MessageState.Data[4+CrcLenExtension]:=WORD_TO_BYTE(MessageStCrc.lo); IF MessageStCrc.lo=254 THEN (*if CRC.lo=254 add 240 after it*) MessageState.Data[5+CrcLenExtension]:=240; CrcLenExtension:=CrcLenExtension+1; END_IF; MessageState.Data[5+CrcLenExtension]:=254; MessageState.Data[6+CrcLenExtension]:=13; MessageState.Index:=7+CrcLenExtension; END_IF; (*End of IF new event found*) i:=i+1; END_WHILE; (*end of loop for checking NewState ARRAY*) (*the clue of this IF statement – decide what message to send*) IF FoundNewState THEN (*if there is an new state to be red*) Message:=ADR(MessageState.Data); MessageLen:=INT_TO_BYTE(MessageState.Index); ELSE (*otherwise send the standard 7F message*) Message:=ADR(Message7F); MessageLEN:=7; END_IF; END_IF; (* End of IF Send Start detected*) (*definition of the Communication module*) CommContext( xOPEN_COM_PORT:=TRUE, bCOM_PORT_NR:=2, cbBAUDRATE:=1920, cpPARITY:=0, csSTOPBITS:=1, cbsBYTESIZE:=8, cfFLOW_CONTROL:= 0, iBYTES_TO_SEND:=MessageLEN, ptSEND_BUFFER:=Message, (*here is where the message pointer goes*) xSTART_SEND:=Send, utRECEIVE_BUFFER:=RMessage, xINIT:= Init, bERROR=>Error, xCOM_PORT_IS_OPEN=> IsOpen); SendEnds(CLK:= Send); (*trigger sensing the end of sending*) IF SendEnds.Q THEN (*if sending ends…*) WaitForData:=TRUE; END_IF; (* Impulse used to start all over if correct answer is not coming*) StopWaiting(IN:= WaitForData, PT:=t#1s); IF WaitForData THEN (*looking for the bytes 0xFE 0xFE = frame starts repeated in next cycles despite finding one 0xFE 0xFE in case new sync frame signal comes analysis starts from where it ended in the previous cycle*) WHILE (LastRecord<RMessage.Index-1) DO IF RMessage.Data[LastRecord]=254 AND RMessage.Data[LastRecord+1]=254 THEN SyncSignal:=TRUE; mStart:=LastRecord+2; (*message start marker*) LastRecord:=LastRecord+1; END_IF; LastRecord:=LastRecord+1; END_WHILE; (*reset LastRecord to where the 0xFE 0xFE ended*) IF SyncSignal THEN LastRecord:=mStart; END_IF; (*looking for bytes 0xFE 0x0D = frame ends*) WHILE (LastRecord<RMessage.Index-1) AND SyncSignal DO IF RMessage.Data[LastRecord]=254 AND RMessage.Data[LastRecord+1]=13 THEN EndSignal:=TRUE; mEnd:=LastRecord-1; (*message end marker*) END_IF; LastRecord:=LastRecord+1; END_WHILE; (*if a complete frame has been received*) IF SyncSignal AND EndSignal THEN (*check CRC, watch out for 240 bytes - they should be dropped*) IF RMessage.Data[mEnd]=240 THEN (*if one of the CRC numbers is 0xF0, drop it*) mEnd:=mEnd-1; END_IF; crcLow:=RMessage.Data[mEnd]; IF RMessage.Data[mEnd-1]=240 THEN (*if one of the CRC numbers is 0xF0, drop it*) mEnd:=mEnd-1; END_IF; crcHigh:=RMessage.Data[mEnd-1]; mEnd:=mEnd-2; (*function block for calculating CRC*) RMessageCRC(m:=ADR(RMessage), start:=mStart, end:=mEnd); (*check if CRC is okay*) IF RMessageCRC.hi=crcHigh AND RMessageCRC.lo=crcLow THEN (*analysis of the received command*) Command:=RMessage.Data[mStart]; CASE Command OF (*zones violation*) 0: IF (mEnd-mStart)>15 THEN (*check for required response length*) FOR i:=0 TO 6 DO Sensors[0+i*8]:=RMessage.Data[mStart+1+i].0; Sensors[1+i*8]:=RMessage.Data[mStart+1+i].1; Sensors[2+i*8]:=RMessage.Data[mStart+1+i].2; Sensors[3+i*8]:=RMessage.Data[mStart+1+i].3; Sensors[4+i*8]:=RMessage.Data[mStart+1+i].4; Sensors[5+i*8]:=RMessage.Data[mStart+1+i].5; Sensors[6+i*8]:=RMessage.Data[mStart+1+i].6; Sensors[7+i*8]:=RMessage.Data[mStart+1+i].7; END_FOR; END_IF; (*here other commands are to be filled*) (*list new states*) 127: IF (mEnd-mStart)>4 THEN (*check for required response length*) FOR i:=0 TO 4 DO NewStates[0+i*8]:=RMessage.Data[mStart+1+i].0; NewStates[1+i*8]:=RMessage.Data[mStart+1+i].1; NewStates[2+i*8]:=RMessage.Data[mStart+1+i].2; NewStates[3+i*8]:=RMessage.Data[mStart+1+i].3; NewStates[4+i*8]:=RMessage.Data[mStart+1+i].4; NewStates[5+i*8]:=RMessage.Data[mStart+1+i].5; NewStates[6+i*8]:=RMessage.Data[mStart+1+i].6; NewStates[7+i*8]:=RMessage.Data[mStart+1+i].7; END_FOR; END_IF; END_CASE; END_IF; (*End of IF CRC OK*) (*marker to finish waiting and send new message*) FrameComplete:=TRUE; END_IF; (*END of IF a frame has been received*) END_IF; (*END of IF WaitingForData*) (*if a complete frame has been received or waiting time has passed*) IF StopWaiting.Q OR FrameComplete THEN (*reset all the variables*) WaitForData:=FALSE; RMessage.Index:=0; SyncSignal:=FALSE; EndSignal:=FALSE; LastRecord:=0; mStart:=0; mEnd:=0; FrameComplete:=FALSE; Send:=TRUE; END_IF;
The program uses the CRC_Calculate function block, which calculates CRC in line with the principles set in Satel's INT-RS documentation. The code:
FUNCTION_BLOCK CRC_Calculate VAR_INPUT m : POINTER TO typRing_BUFFER; start : INT; end :INT; END_VAR VAR_OUTPUT lo : WORD; hi : WORD; crc : WORD; END_VAR VAR i : INT; d : WORD; tcrc : WORD; END_VAR * * * * * IF end=0 THEN end:=m^.Index; END_IF; tcrc:=16#147A; FOR i:=start TO end DO tcrc:=ROL(tcrc, 1); tcrc:=tcrc XOR 16#FFFF; tcrc:=tcrc+HEX_HI(in:=tcrc)+m^.Data[i]; END_FOR; crc:=tcrc; d:=tcrc / 4096; hi:=d*16; tcrc:=tcrc-d*4096; d:=tcrc/256; hi:=hi+d; tcrc:=tcrc-d*256; d:=tcrc/16; lo:=d*16; tcrc:=tcrc-d*16; lo:=lo+tcrc;
...the function block above uses a HEX_HI function:
FUNCTION HEX_HI : WORD VAR_INPUT in : WORD; END_VAR VAR div4096 : WORD; END_VAR * * * * * div4096:=in/4096; HEX_HI:=div4096*16; in:=in-4096*div4096; HEX_HI:=HEX_HI+in/256;
Finally the whole set comprises of 3 elements: RS232 program, CRC_Calculate function block and HEX_HI function:
..what else is to be done? The states of the movement sensors (placed in the Sensors array), they can be followed from the main program PLC_PRG:
VAR Move_Wejscie, Move_Hol, (...) Move_Kotl : R_TRIG; END_VAR{/codecitation} * * * * * Move_Wejscie(CLK:=RS232.Sensors[0]); Move_Hol(CLK:=RS232.Sensors[1]); (...) Move_Kotl(CLK:=RS232.Sensors[12]);
When a movement is discovered by any of the sesors, Integra, while replying to the 127 command will inform about the new state available under command 0 (NewStates[0]=TRUE). The next command sent by the PLC is 0; Integra will reply informing, which sensor has been ignited (Sensors[x]=True). That event will be recorded by one of the triggers from the main program PLC_PRG, which for 1 cycle will return Q=TRUE (Move_xxx.Q=TRUE). This fact can be used to, for example, turn on the lights in a corridor (Light_XXX is a Fb_LatchingRelay funcion block):
LIGHT_XXX(xSwitch:=IN_X, xCentON:=Move_xxx.Q);
I cannot list any business/money-wise arguments to support abandoning the previous solution (communication via relays and DI). Moving to RS232 was driven more by curiosity. Additionally I was slightly annoyed by the clicking sound I could hear despite placing the relays in the metal box of the alarm installation and closed door of that room... Exploring the unknown, trying and testing, has been, as usual, a source of many priceless 'wow' effects.