OpenPLC_v3/webserver/core/pccc.cpp

579 lines
22 KiB
C++

//-----------------------------------------------------------------------------
// Copyright 2019 Thiago Alves
// This file is part of the OpenPLC Software Stack.
//
// OpenPLC is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenPLC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with OpenPLC. If not, see <http://www.gnu.org/licenses/>.
//------
//
// This file has all the PCCC functions supported by the OpenPLC. If any
// other function is to be added to the project, it must be added here
// UAH, Sep 2019
//-----------------------------------------------------------------------------
//------------Libraries-------------//
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <string.h>
#include <math.h>
#include "ladder.h"
//--------------------------------------------------------------Defines--------------------------------------------------------------------------------//
/*------------Maximum/Minimum Sizes for each buffer------------------*/
#define MAX_DISCRETE_INPUT 1024 // Digital Inputs
#define MAX_COILS 1024 // Digital Outputs
#define MAX_HOLD_REGS 1024 // Pure registers Analog Outputs
//#define MAX_INP_REGS 1024 // Analog Inputs
#define MIN_16B_RANGE 1024 //Holding Register Size 16bit (memory)
#define MAX_16B_RANGE 2047 //Holding Register Size 16bit (memory)
#define MAX_32B_RANGE 2047 //Holding Register Size 32bit (memory)
/*------------File Type for PCCC--------------*/
#define PCCC_INPUT_LOGICAL_SLOT 0x8c
#define PCCC_OUTPUT_LOGICAL_SLOT 0x8b
#define PCCC_INTEGER 0x89
#define PCCC_FLOATING_POINT 0x8A
#define PCCC_FN_OUTPUT 0x00
#define PCCC_FN_INPUT 0x01
#define PCCC_FN_INT 0x07
#define PCCC_FN_FLOAT 0x08
/*----------------Define functions for bit/byte operations-------------------*/
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
#define lowByte(w) ((unsigned char) ((w) & 0xff))
#define highByte(w) ((unsigned char) ((w) >> 8))
/*---------------------------------------------------------------------------*/
/*-----PLC Buffers - These buffer store the contents from OpenPLC-----------*/
IEC_BOOL pccc_discrete_input[MAX_DISCRETE_INPUT];
IEC_BOOL pccc_coils[MAX_COILS];
//IEC_UINT pccc_input_regs[MAX_INP_REGS];
IEC_UINT pccc_holding_regs[MAX_HOLD_REGS];
//-----------------------------------------------------------------------------------------------------------------------------------------------------//
thread_local int Pccc_MessageLength;
using namespace std;
//-----------------------------------------------------------Structure Defines--------------------------------------------------//
struct pccc_header //Structure for the Header Information for EthernetIP
{
unsigned char *Data;
unsigned char *Data_Size;
unsigned char *HD_length = 5;//[5] -> Typical Header Length for Command; Response Header Length is 4
unsigned char *HD_CMD_Code;//[1] -> Command Code
unsigned char *HD_Status;//[1] -> Status Code
unsigned char *HD_TransactionNum;//[2] -> Transaction Number
unsigned char *HD_Data_Function_Code;//[1] -> Function code MSB
unsigned char *HD_Ext_Status; //Ext Status -> only appended if Status = 0x0f[1]
unsigned char resp_cod_hex = 0x4f; //Response Hex Value
unsigned char *RP_CMD_Code = &resp_cod_hex;//[1] -> Reply Command Code = 0x4f
};
struct protected_logical_read_command //Struct for Reply and Command values of Read
{
unsigned char *CMD_Byte_Size;//[1]
unsigned char *RP_EXT_Status;//[1]* -> Ext Status -> only appended if Status = 0x0f[1]
};
struct protected_logical_write_command
{
unsigned char *CMD_Byte_Size;//[1]*
unsigned char *RP_EXT_Status;//[1]* -> Ext Status -> only appended if Status = 0x0f[1]
};
//--------------------------------------------------------------------------------------------------------------------------------------//
//------------------------Function Declaration---------------------------------//
uint16_t Command_Protocol(pccc_header header,unsigned char *buffer, int buffer_size);
uint16_t ParsePCCCData(unsigned char *buffer, int buffer_size);
uint16_t Protected_Logical_Read_Reply(pccc_header, unsigned char *buffer, int buffer_size);
uint16_t Protected_Logical_Write_Reply(pccc_header, unsigned char *buffer, int buffer_size);
void Pccc_ReadCoils(unsigned char *buffer, int buffer_size);
void Pccc_WriteCoil(unsigned char *buffer, int buffer_size);
void Pccc_ReadDiscreteInputs(unsigned char *buffer, int buffer_size);
void Pccc_ReadHoldingRegisters(unsigned char *buffer, int buffer_size);
//void Pccc_ReadInputRegisters(unsigned char *buffer, int buffer_size);
void Pccc_WriteRegister(unsigned char *buffer, int buffer_size);
int word_pccc(unsigned char byte1, unsigned char byte2);
int an_word_pccc(unsigned char byte1, unsigned char byte2);
//----------------------------------------------------------------------------//
//This function takes in the data from enip.cpp and places the data in the appropriate structure variables
uint16_t processPCCCMessage(unsigned char *buffer, int buffer_size)
{
/* Variables */
int new_pccc_length; //New PCCC Length
pccc_header header;
header.Data = buffer;
header.Data_Size = buffer_size;
/*Determine the new pccc length*/
new_pccc_length = ParsePCCCData(buffer,buffer_size);
return new_pccc_length; //Return the length to enip.cpp
}
uint16_t ParsePCCCData(unsigned char *buffer, int buffer_size)
{
/*Variables*/
int new_pccc_length; //Variable for new PCCC length
pccc_header header;
header.HD_CMD_Code = &buffer[0];//[1] -> Command Code
header.HD_Status = &buffer[1];////[1] -> Status Code
header.HD_TransactionNum = &buffer[2];//[2] -> Transaction Number
header.HD_Data_Function_Code = &buffer[4];//[1] -> Data Function Code
/*Determine what command is being requested*/
new_pccc_length = Command_Protocol(header,buffer,buffer_size);
return new_pccc_length; //Return the new pccc length
}
/* Determine the Command that is being requested to execute */
uint16_t Command_Protocol(pccc_header header, unsigned char *buffer, int buffer_size)
{
uint16_t var_pccc_length;
/*If Statement to determine the command code from the Command Packet*/
if(((unsigned int)*header.HD_CMD_Code == 0x0f) && ((unsigned int)*header.HD_Data_Function_Code == 0xA2))//Protected Logical Read
{
var_pccc_length = Protected_Logical_Read_Reply(header,buffer,buffer_size);
return var_pccc_length;
}
else if(((unsigned int)*header.HD_CMD_Code == 0x0f) && ( ((unsigned int)*header.HD_Data_Function_Code == 0xAA) || ((unsigned int)*header.HD_Data_Function_Code == 0xAB)))//Protected Logical Write
{
var_pccc_length = Protected_Logical_Write_Reply(header,buffer,buffer_size);
return var_pccc_length;
}
else
{
/*initialize logging system*/
unsigned char log_msg[1000];
unsigned char *p = log_msg;
sprintf(log_msg, "PCCC: Unsupportedd Command/Data Function Code!\n");
log(log_msg);
return -1;
}//return length as -1 to signify that the CMD Code/Function Code was not recognize
}
uint16_t Protected_Logical_Read_Reply(pccc_header header, unsigned char *buffer, int buffer_size)
{
/*Variables*/
protected_logical_read_command protected_LR;
protected_LR.CMD_Byte_Size = &buffer[5];//Byte Size of data to be read
/*Determining Data Length*/
unsigned int len_resp = 4;
len_resp = len_resp + (unsigned int)*protected_LR.CMD_Byte_Size;
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 8)
{
//PCCC Error Handling; Make sure that the buffer size is at least 8
}*/
//****************** Read Coils **********************//
if(buffer[6] == PCCC_FN_OUTPUT && buffer[7] == PCCC_OUTPUT_LOGICAL_SLOT) // Done/Tested
{
Pccc_ReadCoils(buffer, buffer_size);
}
//*************** Read Discrete Inputs ***************//
else if(buffer[6] == PCCC_FN_INPUT && buffer[7] == PCCC_INPUT_LOGICAL_SLOT)// Done/Tested
{
Pccc_ReadDiscreteInputs(buffer, buffer_size);
}
//****************** Read Holding Registers[PURE, 16Bit Mem, 32bit MEM] ******************//
else if((buffer[6] == PCCC_FN_INT || buffer[6] == PCCC_FN_FLOAT) && (buffer[7] == PCCC_INTEGER || buffer[7] == PCCC_FLOATING_POINT))//Done/Tested
{
Pccc_ReadHoldingRegisters(buffer, buffer_size);
}
else
{
unsigned char log_msg[1000];
unsigned char *p = log_msg;
sprintf(log_msg, "PCCC: Error occured while processing Protected Logical Read\n");
log(log_msg);
return -1;
}//return length as -1 to signify that the CMD Code/Function Code was not recognize
/*Creating the reply packet and memcpy the data into the buffer*/
memmove(&buffer[0], (unsigned int)header.RP_CMD_Code, 1); //0x4f Response Code
memmove(&buffer[1], (unsigned int)header.HD_Status, 1); //Same from COMMAND REQUEST
memmove(&buffer[2], (unsigned int)header.HD_TransactionNum, 2);//Same from COMMAND REQUEST
return len_resp; //Return the Resonse Packet Length for PCCC
}
uint16_t Protected_Logical_Write_Reply(pccc_header header,unsigned char *buffer, int buffer_size) // Connected
{
/*Variables*/
protected_logical_write_command protected_LW;
protected_LW.CMD_Byte_Size = &buffer[5];//Byte Size of data to be read
/*Determining link of new PCCC Packet*/
uint16_t len_resp = header.HD_length - 1;
/*Creating the reply packet and memcpy the data into the buffer*/
memmove(&buffer[0], (unsigned int)header.RP_CMD_Code, 1);
memmove(&buffer[1], (unsigned int)header.HD_Status, 1);
memmove(&buffer[2], (unsigned int)header.HD_TransactionNum, 2);
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 8)
{
//PCCC Error Handling; Make sure that the buffer size is at least 8
}*/
//****************** Write Coil **********************//
if(buffer[6] == PCCC_FN_OUTPUT && buffer[7] == PCCC_OUTPUT_LOGICAL_SLOT)// Done/Tested
{
Pccc_WriteCoil(buffer, buffer_size);
}
//****************** Write Register ******************//
else if((buffer[6] == PCCC_FN_FLOAT || buffer[6] == PCCC_FN_INT) && (buffer[7] == PCCC_INTEGER || buffer[7] == PCCC_FLOATING_POINT))//Done/Tested
{
Pccc_WriteRegister(buffer, buffer_size);
}
//****************** Function Code Error ******************/
/*Left in for future error handling setup*/
else
{
//PCCC Error Handling; Make sure that the buffer size is at least 8. If none of the defined File Type and File Numbers match, error unrecognized File Type and File Number.
}
return len_resp;
}
//-----------------------------------------------------------------------------
// Concatenate two bytes into an int
//-----------------------------------------------------------------------------
int word_pccc(unsigned char byte1, unsigned char byte2)
{
int returnValue;
returnValue = (int)(byte1) | (int)byte2;
return returnValue;
}
//-----------------------------------------------------------------------------
// Concatenate two bytes into an int
//-----------------------------------------------------------------------------
int an_word_pccc(unsigned char byte1, unsigned char byte2)
{
int returnValue;
returnValue = (int)(byte1) | (int)(byte2 << 8);
return returnValue;
}
//-----------------------------------------------------------------------------
// Implementation of PCCC Read Coils
//-----------------------------------------------------------------------------
void Pccc_ReadCoils(unsigned char *buffer, int buffer_size) //Working QX Read
{
int Start, ByteDataLength, Mask;
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]); //Start based on the Element and Subelemnt values in the Command Packet
Mask = log2( word_pccc(buffer[10],buffer[11]) ); //Save the byte size or byte data length to the variable from the command packet
ByteDataLength = buffer[5];
pthread_mutex_lock(&bufferLock);
/*----Reading the values from the PLC bool_output buffer and writing to the PCCC buffer based on position----*/
for (int i = 0; i < ByteDataLength; i++)
{
for(int j = 0; j < 8; j++)
{
int position = Start + i * 8 + j;
if (position < MAX_COILS)
{
if(bool_output[position/8][position%8] != NULL)
{
bitWrite(buffer[4+i], j, *bool_output[position/8][position%8]);
}
else
{
bitWrite(buffer[4+i],j,0);
}
}
else
{
//PCCC Error Handling (Fill in?); If the position is greater than the MAX COILS, ERROR Overflow?
}
}
}
pthread_mutex_unlock(&bufferLock);
/*Left in for future error handling setup*/
/*if (pccc_error != ERR_NONE)
{
//PCCC Error Handling (Fill in?); Deetermine if there was an error:
}*/
}
//-----------------------------------------------------------------------------
// Implementation of PCCC Read Discrete Inputs
//-----------------------------------------------------------------------------
void Pccc_ReadDiscreteInputs(unsigned char *buffer, int buffer_size) //Working IX Read Only
{
int Start, ByteDataLength;
/*This Request must have at least 10 bytes. If it doesn't, its a corrupted messageLeft in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
pthread_mutex_lock(&bufferLock);
/*--------Reading the values from the PLC bool_input buffer and writing to the PCCC buffer based on position--------*/
for (int i = 0; i < ByteDataLength; i++)
{
for(int j = 0; j < 8; j++)
{
int position = Start + i * 8 + j;
if (position < MAX_DISCRETE_INPUT)
{
if(bool_input[position/8][position%8] != NULL)
{
bitWrite(buffer[4+i], j, *bool_input[position/8][position%8]);
}
else
{
bitWrite(buffer[4+i],j,0);
}
}
else
{
//PCCC Error Handling (Fill in?); If the position is greater than the MAX, ERROR Overflow?
}
}
}
pthread_mutex_unlock(&bufferLock);
/*Left in for future error handling setup*/
/*if (mb_error != ERR_NONE)
{
//PCCC Error Handling (Fill in?); Deetermine if there was an error:
}*/
}
//-----------------------------------------------------------------------------
// Implementation of PCCC Read Holding Registers
//-----------------------------------------------------------------------------
void Pccc_ReadHoldingRegisters(unsigned char *buffer, int buffer_size) // QW Read
{
int Start, an_Start, WordDataLength, ByteDataLength;
/*this request must have at least 10 bytes. If it doesn't, it's a corrupted message - Left in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
WordDataLength = ByteDataLength / 2;//Calculate the word data length based on the byte data length
unsigned int Temp_FileT = buffer[7];//Value will be changed potentially during this process, save the File Type Value from command packet
unsigned int Temp_FileN = buffer[6];//Value will be changed potentially during this process, save the File Number Value from command packet
/*asked for too many registers - Left in for future error handling setup*/
/*if (ByteDataLength > 255)
{
//PCCC Error Handling (Fill in?); This Request must have at greater than 255 bytes. If it does, its a corrupted message
//return;
}*/
pthread_mutex_lock(&bufferLock);
/*--------Reading the values from the PLC int_output, int_memory, and dint_memory buffer and writing to the PCCC buffer based on position--------*/
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
//int an_position = an_Start + i;
if ((position <= MIN_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && Temp_FileT == PCCC_INTEGER))
{
if (int_output[position] != NULL)
{
buffer[ 4 + position * 2] = lowByte(*int_output[position]);
buffer[5 + position * 2] = highByte(*int_output[position]);
}
else
{
buffer[ 4 + position * 2] = 0;
buffer[5 + position * 2] = 0;
}
}
//accessing memory
//16-bit registers
else if ((position >= MIN_16B_RANGE && position <= MAX_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && Temp_FileT == PCCC_INTEGER))
{
if (int_memory[position - MIN_16B_RANGE] != NULL)
{
buffer[ 4 + position * 2] = lowByte(*int_memory[position - MIN_16B_RANGE]);
buffer[5 + position * 2] = highByte(*int_memory[position - MIN_16B_RANGE]);
}
else
{
buffer[ 4 + position * 2] = 0;
buffer[5 + position * 2] = 0;
}
}
//32-bit registers
else if (Temp_FileN == PCCC_FN_FLOAT && Temp_FileT == PCCC_FLOATING_POINT && (position % 2 == 0))
{
position = position/2;
uint32_t tempValue = *dint_memory[position];
buffer[4+(4*position)] = tempValue;
buffer[5+(4*position)] = tempValue >> 8;
buffer[6+(4*position)] = tempValue >> 16;
buffer[7+(4*position)] = tempValue >> 24;
}
/*Left in for future error handling setup-Invalid Address*/
else
{
//PCCC Error Handling (Fill in?); If none of the above are recognized, error
}
}
pthread_mutex_unlock(&bufferLock);
}
//-----------------------------------------------------------------------------
// Implementation of PCCC Write Coil
//-----------------------------------------------------------------------------
void Pccc_WriteCoil(unsigned char *buffer, int buffer_size) //QX Write NEEDS WRITE MULTIPLE
{
int Start, Mask;
int mask_offset = 0;
/*Left in for future error handling setup*/
/*if(buffer_size < 10)
{
//ModbusError(buffer, ERR_ILLEGAL_DATA_ADDRESS);
//return;
}
*/
/*For the Write Mask, there has to be a maskoffset due to an extra two bytes */
if((unsigned int)buffer[4] == 0xAB)
{
mask_offset = buffer[5]; //Byte Size
}
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
Mask = log2( word_pccc(buffer[10],buffer[11]) );// Maskoffset based on the mask value in Masked Protected Write Command Packet
/*--------Determines if the values inside the PCCC data has a 1 or 0 in it. Writes that value to the bool_output based on the contents of the data in PCCC Buffer-------*/
if(Start < MAX_COILS)
{
unsigned char value;
if(word_pccc(buffer[10 + mask_offset],buffer[11 + mask_offset]) > 0)
{
value = 1;
}
else
{
value = 0;
}
pthread_mutex_lock(&bufferLock);
if(bool_output[Start][Mask] != NULL)
{
*bool_output[Start][Mask] = value;
}
pthread_mutex_unlock(&bufferLock);
}
}
//-----------------------------------------------------------------------------
// Implementation of PCCC Write Holding Register
//-----------------------------------------------------------------------------
void Pccc_WriteRegister(unsigned char *buffer, int buffer_size) // QW Write
{
int Start, WordDataLength, ByteDataLength;
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
int an_Start = an_word_pccc(buffer[8],buffer[9]);//Different Start method for INTs based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
WordDataLength = ByteDataLength / 2;//Calculate the word data length based on the byte data length
unsigned int Temp_FileT = buffer[7];//Value will be changed potentially during this process, save the File Type Value from command packet
unsigned int Temp_FileN = buffer[6];//Value will be changed potentially during this process, save the File Number Value from command packet
pthread_mutex_lock(&bufferLock);
/*--------Determines if the values inside the PCCC data has data. Writes that value to the appropriate PLC Buffer based on the contents of the data in PCCC Buffer-------*/
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
//analog outputs
if ((position <= MIN_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && (Temp_FileT == PCCC_INTEGER)))
{
if (int_output[position] != NULL) *int_output[position] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer
}
//accessing memory
//16-bit registers
else if ((position >= MIN_16B_RANGE && position <= MAX_16B_RANGE) && (Temp_FileN == PCCC_FN_OUTPUT && (Temp_FileT == PCCC_INTEGER)))
{
if (int_memory[position - MIN_16B_RANGE] != NULL) *int_memory[position - MIN_16B_RANGE] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer
}
//32-bit registers
if (Temp_FileN == PCCC_FN_FLOAT && (Temp_FileT == PCCC_FLOATING_POINT))
{
if (dint_memory[position] != NULL)
{
uint32_t tempValue = buffer[10 + i] | buffer[11 + i] << 8 | buffer[12 + i] << 16 | buffer[13 + i] <<24;//look at this closer
*dint_memory[position] = tempValue;
i += 4;
}
else
{
pccc_holding_regs[position] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer might need to copy from temp
}
}
pthread_mutex_unlock(&bufferLock);
}
}