diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ed21d6..139bc85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,6 +48,14 @@ jobs: config: ARM build-mode: ${{ inputs.build_mode || 'Build' }} + - name: Build ARsim + uses: loupeteam/br-actions/build-as-project@v1 + with: + exe-path: ${{ steps.find-as.outputs.exe-path }} + project: ${{ env.PROJECT }} + config: ARsim + build-mode: ${{ inputs.build_mode || 'Build' }} + - name: Upload build diagnostics if: always() uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 20aff9e..bfdf865 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ ignored/ # Python **/__pycache__/ +example/UserPartition/*/result.csv +example/UserPartition/*/bitstring.csv diff --git a/README.md b/README.md index 7a8bce3..b70da97 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,46 @@ For more documentation and examples, see https://loupeteam.github.io/LoupeDocs/l # Installation To install using the Loupe Package Manager (LPM), in an initialized Automation Studio project directory run `lpm install csvfilelib`. For more information about LPM, see https://loupeteam.github.io/LoupeDocs/tools/lpm.html +# Tests + +The example project carries a self checking test suite for the bit string +(BYTE/WORD/DWORD) support, in `example/AsProject/Logical/Programs/Default`. +It runs automatically a few scans after boot, and can be re-run by setting +`runTest`. + +The `ARsim` configuration exists so the suite can actually be executed; the +`Intel` and `ARM` configurations target hardware. CI compiles all three, but +does not run the suite - executing it is a manual step. + +## Running it + +The suite reads two hand written input files, `crafted.csv` and `badline.csv`, +from the USER file device. They live in `example/UserPartition/ARsim/` and +have to be present on the device before the suite runs, otherwise the file +phases cannot complete. + +Either copy them onto the USER file device by hand, or let a tool that reads +`example/AsProject/.loupe/partition.json` place them for you. That file is the +Loupe VS Code PLC Toolkit / bnr-build partition mapping - it is not something +LPM or the Automation Studio build does on its own. + +## Reading the result + +The suite writes `result.csv` to the USER file device. + +**`suiteOk` is the verdict.** It is TRUE only when there were no failures, the +full expected number of assertions actually ran, and the suite reached the +end. Checking `testFail` alone is not enough: a file operation that never +completes leaves `testFail` at 0, which would otherwise read as a pass. + +`firstFail` names the first case that failed, if any. The remaining values in +the file are there to make a failure diagnosable - the data types PV_ninfo +reported, and the values read back from each input file. + +Note that the PV_ninfo data type codes were measured on ARsim running +Automation Runtime 6.7.6. The configurations in this project pin 6.6.2, which +is what CI compiles against. + ## Licensing This project is licensed under the [MIT License](LICENSE). \ No newline at end of file diff --git a/example/AsProject/.loupe/partition.json b/example/AsProject/.loupe/partition.json new file mode 100644 index 0000000..8f45f35 --- /dev/null +++ b/example/AsProject/.loupe/partition.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "mappings": [ + { "configuration": "ARsim", "fileDevice": "USER", "localFolder": "../UserPartition/ARsim" } + ] +} diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/Binary.lby b/example/AsProject/Logical/Libraries/Loupe/stringext/Binary.lby index 64a2a0b..4376f04 100644 --- a/example/AsProject/Logical/Libraries/Loupe/stringext/Binary.lby +++ b/example/AsProject/Logical/Libraries/Loupe/stringext/Binary.lby @@ -1,5 +1,5 @@ - + StringExt.typ StringExt.var diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/SG3/StringExt.h b/example/AsProject/Logical/Libraries/Loupe/stringext/SG3/StringExt.h index 286285c..29f1003 100644 --- a/example/AsProject/Logical/Libraries/Loupe/stringext/SG3/StringExt.h +++ b/example/AsProject/Logical/Libraries/Loupe/stringext/SG3/StringExt.h @@ -1,6 +1,6 @@ /* Automation Studio generated header file */ /* Do not edit ! */ -/* stringext 1.0.0 */ +/* stringext 1.1.0 */ #ifndef _STRINGEXT_ #define _STRINGEXT_ @@ -9,7 +9,7 @@ extern "C" { #endif #ifndef _stringext_VERSION -#define _stringext_VERSION 1.0.0 +#define _stringext_VERSION 1.1.0 #endif #include @@ -36,9 +36,19 @@ extern "C" /* Datatypes and datatypes of function blocks */ typedef enum STREXT_ERR_enum -{ STREXT_ERR_INVALID_INPUT = -1 +{ STREXT_ERR_BUFFER_TOO_SMALL = -4, + STREXT_ERR_RANGE = -3, + STREXT_ERR_INVALID_FORMAT = -2, + STREXT_ERR_INVALID_INPUT = -1 } STREXT_ERR_enum; +typedef enum STREXT_HEXPREFIX_enum +{ STREXT_HEXPREFIX_NONE = 0, + STREXT_HEXPREFIX_0X, + STREXT_HEXPREFIX_IEC, + STREXT_HEXPREFIX_DOLLAR +} STREXT_HEXPREFIX_enum; + typedef struct StrExtArgs_typ { float r[5]; unsigned long s[5]; @@ -55,6 +65,8 @@ _BUR_PUBLIC unsigned long atoui(unsigned long pString); _BUR_PUBLIC unsigned long uitoa(unsigned long Value, unsigned long pString); _BUR_PUBLIC unsigned long ByteToHexString(unsigned long pByte, unsigned long NumBytes, unsigned long pString); _BUR_PUBLIC signed long HexStringToDINT(unsigned long pHexStr); +_BUR_PUBLIC signed long HexStringToUDINT(unsigned long pHexStr, unsigned long pValue); +_BUR_PUBLIC signed long UDINTToHexString(unsigned long Value, unsigned long pString, unsigned long Size, unsigned char NumDigits, unsigned char Prefix); _BUR_PUBLIC unsigned short GenerateTimestampMS_1(unsigned long pDTStructure, unsigned long pTimestamp, unsigned long TimestampLength); _BUR_PUBLIC unsigned short GenerateTimestampMS(unsigned long pDTStructure, unsigned long pTimestamp, unsigned long TimestampLength); _BUR_PUBLIC unsigned short GenerateTimestamp(plcdt DT1, unsigned long pTimestamp, unsigned long TimestampLength); diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/libstringext.a b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/libstringext.a index 42007b7..ad5edb9 100644 Binary files a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/libstringext.a and b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/libstringext.a differ diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/stringext.br b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/stringext.br index a021a7a..266883b 100644 Binary files a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/stringext.br and b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/Arm/stringext.br differ diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/libstringext.a b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/libstringext.a index d0ba07f..52c7f00 100644 Binary files a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/libstringext.a and b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/libstringext.a differ diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.br b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.br index 24cede3..517c86e 100644 Binary files a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.br and b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.br differ diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.h b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.h index 286285c..29f1003 100644 --- a/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.h +++ b/example/AsProject/Logical/Libraries/Loupe/stringext/SG4/stringext.h @@ -1,6 +1,6 @@ /* Automation Studio generated header file */ /* Do not edit ! */ -/* stringext 1.0.0 */ +/* stringext 1.1.0 */ #ifndef _STRINGEXT_ #define _STRINGEXT_ @@ -9,7 +9,7 @@ extern "C" { #endif #ifndef _stringext_VERSION -#define _stringext_VERSION 1.0.0 +#define _stringext_VERSION 1.1.0 #endif #include @@ -36,9 +36,19 @@ extern "C" /* Datatypes and datatypes of function blocks */ typedef enum STREXT_ERR_enum -{ STREXT_ERR_INVALID_INPUT = -1 +{ STREXT_ERR_BUFFER_TOO_SMALL = -4, + STREXT_ERR_RANGE = -3, + STREXT_ERR_INVALID_FORMAT = -2, + STREXT_ERR_INVALID_INPUT = -1 } STREXT_ERR_enum; +typedef enum STREXT_HEXPREFIX_enum +{ STREXT_HEXPREFIX_NONE = 0, + STREXT_HEXPREFIX_0X, + STREXT_HEXPREFIX_IEC, + STREXT_HEXPREFIX_DOLLAR +} STREXT_HEXPREFIX_enum; + typedef struct StrExtArgs_typ { float r[5]; unsigned long s[5]; @@ -55,6 +65,8 @@ _BUR_PUBLIC unsigned long atoui(unsigned long pString); _BUR_PUBLIC unsigned long uitoa(unsigned long Value, unsigned long pString); _BUR_PUBLIC unsigned long ByteToHexString(unsigned long pByte, unsigned long NumBytes, unsigned long pString); _BUR_PUBLIC signed long HexStringToDINT(unsigned long pHexStr); +_BUR_PUBLIC signed long HexStringToUDINT(unsigned long pHexStr, unsigned long pValue); +_BUR_PUBLIC signed long UDINTToHexString(unsigned long Value, unsigned long pString, unsigned long Size, unsigned char NumDigits, unsigned char Prefix); _BUR_PUBLIC unsigned short GenerateTimestampMS_1(unsigned long pDTStructure, unsigned long pTimestamp, unsigned long TimestampLength); _BUR_PUBLIC unsigned short GenerateTimestampMS(unsigned long pDTStructure, unsigned long pTimestamp, unsigned long TimestampLength); _BUR_PUBLIC unsigned short GenerateTimestamp(plcdt DT1, unsigned long pTimestamp, unsigned long TimestampLength); diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/SGC/StringExt.h b/example/AsProject/Logical/Libraries/Loupe/stringext/SGC/StringExt.h index 286285c..29f1003 100644 --- a/example/AsProject/Logical/Libraries/Loupe/stringext/SGC/StringExt.h +++ b/example/AsProject/Logical/Libraries/Loupe/stringext/SGC/StringExt.h @@ -1,6 +1,6 @@ /* Automation Studio generated header file */ /* Do not edit ! */ -/* stringext 1.0.0 */ +/* stringext 1.1.0 */ #ifndef _STRINGEXT_ #define _STRINGEXT_ @@ -9,7 +9,7 @@ extern "C" { #endif #ifndef _stringext_VERSION -#define _stringext_VERSION 1.0.0 +#define _stringext_VERSION 1.1.0 #endif #include @@ -36,9 +36,19 @@ extern "C" /* Datatypes and datatypes of function blocks */ typedef enum STREXT_ERR_enum -{ STREXT_ERR_INVALID_INPUT = -1 +{ STREXT_ERR_BUFFER_TOO_SMALL = -4, + STREXT_ERR_RANGE = -3, + STREXT_ERR_INVALID_FORMAT = -2, + STREXT_ERR_INVALID_INPUT = -1 } STREXT_ERR_enum; +typedef enum STREXT_HEXPREFIX_enum +{ STREXT_HEXPREFIX_NONE = 0, + STREXT_HEXPREFIX_0X, + STREXT_HEXPREFIX_IEC, + STREXT_HEXPREFIX_DOLLAR +} STREXT_HEXPREFIX_enum; + typedef struct StrExtArgs_typ { float r[5]; unsigned long s[5]; @@ -55,6 +65,8 @@ _BUR_PUBLIC unsigned long atoui(unsigned long pString); _BUR_PUBLIC unsigned long uitoa(unsigned long Value, unsigned long pString); _BUR_PUBLIC unsigned long ByteToHexString(unsigned long pByte, unsigned long NumBytes, unsigned long pString); _BUR_PUBLIC signed long HexStringToDINT(unsigned long pHexStr); +_BUR_PUBLIC signed long HexStringToUDINT(unsigned long pHexStr, unsigned long pValue); +_BUR_PUBLIC signed long UDINTToHexString(unsigned long Value, unsigned long pString, unsigned long Size, unsigned char NumDigits, unsigned char Prefix); _BUR_PUBLIC unsigned short GenerateTimestampMS_1(unsigned long pDTStructure, unsigned long pTimestamp, unsigned long TimestampLength); _BUR_PUBLIC unsigned short GenerateTimestampMS(unsigned long pDTStructure, unsigned long pTimestamp, unsigned long TimestampLength); _BUR_PUBLIC unsigned short GenerateTimestamp(plcdt DT1, unsigned long pTimestamp, unsigned long TimestampLength); diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.fun b/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.fun index 756c061..241f248 100644 --- a/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.fun +++ b/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.fun @@ -48,6 +48,23 @@ FUNCTION HexStringToDINT : DINT (*Converts a Hex String to a DINT*) (*$GROUP=Use END_VAR END_FUNCTION +FUNCTION HexStringToUDINT : DINT (*Parses a hex string with an optional 0x, 16# or $ prefix. Returns 0 on success or a STREXT_ERR_enum value; the parsed value is written through pValue*) (*$GROUP=User,$CAT=User,$GROUPICON=User.png,$CATICON=User.png*) + VAR_INPUT + pHexStr : UDINT; (*Pointer to the string to parse*) + pValue : UDINT; (*Pointer to the UDINT that receives the value*) + END_VAR +END_FUNCTION + +FUNCTION UDINTToHexString : DINT (*Formats a UDINT as a hex string. Returns the string length or a STREXT_ERR_enum value*) (*$GROUP=User,$CAT=User,$GROUPICON=User.png,$CATICON=User.png*) + VAR_INPUT + Value : UDINT; (*Value to format*) + pString : UDINT; (*Pointer to the destination buffer*) + Size : UDINT; (*Size of the destination buffer, including the terminator*) + NumDigits : USINT; (*Zero pad to this many digits, 1..8. 0 for the shortest representation*) + Prefix : USINT; (*Notation to use, see STREXT_HEXPREFIX_enum*) + END_VAR +END_FUNCTION + FUNCTION GenerateTimestampMS_1 : UINT (*Generate a time stamp string from a DTStructure variable*) VAR_INPUT pDTStructure : UDINT; diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.typ b/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.typ index 05f77da..b3389f0 100644 --- a/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.typ +++ b/example/AsProject/Logical/Libraries/Loupe/stringext/StringExt.typ @@ -10,8 +10,18 @@ TYPE STREXT_ERR_enum : ( + STREXT_ERR_BUFFER_TOO_SMALL := -4, (*Destination buffer is too small for the result*) + STREXT_ERR_RANGE := -3, (*Value does not fit in the destination type*) + STREXT_ERR_INVALID_FORMAT := -2, (*String is not a valid literal of the expected form*) STREXT_ERR_INVALID_INPUT := -1 ); + STREXT_HEXPREFIX_enum : + ( + STREXT_HEXPREFIX_NONE := 0, (*FF*) + STREXT_HEXPREFIX_0X, (*0xFF*) + STREXT_HEXPREFIX_IEC, (*16#FF*) + STREXT_HEXPREFIX_DOLLAR (*$FF*) + ); StrExtArgs_typ : STRUCT (*Arguments for formatter*) r : ARRAY[0..4]OF REAL; (*Reals*) s : ARRAY[0..4]OF UDINT; (*Pointers to String*) diff --git a/example/AsProject/Logical/Libraries/Loupe/stringext/package.json b/example/AsProject/Logical/Libraries/Loupe/stringext/package.json index 275ab8d..6daf7ab 100644 --- a/example/AsProject/Logical/Libraries/Loupe/stringext/package.json +++ b/example/AsProject/Logical/Libraries/Loupe/stringext/package.json @@ -1,6 +1,6 @@ { "name": "@loupeteam/stringext", - "version": "1.0.0", + "version": "1.1.0", "description": "String manipulation functions for B&R Automation Studio (ANSIC library)", "homepage": "https://loupeteam.github.io/LoupeDocs/libraries/stringext.html", "scripts": {}, diff --git a/example/AsProject/Logical/Programs/Default/Main.c b/example/AsProject/Logical/Programs/Default/Main.c index 86fb279..2d4d265 100644 --- a/example/AsProject/Logical/Programs/Default/Main.c +++ b/example/AsProject/Logical/Programs/Default/Main.c @@ -5,18 +5,626 @@ #include #endif +#include + + +/* ------------------------------------------------------------------ */ +/* Bit string (BYTE/WORD/DWORD) test suite for CSVFileLib */ +/* */ +/* Runs once a few scans after boot; set runTest to run it again. */ +/* */ +/* The suite PASSED only when suiteOk is 1. It is cleared at the start */ +/* of every run and set only at the end, after the full assertion */ +/* count has been checked - so a run that fails part way and skips the */ +/* end, or one that never completes, leaves it 0. Checking testFail */ +/* alone is not enough: that stays 0 when a phase never ran. */ +/* ------------------------------------------------------------------ */ + +/* Every assertion the suite makes, counted at the call sites: + 1 PV_ninfo + 41 read + 12 write + 36 round trip + 1 file round trip + + 1 crafted.csv + 1 badline.csv */ + +#define CSVTEST_EXPECTED_ASSERTIONS 93 + + +typedef struct readCase_typ { + char* Value; /* what the file contains */ + unsigned long DataType; /* CSV_TYPE_BYTE / WORD / DWORD */ + unsigned short ExpectStatus; /* 0, or the CSV_ERR_ expected */ + unsigned long ExpectStored; /* only checked when ExpectStatus is 0 */ +} readCase_typ; + + +static const readCase_typ readCases[] = { + + /* --- every accepted prefix, DWORD --- */ + { "0xFF", CSV_TYPE_DWORD, 0, 0xFF }, + { "0XFF", CSV_TYPE_DWORD, 0, 0xFF }, + { "16#FF", CSV_TYPE_DWORD, 0, 0xFF }, + { "$FF", CSV_TYPE_DWORD, 0, 0xFF }, + + /* --- bare digits are decimal, never hex --- */ + { "10", CSV_TYPE_DWORD, 0, 10 }, + { "255", CSV_TYPE_DWORD, 0, 255 }, + { "0", CSV_TYPE_DWORD, 0, 0 }, + + /* --- case, padding, whitespace --- */ + { "0xdeadbeef", CSV_TYPE_DWORD, 0, 0xDEADBEEF }, + { "0xDEADBEEF", CSV_TYPE_DWORD, 0, 0xDEADBEEF }, + { "16#000000FF", CSV_TYPE_DWORD, 0, 0xFF }, + { " 0x10 ", CSV_TYPE_DWORD, 0, 0x10 }, + { "0x00000000", CSV_TYPE_DWORD, 0, 0 }, + { "0000000000255", CSV_TYPE_DWORD, 0, 255 }, + + /* --- full 32 bit range, both notations --- */ + { "0xFFFFFFFF", CSV_TYPE_DWORD, 0, 0xFFFFFFFF }, + { "4294967295", CSV_TYPE_DWORD, 0, 0xFFFFFFFF }, + { "0x80000000", CSV_TYPE_DWORD, 0, 0x80000000 }, + + /* --- width limits per type --- */ + { "0xFF", CSV_TYPE_BYTE, 0, 0xFF }, + { "255", CSV_TYPE_BYTE, 0, 255 }, + { "0xFFFF", CSV_TYPE_WORD, 0, 0xFFFF }, + { "65535", CSV_TYPE_WORD, 0, 65535 }, + + /* --- out of range for the target width --- */ + { "0x100", CSV_TYPE_BYTE, CSV_ERR_INVALIDVALUE, 0 }, + { "256", CSV_TYPE_BYTE, CSV_ERR_INVALIDVALUE, 0 }, + { "0x10000", CSV_TYPE_WORD, CSV_ERR_INVALIDVALUE, 0 }, + { "65536", CSV_TYPE_WORD, CSV_ERR_INVALIDVALUE, 0 }, + + /* --- out of range for a UDINT --- */ + { "0x100000000", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "4294967296", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "99999999999", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + + /* --- malformed --- */ + { "", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { " ", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "0x", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "16#", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "$", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "0xGG", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "FF", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "0xFF junk", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "12abc", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "-0x10", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "-1", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "+0x10", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "0x0xFF", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + { "1_000", CSV_TYPE_DWORD, CSV_ERR_INVALIDVALUE, 0 }, + +}; + + +typedef struct writeCase_typ { + unsigned long Value; + unsigned long DataType; + unsigned char Format; /* CSV_BITFORMAT_ */ + char* Expect; +} writeCase_typ; + + +static const writeCase_typ writeCases[] = { + + /* --- padded to the width of the source type --- */ + { 0xFF, CSV_TYPE_BYTE, CSV_BITFORMAT_HEX_0X, "0xFF" }, + { 0x05, CSV_TYPE_BYTE, CSV_BITFORMAT_HEX_0X, "0x05" }, + { 0xFF, CSV_TYPE_WORD, CSV_BITFORMAT_HEX_0X, "0x00FF" }, + { 0xFF, CSV_TYPE_DWORD, CSV_BITFORMAT_HEX_0X, "0x000000FF" }, + + /* --- IEC notation --- */ + { 0xFF, CSV_TYPE_BYTE, CSV_BITFORMAT_HEX_IEC, "16#FF" }, + { 0xFF, CSV_TYPE_DWORD, CSV_BITFORMAT_HEX_IEC, "16#000000FF" }, + + /* --- decimal, deliberately not padded --- */ + { 0xFF, CSV_TYPE_BYTE, CSV_BITFORMAT_DECIMAL, "255" }, + { 0x05, CSV_TYPE_BYTE, CSV_BITFORMAT_DECIMAL, "5" }, + { 0xFFFFFFFF, CSV_TYPE_DWORD, CSV_BITFORMAT_DECIMAL, "4294967295" }, + + /* --- extremes --- */ + { 0, CSV_TYPE_DWORD, CSV_BITFORMAT_HEX_0X, "0x00000000" }, + { 0xFFFFFFFF, CSV_TYPE_DWORD, CSV_BITFORMAT_HEX_0X, "0xFFFFFFFF" }, + + /* --- an out of range setting must not silently corrupt output --- */ + { 0xFF, CSV_TYPE_BYTE, 99, "0xFF" }, + +}; + + +static void recordFail(char* what) +{ + testFail++; + + if(strlen((char*)firstFail) == 0){ + + /* An empty input is itself a case, so it needs a label of its own + rather than leaving firstFail looking unset */ + + if(strlen(what) == 0) strcpy((char*)firstFail, "(empty value)"); + else strncpy((char*)firstFail, what, sizeof(firstFail)-1); + + } +} + + +/* Confirm PV_ninfo really reports 17/18/19 for BYTE/WORD/DWORD on this + runtime. If it does not, the whole feature is unreachable. */ + +static void checkPvTypes(void) +{ + UDINT Len; + UINT Dim; + + pvTypeByte= 0xFFFFFFFF; + pvTypeWord= 0xFFFFFFFF; + pvTypeDword= 0xFFFFFFFF; + + PV_ninfo( "Default:tByte", &pvTypeByte, &Len, &Dim ); + PV_ninfo( "Default:tWord", &pvTypeWord, &Len, &Dim ); + PV_ninfo( "Default:tDword", &pvTypeDword, &Len, &Dim ); + + pvTypeOk= (pvTypeByte == CSV_TYPE_BYTE) + && (pvTypeWord == CSV_TYPE_WORD) + && (pvTypeDword == CSV_TYPE_DWORD); + + if(pvTypeOk) testPass++; else recordFail("PV_ninfo type codes"); +} + + +static void runReadCases(void) +{ + UDINT i; + CSVFileVariable_typ Var; + UDINT Storage; + UDINT Got; + UINT Status; + + for(i=0; i<(sizeof(readCases)/sizeof(readCases[0])); i++){ + + Storage= 0xA5A5A5A5; + + memset(&Var, 0, sizeof(Var)); + strcpy(Var.Name, "testVar"); + strcpy(Var.Value, readCases[i].Value); + Var.DataType= readCases[i].DataType; + Var.Address= (UDINT)&Storage; + Var.Length= 4; + + Status= csvOpenVar( 1, &Var, &CsvMgr ); + + if(Status != readCases[i].ExpectStatus){ + recordFail(readCases[i].Value); + continue; + } + + if(Status == 0){ + + /* Compare only the bytes the type actually writes. This reads the + low bytes, which assumes a little endian target - true for + ARsim/x86 and for the X20 ARM target this project builds. */ + + Got= Storage; + if(readCases[i].DataType == CSV_TYPE_BYTE) Got= Storage & 0xFF; + else if(readCases[i].DataType == CSV_TYPE_WORD) Got= Storage & 0xFFFF; + + if(Got != readCases[i].ExpectStored){ + recordFail(readCases[i].Value); + continue; + } + + } + else{ + + /* A rejected value must leave the target untouched */ + if(Storage != 0xA5A5A5A5){ + recordFail(readCases[i].Value); + continue; + } + + } + + testPass++; + + } +} + + +static void runWriteCases(void) +{ + UDINT i; + CSVFileVariable_typ Var; + UDINT Storage; + UINT Status; + + for(i=0; i<(sizeof(writeCases)/sizeof(writeCases[0])); i++){ + + Storage= writeCases[i].Value; + + memset(&Var, 0, sizeof(Var)); + strcpy(Var.Name, "testVar"); + Var.DataType= writeCases[i].DataType; + Var.Address= (UDINT)&Storage; + Var.Length= 4; + + CsvMgr.IN.CFG.BitStringFormat= writeCases[i].Format; + + Status= csvSaveVar( 1, &Var, &CsvMgr ); + + if( (Status != 0) || (strcmp(Var.Value, writeCases[i].Expect) != 0) ){ + recordFail(writeCases[i].Expect); + } + else{ + testPass++; + } + + } + + CsvMgr.IN.CFG.BitStringFormat= CSV_BITFORMAT_HEX_0X; +} + + +/* Every value the formatter can produce must read back as itself, for + each of the three formats and each of the three widths. */ + +static void runRoundTrip(void) +{ + UDINT iFmt, iType, iVal; + CSVFileVariable_typ Var; + UDINT Storage, Original; + STRING Written[CSV_STRLEN_VARVALUE+1]; + + UDINT types[3]; + UDINT values[4]; + + types[0]= CSV_TYPE_BYTE; + types[1]= CSV_TYPE_WORD; + types[2]= CSV_TYPE_DWORD; + + for(iFmt=0; iFmt<3; iFmt++){ + for(iType=0; iType<3; iType++){ + + values[0]= 0; + values[1]= 1; + + if(types[iType] == CSV_TYPE_BYTE){ + values[2]= 0x7F; + values[3]= 0xFF; + } + else if(types[iType] == CSV_TYPE_WORD){ + values[2]= 0x7FFF; + values[3]= 0xFFFF; + } + else{ + values[2]= 0x7FFFFFFF; + values[3]= 0xFFFFFFFF; + } + + for(iVal=0; iVal<4; iVal++){ + + Original= values[iVal]; + Storage= Original; + + memset(&Var, 0, sizeof(Var)); + strcpy(Var.Name, "testVar"); + Var.DataType= types[iType]; + Var.Address= (UDINT)&Storage; + Var.Length= 4; + + CsvMgr.IN.CFG.BitStringFormat= (USINT)iFmt; + + if( csvSaveVar( 1, &Var, &CsvMgr ) != 0 ){ + recordFail("round trip save"); + continue; + } + + strcpy(Written, Var.Value); + + Storage= 0; + strcpy(Var.Value, Written); + + if( csvOpenVar( 1, &Var, &CsvMgr ) != 0 ){ + recordFail(Written); + continue; + } + + if(Storage != Original){ + recordFail(Written); + continue; + } + + testPass++; + + } + } + } + + CsvMgr.IN.CFG.BitStringFormat= CSV_BITFORMAT_HEX_0X; +} + + void _INIT ProgramInit(void) { + memset(&CsvMgr, 0, sizeof(CsvMgr)); + CsvMgr.IN.CFG.DisableLogging= 1; + strcpy(CsvMgr.IN.PAR.FileDevice, "USER"); + CSVFn_Init(&CsvMgr); + testState= 0; } + void _CYCLIC ProgramCyclic(void) { + CSVFn_Cyclic(&CsvMgr); + + /* Run once automatically a few scans after boot, so no external + trigger is needed. runTest re-runs it on demand. */ + + if(bootDelay < 50){ + bootDelay++; + if(bootDelay == 50) runTest= 1; + } + + switch(testState){ + + case 0: + + if(runTest){ + + runTest= 0; + testPass= 0; + testFail= 0; + testDone= 0; + roundTripOk= 0; + + /* Cleared here, not recomputed on the way out: the error + paths in states 1 and 2 jump straight to state 3 and + never reach the state 9 that sets it, so a previous + passing run would otherwise leave it TRUE */ + + suiteOk= 0; + strcpy((char*)firstFail, ""); + + checkPvTypes(); + runReadCases(); + runWriteCases(); + runRoundTrip(); + + /* Now the end to end path: write real PVs to a file, + clear them, and read the file back. This is the only + part that exercises csvGetVarInfo/PV_ninfo for real. */ + + tByte= 0xAB; + tWord= 0xBEEF; + tDword= 0xDEADBEEF; + tUsint= 42; + + /* Cleared in full: CSVFn_Cyclic walks every entry regardless of + the empty terminator, so a shorter list would leave rows + behind from the previous run */ + + memset( CsvMgr.IN.PAR.VariableList, 0, sizeof(CsvMgr.IN.PAR.VariableList) ); + + strcpy(CsvMgr.IN.PAR.FileName, "bitstring.csv"); + strcpy(CsvMgr.IN.PAR.VariableList[0], "Default:tByte"); + strcpy(CsvMgr.IN.PAR.VariableList[1], "Default:tWord"); + strcpy(CsvMgr.IN.PAR.VariableList[2], "Default:tDword"); + strcpy(CsvMgr.IN.PAR.VariableList[3], "Default:tUsint"); + strcpy(CsvMgr.IN.PAR.VariableList[4], ""); + + CsvMgr.IN.CMD.SaveVariableListToFile= 1; + + testState= 1; + + } + + break; + + + case 1: + + if(CsvMgr.OUT.STAT.Done){ + + CsvMgr.IN.CMD.SaveVariableListToFile= 0; + + /* Wipe the PVs so a successful read is unambiguous */ + tByte= 0; + tWord= 0; + tDword= 0; + tUsint= 0; + + CsvMgr.IN.CMD.OpenFile= 1; + testState= 2; + + } + else if(CsvMgr.OUT.STAT.Error){ + CsvMgr.IN.CMD.SaveVariableListToFile= 0; + recordFail("save to file"); + CsvMgr.IN.CMD.AcknowledgeError= 1; + testState= 3; + } + + break; + + + case 2: + + if(CsvMgr.OUT.STAT.Done){ + + CsvMgr.IN.CMD.OpenFile= 0; + + roundTripOk= (tByte == 0xAB) + && (tWord == 0xBEEF) + && (tDword == 0xDEADBEEF) + && (tUsint == 42); + + if(roundTripOk) testPass++; else recordFail("file round trip"); + + testState= 5; + + } + else if(CsvMgr.OUT.STAT.Error){ + CsvMgr.IN.CMD.OpenFile= 0; + recordFail("open file"); + CsvMgr.IN.CMD.AcknowledgeError= 1; + testState= 3; + } + + break; + + + case 3: + + CsvMgr.IN.CMD.AcknowledgeError= 0; + + testDone= 1; + testState= 0; + break; + + /* Read a hand written file, exercising csvParseLine and + csvGetVarInfo rather than calling csvOpenVar directly */ + + case 5: + + tByte= 0; tWord= 0; tDword= 0; tUsint= 0; + + strcpy(CsvMgr.IN.PAR.FileName, "crafted.csv"); + CsvMgr.IN.CMD.OpenFile= 1; + testState= 6; + break; + + + case 6: + + if(CsvMgr.OUT.STAT.Done || CsvMgr.OUT.STAT.Error){ + + CsvMgr.IN.CMD.OpenFile= 0; + + c1Byte= tByte; + c1Word= tWord; + c1Dword= tDword; + c1Usint= tUsint; + c1Status= CsvMgr.OUT.STAT.ErrorID; + + if( (c1Status == 0) && (c1Byte == 0x7F) && (c1Word == 0x00FF) + && (c1Dword == 0xFFFFFFFF) && (c1Usint == 99) ){ + testPass++; + } + else{ + recordFail("crafted.csv values"); + } + + CsvMgr.IN.CMD.AcknowledgeError= CsvMgr.OUT.STAT.Error; + testState= 7; + + } + + break; + + + /* A file with one out of range line: the bad line must fail and + the good lines must still load */ + + case 7: + + CsvMgr.IN.CMD.AcknowledgeError= 0; + + tByte= 0x11; tWord= 0; tDword= 0; + + strcpy(CsvMgr.IN.PAR.FileName, "badline.csv"); + CsvMgr.IN.CMD.OpenFile= 1; + testState= 8; + break; + + + case 8: + + if(CsvMgr.OUT.STAT.Done || CsvMgr.OUT.STAT.Error){ + + CsvMgr.IN.CMD.OpenFile= 0; + + c2Byte= tByte; + c2Word= tWord; + c2Dword= tDword; + c2ErrorID= CsvMgr.OUT.STAT.ErrorID; + + /* 0x1FF does not fit a BYTE, so tByte keeps 0x11, the + other two load, and the operation reports a line failure */ + + if( (c2Byte == 0x11) && (c2Word == 0x1234) + && (c2Dword == 0xABCDEF) && (c2ErrorID == CSV_ERR_LINEFAILURE) ){ + testPass++; + } + else{ + recordFail("badline.csv handling"); + } + + CsvMgr.IN.CMD.AcknowledgeError= CsvMgr.OUT.STAT.Error; + testState= 9; + + } + + break; + + + case 9: + + CsvMgr.IN.CMD.AcknowledgeError= 0; + + /* Computed before the results are written, so the file carries + this run's verdict. The count is checked as well as the + failure tally, so a phase that never ran cannot pass by + silence. */ + + suiteOk= (testFail == 0) + && (testPass == CSVTEST_EXPECTED_ASSERTIONS) + && (pvTypeOk != 0) + && (roundTripOk != 0); + + memset( CsvMgr.IN.PAR.VariableList, 0, sizeof(CsvMgr.IN.PAR.VariableList) ); + + strcpy(CsvMgr.IN.PAR.FileName, "result.csv"); + strcpy(CsvMgr.IN.PAR.VariableList[0], "Default:suiteOk"); + strcpy(CsvMgr.IN.PAR.VariableList[1], "Default:testPass"); + strcpy(CsvMgr.IN.PAR.VariableList[2], "Default:testFail"); + strcpy(CsvMgr.IN.PAR.VariableList[3], "Default:firstFail"); + strcpy(CsvMgr.IN.PAR.VariableList[4], "Default:pvTypeByte"); + strcpy(CsvMgr.IN.PAR.VariableList[5], "Default:pvTypeWord"); + strcpy(CsvMgr.IN.PAR.VariableList[6], "Default:pvTypeDword"); + strcpy(CsvMgr.IN.PAR.VariableList[7], "Default:pvTypeOk"); + strcpy(CsvMgr.IN.PAR.VariableList[8], "Default:roundTripOk"); + strcpy(CsvMgr.IN.PAR.VariableList[9], "Default:c1Byte"); + strcpy(CsvMgr.IN.PAR.VariableList[10], "Default:c1Word"); + strcpy(CsvMgr.IN.PAR.VariableList[11], "Default:c1Dword"); + strcpy(CsvMgr.IN.PAR.VariableList[12], "Default:c1Usint"); + strcpy(CsvMgr.IN.PAR.VariableList[13], "Default:c2Byte"); + strcpy(CsvMgr.IN.PAR.VariableList[14], "Default:c2Word"); + strcpy(CsvMgr.IN.PAR.VariableList[15], "Default:c2Dword"); + strcpy(CsvMgr.IN.PAR.VariableList[16], "Default:c2ErrorID"); + strcpy(CsvMgr.IN.PAR.VariableList[17], ""); + + CsvMgr.IN.CMD.SaveVariableListToFile= 1; + testState= 10; + break; + + + case 10: + + if(CsvMgr.OUT.STAT.Done || CsvMgr.OUT.STAT.Error){ + CsvMgr.IN.CMD.SaveVariableListToFile= 0; + CsvMgr.IN.CMD.AcknowledgeError= CsvMgr.OUT.STAT.Error; + testDone= 1; + testState= 3; + } + + break; + + } } + void _EXIT ProgramExit(void) { - } - diff --git a/example/AsProject/Logical/Programs/Default/Variables.var b/example/AsProject/Logical/Programs/Default/Variables.var index 3e8a206..b18f722 100644 --- a/example/AsProject/Logical/Programs/Default/Variables.var +++ b/example/AsProject/Logical/Programs/Default/Variables.var @@ -1,6 +1,35 @@ VAR + CsvMgr : CSVFileMgr_typ; + runTest : BOOL; (*Rising edge runs the bit string test suite*) + testState : DINT; + testPass : UDINT; + testFail : UDINT; + firstFail : STRING[120]; + testDone : BOOL; + suiteOk : BOOL; (*The one flag that means PASSED: no failures, full count, and the suite actually finished*) + pvTypeByte : UDINT; (*DataType PV_ninfo reports for a BYTE*) + pvTypeWord : UDINT; (*DataType PV_ninfo reports for a WORD*) + pvTypeDword : UDINT; (*DataType PV_ninfo reports for a DWORD*) + pvTypeOk : BOOL; (*Set when the reported types match CSV_TYPE_BYTE/WORD/DWORD*) + bootDelay : UDINT; + c1Byte : UDINT; + c1Word : UDINT; + c1Dword : UDINT; + c1Usint : UDINT; + c1Status : UINT; + c2Byte : UDINT; + c2Word : UDINT; + c2Dword : UDINT; + c2ErrorID : UINT; + roundTripOk : BOOL; (*Set when the file round trip returned the original values*) +END_VAR +VAR + tByte : BYTE; (*Round trip targets, written by the file*) + tWord : WORD; + tDword : DWORD; + tUsint : USINT; (*Control: an already supported type*) END_VAR VAR CONSTANT diff --git a/example/AsProject/Physical/ARsim/Config.pkg b/example/AsProject/Physical/ARsim/Config.pkg new file mode 100644 index 0000000..8fe8fdb --- /dev/null +++ b/example/AsProject/Physical/ARsim/Config.pkg @@ -0,0 +1,10 @@ + + + + + Hardware.hw + Hardware.hwl + PC + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/Hardware.hw b/example/AsProject/Physical/ARsim/Hardware.hw new file mode 100644 index 0000000..7a6032a --- /dev/null +++ b/example/AsProject/Physical/ARsim/Hardware.hw @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/Hardware.hwl b/example/AsProject/Physical/ARsim/Hardware.hwl new file mode 100644 index 0000000..4103978 --- /dev/null +++ b/example/AsProject/Physical/ARsim/Hardware.hwl @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/CertificateRevocationLists/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/CertificateRevocationLists/Package.pkg new file mode 100644 index 0000000..973c732 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/CertificateRevocationLists/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/CertificateSigningRequests/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/CertificateSigningRequests/Package.pkg new file mode 100644 index 0000000..683b25c --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/CertificateSigningRequests/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/Certificates/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/Certificates/Package.pkg new file mode 100644 index 0000000..66601a5 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/Certificates/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/Package.pkg new file mode 100644 index 0000000..08d5735 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/Package.pkg @@ -0,0 +1,8 @@ + + + + + Certificates + PrivateKeys + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/PrivateKeys/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/PrivateKeys/Package.pkg new file mode 100644 index 0000000..b03ca4c --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/OwnCertificates/PrivateKeys/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/Package.pkg new file mode 100644 index 0000000..3748db4 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/Package.pkg @@ -0,0 +1,10 @@ + + + + + OwnCertificates + ThirdPartyCertificates + CertificateRevocationLists + CertificateSigningRequests + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/ThirdPartyCertificates/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/ThirdPartyCertificates/Package.pkg new file mode 100644 index 0000000..7ea4734 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/ThirdPartyCertificates/Package.pkg @@ -0,0 +1,7 @@ + + + + + SoftwareCertificates + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/ThirdPartyCertificates/SoftwareCertificates/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/ThirdPartyCertificates/SoftwareCertificates/Package.pkg new file mode 100644 index 0000000..0c531bc --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/CertificateStore/ThirdPartyCertificates/SoftwareCertificates/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Firewall/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Firewall/Package.pkg new file mode 100644 index 0000000..591a078 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Firewall/Package.pkg @@ -0,0 +1,7 @@ + + + + + Rules.firewallRules + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Firewall/Rules.firewallRules b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Firewall/Rules.firewallRules new file mode 100644 index 0000000..8748b19 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Firewall/Rules.firewallRules @@ -0,0 +1,6 @@ + + + + + + diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/ManagedCertificateStores/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/ManagedCertificateStores/Package.pkg new file mode 100644 index 0000000..eb0989c --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/ManagedCertificateStores/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Package.pkg new file mode 100644 index 0000000..644ca07 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/Package.pkg @@ -0,0 +1,11 @@ + + + + + UserRoleSystem + CertificateStore + ManagedCertificateStores + TransportLayerSecurity + Firewall + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/TransportLayerSecurity/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/TransportLayerSecurity/Package.pkg new file mode 100644 index 0000000..58a4ff8 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/TransportLayerSecurity/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/BRRole.brrole b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/BRRole.brrole new file mode 100644 index 0000000..6795c17 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/BRRole.brrole @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/Package.pkg b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/Package.pkg new file mode 100644 index 0000000..4d74aab --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/Package.pkg @@ -0,0 +1,9 @@ + + + + + BRRole.brrole + User.user + Role.role + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/Role.role b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/Role.role new file mode 100644 index 0000000..39281ec --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/Role.role @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/User.user b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/User.user new file mode 100644 index 0000000..a6b973c --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/AccessAndSecurity/UserRoleSystem/User.user @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/Package.pkg b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/Package.pkg new file mode 100644 index 0000000..1faa651 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/Package.pkg @@ -0,0 +1,8 @@ + + + + + UaCsConfig.uacfg + UaDvConfig.uadcfg + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/UaCsConfig.uacfg b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/UaCsConfig.uacfg new file mode 100644 index 0000000..1c08ea0 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/UaCsConfig.uacfg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/UaDvConfig.uadcfg b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/UaDvConfig.uadcfg new file mode 100644 index 0000000..3c2eab6 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaCs/UaDvConfig.uadcfg @@ -0,0 +1,5 @@ + + + + + diff --git a/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaFx/Package.pkg b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaFx/Package.pkg new file mode 100644 index 0000000..40ab206 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Connectivity/OpcUaFx/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/Connectivity/Package.pkg b/example/AsProject/Physical/ARsim/PC/Connectivity/Package.pkg new file mode 100644 index 0000000..1727635 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Connectivity/Package.pkg @@ -0,0 +1,8 @@ + + + + + OpcUaCs + OpcUaFx + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/Cpu.per b/example/AsProject/Physical/ARsim/PC/Cpu.per new file mode 100644 index 0000000..fb5c92e --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Cpu.per @@ -0,0 +1,4 @@ + +VAR_CONFIG + +END_VAR \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/Cpu.pkg b/example/AsProject/Physical/ARsim/PC/Cpu.pkg new file mode 100644 index 0000000..7e9af97 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Cpu.pkg @@ -0,0 +1,21 @@ + + + + + Cpu.sw + Cpu.per + IoMap.iom + PvMap.vvm + Connectivity + TextSystem + UnitSystem + AccessAndSecurity + + + + + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/Cpu.sw b/example/AsProject/Physical/ARsim/PC/Cpu.sw new file mode 100644 index 0000000..8300fe2 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/Cpu.sw @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/AsProject/Physical/ARsim/PC/IoMap.iom b/example/AsProject/Physical/ARsim/PC/IoMap.iom new file mode 100644 index 0000000..fb5c92e --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/IoMap.iom @@ -0,0 +1,4 @@ + +VAR_CONFIG + +END_VAR \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/PvMap.vvm b/example/AsProject/Physical/ARsim/PC/PvMap.vvm new file mode 100644 index 0000000..fb5c92e --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/PvMap.vvm @@ -0,0 +1,4 @@ + +VAR_CONFIG + +END_VAR \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/TextSystem/Package.pkg b/example/AsProject/Physical/ARsim/PC/TextSystem/Package.pkg new file mode 100644 index 0000000..24b827e --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/TextSystem/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/ARsim/PC/UnitSystem/Package.pkg b/example/AsProject/Physical/ARsim/PC/UnitSystem/Package.pkg new file mode 100644 index 0000000..f45ab49 --- /dev/null +++ b/example/AsProject/Physical/ARsim/PC/UnitSystem/Package.pkg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/AsProject/Physical/Physical.pkg b/example/AsProject/Physical/Physical.pkg index 351df13..7fed428 100644 --- a/example/AsProject/Physical/Physical.pkg +++ b/example/AsProject/Physical/Physical.pkg @@ -2,6 +2,7 @@ + ARsim Intel ARM diff --git a/example/AsProject/package-lock.json b/example/AsProject/package-lock.json index b41c6d6..179bd72 100644 --- a/example/AsProject/package-lock.json +++ b/example/AsProject/package-lock.json @@ -13,7 +13,7 @@ "@loupeteam/hmitools": "^1.0.0", "@loupeteam/librarybuilderproject": "^1.0.1", "@loupeteam/logthat": "^1.0.0", - "@loupeteam/stringext": "^1.0.0" + "@loupeteam/stringext": "^1.1.0" } }, "node_modules/@loupeteam/fiowrap": { @@ -58,9 +58,9 @@ "license": "MIT" }, "node_modules/@loupeteam/stringext": { - "version": "1.0.0", - "resolved": "https://npm.pkg.github.com/download/@loupeteam/stringext/1.0.0/9da05bfa435b70b88d57f6a3abe7e1b03d1ac9f4", - "integrity": "sha512-CkwWziPxGwLisndcMunmR7DLdX1tvrt1hxHVn9ZYqJ8L6defPZr/ZWutpT+qhQJgoQnt5n+N48M1gbv6WclTHg==", + "version": "1.1.0", + "resolved": "https://npm.pkg.github.com/download/@loupeteam/stringext/1.1.0/afe5d94fd4e1a39806d6bc5c258716336fe2089e", + "integrity": "sha512-F5/Csobt7DMbxpIUPnvKdGcr+Ohp/3RUiYgr2ROnwtkrXTtTUYdcU0Q+60A2dYsNKao+eG9/R0aYi2/deGmaXw==", "license": "MIT" } } diff --git a/example/AsProject/package.json b/example/AsProject/package.json index 0a752dd..8757642 100644 --- a/example/AsProject/package.json +++ b/example/AsProject/package.json @@ -18,6 +18,6 @@ "@loupeteam/hmitools": "^1.0.0", "@loupeteam/librarybuilderproject": "^1.0.1", "@loupeteam/logthat": "^1.0.0", - "@loupeteam/stringext": "^1.0.0" + "@loupeteam/stringext": "^1.1.0" } } diff --git a/example/UserPartition/ARsim/badline.csv b/example/UserPartition/ARsim/badline.csv new file mode 100644 index 0000000..12067bb --- /dev/null +++ b/example/UserPartition/ARsim/badline.csv @@ -0,0 +1,4 @@ +// one bad line among good ones +Default:tByte,0x1FF +Default:tWord,0x1234 +Default:tDword,$ABCDEF diff --git a/example/UserPartition/ARsim/crafted.csv b/example/UserPartition/ARsim/crafted.csv new file mode 100644 index 0000000..bfe4b6f --- /dev/null +++ b/example/UserPartition/ARsim/crafted.csv @@ -0,0 +1,5 @@ +// hand written, awkward but valid +Default:tByte, 0x7F , description with, commas +Default:tWord,16#00FF +Default:tDword, 4294967295 +Default:tUsint, 99 diff --git a/src/Ar/CSVFileLib/ANSIC.lby b/src/Ar/CSVFileLib/ANSIC.lby index f9b3fa5..34da68d 100644 --- a/src/Ar/CSVFileLib/ANSIC.lby +++ b/src/Ar/CSVFileLib/ANSIC.lby @@ -1,6 +1,6 @@  - + CSVFileLib.typ CSVFileLib.var @@ -38,7 +38,7 @@ - + diff --git a/src/Ar/CSVFileLib/CHANGELOG.md b/src/Ar/CSVFileLib/CHANGELOG.md index f5250ef..d2c2035 100644 --- a/src/Ar/CSVFileLib/CHANGELOG.md +++ b/src/Ar/CSVFileLib/CHANGELOG.md @@ -1,3 +1,10 @@ +2.1.0 - Add support for BYTE, WORD and DWORD variables + BEHAVIOUR CHANGE: opening a file now writes BYTE/WORD/DWORD variables that + were previously left untouched and reported as an unsupported type + Written as hex by default; IN.CFG.BitStringFormat selects 0x, 16# or decimal + Reading accepts hex (0x, 16#, $) and decimal regardless of the setting + Requires StringExt 1.1.0 + 2.0.1 - Fix page fault when the file being read is larger than IN.CFG.MaxFileSize Read buffers now allocate one extra byte so a completely full buffer is still a valid string, and CSV_ST_OPEN reports CSV_ERR_BUFFERFULL instead of parsing a diff --git a/src/Ar/CSVFileLib/CSVFileLib.var b/src/Ar/CSVFileLib/CSVFileLib.var index 0a4f682..81cf7ab 100644 --- a/src/Ar/CSVFileLib/CSVFileLib.var +++ b/src/Ar/CSVFileLib/CSVFileLib.var @@ -53,6 +53,9 @@ VAR CONSTANT CSV_MAX_UINT : DINT := 65535; CSV_MIN_UDINT : UDINT := 0; CSV_MAX_UDINT : UDINT := 4294967295; + CSV_MAX_BYTE : UDINT := 255; + CSV_MAX_WORD : UDINT := 65535; + CSV_MAX_DWORD : UDINT := 4294967295; CSV_MIN_REAL : REAL := -3.4E038; CSV_MAX_REAL : REAL := 3.4E038; CSV_MIN_LREAL : LREAL := -1.7976931348623158E308; diff --git a/src/Ar/CSVFileLib/CSVFileLib_enum.typ b/src/Ar/CSVFileLib/CSVFileLib_enum.typ index 3d28895..58eefae 100644 --- a/src/Ar/CSVFileLib/CSVFileLib_enum.typ +++ b/src/Ar/CSVFileLib/CSVFileLib_enum.typ @@ -57,6 +57,12 @@ TYPE CSV_TYPE_WSTRING, (*21*) CSV_TYPE_LINT := 23 (*23*) ); + CSV_BITFORMAT_enum : + ( + CSV_BITFORMAT_HEX_0X := 0, (*0x00FF - default*) + CSV_BITFORMAT_HEX_IEC, (*16#00FF*) + CSV_BITFORMAT_DECIMAL (*255*) + ); CSV_INFO_enum : ( CSV_INFO_NOVARNAMEFOUND, (*0*) diff --git a/src/Ar/CSVFileLib/CSVFileMgr.typ b/src/Ar/CSVFileLib/CSVFileMgr.typ index a549e93..968d1ca 100644 --- a/src/Ar/CSVFileLib/CSVFileMgr.typ +++ b/src/Ar/CSVFileLib/CSVFileMgr.typ @@ -82,6 +82,7 @@ TYPE DisableLogging : BOOL; LoggerName : STRING[LOG_STRLEN_LOGGERNAME]; DoubleSigFigs : USINT; + BitStringFormat : USINT; (*How BYTE/WORD/DWORD values are written, see CSV_BITFORMAT_enum. Reading accepts hex and decimal regardless of this setting*) END_STRUCT; CSVFileMgr_IN_PAR_typ : STRUCT FileDevice : STRING[CSV_STRLEN_FILEDEVICE]; diff --git a/src/Ar/CSVFileLib/csvOpenVar.c b/src/Ar/CSVFileLib/csvOpenVar.c index 6525aa4..dfc57fa 100644 --- a/src/Ar/CSVFileLib/csvOpenVar.c +++ b/src/Ar/CSVFileLib/csvOpenVar.c @@ -74,6 +74,100 @@ extern "C" /* Invalid type DONE and TESTED DFB 20120314 */ + +/********************************************************************/ +/* Parse a BYTE/WORD/DWORD value from the file */ +/********************************************************************/ + +/* Reading is deliberately permissive, so that a file written with either + BitStringFormat setting reads back, as do hand written files. + + A prefixed literal (0xFF, 16#FF, $FF) is hexadecimal, bare digits are + decimal. Requiring the prefix for hex is what keeps this unambiguous - + otherwise "10" could reasonably mean either ten or sixteen. */ + + +static unsigned short csvParseBitString(const char* pValue, unsigned long* pResult) +{ + + const char* p; + + UDINT Value, Digit; + + + if( (pValue == 0) + || (pResult == 0) + ){ + + return CSV_ERR_INVALIDINPUT; + + } + + + p= pValue; + + p+= strspn( p, " \t\r\n\v\f" ); + + + /* Hexadecimal, if there is a prefix */ + + if( ( (p[0] == '0') && ((p[1] == 'x') || (p[1] == 'X')) ) + || ( (p[0] == '1') && (p[1] == '6') && (p[2] == '#') ) + || ( p[0] == '$' ) + ){ + + if( HexStringToUDINT( (UDINT)p, (UDINT)pResult ) != 0 ) return CSV_ERR_INVALIDVALUE; + + return 0; + + } + + + /* Decimal. Accumulated by hand rather than with atoui(), which + saturates on overflow instead of reporting it. */ + + if( (*p < '0') + || (*p > '9') + ){ + + return CSV_ERR_INVALIDVALUE; + + } + + Value= 0; + + while( (*p >= '0') + && (*p <= '9') + ){ + + Digit= *p - '0'; + + /* Checked before multiplying so that an overflow is caught rather than wrapping */ + + if( Value > ((CSV_MAX_UDINT - Digit) / 10) ) return CSV_ERR_INVALIDVALUE; + + Value= Value * 10 + Digit; + + p++; + + } + + + /* Anything after the digits other than white space is an error */ + + p+= strspn( p, " \t\r\n\v\f" ); + + if( *p != '\0' ) return CSV_ERR_INVALIDVALUE; + + + *pResult= Value; + + return 0; + + +} // End Fn // + + unsigned short csvOpenVar(unsigned long LineNumber, struct CSVFileVariable_typ* pVariable, struct CSVFileMgr_typ* t) { @@ -137,6 +231,8 @@ unsigned short csvOpenVar(unsigned long LineNumber, struct CSVFileVariable_typ* /************************************************/ UDINT i, ValueStringLength, ValueUdint; + UDINT MaxValue; + UINT ParseStatus; DINT ValueDint; REAL ValueReal; LREAL ValueLReal; @@ -391,6 +487,44 @@ unsigned short csvOpenVar(unsigned long LineNumber, struct CSVFileVariable_typ* break; + /********************************************************************************/ + /* Bit string types */ + /********************************************************************************/ + + case CSV_TYPE_BYTE: + case CSV_TYPE_WORD: + case CSV_TYPE_DWORD: + + /* Initialised because csvParseBitString() leaves it alone on failure, + and the range check below must not read an indeterminate value */ + + ValueUdint= 0; + + ParseStatus= csvParseBitString( pVariable->Value, &ValueUdint ); + + if( pVariable->DataType == CSV_TYPE_BYTE ) MaxValue= CSV_MAX_BYTE; + else if( pVariable->DataType == CSV_TYPE_WORD ) MaxValue= CSV_MAX_WORD; + else MaxValue= CSV_MAX_DWORD; + + if( (ParseStatus != 0) + || (ValueUdint > MaxValue) + ){ + + csvAddLogInfo( (UINT)CSV_INFO_INVALIDVALUE, LineNumber, (UDINT)pVariable->Name, t); + return CSV_ERR_INVALIDVALUE; + + } + else{ + + if( pVariable->DataType == CSV_TYPE_BYTE ) *(USINT*)(pWriteAddress)= (USINT)ValueUdint; + else if( pVariable->DataType == CSV_TYPE_WORD ) *(UINT*)(pWriteAddress)= (UINT)ValueUdint; + else *(UDINT*)(pWriteAddress)= (UDINT)ValueUdint; + + } + + break; + + /********************************************************************************/ /* Unsupported and Invalid types */ /********************************************************************************/ @@ -400,9 +534,6 @@ unsigned short csvOpenVar(unsigned long LineNumber, struct CSVFileVariable_typ* case CSV_TYPE_DATE: case CSV_TYPE_ARRAY_OF_STRUCT: case CSV_TYPE_TIME_OF_DAY: - case CSV_TYPE_BYTE: - case CSV_TYPE_WORD: - case CSV_TYPE_DWORD: case CSV_TYPE_LWORD: case CSV_TYPE_WSTRING: case CSV_TYPE_LINT: diff --git a/src/Ar/CSVFileLib/csvSaveVar.c b/src/Ar/CSVFileLib/csvSaveVar.c index b35b3d7..c93a0ad 100644 --- a/src/Ar/CSVFileLib/csvSaveVar.c +++ b/src/Ar/CSVFileLib/csvSaveVar.c @@ -132,6 +132,7 @@ unsigned short csvSaveVar(unsigned long LineNumber, struct CSVFileVariable_typ* DINT ValueDint; UDINT ValueUdint; + USINT NumDigits, HexPrefix; REAL ValueReal; LREAL ValueLReal; @@ -262,6 +263,60 @@ unsigned short csvSaveVar(unsigned long LineNumber, struct CSVFileVariable_typ* break; + /********************************************************************************/ + /* Bit string types */ + /********************************************************************************/ + + case CSV_TYPE_BYTE: + case CSV_TYPE_WORD: + case CSV_TYPE_DWORD: + + + /* Pad to the width of the source type, so that a column of values lines up + and the width of the underlying variable is visible in the file */ + + if( pVariable->DataType == CSV_TYPE_BYTE ){ + + ValueUdint= *(USINT*)(pReadAddress); + NumDigits= 2; + + } + else if( pVariable->DataType == CSV_TYPE_WORD ){ + + ValueUdint= *(UINT*)(pReadAddress); + NumDigits= 4; + + } + else{ + + ValueUdint= *(UDINT*)(pReadAddress); + NumDigits= 8; + + } + + + if( t->IN.CFG.BitStringFormat == CSV_BITFORMAT_DECIMAL ){ + + uitoa( ValueUdint, (UDINT)pVariable->Value ); + + } + else{ + + if( t->IN.CFG.BitStringFormat == CSV_BITFORMAT_HEX_IEC ) HexPrefix= STREXT_HEXPREFIX_IEC; + else HexPrefix= STREXT_HEXPREFIX_0X; + + if( UDINTToHexString( ValueUdint, (UDINT)pVariable->Value, sizeof(pVariable->Value), NumDigits, HexPrefix ) < 0 ){ + + csvAddLogInfo( (UINT)CSV_INFO_INVALIDVALUE, LineNumber, (UDINT)pVariable->Name, t); + return CSV_ERR_INVALIDVALUE; + + } + + } + + break; + + /********************************************************************************/ /* Unsupported and Invalid types */ /********************************************************************************/ @@ -271,9 +326,6 @@ unsigned short csvSaveVar(unsigned long LineNumber, struct CSVFileVariable_typ* case CSV_TYPE_DATE: case CSV_TYPE_ARRAY_OF_STRUCT: case CSV_TYPE_TIME_OF_DAY: - case CSV_TYPE_BYTE: - case CSV_TYPE_WORD: - case CSV_TYPE_DWORD: case CSV_TYPE_LWORD: case CSV_TYPE_WSTRING: case CSV_TYPE_LINT: diff --git a/src/Ar/CSVFileLib/package.json b/src/Ar/CSVFileLib/package.json index 166160a..a9be05e 100644 --- a/src/Ar/CSVFileLib/package.json +++ b/src/Ar/CSVFileLib/package.json @@ -13,6 +13,6 @@ "@loupeteam/fiowrap": ">=1.0.0", "@loupeteam/hmitools": ">=1.0.0", "@loupeteam/logthat": ">=1.0.0", - "@loupeteam/stringext": ">=1.0.0" + "@loupeteam/stringext": ">=1.1.0" } }