From 8640c339ea908ca052d1135a6d63a03d246e055f Mon Sep 17 00:00:00 2001 From: brickpool Date: Sun, 17 Jan 2021 17:44:30 +0100 Subject: [PATCH] 0.5.3-20200117 --- .gitignore | 6 + CHANGELOG.md | 26 +- README.md | 3 +- examples/CyclicReading/CyclicReading.ino | 24 +- examples/FetchDataDemo/FetchDataDemo.ino | 22 +- examples/PlcInfoDemo/PlcInfoDemo.ino | 43 +- examples/ProtocolTester/ProtocolTester.ino | 17 +- examples/ReadClockDemo/ReadClockDemo.ino | 38 +- examples/RunStopDemo/RunStopDemo.ino | 40 +- examples/SerialPlotter/SerialPlotter.ino | 179 +++ examples/WriteClockDemo/WriteClockDemo.ino | 44 +- extras/docs/RefManual.md | 12 +- platformio.ini | 31 + src/DebugUtils.cpp | 38 + src/DebugUtils.h | 72 ++ src/LogoPG.cpp | 1244 ++++++++++++++------ src/LogoPG.h | 1025 ++++++++-------- 17 files changed, 1897 insertions(+), 967 deletions(-) create mode 100644 examples/SerialPlotter/SerialPlotter.ino create mode 100644 platformio.ini create mode 100644 src/DebugUtils.cpp create mode 100644 src/DebugUtils.h diff --git a/.gitignore b/.gitignore index d20f919..a8e684f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,13 @@ Thumbs.db # Ignore some sketches /src/src.ino +/src/main.cpp # Komodo files *.komodoproject /.komodotools/ + +# PlatformIO +.pio +.vscode +*.code-workspace \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cc1c1cf..3c4e807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,28 @@ This CHANGELOG file should help that the library becomes a standardized open sou The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] - 2020-12-23 +## [Unreleased] - 2021-01-17 + +## [0.5.3] - 2021-01-17 +### Added +- Example SerialPlotter.ino added to the project folder +- Updated examples for compatibility with Arduino MKR boards +- Block oriented function LogoClient::GetDBSize +- Block oriented function LogoClient::DBGet +- DebugUtils in LogoPG.h and LogoPG.cpp + +### Changed +- Using fixed-width integer types regardless of which Arduino is being targeted + +### Removed +- ArduinoLog in LogoPG.h and LogoPG.cpp + +### Fixed +- Example ProtocolTester.ino maximum indexing for receivedChars ## [0.5.2] - 2020-12-23 ### Added -- Low Level funtion LogoClient::ReadBlock +- Low Level function LogoClient::ReadBlock - Security function LogoClient::SetSessionPassword - Security function LogoClient::ClearSessionPassword - Logging (library ArduinoLog) @@ -17,7 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Protection Level in GetProtection - Update Library Reference Manual (RefManual.md) to Rev. Ai - DTE Interface Images -- Update Examples for Arduino MEGA: CyclicReading, FetchDataDemo, ProtocolTester +- Update Examples for compatibility with Arduino Mega boards ### Fixed - GetOrderCode: Support of 0BA6.ES10 @@ -149,7 +166,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - LICENCE.md added to the project - README.md added to the project -[Unreleased]: https://github.com/brickpool/logo/compare/v0.5.2...HEAD +[Unreleased]: https://github.com/brickpool/logo/compare/v0.5.3...HEAD +[0.5.3]: https://github.com/brickpool/logo/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/brickpool/logo/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/brickpool/logo/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/brickpool/logo/compare/v0.4.3...v0.5.0 diff --git a/README.md b/README.md index 7b1ffbc..0bcd1bc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Information about the API is described in the document [LOGO! Library Reference All information about the protocols and details to the _LOGO!_ PLC are described in the associated [Wiki](http://github.com/brickpool/logo/wiki). ## Releases -The current library version is [0.5.2](https://github.com/brickpool/logo/releases). This version is not the final version, it is a release candidate and has implemented only the PG protocol. +The current library version is [0.5.3](https://github.com/brickpool/logo/releases). This version is not the final version, it is a release candidate and has implemented only the PG protocol. ## Examples This directory contains the library and some examples that illustrate how the library can be used. The examples were tested with an Arduino Mega (Arduino >= 1.8.5) and Arduino UNO (only Arduino 1.8.5 is supported). Other hardware has not been tried. @@ -19,6 +19,7 @@ This directory contains the library and some examples that illustrate how the li - [ReadClockDemo.ino](/examples/ReadClockDemo/ReadClockDemo.ino) The example reads the date and time from the _LOGO!_ controller. - [WriteClockDemo.ino](/examples/WriteClockDemo/WriteClockDemo.ino) The example writes a date and time to the _LOGO!_ controller. - [PlcInfoDemo.ino](/examples/PlcInfoDemo/PlcInfoDemo.ino) The example reads the Ident Number, Firmware version and Protection level from the PLC. +- [SerialPlotter.ino](/examples/SerialPlotter/SerialPlotter.ino) This example allowing you to natively graph data from your _LOGO!_ controller by using the Arduino Serial Plotter function. ## Dependencies - _LOGO!_ controller version __0BA4__, __0BA5__ or __0BA6__, e.g. part number `6ED1052-1MD00-0BA6` diff --git a/examples/CyclicReading/CyclicReading.ino b/examples/CyclicReading/CyclicReading.ino index cc8d247..0a9afd2 100644 --- a/examples/CyclicReading/CyclicReading.ino +++ b/examples/CyclicReading/CyclicReading.ino @@ -20,9 +20,12 @@ const unsigned long cycleDelay = 500; // min > 200ms #define txPin 3 CustomSoftwareSerial LogoSerial(rxPin, txPin); #else - // Serial1: - // rxPin 18 - // txPin 19 + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 #define LogoSerial Serial1 #endif @@ -36,12 +39,10 @@ void setup() { ; // wait for serial port to connect. Needed for native USB port only } - // Start the SoftwareSerial Library + // Start the LOGO Serial interface LogoSerial.begin(9600, SERIAL_8E1); // Setup Time, 1s. delay(1000); - Serial.println(""); - Serial.println("Cable connected"); #ifdef CustomSoftwareSerial_h if (LogoSerial.isListening()) #else @@ -50,11 +51,6 @@ void setup() { Serial.println("LogoSerial is ready."); cycleTime = millis(); - // LOG_LEVEL_SILENT, LOG_LEVEL_FATAL, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_NOTICE, LOG_LEVEL_TRACE, LOG_LEVEL_VERBOSE - // Note: if you want to fully remove all logging code, uncomment #define DISABLE_LOGGING in Logging.h - // this will significantly reduce your project size - Log.begin(LOG_LEVEL_VERBOSE, &Serial); - Log.setPrefix(printTimestamp); } void loop() @@ -141,9 +137,3 @@ void printBinaryByte(byte value) Serial.print((mask & value) ? '1' : '0'); } } - -void printTimestamp(Print* _logOutput) { - char c[12]; - int m = sprintf(c, "%10lu ", millis()); - _logOutput->print(c); -} diff --git a/examples/FetchDataDemo/FetchDataDemo.ino b/examples/FetchDataDemo/FetchDataDemo.ino index d062a44..e89384d 100644 --- a/examples/FetchDataDemo/FetchDataDemo.ino +++ b/examples/FetchDataDemo/FetchDataDemo.ino @@ -19,9 +19,12 @@ byte buf[2]; #define txPin 3 CustomSoftwareSerial LogoSerial(rxPin, txPin); #else - // Serial1: - // rxPin 18 - // txPin 19 + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 #define LogoSerial Serial1 #endif @@ -35,23 +38,20 @@ void setup() { ; // wait for serial port to connect. Needed for native USB port only } - // Start the SoftwareSerial Library + // LOG_LEVEL_SILENT, LOG_LEVEL_FATAL, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_NOTICE, LOG_LEVEL_TRACE, LOG_LEVEL_VERBOSE + // Note: set to LOG_LEVEL_SILENT if you want to use the serial plotter. This will stop the serial logging + Log.begin(LOG_LEVEL_SILENT, &Serial); + + // Start the LOGO Serial interface LogoSerial.begin(9600, SERIAL_8E1); // Setup Time, 1s. delay(1000); - Serial.println(""); - Serial.println("Cable connected"); #ifdef CustomSoftwareSerial_h if (LogoSerial.isListening()) #else if (LogoSerial) #endif Serial.println("LogoSerial is ready."); - - // LOG_LEVEL_SILENT, LOG_LEVEL_FATAL, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_NOTICE, LOG_LEVEL_TRACE, LOG_LEVEL_VERBOSE - // Note: if you want to fully remove all logging code, uncomment #define DISABLE_LOGGING in Logging.h - // this will significantly reduce your project size - Log.begin(LOG_LEVEL_ERROR, &Serial); } void loop() diff --git a/examples/PlcInfoDemo/PlcInfoDemo.ino b/examples/PlcInfoDemo/PlcInfoDemo.ino index c8d0524..b3dffa5 100644 --- a/examples/PlcInfoDemo/PlcInfoDemo.ino +++ b/examples/PlcInfoDemo/PlcInfoDemo.ino @@ -1,11 +1,27 @@ -#include +#ifdef ARDUINO_AVR_UNO + #include +#endif #include "LogoPG.h" -const byte rxPin = 2; -const byte txPin = 3; +// set up the LogoSerial object +#ifdef CustomSoftwareSerial_h + #if defined(SERIAL_8E1) + #undef SERIAL_8E1 + #endif + #define SERIAL_8E1 CSERIAL_8E1 + #define rxPin 2 + #define txPin 3 + CustomSoftwareSerial LogoSerial(rxPin, txPin); +#else + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 + #define LogoSerial Serial1 +#endif -// set up the SoftwareSerial object -CustomSoftwareSerial LogoSerial(rxPin, txPin); // set up the LogoClient object LogoClient LOGO(&LogoSerial); @@ -16,16 +32,20 @@ void setup() { // Init Monitor interface Serial.begin(9600); - while (!Serial) ; // Needed for Leonardo only + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } - // Start the SoftwareSerial Library - LogoSerial.begin(9600, CSERIAL_8E1); + // Start the LOGO Serial interface + LogoSerial.begin(9600, SERIAL_8E1); // Setup Time, 1s. delay(1000); - Serial.println(""); - Serial.println("Cable connected"); +#ifdef CustomSoftwareSerial_h if (LogoSerial.isListening()) - Serial.println("Softserial is listening !"); +#else + if (LogoSerial) +#endif + Serial.println("LogoSerial is ready."); } void loop() @@ -120,4 +140,3 @@ void CheckError(int ErrNo) LOGO.Disconnect(); } } - diff --git a/examples/ProtocolTester/ProtocolTester.ino b/examples/ProtocolTester/ProtocolTester.ino index 43c4fc2..c71b79a 100644 --- a/examples/ProtocolTester/ProtocolTester.ino +++ b/examples/ProtocolTester/ProtocolTester.ino @@ -21,9 +21,12 @@ byte message[BUFFER_SIZE]; // an array to store the message data #define txPin 3 CustomSoftwareSerial LogoSerial(rxPin, txPin); #else - // Serial1: - // rxPin 18 - // txPin 19 + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 #define LogoSerial Serial1 #endif @@ -39,7 +42,7 @@ void setup() { } Serial.println("Monitor is ready."); - // Init LogoSerial + // Start the LOGO Serial interface LogoSerial.begin(9600, SERIAL_8E1); // Setup Time, 1s. delay(1000); @@ -69,8 +72,8 @@ void loop() { receivedChars[ndx] = rc; ndx++; - if (ndx >= BUFFER_SIZE) { - ndx = BUFFER_SIZE - 1; + if (ndx >= sizeof(receivedChars)) { + ndx = sizeof(receivedChars) - 1; } } else @@ -136,5 +139,3 @@ void printHex(byte *data, size_t length) // prints 8-bit data in hex with leadin Serial.print(tmp); Serial.print(" "); } } - - diff --git a/examples/ReadClockDemo/ReadClockDemo.ino b/examples/ReadClockDemo/ReadClockDemo.ino index 9a2c6f2..1481e59 100644 --- a/examples/ReadClockDemo/ReadClockDemo.ino +++ b/examples/ReadClockDemo/ReadClockDemo.ino @@ -1,12 +1,28 @@ -#include +#ifdef ARDUINO_AVR_UNO + #include +#endif #include #include "LogoPG.h" -const byte rxPin = 2; -const byte txPin = 3; +// set up the LogoSerial object +#ifdef CustomSoftwareSerial_h + #if defined(SERIAL_8E1) + #undef SERIAL_8E1 + #endif + #define SERIAL_8E1 CSERIAL_8E1 + #define rxPin 2 + #define txPin 3 + CustomSoftwareSerial LogoSerial(rxPin, txPin); +#else + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 + #define LogoSerial Serial1 +#endif -// set up the SoftwareSerial object -CustomSoftwareSerial LogoSerial(rxPin, txPin); // set up the LogoClient object LogoClient LOGO(&LogoSerial); @@ -21,14 +37,16 @@ void setup() { ; // wait for serial port to connect. Needed for native USB port only } - // Start the SoftwareSerial Library - LogoSerial.begin(9600, CSERIAL_8E1); + // Start the LOGO Serial interface + LogoSerial.begin(9600, SERIAL_8E1); // Setup Time, 1s. delay(1000); - Serial.println(""); - Serial.println("Cable connected"); +#ifdef CustomSoftwareSerial_h if (LogoSerial.isListening()) - Serial.println("Softserial is listening !"); +#else + if (LogoSerial) +#endif + Serial.println("LogoSerial is ready."); } void loop() diff --git a/examples/RunStopDemo/RunStopDemo.ino b/examples/RunStopDemo/RunStopDemo.ino index f19ba3c..287b3ec 100644 --- a/examples/RunStopDemo/RunStopDemo.ino +++ b/examples/RunStopDemo/RunStopDemo.ino @@ -1,11 +1,27 @@ -#include +#ifdef ARDUINO_AVR_UNO + #include +#endif #include "LogoPG.h" -const byte rxPin = 2; -const byte txPin = 3; +// set up the LogoSerial object +#ifdef CustomSoftwareSerial_h + #if defined(SERIAL_8E1) + #undef SERIAL_8E1 + #endif + #define SERIAL_8E1 CSERIAL_8E1 + #define rxPin 2 + #define txPin 3 + CustomSoftwareSerial LogoSerial(rxPin, txPin); +#else + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 + #define LogoSerial Serial1 +#endif -// set up the SoftwareSerial object -CustomSoftwareSerial LogoSerial(rxPin, txPin); // set up the LogoClient object LogoClient LOGO(&LogoSerial); @@ -20,14 +36,16 @@ void setup() { ; // wait for serial port to connect. Needed for native USB port only } - // Start the SoftwareSerial Library - LogoSerial.begin(9600, CSERIAL_8E1); + // Start the LOGO Serial interface + LogoSerial.begin(9600, SERIAL_8E1); // Setup Time, 1s. delay(1000); - Serial.println(""); - Serial.println("Cable connected"); +#ifdef CustomSoftwareSerial_h if (LogoSerial.isListening()) - Serial.println("Softserial is listening !"); +#else + if (LogoSerial) +#endif + Serial.println("LogoSerial is ready."); } void loop() @@ -94,5 +112,3 @@ void CheckError(int ErrNo) LOGO.Disconnect(); } } - - diff --git a/examples/SerialPlotter/SerialPlotter.ino b/examples/SerialPlotter/SerialPlotter.ino new file mode 100644 index 0000000..c913f28 --- /dev/null +++ b/examples/SerialPlotter/SerialPlotter.ino @@ -0,0 +1,179 @@ +#ifdef ARDUINO_AVR_UNO + #if ARDUINO != 10805 + #error Arduino 1.8.5 required. + #endif + #include +#endif +#include +#include "LogoPG.h" + +unsigned long cycleTime; +const unsigned long cycleDelay = 500; // min > 200ms, max < 600ms + +// set up the LogoSerial object +#ifdef CustomSoftwareSerial_h + #if defined(SERIAL_8E1) + #undef SERIAL_8E1 + #endif + #define SERIAL_8E1 CSERIAL_8E1 + #define rxPin 2 + #define txPin 3 + CustomSoftwareSerial LogoSerial(rxPin, txPin); +#else + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 + #define LogoSerial Serial1 +#endif + +// set up the LogoClient object +LogoClient LOGO(&LogoSerial); + +void setup() { + // Init Monitor interface + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } + + // LOG_LEVEL_SILENT, LOG_LEVEL_FATAL, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_NOTICE, LOG_LEVEL_TRACE, LOG_LEVEL_VERBOSE + // Note: set to LOG_LEVEL_SILENT if you want to use the serial plotter. This will stop the serial logging + Log.begin(LOG_LEVEL_SILENT, &Serial); + Log.setPrefix(printTimestamp); + + // Start the LOGO Serial interface + LogoSerial.begin(9600, SERIAL_8E1); + // Setup Time, 1s. + delay(1000); +#ifdef CustomSoftwareSerial_h + if (LogoSerial.isListening()) +#else + if (LogoSerial) +#endif + Serial.println("LogoSerial is ready."); + + cycleTime = millis(); +} + +void loop() +{ + // Connection + while (!LOGO.Connected) + { + if (Connect()) + { + int Status; + int Result = LOGO.GetPlcStatus(&Status); + if (Result == 0) + { + if (Status != LogoCpuStatusRun) + { + Log.notice(F("STARTING THE PROG" CR)); + LOGO.PlcStart(); + } + } + else + CheckError(Result); + } + else + delay(2000); + } + + // Polling Data + if (millis()-cycleTime > cycleDelay) + { + cycleTime = millis(); + int Result = LOGO.ReadArea(LogoAreaDB, 1, VM_I01_08, 990-923, NULL); + Log.notice(F("POLL DATA ... " CR)); + if (Result == 0) + { +#if ARDUINO >= 10810 + // https://diyrobocars.com/2020/05/04/arduino-serial-plotter-the-missing-manual/ + Serial.print("Min:0,"); + + Serial.print("I1:"); + Serial.print(LH.BitAt(VM_I01_08, 0) ? 50 : 10); + Serial.print(","); + Serial.print("I2:"); + Serial.print(LH.BitAt(VM_I01_08, 1) ? 100 : 60); + Serial.print(","); + Serial.print("I3:"); + Serial.print(LH.BitAt(VM_I01_08, 2) ? 150 : 110); + Serial.print(","); + Serial.print("I4:"); + Serial.print(LH.BitAt(VM_I01_08, 3) ? 200 : 160); + Serial.print(","); + Serial.print("I5:"); + Serial.print(LH.BitAt(VM_I01_08, 4) ? 250 : 210); + Serial.print(","); + Serial.print("I6:"); + Serial.print(LH.BitAt(VM_I01_08, 5) ? 300 : 260); + Serial.print(","); + + Serial.print("AI1:"); + Serial.print(LH.IntegerAt(VM_AI1_Hi)); + Serial.print(","); + Serial.print("AI2:"); + Serial.print(LH.IntegerAt(VM_AI2_Hi)); + Serial.print(","); + + Serial.print("Q1:"); + Serial.print(LH.BitAt(VM_Q01_08, 0) ? 750 : 710); + Serial.print(","); + Serial.print("Q2:"); + Serial.print(LH.BitAt(VM_Q01_08, 1) ? 800 : 760); + Serial.print(","); + Serial.print("Q3:"); + Serial.print(LH.BitAt(VM_Q01_08, 2) ? 850 : 810); + Serial.print(","); + Serial.print("Q4:"); + Serial.print(LH.BitAt(VM_Q01_08, 3) ? 900 : 860); + Serial.print(","); + + Serial.println("Max:1023"); +#else + Log.notice(F("Analog input 1:")); + Serial.print(LH.IntegerAt(VM_AI1_Hi)); +#endif + } + else + CheckError(Result); + } +} + +bool Connect() +{ + int Result = LOGO.Connect(); + Log.notice(F("Try to connect with LOGO" CR)); + if (Result == 0) + { + Log.notice(F("Connected!" CR)); + Log.notice(F("PDU Length = %d" CR), LOGO.GetPDULength()); + } + else + { + Log.error(F("Connection error!" CR)); + } + return Result == 0; +} + +void CheckError(int ErrNo) +{ + Log.error(F("Error No. %d" CR), ErrNo); + + // Checks if it's a LOGO Error => we need to disconnect + if (ErrNo & 0x01FF) + { + Log.error(F("LOGO ERROR, disconnecting." CR)); + LOGO.Disconnect(); + } +} + +void printTimestamp(Print* _logOutput) { + char c[12]; + int m = sprintf(c, "%10lu ", millis()); + _logOutput->print(c); +} diff --git a/examples/WriteClockDemo/WriteClockDemo.ino b/examples/WriteClockDemo/WriteClockDemo.ino index 4cd615b..5b6efc8 100644 --- a/examples/WriteClockDemo/WriteClockDemo.ino +++ b/examples/WriteClockDemo/WriteClockDemo.ino @@ -1,34 +1,52 @@ -#include +#ifdef ARDUINO_AVR_UNO + #include +#endif #include #include "LogoPG.h" -const byte rxPin = 2; -const byte txPin = 3; - const unsigned long MY_TIME = 1522238400; // Mar 28 2018 -// set up the SoftwareSerial object -CustomSoftwareSerial LogoSerial(rxPin, txPin); +// set up the LogoSerial object +#ifdef CustomSoftwareSerial_h + #if defined(SERIAL_8E1) + #undef SERIAL_8E1 + #endif + #define SERIAL_8E1 CSERIAL_8E1 + #define rxPin 2 + #define txPin 3 + CustomSoftwareSerial LogoSerial(rxPin, txPin); +#else + // Mega board Serial1: + // rxPin 19 + // txPin 18 + // MKR board Serial1: + // rxPin 13 + // txPin 14 + #define LogoSerial Serial1 +#endif + // set up the LogoClient object LogoClient LOGO(&LogoSerial); void setup() { // initialize digital pin LED_BUILTIN as an output and turn the LED off pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LOW); + digitalWrite(LED_BUILTIN, LOW); // Init Monitor interface Serial.begin(9600); while (!Serial) ; // Needed for Leonardo only - // Start the SoftwareSerial Library - LogoSerial.begin(9600, CSERIAL_8E1); + // Start the LOGO Serial interface + LogoSerial.begin(9600, SERIAL_8E1); // Setup Time, 1s. delay(1000); - Serial.println(""); - Serial.println("Cable connected"); +#ifdef CustomSoftwareSerial_h if (LogoSerial.isListening()) - Serial.println("Softserial is listening !"); +#else + if (LogoSerial) +#endif + Serial.println("LogoSerial is ready."); // set the system time to the give time MY_TIME if (timeStatus() != timeSet) @@ -138,7 +156,6 @@ timeStatus_t LogoClockStatus() // Su 00:00 01-01-2003 const unsigned long START_TIME = 1041379200; const uint8_t START_WDAY = 1; - const int op_mode; TimeElements tm; if (LOGO.GetPlcDateTime(&tm) == 0) { @@ -147,4 +164,3 @@ timeStatus_t LogoClockStatus() } return timeNotSet; } - diff --git a/extras/docs/RefManual.md b/extras/docs/RefManual.md index 21009bb..6d72224 100644 --- a/extras/docs/RefManual.md +++ b/extras/docs/RefManual.md @@ -1,8 +1,8 @@ # LOGO! PG Library Reference Manual -Rev. Ai +Rev. Aj -September 2019 +September 2021 ## Preface Siemens (TM) LOGO! library for Arduino. @@ -82,12 +82,10 @@ Links the Client to a stream object. - `Interface` Stream object for example "Serial" -Returns a `0` on success or an `Error` code (see Errors Code List [below](#error-codes)). - -### LogoClient.SetConnectionType(type) +### LogoClient.SetConnectionType(ConnectionType) Sets the connection resource type, i.e the way in which the Clients connects to the _LOGO!_ device. - - `type` + - `ConnectionType` Connection Type | Value | Description --- | --- | --- @@ -96,8 +94,6 @@ Connection Type | Value | Description ### LogoClient.Disconnect() Disconnects "gracefully" the Client from the _LOGO!_ device. -Returns a `0` on success or an `Error` code (see Errors Code List [below](#error-codes)). - ## API - Base Data I/O functions ### LogoClient.ReadArea(int Area, word DBNumber, word Start, word Amount, void \*ptrData) diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..c9d9317 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,31 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[common] +default_src_filter = + +<*> + -<.git/> + -<.svn/> + - + - + - + - + - + +[env:mkrwifi1010] +platform = atmelsam +board = mkrwifi1010 +framework = arduino +lib_extra_dirs = ../libraries +lib_ignore = + CustomSoftwareSerial +src_filter = + ${common.default_src_filter} + - diff --git a/src/DebugUtils.cpp b/src/DebugUtils.cpp new file mode 100644 index 0000000..057de76 --- /dev/null +++ b/src/DebugUtils.cpp @@ -0,0 +1,38 @@ +/* + * DebugUtils.cpp + * Utility functions to help debugging running code. + * + * author: J.Schneider + * Copyright (c) 2020 J.Schneider + * The Utilities are licensed under the GNU Lesser General Public License v3.0. + * + * However, these Utilities distributes and uses code from + * other Open Source Projects that have their own licenses: + * + * - https://gr33nonline.wordpress.com/2018/06/26/debug/ + * - https://github.com/sigrokproject/libserialport/blob/master/libserialport_internal.h + * - https://playground.arduino.cc/Main/Printf/ + * + */ + +#include +#include +#include "DebugUtils.h" + +const byte numChars = 128; // resulting string limited to 128 chars + +void _default_debug_handler(const __FlashStringHelper *format, ...) +{ + static char buf[numChars]; + va_list args; + va_start(args, format); +#ifdef __AVR__ + vsnprintf_P(buf, sizeof(buf), (const char *)format, args); // progmem for AVR +#else + vsnprintf(buf, sizeof(buf), (const char *)format, args); // for the rest of the world +#endif + va_end(args); + if (Serial) Serial.print(buf); +} + +void (*_debug_handler)(const __FlashStringHelper *format, ...) = _default_debug_handler; diff --git a/src/DebugUtils.h b/src/DebugUtils.h new file mode 100644 index 0000000..9fd5028 --- /dev/null +++ b/src/DebugUtils.h @@ -0,0 +1,72 @@ +/* + * DebugUtils.h + * Utility functions to help debugging running code. + * + * author: J.Schneider + * Copyright (c) 2020,2021 J.Schneider + * The Utilities are licensed under the GNU Lesser General Public License v3.0. + * + * However, these Utilities distributes and uses code from + * other Open Source Projects that have their own licenses: + * + * - https://gr33nonline.wordpress.com/2018/06/26/debug/ + * - https://github.com/sigrokproject/libserialport/blob/master/libserialport_internal.h + * - https://playground.arduino.cc/Main/Printf/ + * + */ + +#ifndef DEBUGUTILS_H +#define DEBUGUTILS_H + +#ifdef _DEBUG +#include + +extern void (*_debug_handler)(const __FlashStringHelper *format, ...); + +/* Debug output macros. */ +#define DEBUG_FMT(fmt, ...) do { \ + if (_debug_handler) \ + _debug_handler(F(fmt ".\n"), __VA_ARGS__); \ +} while (0) +#define DEBUG(msg) DEBUG_FMT(msg, NULL) +#define DEBUG_ERROR(err, msg) DEBUG_FMT("%s returning " #err ": " msg, __func__) +#define RETURN() do { \ + DEBUG_FMT("%s returning", __func__); \ + return; \ +} while (0) +#define RETURN_CODE(x) do { \ + DEBUG_FMT("%s returning " #x, __func__); \ + return x; \ +} while (0) +#define RETURN_ERROR(err, msg) do { \ + DEBUG_ERROR(err, msg); \ + return err; \ +} while (0) +#define RETURN_INT(x) do { \ + int _x = x; \ + DEBUG_FMT("%s returning %d", __func__, _x); \ + return _x; \ +} while (0) +#define RETURN_STRING(x) do { \ + char *_x = x; \ + DEBUG_FMT("%s returning %s", __func__, _x); \ + return _x; \ +} while (0) +#define SET_ERROR(val, err, msg) do { DEBUG_ERROR(err, msg); val = err; } while (0) +#define TRACE(fmt, ...) DEBUG_FMT("%s(" fmt ") called", __func__, __VA_ARGS__) +#define TRACE_VOID() DEBUG_FMT("%s() called", __func__) +#else +#define DEBUG_FMT(fmt, ...) +#define DEBUG(msg) +#define DEBUG_ERROR(err, msg) +#define RETURN() do { return; } while (0) +#define RETURN_CODE(x) do { return x; } while (0) +#define RETURN_ERROR(err, msg) do { return err; } while (0) +#define RETURN_INT(x) do { return x; } while (0) +#define RETURN_STRING(x) do { return x; } while (0) +#define SET_ERROR(val, err, msg) do { val = err; } while (0) +#define TRACE(fmt, ...) +#define TRACE_VOID() +#endif + +#endif // DEBUGUTILS_H diff --git a/src/LogoPG.cpp b/src/LogoPG.cpp index b9b476f..2311f2d 100644 --- a/src/LogoPG.cpp +++ b/src/LogoPG.cpp @@ -1,7 +1,7 @@ /* - * LogoPG library, Version 0.5.2-20201223 + * LogoPG library, Version 0.5.3-20200117 * - * Portion copyright (c) 2018,2020 by Jan Schneider + * Portion copyright (c) 2018,2020,2021 by Jan Schneider * * LogoPG provided a library under ARDUINO to read data from a * Siemens(tm) LOGO! PLC via the serial programing interface. @@ -29,20 +29,21 @@ * */ -#include "Arduino.h" +#include #ifdef _EXTENDED -#include "TimeLib.h" +#include #endif #include "LogoPG.h" #include -#include "ArduinoLog.h" +#include "DebugUtils.h" -// For further informations about the structures, command codes, byte arrays and their meanings -// see http://github.com/brickpool/logo +// For further informations about the structures, command codes, byte arrays and +// their meanings see http://github.com/brickpool/logo /* - Arduino has not a multithreaded environment and we can only use one LogoClient instance. - To save memory we can define telegrams and data areas as globals, since only one client can be used at time. + Arduino has not a multithreaded environment and we can only use one LogoClient + instance. To save memory we can define telegrams and data areas as globals, + since only one client can be used at time. */ @@ -129,7 +130,8 @@ const PROGMEM char ORDER_CODE[Size_OC] = "6ED1052-xxx00-0BAx"; // and updating the time must be done with unsigned long. // https://playground.arduino.cc/Code/TimingRollover #define TIMEOUT 500UL // set timeout between 200 and 600 ms -#define CYCLETIME 600UL // set cycle time for function 0x13 between 190 and 600 ms +#define CYCLETIME 600UL // set cycle time for function 0x13 + // between 190 and 600 ms #define VM_START 0 // first valid address #define VM_USER_AREA 0 // user defined area @@ -212,6 +214,57 @@ const byte VM_MAP_923_983_0BA6[] PROGMEM = #endif // _EXTENDED */ +#ifdef _DEBUG +#define RETURN_OK() do { \ + DEBUG_FMT("%s returning OK", __func__); \ + return 0; \ +} while (0) +#define RETURN_CODEVAL(x) do { \ + int _x = x; \ + switch (_x) { \ + case 0: RETURN_OK(); \ + case errStreamConnectionFailed: RETURN_CODE(errStreamConnectionFailed); \ + case errStreamConnectionReset: RETURN_CODE(errStreamConnectionReset); \ + case errStreamDataRecvTout: RETURN_CODE(errStreamDataRecvTout); \ + case errStreamDataSend: RETURN_CODE(errStreamDataSend); \ + case errStreamDataRecv: RETURN_CODE(errStreamDataRecv); \ + case errPGConnect: RETURN_CODE(errPGConnect); \ + case errPGInvalidPDU: RETURN_CODE(errPGInvalidPDU); \ + case errCliInvalidPDU: RETURN_CODE(errCliInvalidPDU); \ + case errCliSendingPDU: RETURN_CODE(errCliSendingPDU); \ + case errCliDataRead: RETURN_CODE(errCliDataRead); \ + case errCliDataWrite: RETURN_CODE(errCliDataWrite); \ + case errCliFunction: RETURN_CODE(errCliFunction); \ + case errCliBufferTooSmall: RETURN_CODE(errCliBufferTooSmall); \ + case errCliNegotiatingPDU: RETURN_CODE(errCliNegotiatingPDU); \ + default: RETURN_INT(_x); \ + } \ +} while (0) +#define RETURN_BOOL(x) do { \ + bool _x = x; \ + DEBUG_FMT("%s returning %s", __func__, _x ? "true" : "false"); \ + return _x; \ +} while (0) +#define RETURN_BYTE(x) do { \ + byte _x = x; \ + DEBUG_FMT("%s returning %d", __func__, _x); \ + return _x; \ +} while (0) +#define RETURN_WORD(x) do { \ + word _x = x; \ + DEBUG_FMT("%s returning %u", __func__, _x); \ + return _x; \ +} while (0) +#define TRY(x) do { int retval = x; if (retval != 0) RETURN_CODEVAL(retval); } while (0) +#else +#define RETURN_OK() do { return 0; } while (0) +#define RETURN_CODEVAL(x) do { return x; } while (0) +#define RETURN_BOOL(x) do { return x; } while (0) +#define RETURN_BYTE(x) do { return x; } while (0) +#define RETURN_WORD(x) do { return x; } while (0) +#define TRY(x) do { int retval = x; if (retval != 0) return retval; } while (0) +#endif + TPDU PDU; #ifdef _LOGOHELPER @@ -222,86 +275,102 @@ LogoHelper LH; bool LogoHelper::BitAt(void *Buffer, int ByteIndex, byte BitIndex) { + TRACE("%p, %d, %d", Buffer, ByteIndex, BitIndex); + byte mask[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; pbyte Pointer = pbyte(Buffer) + ByteIndex; if (BitIndex > 7) - return false; + RETURN_ERROR(false, "Index out of Range"); else - return (*Pointer & mask[BitIndex]); + RETURN_BOOL(*Pointer & mask[BitIndex]); } bool LogoHelper::BitAt(int ByteIndex, int BitIndex) { + TRACE("%d, %d", ByteIndex, BitIndex); + if (ByteIndex < VM_FIRST || ByteIndex > VM_LAST - 1) - return false; + RETURN_ERROR(false, "Index out of Range"); // https://www.arduino.cc/reference/en/language/variables/utilities/progmem/ ByteIndex = pgm_read_byte(LogoClient::Mapping + ByteIndex - VM_FIRST); if (ByteIndex > MaxPduSize - Size_RD - 1) - return false; + RETURN_ERROR(false, "Index out of Range"); else - return BitAt(PDU.DATA, ByteIndex, BitIndex); + RETURN_BOOL(BitAt(PDU.DATA, ByteIndex, BitIndex)); } byte LogoHelper::ByteAt(void *Buffer, int index) { + TRACE("%p, %d", Buffer, index); + pbyte Pointer = pbyte(Buffer) + index; - return *Pointer; + RETURN_BYTE( *Pointer ); } byte LogoHelper::ByteAt(int index) { + TRACE("%d", index); + if (index < VM_FIRST || index > VM_LAST - 1) - return 0; + RETURN_ERROR(0, "Index out of Range"); index = pgm_read_byte(LogoClient::Mapping + index - VM_FIRST); if (index > MaxPduSize - Size_RD - 1) - return 0; + RETURN_ERROR(0, "Index out of Range"); else - return ByteAt(PDU.DATA, index); + RETURN_BYTE(ByteAt(PDU.DATA, index)); } word LogoHelper::WordAt(void *Buffer, int index) { + TRACE("%p, %d", Buffer, index); + word hi = (*(pbyte(Buffer) + index)) << 8; - return hi + *(pbyte(Buffer) + index + 1); + RETURN_WORD( hi + *(pbyte(Buffer) + index + 1) ); } word LogoHelper::WordAt(int index) { + TRACE("%d", index); + if (index < VM_FIRST || index > VM_LAST - 2) - return 0; + RETURN_ERROR(0, "Index out of Range"); byte hi = pgm_read_byte(LogoClient::Mapping + index - VM_FIRST); byte lo = pgm_read_byte(LogoClient::Mapping + index + 1 - VM_FIRST); if (hi > MaxPduSize - Size_RD - 1) - return 0; + RETURN_ERROR(0, "Index out of Range"); else - return (PDU.DATA[hi] << 8) + PDU.DATA[lo]; + RETURN_WORD( (PDU.DATA[hi] << 8) + PDU.DATA[lo] ); } int LogoHelper::IntegerAt(void *Buffer, int index) { + TRACE("%p, %d", Buffer, index); + word w = WordAt(Buffer, index); - return *(pint(&w)); + RETURN_INT( (int)w ); } int LogoHelper::IntegerAt(int index) { + TRACE("%d", index); + if (index < VM_FIRST || index > VM_LAST - 2) - return 0; + RETURN_ERROR(0, "Index out of Range"); byte hi = pgm_read_byte(LogoClient::Mapping + index - VM_FIRST); byte lo = pgm_read_byte(LogoClient::Mapping + index + 1 - VM_FIRST); if (hi > MaxPduSize - Size_RD - 1) - return 0; + RETURN_ERROR(0, "Index out of Range"); else - return (PDU.DATA[hi] << 8) + PDU.DATA[lo]; + RETURN_INT( (PDU.DATA[hi] << 8) + PDU.DATA[lo] ); } #endif // _LOGOHELPER @@ -312,6 +381,8 @@ byte* LogoClient::Mapping = NULL; LogoClient::LogoClient() { + TRACE_VOID(); + ConnType = PG; Connected = false; LastError = 0; @@ -322,10 +393,14 @@ LogoClient::LogoClient() Mapping = (byte*)VM_MAP_923_983_0BA6; RecvTimeout = TIMEOUT; StreamClient = NULL; + + RETURN(); } LogoClient::LogoClient(Stream *Interface) { + TRACE("%p", Interface); + ConnType = PG; Connected = false; LastError = 0; @@ -336,40 +411,83 @@ LogoClient::LogoClient(Stream *Interface) Mapping = (byte*)VM_MAP_923_983_0BA6; RecvTimeout = TIMEOUT; StreamClient = Interface; + + RETURN(); } LogoClient::~LogoClient() { + TRACE_VOID(); + Disconnect(); + + RETURN(); } // -- Basic functions --------------------------------------------------- +/** + * @brief Links the Client to a stream object + * + * @param Interface Stream object for example "Serial" + */ void LogoClient::SetConnectionParams(Stream *Interface) { + TRACE("%p", Interface); + StreamClient = Interface; + + RETURN(); } -void LogoClient::SetConnectionType(word ConnectionType) +/** + * @brief Sets the connection resource type, i.e the way in which the Clients + * connects to the LOGO! device. + * + * @param ConnectionType This version supports only the PG protocol 0x01 + */ +void LogoClient::SetConnectionType(uint16_t ConnectionType) { + TRACE("%u", ConnectionType); + // The LOGO 0BA4 to 0BA6 supports only the PG protocol - // The PG protocol is used by the software 'LOGO! Soft Comfort' (the developing tool) - // to perform system tasks such as program upload/download, run/stop and configuration. - // Other protocols (for exapmle the TD protocol) are not implemented yet. + // The PG protocol is used by the software 'LOGO! Soft Comfort' (the + // developing tool) to perform system tasks such as program upload/download, + // run/stop and configuration. Other protocols (for exapmle the TD protocol) + // are not implemented yet. if (ConnectionType != PG) - SetLastError(errCliFunction); + SET_ERROR(LastError, errCliFunction, "Unsupported protocol"); ConnType = ConnectionType; + + RETURN(); } +/** + * @brief Connects the Client to the hardware connected via the serial port. + * + * @param Interface Stream object for example "Serial" + * @return Returns a 0 on success or an Error code + */ int LogoClient::ConnectTo(Stream *Interface) { + TRACE("%p", Interface); + SetConnectionParams(Interface); - return Connect(); + + RETURN_INT(Connect()); } +/** + * @brief Connects the Client to the LOGO! with the parameters specified in the + * previous call of ConnectTo() or SetConnectionParams(). + * + * @return Returns a 0 on success or an Error code + */ int LogoClient::Connect() { - Log.trace(F("try to connect to %X" CR), StreamClient); + TRACE_VOID(); + LastError = 0; + if (!Connected) { StreamConnect(); @@ -384,12 +502,18 @@ int LogoClient::Connect() } } Connected = LastError == 0; - return LastError; + + RETURN_CODEVAL(LastError); } +/** + * @brief Disconnects "gracefully" the Client from the LOGO! device. + * + */ void LogoClient::Disconnect() { - Log.trace(F("disconnecting.." CR)); + TRACE_VOID(); + if (Connected) { Connected = false; @@ -397,34 +521,62 @@ void LogoClient::Disconnect() PDULength = 0; PDURequested = 0; } + + RETURN(); } -int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void *ptrData) +/** + * @brief This is the main function to read data from a LOGO! device. With it + * Inputs, Outputs and Flags can be read. + * + * @param Area identifier, must always be "LogoAreaDB" + * @param DBNumber DB number if area = "LogoAreaDB", must always be 1 + * @param Start Offset to start + * @param Amount Amount of words to read + * @param ptrData Pointer to memory area + * @return Returns a 0 on success or an Error code + */ +int LogoClient::ReadArea(int Area, uint16_t DBNumber, uint16_t Start, + uint16_t Amount, void *ptrData) { + TRACE("%d, %u, %u, %u, %p", Area, DBNumber, Start, Amount, ptrData); + int Address; - size_t NumElements; - size_t MaxElements; - size_t TotElements; - size_t SizeRequested; - size_t Length; + uint16_t NumElements; + uint16_t MaxElements; + uint16_t TotElements; + uint16_t SizeRequested; + uint16_t Length; pbyte Target = pbyte(ptrData); - size_t Offset = 0; + uint16_t Offset = 0; const int WordSize = 1; // DB1 element has a size of one byte - static unsigned long LastCycle; // variable for storing the time from the last call - // declared statically, so that it can used locally + static unsigned long LastCycle; // variable for storing the time from + // the last call declared statically, so + // that it can used locally + LastError = 0; - // For LOGO 0BA4 to 0BA6 the only compatible information that we can read are inputs, outputs, flags and rtc. + // For LOGO 0BA4 to 0BA6 the only compatible information that we can read are + // inputs, outputs, flags and rtc. // We will represented this as V memory, that is seen by all HMI as DB 1. if (Area != LogoAreaDB || DBNumber != 1) - SetLastError(errPGInvalidPDU); + { + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'Area' or 'DBNumber' invalid"); + RETURN_CODEVAL(LastError); + } // Access only allowed between 0 and 1023 if ((int)Start < VM_START || Start > VM_END) - SetLastError(errPGInvalidPDU); + { + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'Start' out of range"); + RETURN_CODEVAL(LastError); + } if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } // fetch data or cyclic data read? // The next line will also notice a rollover after about 50 days. @@ -434,54 +586,69 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void // fetch data or start a continuously polling! #if defined _EXTENDED int Status; - if (GetPlcStatus(&Status) != 0) - return LastError; + TRY(GetPlcStatus(&Status)); // operation mode must be RUN if we use LOGO_FETCH_DATA // so exit with an Error if the LOGO is not in operation mode RUN if (Status != LogoCpuStatusRun) - return SetLastError(errCliFunction); + { + SET_ERROR(LastError, errCliFunction, "LOGO is not in operation mode RUN"); + RETURN_CODEVAL(LastError); + } #endif // _EXTENDED if (StreamClient->write(LOGO_FETCH_DATA, sizeof(LOGO_FETCH_DATA)) != sizeof(LOGO_FETCH_DATA)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending request 'Start Fetch Data'"); + RETURN_CODEVAL(LastError); + } - RecvControlResponse(&Length); - if (LastError) - return LastError; + TRY(RecvControlResponse(&Length)); } else { // cyclic reading of data! - Log.verbose(F("cyclic reading" CR)); // If code 06 is sent to the LOGO device within 600 ms, // the LOGO device sends updated data (cyclic data read). if (StreamClient->write(LOGO_ACK, sizeof(LOGO_ACK)) != sizeof(LOGO_ACK)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending request 'Cyclic Data Reading'"); + RETURN_CODEVAL(LastError); + } + DEBUG("Cyclic Data Eeading"); PDU.H[0] = 0; - RecvPacket(&PDU.H[1], GetPDULength() - 1); - if (LastError) - return LastError; + TRY(RecvPacket(&PDU.H[1], GetPDULength() - 1)); Length = GetPDULength() - 1; // Get End Delimiter (1 byte) if (RecvPacket(PDU.T, 1) != 0) - return LastError; + { + SET_ERROR(LastError, errCliInvalidPDU, "No end delimiter"); + RETURN_CODEVAL(LastError); + } Length++; if (PDU.H[1] != 0x55 || PDU.T[0] != AA) - // Error, the response does not have the expected frame 0x55 .. 0xAA - return SetLastError(errCliInvalidPDU); + { + SET_ERROR(LastError, errCliInvalidPDU, "The response has an unexpected message format"); + RETURN_CODEVAL(LastError); + } PDU.H[0] = ACK; } if (LastPDUType != ACK) // Check Confirmation - return SetLastError(errCliFunction); + { + SET_ERROR(LastError, errCliFunction, "No Confirmation"); + RETURN_CODEVAL(LastError); + } - if (Length != (size_t)GetPDULength()) - return SetLastError(errCliInvalidPDU); + if (Length != (uint16_t)GetPDULength()) + { + SET_ERROR(LastError, errCliInvalidPDU, "Invalid PDU length"); + RETURN_CODEVAL(LastError); + } // To recognize a cycle time we save the number of milliseconds // since the last successful call of the current function @@ -489,7 +656,7 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void // Done, as long as we only use the internal buffer if (ptrData == NULL) - return SetLastError(0); + RETURN_OK(); TotElements = Amount; @@ -583,100 +750,225 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void */ } - return SetLastError(0); + RETURN_OK(); } // -- Extended functions ------------------------------------------------ #ifdef _EXTENDED + +/** + * @brief Returns the size of a given DB Number. This function is useful to + * upload an entire DB. + * + * @param DBNumber DB number if area = "LogoAreaDB", must always be 1 + * @param Size DB Size in bytes + * @return Returns a 0 on success or an Error code + */ +int LogoClient::GetDBSize(uint16_t DBNumber, uint16_t *Size) +{ + TRACE("%d, %u", DBNumber, *Size); + + LastError = 0; + + // We will represented this as V memory, that is seen by all HMI as DB 1. + if (DBNumber != 1) + { + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'DBNumber' invalid"); + RETURN_CODEVAL(LastError); + } + + *Size = VM_END-VM_START+1; + + RETURN_OK(); +} + +/** + * @brief This is a wrapper function of ReadArea(). It simply internally calls + * ReadArea() with Area = LogoAreaDB, Start = 0 and Amount = GetDBSize(). + * + * @param DBNumber DB number if area = "LogoAreaDB", must always be 1 + * @param ptrData Pointer to memory area + * @param Size In: Buffer size available; Out: Bytes Uploaded + * @return Returns a 0 on success or an Error code. + */ +int LogoClient::DBGet(uint16_t DBNumber, void *ptrData, uint16_t *Size) +{ + TRACE("%d, %p, %u", DBNumber, ptrData, *Size); + + uint16_t Amount = *Size; + *Size = 0; + + // We will represented this as V memory, that is seen by all HMI as DB 1. + if (DBNumber != 1) + { + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'DBNumber' invalid"); + RETURN_CODEVAL(LastError); + } + + // Return an error if the DB size is greater than the Buffer size + if (VM_END-VM_START+1 > Amount) + { + SET_ERROR(LastError, errCliBufferTooSmall, "Buffer size too small"); + RETURN_CODEVAL(LastError); + } + + Amount = VM_END-VM_START+1; + TRY(ReadArea(LogoAreaDB, DBNumber, 0, Amount, ptrData)); + *Size = Amount; + + RETURN_OK(); +} + +/** + * @brief Puts the LOGO! device in operation mode RUN. + * + * @return Returns a 0 on success and when the LOGO! device is already running, + * otherwise an Error code + */ int LogoClient::PlcStart() { + TRACE_VOID(); + int Status; + LastError = 0; + if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } - if (GetPlcStatus(&Status) != 0) - return LastError; + TRY(GetPlcStatus(&Status)); if (Status == LogoCpuStatusStop) { if (StreamClient->write(LOGO_START, sizeof(LOGO_START)) != sizeof(LOGO_START)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending command 'Start Operating'"); + RETURN_CODEVAL(LastError); + } // Setup the telegram memset(&PDU, 0, sizeof(PDU)); - size_t Length; - RecvControlResponse(&Length); // Get PDU response - if (LastError) - return LastError; + uint16_t Length; + TRY(RecvControlResponse(&Length)); // Get PDU response if (LastPDUType != ACK) // Check Confirmation - return SetLastError(errCliFunction); + { + SET_ERROR(LastError, errCliFunction, "No confirmation"); + RETURN_CODEVAL(LastError); + } if (Length != 1) // 1 is the expected value - return SetLastError(errCliInvalidPDU); + { + SET_ERROR(LastError, errCliInvalidPDU, "Invalid PDU length"); + RETURN_CODEVAL(LastError); + } } - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Puts the LOGO! device in operation mode STOP. + * + * @return Returns a 0 on success and when the LOGO! device is already stopped, + * otherwise an Error code + */ int LogoClient::PlcStop() { + TRACE_VOID(); + int Status; + LastError = 0; + if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } - if (GetPlcStatus(&Status) != 0) - return LastError; + TRY(GetPlcStatus(&Status)); if (Status != LogoCpuStatusStop) { if (StreamClient->write(LOGO_STOP, sizeof(LOGO_STOP)) != sizeof(LOGO_STOP)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending command 'Stop Operating'"); + RETURN_CODEVAL(LastError); + } // Setup the telegram memset(&PDU, 0, sizeof(PDU)); - size_t Length; - RecvControlResponse(&Length); // Get PDU response - if (LastError) - return LastError; + uint16_t Length; + TRY(RecvControlResponse(&Length)); // Get PDU response if (LastPDUType != ACK) // Check Confirmation - return SetLastError(errCliFunction); + { + SET_ERROR(LastError, errCliFunction, "No confirmation"); + RETURN_CODEVAL(LastError); + } if (Length != 1) // 1 is the expected value - return SetLastError(errCliInvalidPDU); + { + SET_ERROR(LastError, errCliInvalidPDU, "Invalid PDU length"); + RETURN_CODEVAL(LastError); + } } - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Returns the CPU status (running/stopped) into Status reference. + * + * @param Status see below + * - LogoCpuStatusUnknown = 0x00 : The CPU status is unknown. + * - LogoCpuStatusRun = 0x08 : The CPU is running. + * - LogoCpuStatusStop = 0x04 : The CPU is stopped. + * @return Returns a 0 on success or an Error code + */ int LogoClient::GetPlcStatus(int *Status) { + TRACE("%p", Status); + + LastError = 0; + if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } *Status = LogoCpuStatusUnknown; if (StreamClient->write(LOGO_MODE, sizeof(LOGO_MODE)) != sizeof(LOGO_MODE)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending request 'Operating Mode'"); + RETURN_CODEVAL(LastError); + } memset(&PDU, 0, sizeof(PDU)); // Setup the telegram - size_t Length; - RecvControlResponse(&Length); // Get PDU response - if (LastError) - return LastError; + uint16_t Length; + TRY(RecvControlResponse(&Length)); // Get PDU response if (LastPDUType != ACK) // Check Confirmation - return SetLastError(errCliFunction); + { + SET_ERROR(LastError, errCliFunction, "No confirmation"); + RETURN_CODEVAL(LastError); + } - if (Length != sizeof(PDU.H) + 1) // Size of Header + 1 Data Byte is the expected value - return SetLastError(errCliInvalidPDU); + if (Length != sizeof(PDU.H) + 1) // Size of Header + 1 Data Byte is the + { // expected value + SET_ERROR(LastError, errCliInvalidPDU, "Invalid PDU length"); + RETURN_CODEVAL(LastError); + } switch (PDU.DATA[0]) { case RUN: @@ -689,153 +981,206 @@ int LogoClient::GetPlcStatus(int *Status) *Status = LogoCpuStatusUnknown; } - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Reads LOGO! date and time. + * + * @param DateTime a element with the C structure TimeElements + * @return Returns a 0 on success or an Error code + */ int LogoClient::GetPlcDateTime(TimeElements *DateTime) { - Log.trace(F("System clock read requested" CR)); + TRACE("%p", DateTime); + int Status; // Operation mode - if (DateTime == NULL) // Exit with Error if DateTime is undefined - return SetLastError(errPGInvalidPDU); + LastError = 0; + + if (DateTime == NULL) // Exit with Error if DateTime is + { // undefined + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'DateTime' is undefined"); + RETURN_CODEVAL(LastError); + } if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } // Check operation mode - if (GetPlcStatus(&Status) != 0) - return LastError; + TRY(GetPlcStatus(&Status)); if (Status != LogoCpuStatusStop) - return SetLastError(errCliFunction); + { + SET_ERROR(LastError, errCliFunction, "LOGO is not in operation mode STOP"); + RETURN_CODEVAL(LastError); + } // clock reading initialized - if (WriteByte(ADDR_CLK_W_GET, 0x00) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_W_GET, 0x00)); // clock reading day - if (ReadByte(ADDR_CLK_RW_DAY, &DateTime->Day) != 0) - return LastError; + TRY(ReadByte(ADDR_CLK_RW_DAY, &DateTime->Day)); // clock reading month - if (ReadByte(ADDR_CLK_RW_MONTH, &DateTime->Month) != 0) - return LastError; + TRY(ReadByte(ADDR_CLK_RW_MONTH, &DateTime->Month)); // clock reading year - if (ReadByte(ADDR_CLK_RW_YEAR, &DateTime->Year) != 0) - return LastError; + TRY(ReadByte(ADDR_CLK_RW_YEAR, &DateTime->Year)); DateTime->Year = y2kYearToTm(DateTime->Year); // clock reading hour - if (ReadByte(ADDR_CLK_RW_HOUR, &DateTime->Hour) != 0) - return LastError; + TRY(ReadByte(ADDR_CLK_RW_HOUR, &DateTime->Hour)); // clock reading minute - if (ReadByte(ADDR_CLK_RW_MINUTE, &DateTime->Minute) != 0) - return LastError; + TRY(ReadByte(ADDR_CLK_RW_MINUTE, &DateTime->Minute)); // clock reading day-of-week - if (ReadByte(ADDR_CLK_RW_DOW, &DateTime->Wday) != 0) - return LastError; + TRY(ReadByte(ADDR_CLK_RW_DOW, &DateTime->Wday)); DateTime->Wday += 1; // day of week, sunday is day 1 memset(&PDU, 0, PDURequested); // Clear the telegram DateTime->Second = 0; // set secounds to 0 - return SetLastError(0); + RETURN_OK(); } int LogoClient::GetPlcDateTime(time_t *DateTime) { + TRACE("%p", DateTime); + TimeElements tm; - if (GetPlcDateTime(&tm) != 0) - return LastError; + LastError = 0; + + TRY(GetPlcDateTime(&tm)); *DateTime = makeTime(tm); // convert to time_t - return SetLastError(0); + + RETURN_OK(); } +/** + * @brief Sets the LOGO! date and time. + * + * @param DateTime a element with the C structure TimeElements + * @return Returns a 0 on success or an Error code + */ int LogoClient::SetPlcDateTime(TimeElements DateTime) { - Log.trace(F("System clock write requested" CR)); + TRACE("%lu", makeTime(DateTime)); + int Status; // Operation mode + LastError = 0; + if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } // Check operation mode - if (GetPlcStatus(&Status) != 0) - return LastError; + TRY(GetPlcStatus(&Status)); if (Status != LogoCpuStatusStop) - return SetLastError(errCliFunction); + { + SET_ERROR(LastError, errCliFunction, "LOGO is not in operation mode STOP"); + RETURN_CODEVAL(LastError); + }; // clock writing day - if (WriteByte(ADDR_CLK_RW_DAY, DateTime.Day) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_RW_DAY, DateTime.Day)); // clock writing month - if (WriteByte(ADDR_CLK_RW_MONTH, DateTime.Month) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_RW_MONTH, DateTime.Month)); // clock writing year - if (WriteByte(ADDR_CLK_RW_YEAR, tmYearToY2k(DateTime.Year)) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_RW_YEAR, tmYearToY2k(DateTime.Year))); // clock writing hour - if (WriteByte(ADDR_CLK_RW_HOUR, DateTime.Hour) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_RW_HOUR, DateTime.Hour)); // clock writing minute - if (WriteByte(ADDR_CLK_RW_MINUTE, DateTime.Minute) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_RW_MINUTE, DateTime.Minute)); // clock writing day-of-week (sunday is day 0) - if (WriteByte(ADDR_CLK_RW_DOW, (DateTime.Wday - 1) % 7) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_RW_DOW, (DateTime.Wday - 1) % 7)); // clock writing completed - if (WriteByte(ADDR_CLK_W_SET, 0x00) != 0) - return LastError; + TRY(WriteByte(ADDR_CLK_W_SET, 0x00)); memset(&PDU, 0, PDURequested); // Clear the telegram - return SetLastError(0); + RETURN_OK(); } int LogoClient::SetPlcDateTime(time_t DateTime) { + TRACE("%lu", DateTime); + TimeElements tm; + LastError = 0; + breakTime(DateTime, tm); // break time_t into elements - if (SetPlcDateTime(tm) != 0) - return LastError; + TRY(SetPlcDateTime(tm)); - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Sets the LOGO! date and time in accord to the DTE system time. The + * internal system time is based on the standard Unix time (number of + * seconds since Jan 1, 1970). + * + * @return Returns a 0 on success or an Error code + */ int LogoClient::SetPlcSystemDateTime() { + TRACE_VOID(); + + LastError = 0; + if (timeStatus() == timeNotSet) - // the time has never been set, the clock started on Jan 1, 1970 - return SetLastError(errCliFunction); + { + // the clock started on Jan 1, 1970 + SET_ERROR(LastError, errCliFunction, "The time has never been set"); + RETURN_CODEVAL(LastError); + } - if (SetPlcDateTime(now()) != 0) - return LastError; + TRY(SetPlcDateTime(now())); - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Gets CPU order code and version info. + * + * @param Info The Info argument is an C structure defined in the library + * @return Returns a 0 on success or an Error code + */ int LogoClient::GetOrderCode(TOrderCode *Info) { + TRACE("%p", Info); + int Status; // Operation mode + LastError = 0; + if (Info == NULL) // Exit with Error if Info is undefined - return SetLastError(errPGInvalidPDU); + { + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'Info' is undefined"); + RETURN_CODEVAL(LastError); + } if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } // Set Info to predefined values strcpy_P(Info->Code, ORDER_CODE); @@ -862,175 +1207,221 @@ int LogoClient::GetOrderCode(TOrderCode *Info) break; default: // 0BAx - return SetLastError(errCliFunction); + SET_ERROR(LastError, errCliFunction, "Unknown IdentNo"); + RETURN_CODEVAL(LastError); } // Check operation mode - if (GetPlcStatus(&Status) != 0) - return LastError; + TRY(GetPlcStatus(&Status)); if (Status != LogoCpuStatusStop) - return SetLastError(0); + RETURN_OK(); // Abort, further data cannot be read byte ch; // reading ASCII = V - if (ReadByte(ADDR_OC_R_ASC_V, &ch) != 0) - return LastError; - if (ch != 'V') return SetLastError(0); + TRY(ReadByte(ADDR_OC_R_ASC_V, &ch)); + if (ch != 'V') // Abort, further data cannot be read + RETURN_OK(); // reading ASCII = X.__.__ - if (ReadByte(ADDR_OC_R_MAJOR, &ch) != 0) - return LastError; + TRY(ReadByte(ADDR_OC_R_MAJOR, &ch)); if (ch > '0') Info->V1 = ch - '0'; // reading ASCII = _.X_.__ - if (ReadByte(ADDR_OC_R_MINOR1, &ch) != 0) - return LastError; + TRY(ReadByte(ADDR_OC_R_MINOR1, &ch)); if (ch > '0') Info->V2 = (ch - '0') * 10; // reading ASCII = _._X.__ - if (ReadByte(ADDR_OC_R_MINOR2, &ch) != 0) - return LastError; + TRY(ReadByte(ADDR_OC_R_MINOR2, &ch)); if (ch > '0') Info->V2 += ch - '0'; // reading ASCII = _.__.X_ - if (ReadByte(ADDR_OC_R_PATCH1, &ch) != 0) - return LastError; + if (ReadByte(ADDR_OC_R_PATCH1, &ch)); if (ch > '0') Info->V3 = (ch - '0') * 10; // reading ASCII = _.__._X - if (ReadByte(ADDR_OC_R_PATCH2, &ch) != 0) - return LastError; + if (ReadByte(ADDR_OC_R_PATCH2, &ch)); if (ch > '0') Info->V3 += ch - '0'; - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Send the password to the LOGO! to meet its security level. + * + * @param password accepted by LOGO! is an 10 chars string (longer password will + * be trimmed). Only upper case charaters from A to Z can be used for the + * password. + * @return Returns a 0 on success or an Error code + */ int LogoClient::SetSessionPassword(char *password) { + TRACE("%s", password); + int Status; // Operation mode byte by; - Log.trace(F("Security request : Set session password" CR)); + LastError = 0; - if (password == NULL) // Exit with Error if password is undefined - return SetLastError(errPGInvalidPDU); + if (password == NULL) // Exit with Error if password is + { // undefined + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'password' is undefined"); + RETURN_CODEVAL(LastError); + } if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } // By default, define zero mode AccessMode = PWD_LEVEL_ZERO; // Check operation mode - if (GetPlcStatus(&Status) != 0) - return LastError; + TRY(GetPlcStatus(&Status)); + if (Status != LogoCpuStatusStop) + RETURN_OK(); // Abort, further data cannot be read - if (Status == LogoCpuStatusStop) - { - // Set restricted mode - AccessMode = PWD_LEVEL_RESTRICTED; + // Set restricted mode + AccessMode = PWD_LEVEL_RESTRICTED; - // Start the sequence for reading out the password - if (WriteByte(ADDR_PWD_W_ACCESS, 0) != 0) - return LastError; + // Start the sequence for reading out the password + TRY(WriteByte(ADDR_PWD_W_ACCESS, 0)); - if (ReadByte(ADDR_PWD_R_MAGIC1, &by) != 0) - return LastError; - if (by != 0x04) return SetLastError(errCliDataRead); + TRY(ReadByte(ADDR_PWD_R_MAGIC1, &by)); + if (by != 0x04) + { + SET_ERROR(LastError, errCliDataRead, "Receive unexpected data"); + RETURN_CODEVAL(LastError); + } - if (ReadByte(ADDR_PWD_R_MAGIC2, &by) != 0) - return LastError; - if (by != 0x00) return SetLastError(errCliDataRead); + TRY(ReadByte(ADDR_PWD_R_MAGIC2, &by)); + if (by != 0x00) + { + SET_ERROR(LastError, errCliDataRead, "Receive unexpected data"); + RETURN_CODEVAL(LastError); + } - // Check if a password exists - if (ReadByte(ADDR_PWD_R_EXISTS, &by) != 0) - return LastError; + // Check if a password exists + TRY(ReadByte(ADDR_PWD_R_EXISTS, &by)); + if (by == 0x40) + RETURN_OK(); // Abort, password not set - if (by == 0x40) // Password set - { - // Read 10 bytes from addr 0566 - if (ReadBlock(ADDR_PWD_R_MEM, 10, PDU.DATA) != 0) - return LastError; + // Read 10 bytes from addr 0566 + TRY(ReadBlock(ADDR_PWD_R_MEM, 10, PDU.DATA)); - // Compare the two password strings - if (strncmp(password, (const char*)PDU.DATA, 10) != 0) - return SetLastError(errCliFunction); + // Compare the two password strings + if (strncmp(password, (const char*)PDU.DATA, 10) != 0) + { + SET_ERROR(LastError, errCliFunction, "Invalid password"); + RETURN_CODEVAL(LastError); + } - // Login successful - if (WriteByte(ADDR_PWD_W_OK, 0) != 0) - return LastError; - } + // Login successful + TRY(WriteByte(ADDR_PWD_W_OK, 0)); - AccessMode = PWD_LEVEL_FULL; - } + AccessMode = PWD_LEVEL_FULL; - Log.trace(F("--> OK" CR)); - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Clears the password set for the current session. + * + * @return Returns a 0 on success or an Error code + */ int LogoClient::ClearSessionPassword() { + TRACE_VOID(); + int Status; // Operation mode byte pw; - Log.trace(F("Security request : Clear session password" CR)); + LastError = 0; if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } // By default, define zero mode AccessMode = PWD_LEVEL_ZERO; // Check operation mode - if (GetPlcStatus(&Status) != 0) - return LastError; - - if (Status == LogoCpuStatusStop) - { - // Set restricted mode - AccessMode = PWD_LEVEL_RESTRICTED; + TRY(GetPlcStatus(&Status)); + if (Status != LogoCpuStatusStop) + RETURN_OK(); // Abort, further data cannot be read - // Check if a password exists - if (ReadByte(ADDR_PWD_R_EXISTS, &pw) != 0) - return LastError; + // Set restricted mode + AccessMode = PWD_LEVEL_RESTRICTED; - if (pw != 0x40) // Password not set - AccessMode = PWD_LEVEL_FULL; - } + // Check if a password exists + TRY(ReadByte(ADDR_PWD_R_EXISTS, &pw)); + if (pw != 0x40) // Password not set + AccessMode = PWD_LEVEL_FULL; - Log.trace(F("--> OK" CR)); - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Gets the CPU protection level info. + * + * @param Protection argument is an C structure defined in the library: + * - sch_schal = 1,2,3 : Protection level set by the operating mode switch + * (1:STOP, 2:RUN, 3:STOP and password set) + * - sch_par = 0,1,3 : Parameterized protection level (1:no protection, + * 3:write/read protection, 0:no password or cannot + * be determined) + * - sch_rel = 0,1,2,3 : Valid protection level of the CPU (level 1-3, 0: + * cannot be determined) + * - bart_sch = 1,3 : Position of the operating mode switch (1:RUN, + * 3:STOP, 0:undefined or cannot be determined) + * - anl_sch = 0 : Position of the startup mode switch (0:undefined, + * does not exist or cannot be determined) + * @return Returns a 0 on success or an Error code + */ int LogoClient::GetProtection(TProtection *Protection) { + TRACE("%p", Protection); + int Status; // Operation mode - if (Protection == NULL) // Exit with Error if Protection is undefined - return SetLastError(errPGInvalidPDU); + LastError = 0; + + if (Protection == NULL) // Exit with Error if Protection is + { // undefined + SET_ERROR(LastError, errPGInvalidPDU, "Parameter 'Protection' is undefined"); + RETURN_CODEVAL(LastError); + } if (!Connected) // Exit with Error if not connected - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Not connected"); + RETURN_CODEVAL(LastError); + } // Set Protection to predefined values memset(Protection, 0, sizeof(TProtection)); // Check operation mode - if (GetPlcStatus(&Status) != 0) - return LastError; - + TRY(GetPlcStatus(&Status)); if (Status == LogoCpuStatusStop) { byte pw; - if (ReadByte(ADDR_PWD_R_EXISTS, &pw) == 0 && pw == 0x40) + + TRY(ReadByte(ADDR_PWD_R_EXISTS, &pw)); + if (pw == 0x40) { - Protection->sch_schal = 3; // Protection level set with the mode selector = STOP and password set + Protection->sch_schal = 3; // Protection level set with the mode + // selector = STOP and password set Protection->sch_par = 3; // write/read protection Protection->sch_rel = 3; // Set valid protection level } else { - Protection->sch_schal = 1; // Protection level set with the mode selector = STOP + Protection->sch_schal = 1; // Protection level set with the mode + // selector = STOP Protection->sch_par = 1; // no protection Protection->sch_rel = 1; // Set valid protection level } @@ -1039,20 +1430,33 @@ int LogoClient::GetProtection(TProtection *Protection) } else { - Protection->sch_schal = 2; // Protection level set with the mode selector = RUN + Protection->sch_schal = 2; // Protection level set with the mode + // selector = RUN // sch_par = 0; Protection->sch_rel = 2; // Set valid protection level Protection->bart_sch = 1; // Mode selector setting = RUN // anl_sch = 0; } - return SetLastError(0); + RETURN_OK(); } +/** + * @brief Returns a textual explanation of Error. + * + * @param Error Error Code + * @param Text In: Pointer to a C String, Out: \0 teminated error text + * @param TextLen Maximum number of characters to copy. + */ void LogoClient::ErrorText(int Error, char *Text, int TextLen) { - if (Text == NULL || TextLen <= 0) - return; + TRACE("%d, %s, %d", Error, *Text, TextLen); + + if (Text == NULL || TextLen <= 0) // Exit with Error if Text or TextLen is + { // undefined + DEBUG("Parameter 'Text' or 'TextLen' is undefined"); + RETURN(); + } memset(Text, 0, TextLen); switch (Error) @@ -1112,16 +1516,19 @@ void LogoClient::ErrorText(int Error, char *Text, int TextLen) #endif // _EXTENDED // -- Private functions ------------------------------------------------- -int LogoClient::RecvControlResponse(size_t *Size) +int LogoClient::RecvControlResponse(uint16_t *Size) { + TRACE("%p", Size); + *Size = 0; // Start with a size of 0 + LastError = 0; + // Setup the telegram memset(&PDU, 0, sizeof(PDU)); // Get first byte - if (RecvPacket(PDU.H, 1) != 0) - return LastError; + TRY(RecvPacket(PDU.H, 1)); (*Size)++; // Update size = 1 LastPDUType = PDU.H[0]; // Store PDU Type @@ -1130,100 +1537,118 @@ int LogoClient::RecvControlResponse(size_t *Size) // Get next Byte RecvPacket(&PDU.H[1], 1); if (LastError & errStreamDataRecvTout) - // OK, there are probably no more data available, for example Control Commands 0x12, 0x18 - return SetLastError(0); - else if (LastError) + // OK, there are probably no more data available, + // for example Control Commands 0x12, 0x18 + RETURN_OK(); + else if (LastError != 0) // Other error than timeout - return LastError; + RETURN_CODE(LastError); (*Size)++; // Update size = 2 if (PDU.H[1] != 0x55) // Control Command Code { - // OK, the response has no Control Code 0x55, for example Control Commands 0x17 + // OK, the response has no Control Code 0x55, + // for example Control Commands 0x17 *Size = Size_RD + 1; // Update size = 7 // We need to align with PDU.DATA PDU.DATA[0] = PDU.H[1]; PDU.H[1] = 0; - return SetLastError(0); + + RETURN_OK(); } // Get Function Code (2 bytes) - if (RecvPacket(&PDU.H[2], 2) != 0) - return LastError; + TRY(RecvPacket(&PDU.H[2], 2)); *Size += 2; // Update size = 4 if (PDU.H[2] != 0x11 || PDU.H[3] != 0x11) + { // Error, the response does not have the expected Function Code 0x11 - return SetLastError(errCliDataRead); + SET_ERROR(LastError, errCliDataRead, "Receive unexpected 'Function Code'"); + RETURN_CODEVAL(LastError); + } // Get Number of Bytes and the Padding Byte (2 bytes) - if (RecvPacket(&PDU.H[4], 2) != 0) - return LastError; + TRY(RecvPacket(&PDU.H[4], 2)); *Size += 2; // Update Size = 6 // Store Number of Bytes - size_t ByteCount = PDU.H[4]; + uint16_t ByteCount = PDU.H[4]; if (*Size + ByteCount < MinPduSize) - return SetLastError(errCliInvalidPDU); + { + SET_ERROR(LastError, errCliInvalidPDU, "Invalid PDU length"); + RETURN_CODEVAL(LastError); + } else if (*Size + ByteCount > MaxPduSize) - return SetLastError(errCliBufferTooSmall); + { + SET_ERROR(LastError, errCliBufferTooSmall, "PDU Buffer too small"); + RETURN_CODEVAL(LastError); + } // Get the Data Block (n bytes) - if (RecvPacket(PDU.DATA, ByteCount) != 0) - return LastError; + TRY(RecvPacket(PDU.DATA, ByteCount)); *Size += ByteCount; // Update Size = 6+ByteCount // Get End Delimiter (1 byte) - if (RecvPacket(PDU.T, 1) != 0) - return LastError; - + TRY(RecvPacket(PDU.T, 1)); if (PDU.T[0] != AA) + { // Error, the response does not have the expected value 0xAA - return SetLastError(errCliInvalidPDU); + SET_ERROR(LastError, errCliInvalidPDU, "Receive unexpected data"); + RETURN_CODEVAL(LastError); + } } else if (LastPDUType == NOK) // Request not confirmed { // Get next byte - if (RecvPacket(&PDU.H[1], 1) != 0) - return LastError; + TRY(RecvPacket(&PDU.H[1], 1)); (*Size)++; // Update Size = 2 // Get Error Type - return SetLastError(CpuError(PDU.H[1])); + SET_ERROR(LastError, CpuError(PDU.H[1]), "CPU error"); + RETURN_CODEVAL(LastError); + } + else { + SET_ERROR(LastError, errCliInvalidPDU, "Unknown error"); + RETURN_CODEVAL(LastError); } - else - return SetLastError(errCliInvalidPDU); - return SetLastError(0); + RETURN_OK(); } /* #define SIZE_OF_BUFFER MaxPduSize */ -int LogoClient::RecvPacket(byte buf[], size_t Size) +int LogoClient::RecvPacket(uint8_t *buf, uint16_t Size) { + TRACE("%p, %u", buf, Size); + /* // http://github.com/charlesdobson/circular-buffer - static int circularBuffer[SIZE_OF_BUFFER] = { 0 }; // Empty circular buffer - static int readIndex = 0; // Index of the read pointer - static int writeIndex = 0; // Index of the write pointer - static int bufferLength = 0; // Number of values in circular buffer + static int circularBuffer[SIZE_OF_BUFFER] = { 0 }; + // Empty circular buffer + static int readIndex = 0; // Index of the read pointer + static int writeIndex = 0; // Index of the write pointer + static int bufferLength = 0; // Number of values in circular buffer */ - size_t Length = 0; + uint16_t Length = 0; - /* - // If you doesn't need to transfer more than 80 byte (the length of a PDU) you can uncomment the next two lines - if (Size > MaxPduSize) - return SetLastError(errCliBufferTooSmall); - */ + LastError = 0; + +/* + // If you doesn't need to transfer more than 80 byte (the length of a PDU) you + // can uncomment the next two lines + if (Size > MaxPduSize) + RETURN_CODEVAL(SetLastError(errCliBufferTooSmall)); +*/ /* // Check if circular buffer contains data if (bufferLength > 0) - Log.verbose(F("circular buffer contains data" CR)); + DEBUG("circular buffer contains data"); while (Length < Size && bufferLength > 0) { @@ -1231,7 +1656,8 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) buf[Length++] = circularBuffer[readIndex]; bufferLength--; // Decrease used buffer size after reading - readIndex++; // Increase readIndex position to prepare for next read + readIndex++; // Increase readIndex position to prepare + // for next read // If at last index in circular buffer, set readIndex back to 0 if (readIndex == SIZE_OF_BUFFER) { @@ -1240,7 +1666,8 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) } */ - // To recognize a timeout we save the number of milliseconds (since the Arduino began running the current program) + // To recognize a timeout we save the number of milliseconds (since the + // Arduino began running the current program) unsigned long Elapsed = millis(); // The Serial buffer can hold only 64 bytes, so we can't use readBytes() @@ -1253,13 +1680,14 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) // The next line will also notice a rollover after about 50 days. // https://playground.arduino.cc/Code/TimingRollover - if (millis() - Elapsed > RecvTimeout) { - Log.warning(F("Timeout > %d" CR), RecvTimeout); + if (millis() - Elapsed > (unsigned long)RecvTimeout) { + DEBUG_FMT("Timeout > %d", RecvTimeout); break; // Timeout } } - // Here we are in timeout zone, if there's something into the buffer, it must be discarded. + // Here we are in timeout zone, if there's something into the buffer, + // it must be discarded. if (Length < Size) { /* @@ -1275,10 +1703,15 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) } if (Length > 0 && buf[Length - 1] == AA) + { // Timeout, but we have an End delimiter - return SetLastError(errStreamConnectionReset); - - return SetLastError(errStreamDataRecvTout); + SET_ERROR(LastError, errStreamConnectionReset, "Timeout with end delimiter"); + } + else + { + SET_ERROR(LastError, errStreamDataRecvTout, "Timeout without end delimiter"); + } + RETURN_CODEVAL(LastError); } /* @@ -1286,13 +1719,15 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) while (StreamClient->available()) { // Check if buffer is full if (bufferLength == SIZE_OF_BUFFER) { - Log.error(F("RX buffer is full" CR)); + DEBUG("RX buffer is full"); break; } // Write byte to address of circular buffer index circularBuffer[writeIndex] = StreamClient->read(); - bufferLength++; // Increase used buffer size after writing - writeIndex++; // Increase writeIndex position to prepare for next write + bufferLength++; // Increase used buffer size after + // writing + writeIndex++; // Increase writeIndex position to + // prepare for next write // If at last index in circular buffer, set writeIndex back to 0 if (writeIndex == SIZE_OF_BUFFER) { @@ -1301,29 +1736,46 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) } */ - return SetLastError(0); + RETURN_OK(); } int LogoClient::StreamConnect() { + TRACE_VOID(); + + LastError = 0; + if (StreamClient == NULL) // Is a stream already assigned? - return SetLastError(errStreamConnectionFailed); + { + SET_ERROR(LastError, errStreamConnectionFailed, "Stream is not assigned"); + RETURN_CODEVAL(LastError); + } // Clearing serial incomming buffer // http://forum.arduino.cc/index.php?topic=396450.0 while (StreamClient->available() > 0) StreamClient->read(); - return SetLastError(0); + RETURN_OK(); } int LogoClient::LogoConnect() { - if (StreamClient == NULL) // Exit with Error if stream is not assigned - return SetLastError(errStreamConnectionFailed); + TRACE_VOID(); + + LastError = 0; + + if (StreamClient == NULL) // Exit with Error if stream is not + { // assigned + SET_ERROR(LastError, errStreamConnectionFailed, "Stream is not assigned"); + RETURN_CODEVAL(LastError); + } if (StreamClient->write(LOGO6_CR, sizeof(LOGO6_CR)) != sizeof(LOGO6_CR)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending command 'Connection Request'"); + RETURN_CODEVAL(LastError); + } // Setup the telegram memset(&PDU, 0, sizeof(PDU)); @@ -1335,30 +1787,49 @@ int LogoClient::LogoConnect() if (LastError & errStreamDataRecvTout) { if (StreamClient->write(LOGO4_CR, sizeof(LOGO4_CR)) != sizeof(LOGO4_CR)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending command 'Connection Request'"); + RETURN_CODEVAL(LastError); + } // Get 5 bytes PDURequested = 5; if (RecvPacket(PDU.H, PDURequested) != 0) - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Error receiving data"); + RETURN_CODEVAL(LastError); + } } else if (LastError) - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "Error receiving data"); + RETURN_CODEVAL(LastError); + } LastPDUType = PDU.H[0]; // Store PDU Type if (LastPDUType != ACK) // Check Confirmation - return SetLastError(errPGConnect); + { + SET_ERROR(LastError, errPGConnect, "No confirmation"); + RETURN_CODEVAL(LastError); + } // Note PDU is not aligned - return SetLastError(0); + RETURN_OK(); } int LogoClient::NegotiatePduLength() { + TRACE_VOID(); + PDULength = 0; + LastError = 0; + if (StreamClient->write(LOGO6_CR, sizeof(LOGO6_CR)) != sizeof(LOGO6_CR)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending command 'Connection Request'"); + RETURN_CODEVAL(LastError); + } // Setup the telegram memset(&PDU, 0, sizeof(PDU)); @@ -1370,19 +1841,31 @@ int LogoClient::NegotiatePduLength() { // 0BA4 and 0BA5 doesn't support a connection request if (StreamClient->write(LOGO4_CR, sizeof(LOGO4_CR)) != sizeof(LOGO4_CR)) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending command 'Connection Request'"); + RETURN_CODEVAL(LastError); + } // Get 5 bytes PDURequested = 5; if (RecvPacket(PDU.H, PDURequested) != 0) - return SetLastError(errCliNegotiatingPDU); + { + SET_ERROR(LastError, errCliNegotiatingPDU, "Error receiving data"); + RETURN_CODEVAL(LastError); + } } else if (LastError) - return LastError; + { + SET_ERROR(LastError, errCliNegotiatingPDU, "Error receiving data"); + RETURN_CODEVAL(LastError); + } LastPDUType = PDU.H[0]; // Store PDU Type if (LastPDUType != ACK) // Check Confirmation - return SetLastError(errCliNegotiatingPDU); + { + SET_ERROR(LastError, errCliNegotiatingPDU, "No confirmation"); + RETURN_CODEVAL(LastError); + } IdentNo = PDU.H[PDURequested - 1]; switch (IdentNo) { @@ -1406,11 +1889,13 @@ int LogoClient::NegotiatePduLength() break; default: // 0BAx - return SetLastError(errCliNegotiatingPDU); + SET_ERROR(LastError, errCliNegotiatingPDU, "Unknown IdentNo"); + RETURN_CODEVAL(LastError); } - Log.trace(F("PDU negotiated length: %d bytes" CR), PDULength); - return SetLastError(0); + DEBUG_FMT("PDU negotiated length: %d bytes", PDULength); + + RETURN_OK(); } int LogoClient::SetLastError(int Error) @@ -1421,10 +1906,17 @@ int LogoClient::SetLastError(int Error) int LogoClient::ReadByte(dword Addr, byte *Data) { - size_t Length = 0; + TRACE("%lu, %p", Addr, Data); + + uint16_t Length = 0; + + LastError = 0; if (Data == NULL) - return SetLastError(errCliInvalidPDU); + { + SET_ERROR(LastError, errCliInvalidPDU, "Parameter 'Data' is undefined"); + RETURN_CODEVAL(LastError); + } *Data = 0; // Setup the telegram @@ -1440,29 +1932,32 @@ int LogoClient::ReadByte(dword Addr, byte *Data) PDU.H[Length++] = lowByte(word(Addr)); if (StreamClient->write(PDU.H, Length) != Length) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending request 'Read Byte'"); + RETURN_CODEVAL(LastError); + } // Setup the telegram memset(&PDU, 0, Length); PDURequested = 2 + AddrLength + 1; // Get first byte - if (RecvPacket(PDU.H, 1) != 0) - return LastError; + TRY(RecvPacket(PDU.H, 1)); LastPDUType = PDU.H[0]; // Store PDU Type if (LastPDUType == ACK) // Connection confirmed { // Get next bytes - if (RecvPacket(&PDU.H[1], PDURequested - 1) != 0) - return LastError; - + TRY(RecvPacket(&PDU.H[1], PDURequested - 1)); if (PDU.H[1] != 0x03) + { // Error, the response does not have the expected Code 0x03 - return SetLastError(errCliDataRead); + SET_ERROR(LastError, errCliDataRead, "Receive unexpected data"); + RETURN_CODEVAL(LastError); + } // We should align PDU - if ((size_t)PDURequested < sizeof(PDU.H)) + if ((uint16_t)PDURequested < sizeof(PDU.H)) { PDU.DATA[0] = PDU.H[PDURequested - 1]; PDU.H[PDURequested - 1] = 0; @@ -1472,22 +1967,30 @@ int LogoClient::ReadByte(dword Addr, byte *Data) else if (LastPDUType == NOK) // Request not confirmed { // Get second byte - if (RecvPacket(&PDU.H[1], 1) != 0) - return LastError; + TRY(RecvPacket(&PDU.H[1], 1)); // Get Error Type - return SetLastError(CpuError(PDU.H[1])); + SET_ERROR(LastError, CpuError(PDU.H[1]), "CPU error"); + RETURN_CODEVAL(LastError); } else - return SetLastError(errCliDataRead); + { + SET_ERROR(LastError, errCliDataRead, "Unknown error"); + RETURN_CODEVAL(LastError); + } *Data = PDU.DATA[0]; - return SetLastError(0); + + RETURN_OK(); } int LogoClient::WriteByte(dword Addr, byte Data) { - size_t Length = 0; + TRACE("%lu, %d", Addr, Data); + + uint16_t Length = 0; + + LastError = 0; // Setup the telegram memset(&PDU, 0, sizeof(PDU)); @@ -1504,38 +2007,57 @@ int LogoClient::WriteByte(dword Addr, byte Data) PDU.H[Length++] = Data; if (StreamClient->write(PDU.H, Length) != Length) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending request 'Write Byte'"); + RETURN_CODEVAL(LastError); + } // Setup the telegram memset(&PDU, 0, Length); PDURequested = 1; // Get first byte - if (RecvPacket(PDU.H, 1) != 0) - return LastError; + TRY(RecvPacket(PDU.H, 1)); LastPDUType = PDU.H[0]; // Store PDU Type - if (LastPDUType != ACK) // Check Confirmation + + if (LastPDUType == ACK) // Check Confirmation { - if (LastPDUType == NOK) // Get Exception Code - { - // Get second byte - if (RecvPacket(&PDU.H[1], 1) != 0) - return LastError; - } - return SetLastError(errCliDataWrite); + // OK } + else if (LastPDUType == NOK) // Get Exception Code + { + // Get second byte + TRY(RecvPacket(&PDU.H[1], 1)); - return SetLastError(0); + // Get Error Type + SET_ERROR(LastError, CpuError(PDU.H[1]), "CPU error"); + RETURN_CODEVAL(LastError); + } + else + { + SET_ERROR(LastError, errCliDataWrite, "Unknown error"); + RETURN_CODEVAL(LastError); + } + + RETURN_OK(); } -int LogoClient::ReadBlock(dword Addr, word ByteCount, byte *Data) +int LogoClient::ReadBlock(dword Addr, uint16_t ByteCount, byte *Data) { - size_t Length = 0; + TRACE("%lu, %u, %p", Addr, ByteCount, Data); + + uint16_t Length = 0; byte Checksum = 0; + LastError = 0; + if (Data == NULL) - return SetLastError(errCliInvalidPDU); + { + SET_ERROR(LastError, errCliInvalidPDU, "Parameter 'Data' is undefined"); + RETURN_CODEVAL(LastError); + } + memset(Data, 0, ByteCount); // Send the Query message @@ -1555,13 +2077,15 @@ int LogoClient::ReadBlock(dword Addr, word ByteCount, byte *Data) PDU.H[Length++] = lowByte(ByteCount); // Send Query if (StreamClient->write(PDU.H, Length) != Length) - return SetLastError(errStreamDataSend); + { + SET_ERROR(LastError, errStreamDataSend, "Error sending request 'Read Block'"); + RETURN_CODEVAL(LastError); + } // Receive the Response message memset(&PDU, 0, Length); // Get first byte - if (RecvPacket(PDU.H, 1) != 0) - return LastError; + TRY(RecvPacket(PDU.H, 1)); LastPDUType = PDU.H[0]; // Store PDU Type if (LastPDUType == ACK) // Connection confirmed @@ -1573,40 +2097,46 @@ int LogoClient::ReadBlock(dword Addr, word ByteCount, byte *Data) PDURequested = ByteCount - Length; if (PDURequested > MaxPduSize) PDURequested = MaxPduSize; - if (RecvPacket(Data + Length, PDURequested) != 0) - return LastError; + TRY(RecvPacket(Data + Length, PDURequested)); Length += PDURequested; // Iteration expression } // Get Checksum - if (RecvPacket(PDU.T, 1) != 0) - return LastError; + TRY(RecvPacket(PDU.T, 1)); // Calculate Checksum (only the data field are used for this) for (unsigned int i = 0; i < ByteCount; i++) Checksum = (*(Data + i) ^ Checksum) % 256; if (PDU.T[0] != Checksum) + { // Error, the response does not have the correct Checksum - return SetLastError(errCliDataRead); + SET_ERROR(LastError, errCliDataRead, "Invalid Checksum"); + RETURN_CODEVAL(LastError); + } } else if (LastPDUType == NOK) // Request not confirmed { // Get second byte - if (RecvPacket(&PDU.H[1], 1) != 0) - return LastError; + TRY(RecvPacket(&PDU.H[1], 1)); // Get Error Type - return SetLastError(CpuError(PDU.H[1])); + SET_ERROR(LastError, CpuError(PDU.H[1]), "CPU error"); + RETURN_CODEVAL(LastError); } else - return SetLastError(errCliDataRead); + { + SET_ERROR(LastError, errCliDataRead, "Unknown error"); + RETURN_CODEVAL(LastError); + } - return SetLastError(0); + RETURN_OK(); } int LogoClient::CpuError(int Error) { + TRACE("%d", Error); + switch (Error) { case 0: diff --git a/src/LogoPG.h b/src/LogoPG.h index e6b47ec..651e672 100644 --- a/src/LogoPG.h +++ b/src/LogoPG.h @@ -1,514 +1,513 @@ -/* - * LogoPG library, Version 0.5.2-20201223 - * - * Portion copyright (c) 2018,2020 by Jan Schneider - * - * LogoPG provided a library under ARDUINO to read data from a - * Siemens(tm) LOGO! PLC via the serial programing interface. - * Only LOGO 0BA4 to 0BA6 are supported. - * - *********************************************************************** - * - * This file is based on the implementation of SETTIMINO Version 1.1.0 - * The project SETTIMINO is an ARDUINO Ethernet communication library - * for S7 Siemens(tm) PLC - * - * Copyright (c) of SETTIMINO 2013 by Davide Nardella - * - * SETTIMINO is free software: we can redistribute it and/or modify - * it under the terms of the Lesser GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License. - * - * Please visit http://settimino.sourceforge.net/ for more information - * of SETTIMINO - * - *********************************************************************** - * - * Changelog: https://github.com/brickpool/logo/blob/master/CHANGELOG.md - * -*/ - -#ifndef LogoPG_h_ -#define LogoPG_h_ - -#define LOGO_PG_0_5 - -// Memory models -// _SMALL -// _NORMAL -// _EXTENDED - -#define _EXTENDED - -#if defined(_NORMAL) || defined(_EXTENDED) -#define _LOGOHELPER -#endif - -#include "Arduino.h" -#ifdef _EXTENDED -#include "TimeLib.h" -#endif - -// Error Codes -// from 0x0001 up to 0x00FF are severe errors, the Client should be disconnected -// from 0x0100 are LOGO Errors such as DB not found or address beyond the limit etc.. -#define errStreamConnectionFailed 0x0001 -#define errStreamConnectionReset 0x0002 -#define errStreamDataRecvTout 0x0003 -#define errStreamDataSend 0x0004 -#define errStreamDataRecv 0x0005 -#define errPGConnect 0x0006 -#define errPGInvalidPDU 0x0008 - -#define errCliInvalidPDU 0x0100 -#define errCliSendingPDU 0x0200 -#define errCliDataRead 0x0300 -#define errCliDataWrite 0x0400 -#define errCliFunction 0x0500 -#define errCliBufferTooSmall 0x0600 -#define errCliNegotiatingPDU 0x0700 - -// Connection Type -#define PG 0x01 - -// PDU related constants -#define AddrSize0BA4 2 // Size of Address for 0BA4 and 0BA5 -#define AddrSize0BA6 4 // Size of Address for 0BA6 -#define PduSize0BA4 70 // Fetch Data PDU size for 0BA4 and 0BA5 -#define PduSize0BA6 80 // Fetch Data PDU size for 0BA6 -#define MinPduSize 70 // Minimum Fetch Data PDU size (0BA4, 0BA5) -#define MaxPduSize 80 // Maximum Fetch Data PDU size (0BA6) - -#define ACK 0x06 // Confirmation Response -#define RUN 0x01 // Mode run -#define STOP 0x42 // Mode stop -#define NOK 0x15 // Exception Response -#define AA 0xAA // End delimiter - -// LOGO ID Area (Area that we want to read/write) -#define LogoAreaDB 0x84 - -const byte LogoCpuStatusUnknown = 0x00; -const byte LogoCpuStatusRun = 0x08; -const byte LogoCpuStatusStop = 0x04; - -#define Size_OC 19 // Order Code -#define Size_RD 6 - -typedef unsigned long dword; // 32 bit unsigned integer - -typedef byte *pbyte; -typedef int *pint; - -typedef struct { - byte H[Size_RD]; // PDU Header - byte DATA[MaxPduSize-Size_RD]; // PDU Data - byte T[1]; // PDU Trailer -} TPDU; -extern TPDU PDU; - -#ifdef _EXTENDED -typedef struct { - char Code[Size_OC]; // Order Code - byte V1; // Version V1.V2.V3 - byte V2; - byte V3; -} TOrderCode; - -typedef struct { - byte sch_schal; - byte sch_par; - byte sch_rel; - byte bart_sch; - byte anl_sch; -} TProtection; -#endif - -#ifdef _LOGOHELPER - -class LogoHelper -{ -public: - bool BitAt(void *Buffer, int ByteIndex, byte BitIndex); - bool BitAt(int ByteIndex, int BitIndex); - - byte ByteAt(void *Buffer, int index); - byte ByteAt(int index); - - word WordAt(void *Buffer, int index); - word WordAt(int index); - - int IntegerAt(void *Buffer, int index); - int IntegerAt(int index); -}; -extern LogoHelper LH; - -#endif // _LOGOHELPER - -class LogoClient -{ -public: - friend class LogoHelper; - - // Output properties - bool Connected; // true if the Client is connected - int LastError; // Last Operation error - - // Input properties - unsigned long RecvTimeout; // Receving timeout in millis - - // Methods - LogoClient(); - LogoClient(Stream *Interface); - ~LogoClient(); - - // Basic functions - void SetConnectionParams(Stream *Interface); - void SetConnectionType(word ConnectionType); - int ConnectTo(Stream *Interface); - int Connect(); - void Disconnect(); - int ReadArea(int Area, word DBNumber, word Start, word Amount, void *ptrData); - int GetPDULength() { return PDULength; } - - // Extended functions -#ifdef _EXTENDED -/* - int GetDBSize(word DBNumber, size_t *Size); - int DBGet(word DBNumber, void *ptrData, size_t *Size); -*/ - // Control functions - int PlcStart(); // start PLC - int PlcStop(); // stop PLC - int GetPlcStatus(int *Status); - // Date/Time functions - int GetPlcDateTime(TimeElements *DateTime); - int GetPlcDateTime(time_t *DateTime); - int SetPlcDateTime(TimeElements DateTime); - int SetPlcDateTime(time_t DateTime); - int SetPlcSystemDateTime(); - // System info functions - int GetOrderCode(TOrderCode *Info); - // Security functions - int SetSessionPassword(char *password); - int ClearSessionPassword(); - int GetProtection(TProtection *Protection); - // Miscellaneous functions -/* - GetExecTime -*/ - void ErrorText(int Error, char *Text, int TextLen); -#endif - -private: - static byte* Mapping; // PDU Data mapping to VM - - byte LastPDUType; // First byte of a received message - word ConnType; // Usually a PG connection - - // We use the Stream class as a common class, - // so LogoClient can use HardwareSerial or CustomSoftwareSerial - Stream *StreamClient; - - byte IdentNo; // Ident Number - int PDULength; // PDU length negotiated (0 if not negotiated) - int PDURequested; // PDU length requested by the client - int AddrLength; // Address length negotiated (in bytes for function sizeof) - int AccessMode; // By default, LOGO! have two privileged levels: protected and full access - - int RecvControlResponse(size_t *Size); - int RecvPacket(byte buf[], size_t Size); - int StreamConnect(); - int LogoConnect(); - int NegotiatePduLength(); - int SetLastError(int Error); - - // Low level functions - int ReadByte(dword Addr, byte *Data); - int WriteByte(dword Addr, byte Data); - int ReadBlock(dword Addr, word ByteCount, byte *Data); - int CpuError(int Error); -}; - - -/* -*********************************************************************** -* PDU data -* position description -* 0BA4 0BA5 0BA6 -*********************************************************************** - 6 6 6 block 1-128 - 22 22 block 129-130 - 22 block 129-200 - - 23 23 31 input 1-8 - 24 24 32 input 9-16 - 25 25 33 input 17-24 - - 34 TD function key F1-F4 - - 26 26 35 output 1-8 - 27 27 36 output 9-16 - - 28 28 37 merker 1-8 - 29 29 38 merker 9-16 - 30 30 39 merker 17-24 - 40 merker 25-27 - - 31 31 41 shift register 1-8 - - 32 32 42 cursor key C1-C4 - - 33 33 43 analog input 1 low - 34 34 44 analog input 1 high - 35 35 45 analog input 2 low - 36 36 46 analog input 2 high - 37 37 47 analog input 3 low - 38 38 48 analog input 3 high - 39 39 49 analog input 4 low - 40 40 50 analog input 4 high - 41 41 51 analog input 5 low - 42 42 52 analog input 5 high - 43 43 53 analog input 6 low - 44 44 54 analog input 6 high - 45 45 55 analog input 7 low - 46 46 56 analog input 7 high - 47 47 57 analog input 8 low - 48 48 58 analog input 8 high - - 49 49 59 analog output 1 low - 50 50 60 analog output 1 high - 51 51 61 analog output 2 low - 52 52 62 analog output 2 high - - 53 53 63 analog merker 1 low - 54 54 64 analog merker 1 high - 55 55 65 analog merker 2 low - 56 56 66 analog merker 2 high - 57 57 67 analog merker 3 low - 58 58 68 analog merker 3 high - 59 59 69 analog merker 4 low - 60 60 70 analog merker 4 high - 61 61 71 analog merker 5 low - 62 62 72 analog merker 5 high - 63 63 73 analog merker 6 low - 64 64 74 analog merker 6 high -*********************************************************************** -*/ - -// LOGO 0BA4 and 0BA5 index in PDU.DATA -#define B_0BA4 0x06 -#define I_0BA4 0x16 -#define Q_0BA4 0x19 -#define M_0BA4 0x1B -#define S_0BA4 0x1E -#define C_0BA4 0x1F -#define AI_0BA4 0x20 -#define AQ_0BA4 0x30 -#define AM_0BA4 0x34 - -// LOGO 0BA6 index in PDU.DATA -#define B_0BA6 0x06 -#define I_0BA6 0x1E -#define F_0BA6 0x21 -#define Q_0BA6 0x22 -#define M_0BA6 0x24 -#define S_0BA6 0x28 -#define C_0BA6 0x29 -#define AI_0BA6 0x2A -#define AQ_0BA6 0x3A -#define AM_0BA6 0x3E - -/* - * The LOGO > 0BA6 has reserved fixed memory addresses for inputs, - * outputs, merkers in the Variable Memory above the 850th byte. - * These depending on the model (0BA7 or 0BA8). Our library should - * represent the Variable Memory as 0BA7 address scheme. - * - * The function keys and cursor keys can not be read directly. - * The keys must be mapped to the V address in the software via the - * VM (Variable Memory) assignment. - * - * Not all of the VM space is available for accessing. You can specify - * a maximum of 64 parameters. If you try to specify more than 64 - * parameters, LogoClient may generate an error code. - * - *********************************************************************** - * VM data - * position description - * 0BA7 0BA8 - *********************************************************************** - 923 1024 input 1-8 - 924 1025 input 9-16 - 925 1026 input 17-24 - - 942 1064 output 1-8 - 943 1065 output 9-16 - 1066 output 17-20 - - 926 1032 analog input 1 high - 927 1033 analog input 1 low - 928 1034 analog input 2 high - 929 1035 analog input 2 low - 930 1036 analog input 3 high - 931 1037 analog input 3 low - 932 1038 analog input 4 high - 933 1039 analog input 4 low - 934 1040 analog input 5 high - 935 1041 analog input 5 low - 936 1042 analog input 6 high - 937 1043 analog input 6 low - 938 1044 analog input 7 high - 939 1045 analog input 7 low - 930 1046 analog input 8 high - 941 1047 analog input 8 low - - 944 1072 analog output 1 high - 945 1073 analog output 1 low - 946 1074 analog output 2 high - 947 1075 analog output 2 low - 1076 analog output 3 high - 1077 analog output 3 low - 1078 analog output 4 high - 1079 analog output 4 low - 1080 analog output 5 high - 1081 analog output 5 low - 1082 analog output 6 high - 1083 analog output 6 low - 1084 analog output 7 high - 1085 analog output 7 low - 1086 analog output 8 high - 1087 analog output 8 low - - 948 1104 merker 1-8 - 949 1105 merker 9-16 - 950 1106 merker 17-24 - 951 merker 25-27 - 1107 merker 25-32 - 1108 merker 33-40 - 1109 merker 41-48 - 1110 merker 49-56 - 1111 merker 47-64 - - 952 1118 analog merker 1 high - 953 1119 analog merker 1 low - 954 1120 analog merker 2 high - 955 1121 analog merker 2 low - 956 1122 analog merker 3 high - 957 1123 analog merker 3 low - 958 1124 analog merker 4 high - 959 1125 analog merker 4 low - 960 1126 analog merker 5 high - 961 1127 analog merker 5 low - 962 1128 analog merker 6 high - 963 1129 analog merker 6 low - 964 1130 analog merker 7 high - 965 1131 analog merker 7 low - 966 1132 analog merker 8 high - 967 1133 analog merker 8 low - 968 1134 analog merker 9 high - 969 1135 analog merker 9 low - 970 1136 analog merker 10 high - 971 1137 analog merker 10 low - 972 1138 analog merker 11 high - 973 1139 analog merker 11 low - 974 1140 analog merker 12 high - 975 1141 analog merker 12 low - 976 1142 analog merker 13 high - 977 1143 analog merker 13 low - 978 1144 analog merker 14 high - 979 1145 analog merker 14 low - 980 1146 analog merker 15 high - 981 1147 analog merker 15 low - 982 1148 analog merker 16 high - 983 1149 analog merker 16 low - *********************************************************************** -*/ - -// LOGO 0BA7 adress values in VM memory -#define VM_I01_08 923 -#define VM_I09_16 924 -#define VM_I17_24 925 - -#define VM_AI1_Hi 926 -#define VM_AI1_Lo 927 -#define VM_AI2_Hi 928 -#define VM_AI2_Lo 929 -#define VM_AI3_Hi 930 -#define VM_AI3_Lo 931 -#define VM_AI4_Hi 932 -#define VM_AI4_Lo 933 -#define VM_AI5_Hi 934 -#define VM_AI5_Lo 935 -#define VM_AI6_Hi 936 -#define VM_AI6_Lo 937 -#define VM_AI7_Hi 938 -#define VM_AI7_Lo 939 -#define VM_AI8_Hi 940 -#define VM_AI8_Lo 941 - -#define VM_Q01_08 942 -#define VM_Q09_16 943 - -#define VM_AQ1_Hi 944 -#define VM_AQ1_Lo 945 -#define VM_AQ2_Hi 946 -#define VM_AQ2_Lo 947 - -#define VM_M01_08 948 -#define VM_M09_16 949 -#define VM_M17_24 950 -#define VM_M25_27 951 - -#define VM_AM1_Hi 952 -#define VM_AM1_Lo 953 -#define VM_AM2_Hi 954 -#define VM_AM2_Lo 955 -#define VM_AM3_Hi 956 -#define VM_AM3_Lo 957 -#define VM_AM4_Hi 958 -#define VM_AM4_Lo 959 -#define VM_AM5_Hi 960 -#define VM_AM5_Lo 961 -#define VM_AM6_Hi 962 -#define VM_AM6_Lo 963 -#define VM_AM7_Hi 964 -#define VM_AM7_Lo 965 -#define VM_AM8_Hi 966 -#define VM_AM8_Lo 967 - -#define VM_AM9_Hi 968 -#define VM_AM9_Lo 969 -#define VM_AM10_Hi 970 -#define VM_AM10_Lo 971 -#define VM_AM11_Hi 972 -#define VM_AM11_Lo 973 -#define VM_AM12_Hi 974 -#define VM_AM12_Lo 975 -#define VM_AM13_Hi 976 -#define VM_AM13_Lo 977 -#define VM_AM14_Hi 978 -#define VM_AM14_Lo 979 -#define VM_AM15_Hi 980 -#define VM_AM15_Lo 981 -#define VM_AM16_Hi 982 -#define VM_AM16_Lo 983 - -#define VM_DIAG 984 -#define VM_RTC_YY 985 -#define VM_RTC_MM 986 -#define VM_RTC_DD 987 -#define VM_RTC_hh 988 -#define VM_RTC_mm 989 -#define VM_RTC_ss 990 - -// LOGO 0BA7 index in VM -#define I_0BA7 VM_I01_08 -#define Q_0BA7 VM_Q01_08 -#define M_0BA7 VM_M01_08 -#define AI_0BA7 VM_AI1_Hi -#define AQ_0BA7 VM_AQ1_Hi -#define AM_0BA7 VM_AM9_Hi - +/* + * LogoPG library, Version 0.5.3-20200117 + * + * Portion copyright (c) 2018,2020,2021 by Jan Schneider + * + * LogoPG provided a library under ARDUINO to read data from a + * Siemens(tm) LOGO! PLC via the serial programing interface. + * Only LOGO 0BA4 to 0BA6 are supported. + * + *********************************************************************** + * + * This file is based on the implementation of SETTIMINO Version 1.1.0 + * The project SETTIMINO is an ARDUINO Ethernet communication library + * for S7 Siemens(tm) PLC + * + * Copyright (c) of SETTIMINO 2013 by Davide Nardella + * + * SETTIMINO is free software: we can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + * + * Please visit http://settimino.sourceforge.net/ for more information + * of SETTIMINO + * + *********************************************************************** + * + * Changelog: https://github.com/brickpool/logo/blob/master/CHANGELOG.md + * +*/ + +#ifndef LogoPG_h_ +#define LogoPG_h_ + +#define LOGO_PG_0_5 + +// Memory models +// _SMALL +// _NORMAL +// _EXTENDED + +#define _EXTENDED +// #define _DEBUG + +#if defined(_NORMAL) || defined(_EXTENDED) +#define _LOGOHELPER +#endif + +#include +#ifdef _EXTENDED +#include +#endif + +// Error Codes +// from 0x0001 up to 0x00FF are severe errors, the Client should be disconnected +// from 0x0100 are LOGO Errors such as DB not found or address beyond the limit etc.. +#define errStreamConnectionFailed 0x0001 +#define errStreamConnectionReset 0x0002 +#define errStreamDataRecvTout 0x0003 +#define errStreamDataSend 0x0004 +#define errStreamDataRecv 0x0005 +#define errPGConnect 0x0006 +#define errPGInvalidPDU 0x0008 + +#define errCliInvalidPDU 0x0100 +#define errCliSendingPDU 0x0200 +#define errCliDataRead 0x0300 +#define errCliDataWrite 0x0400 +#define errCliFunction 0x0500 +#define errCliBufferTooSmall 0x0600 +#define errCliNegotiatingPDU 0x0700 + +// Connection Type +#define PG 0x01 + +// PDU related constants +#define AddrSize0BA4 2 // Size of Address for 0BA4 and 0BA5 +#define AddrSize0BA6 4 // Size of Address for 0BA6 +#define PduSize0BA4 70 // Fetch Data PDU size for 0BA4 and 0BA5 +#define PduSize0BA6 80 // Fetch Data PDU size for 0BA6 +#define MinPduSize 70 // Minimum Fetch Data PDU size (0BA4, 0BA5) +#define MaxPduSize 80 // Maximum Fetch Data PDU size (0BA6) + +#define ACK 0x06 // Confirmation Response +#define RUN 0x01 // Mode run +#define STOP 0x42 // Mode stop +#define NOK 0x15 // Exception Response +#define AA 0xAA // End delimiter + +// LOGO ID Area (Area that we want to read/write) +#define LogoAreaDB 0x84 + +const byte LogoCpuStatusUnknown = 0x00; +const byte LogoCpuStatusRun = 0x08; +const byte LogoCpuStatusStop = 0x04; + +#define Size_OC 19 // Order Code +#define Size_RD 6 + +typedef unsigned long dword; // 32 bit unsigned integer + +typedef byte *pbyte; +typedef int *pint; + +typedef struct { + byte H[Size_RD]; // PDU Header + byte DATA[MaxPduSize-Size_RD]; // PDU Data + byte T[1]; // PDU Trailer +} TPDU; +extern TPDU PDU; + +#ifdef _EXTENDED +typedef struct { + char Code[Size_OC]; // Order Code + byte V1; // Version V1.V2.V3 + byte V2; + byte V3; +} TOrderCode; + +typedef struct { + byte sch_schal; + byte sch_par; + byte sch_rel; + byte bart_sch; + byte anl_sch; +} TProtection; +#endif + +#ifdef _LOGOHELPER + +class LogoHelper +{ +public: + bool BitAt(void *Buffer, int ByteIndex, byte BitIndex); + bool BitAt(int ByteIndex, int BitIndex); + + byte ByteAt(void *Buffer, int index); + byte ByteAt(int index); + + word WordAt(void *Buffer, int index); + word WordAt(int index); + + int IntegerAt(void *Buffer, int index); + int IntegerAt(int index); +}; +extern LogoHelper LH; + +#endif // _LOGOHELPER + +class LogoClient +{ +public: + friend class LogoHelper; + + // Output properties + bool Connected; // true if the Client is connected + int LastError; // Last Operation error + + // Input properties + uint16_t RecvTimeout; // Receving timeout in millis + + // Methods + LogoClient(); + LogoClient(Stream *Interface); + ~LogoClient(); + + // Basic functions + void SetConnectionParams(Stream *Interface); + void SetConnectionType(uint16_t ConnectionType); + int ConnectTo(Stream *Interface); + int Connect(); + void Disconnect(); + int ReadArea(int Area, uint16_t DBNumber, uint16_t Start, uint16_t Amount, void *ptrData); + int GetPDULength() { return PDULength; } + + // Extended functions +#ifdef _EXTENDED + int GetDBSize(uint16_t DBNumber, uint16_t *Size); + int DBGet(uint16_t DBNumber, void *ptrData, uint16_t *Size); + // Control functions + int PlcStart(); // start PLC + int PlcStop(); // stop PLC + int GetPlcStatus(int *Status); + // Date/Time functions + int GetPlcDateTime(TimeElements *DateTime); + int GetPlcDateTime(time_t *DateTime); + int SetPlcDateTime(TimeElements DateTime); + int SetPlcDateTime(time_t DateTime); + int SetPlcSystemDateTime(); + // System info functions + int GetOrderCode(TOrderCode *Info); + // Security functions + int SetSessionPassword(char *password); + int ClearSessionPassword(); + int GetProtection(TProtection *Protection); + // Miscellaneous functions +/* + GetExecTime +*/ + void ErrorText(int Error, char *Text, int TextLen); +#endif + +private: + static byte* Mapping; // PDU Data mapping to VM + + byte LastPDUType; // First byte of a received message + uint16_t ConnType; // Usually a PG connection + + // We use the Stream class as a common class, + // so LogoClient can use HardwareSerial or CustomSoftwareSerial + Stream *StreamClient; + + byte IdentNo; // Ident Number + int PDULength; // PDU length negotiated (0 if not negotiated) + int PDURequested; // PDU length requested by the client + int AddrLength; // Address length negotiated (in bytes for function sizeof) + int AccessMode; // By default, LOGO! have two privileged levels: protected and full access + + int RecvControlResponse(uint16_t *Size); + int RecvPacket(uint8_t *buf, uint16_t Size); + int StreamConnect(); + int LogoConnect(); + int NegotiatePduLength(); + int SetLastError(int Error); + + // Low level functions + int ReadByte(dword Addr, byte *Data); + int WriteByte(dword Addr, byte Data); + int ReadBlock(dword Addr, uint16_t ByteCount, byte *Data); + int CpuError(int Error); +}; + + +/* +*********************************************************************** +* PDU data +* position description +* 0BA4 0BA5 0BA6 +*********************************************************************** + 6 6 6 block 1-128 + 22 22 block 129-130 + 22 block 129-200 + + 23 23 31 input 1-8 + 24 24 32 input 9-16 + 25 25 33 input 17-24 + + 34 TD function key F1-F4 + + 26 26 35 output 1-8 + 27 27 36 output 9-16 + + 28 28 37 merker 1-8 + 29 29 38 merker 9-16 + 30 30 39 merker 17-24 + 40 merker 25-27 + + 31 31 41 shift register 1-8 + + 32 32 42 cursor key C1-C4 + + 33 33 43 analog input 1 low + 34 34 44 analog input 1 high + 35 35 45 analog input 2 low + 36 36 46 analog input 2 high + 37 37 47 analog input 3 low + 38 38 48 analog input 3 high + 39 39 49 analog input 4 low + 40 40 50 analog input 4 high + 41 41 51 analog input 5 low + 42 42 52 analog input 5 high + 43 43 53 analog input 6 low + 44 44 54 analog input 6 high + 45 45 55 analog input 7 low + 46 46 56 analog input 7 high + 47 47 57 analog input 8 low + 48 48 58 analog input 8 high + + 49 49 59 analog output 1 low + 50 50 60 analog output 1 high + 51 51 61 analog output 2 low + 52 52 62 analog output 2 high + + 53 53 63 analog merker 1 low + 54 54 64 analog merker 1 high + 55 55 65 analog merker 2 low + 56 56 66 analog merker 2 high + 57 57 67 analog merker 3 low + 58 58 68 analog merker 3 high + 59 59 69 analog merker 4 low + 60 60 70 analog merker 4 high + 61 61 71 analog merker 5 low + 62 62 72 analog merker 5 high + 63 63 73 analog merker 6 low + 64 64 74 analog merker 6 high +*********************************************************************** +*/ + +// LOGO 0BA4 and 0BA5 index in PDU.DATA +#define B_0BA4 0x06 +#define I_0BA4 0x16 +#define Q_0BA4 0x19 +#define M_0BA4 0x1B +#define S_0BA4 0x1E +#define C_0BA4 0x1F +#define AI_0BA4 0x20 +#define AQ_0BA4 0x30 +#define AM_0BA4 0x34 + +// LOGO 0BA6 index in PDU.DATA +#define B_0BA6 0x06 +#define I_0BA6 0x1E +#define F_0BA6 0x21 +#define Q_0BA6 0x22 +#define M_0BA6 0x24 +#define S_0BA6 0x28 +#define C_0BA6 0x29 +#define AI_0BA6 0x2A +#define AQ_0BA6 0x3A +#define AM_0BA6 0x3E + +/* + * The LOGO > 0BA6 has reserved fixed memory addresses for inputs, + * outputs, merkers in the Variable Memory above the 850th byte. + * These depending on the model (0BA7 or 0BA8). Our library should + * represent the Variable Memory as 0BA7 address scheme. + * + * The function keys and cursor keys can not be read directly. + * The keys must be mapped to the V address in the software via the + * VM (Variable Memory) assignment. + * + * Not all of the VM space is available for accessing. You can specify + * a maximum of 64 parameters. If you try to specify more than 64 + * parameters, LogoClient may generate an error code. + * + *********************************************************************** + * VM data + * position description + * 0BA7 0BA8 + *********************************************************************** + 923 1024 input 1-8 + 924 1025 input 9-16 + 925 1026 input 17-24 + + 942 1064 output 1-8 + 943 1065 output 9-16 + 1066 output 17-20 + + 926 1032 analog input 1 high + 927 1033 analog input 1 low + 928 1034 analog input 2 high + 929 1035 analog input 2 low + 930 1036 analog input 3 high + 931 1037 analog input 3 low + 932 1038 analog input 4 high + 933 1039 analog input 4 low + 934 1040 analog input 5 high + 935 1041 analog input 5 low + 936 1042 analog input 6 high + 937 1043 analog input 6 low + 938 1044 analog input 7 high + 939 1045 analog input 7 low + 930 1046 analog input 8 high + 941 1047 analog input 8 low + + 944 1072 analog output 1 high + 945 1073 analog output 1 low + 946 1074 analog output 2 high + 947 1075 analog output 2 low + 1076 analog output 3 high + 1077 analog output 3 low + 1078 analog output 4 high + 1079 analog output 4 low + 1080 analog output 5 high + 1081 analog output 5 low + 1082 analog output 6 high + 1083 analog output 6 low + 1084 analog output 7 high + 1085 analog output 7 low + 1086 analog output 8 high + 1087 analog output 8 low + + 948 1104 merker 1-8 + 949 1105 merker 9-16 + 950 1106 merker 17-24 + 951 merker 25-27 + 1107 merker 25-32 + 1108 merker 33-40 + 1109 merker 41-48 + 1110 merker 49-56 + 1111 merker 47-64 + + 952 1118 analog merker 1 high + 953 1119 analog merker 1 low + 954 1120 analog merker 2 high + 955 1121 analog merker 2 low + 956 1122 analog merker 3 high + 957 1123 analog merker 3 low + 958 1124 analog merker 4 high + 959 1125 analog merker 4 low + 960 1126 analog merker 5 high + 961 1127 analog merker 5 low + 962 1128 analog merker 6 high + 963 1129 analog merker 6 low + 964 1130 analog merker 7 high + 965 1131 analog merker 7 low + 966 1132 analog merker 8 high + 967 1133 analog merker 8 low + 968 1134 analog merker 9 high + 969 1135 analog merker 9 low + 970 1136 analog merker 10 high + 971 1137 analog merker 10 low + 972 1138 analog merker 11 high + 973 1139 analog merker 11 low + 974 1140 analog merker 12 high + 975 1141 analog merker 12 low + 976 1142 analog merker 13 high + 977 1143 analog merker 13 low + 978 1144 analog merker 14 high + 979 1145 analog merker 14 low + 980 1146 analog merker 15 high + 981 1147 analog merker 15 low + 982 1148 analog merker 16 high + 983 1149 analog merker 16 low + *********************************************************************** +*/ + +// LOGO 0BA7 adress values in VM memory +#define VM_I01_08 923 +#define VM_I09_16 924 +#define VM_I17_24 925 + +#define VM_AI1_Hi 926 +#define VM_AI1_Lo 927 +#define VM_AI2_Hi 928 +#define VM_AI2_Lo 929 +#define VM_AI3_Hi 930 +#define VM_AI3_Lo 931 +#define VM_AI4_Hi 932 +#define VM_AI4_Lo 933 +#define VM_AI5_Hi 934 +#define VM_AI5_Lo 935 +#define VM_AI6_Hi 936 +#define VM_AI6_Lo 937 +#define VM_AI7_Hi 938 +#define VM_AI7_Lo 939 +#define VM_AI8_Hi 940 +#define VM_AI8_Lo 941 + +#define VM_Q01_08 942 +#define VM_Q09_16 943 + +#define VM_AQ1_Hi 944 +#define VM_AQ1_Lo 945 +#define VM_AQ2_Hi 946 +#define VM_AQ2_Lo 947 + +#define VM_M01_08 948 +#define VM_M09_16 949 +#define VM_M17_24 950 +#define VM_M25_27 951 + +#define VM_AM1_Hi 952 +#define VM_AM1_Lo 953 +#define VM_AM2_Hi 954 +#define VM_AM2_Lo 955 +#define VM_AM3_Hi 956 +#define VM_AM3_Lo 957 +#define VM_AM4_Hi 958 +#define VM_AM4_Lo 959 +#define VM_AM5_Hi 960 +#define VM_AM5_Lo 961 +#define VM_AM6_Hi 962 +#define VM_AM6_Lo 963 +#define VM_AM7_Hi 964 +#define VM_AM7_Lo 965 +#define VM_AM8_Hi 966 +#define VM_AM8_Lo 967 + +#define VM_AM9_Hi 968 +#define VM_AM9_Lo 969 +#define VM_AM10_Hi 970 +#define VM_AM10_Lo 971 +#define VM_AM11_Hi 972 +#define VM_AM11_Lo 973 +#define VM_AM12_Hi 974 +#define VM_AM12_Lo 975 +#define VM_AM13_Hi 976 +#define VM_AM13_Lo 977 +#define VM_AM14_Hi 978 +#define VM_AM14_Lo 979 +#define VM_AM15_Hi 980 +#define VM_AM15_Lo 981 +#define VM_AM16_Hi 982 +#define VM_AM16_Lo 983 + +#define VM_DIAG 984 +#define VM_RTC_YY 985 +#define VM_RTC_MM 986 +#define VM_RTC_DD 987 +#define VM_RTC_hh 988 +#define VM_RTC_mm 989 +#define VM_RTC_ss 990 + +// LOGO 0BA7 index in VM +#define I_0BA7 VM_I01_08 +#define Q_0BA7 VM_Q01_08 +#define M_0BA7 VM_M01_08 +#define AI_0BA7 VM_AI1_Hi +#define AQ_0BA7 VM_AQ1_Hi +#define AM_0BA7 VM_AM9_Hi + #endif