Post by s1ack on Wago PFC200 Ethernet / IoDrvEthernet Issue?
CODESYS Forge
talk
(Post)
CODESYS Control for PFC200 4.13.00 CODESYS IIoT Libraries SL 1.11.0.0 Application Based License: CODESYS Control Standard M I have (2) projects with similar code base and both using Wago PFC200. On the second project I am seeing 2 things that I have not been able to resolve: 1) Errors in the log under the Ethernet Item: IoDrvEthernet: [PCI\E1Q51CE62] Could not be found on target! Please check the Ethernet settings. 2) Exception for CPU Load. "Processorload watchdog: plcload=100, maxplcload=95" I have slowed down cyclic tasks on this project, even though on the first project there is actually more code - and it does not exhibit this behavior. In looking at the CPU loading via Linux 'TOP' command there appears to be 20% idle. So plenty of headroom (my opinion). In an effort to troubleshoot I have duplicated the target hardware on my bench. It ran overnight without a CPU load exception. On the test system, I have tried (3) version of the ethernet device. 3.5.16.0 3.5.17.0 and 4.2.0.0 Presently the log does not show the error, but status says the module reports an error. I really do not know if this issue is related to the CPU load exception. The issue does not appear to effect ethernet communications (ModbusTCP or SNMP or Webvisu or PuTTy into Linux Shell). On my test bench I have ModbusTCP slave simulators for all ModbusTCP slaves, but only (1) of the (5) SNMP devices present.
Last updated: 2025-01-21
Post by clarenced on Multiple applications on one device sharing variables.
CODESYS Forge
talk
(Post)
I am looking for an example of how to run 2 applications on 1 device that would have global variables shared between the two apps. In this case what I am trying to do is to have the main program logic run as one application and then have the visualization run as a second application. I find this https://us.store.codesys.com/media/n98_media_assets/files/000013-F/0/Multiple%20Applications_en.pdf, that talks about exactly what I want to do, but I can't find the download. The main reason I want to do this is for download speed. Right now it takes at least a minute to download our application to the PLC. A simple program downloads to the PLC in a very short time, but add visualization and the download time gets much longer. If I could download the program separately from the visualization I think that a simple program change would take very little time to download. I have tried create a GVL in the POUs view. This partly works in that both applications can see the GVL, but they each create their own instance of it and the variables are not shared between the two apps. There are ideas of using OPCUA but this raises problems with licensing and having to move any variable that needs to be shared into the Symbol Configuration. This adds a lot of management to keep this up to date. Any ideas would be appreciated thanks.
Last updated: 2025-02-20
Post by fmon on Modbus : dis- / re-connect cable: modbus does not re-start
CODESYS Forge
talk
(Post)
Hello, I am using codesys Modbus TCP client (4.4.0.0) to communicate with a python modbus server (package pyModbusTCP). I first start my python server on the distant machine. After a fresh codesys compilation, a plc connection/transfer and a PLC run, the modbus connection is OK. Every time in this context the connection is created correctly. When I shut down the server, the modbus connection falls, that is normal. When I restart the python server, impossible to recreate the modbus connexion. With the client autoreconnection, I see on my server that the client tries to connect but unsuccesfully. I have the following message : DEBUG:pyModbusTCP.server:accept new connection from ClientInfo(address='192.168.1.20', port=33476) DEBUG:pyModbusTCP.server:Exception during request handling: NetworkError('recv return null') I tried to stop the codesys client and to restart it with these commands : Modbus_TCP_Client.xStop := True; // Or False Modbus_TCP_Client.Enable := True; // Or False Modbus_TCP_Server.Enable := True; // Or False I tried to confirm the error manually to force reconnection with: Modbus_TCP_Server_Motors.xConfirmError := TRUE; I tried also this command to STOP/RESET/START client and server (codesys side) but nothing happens : status_client := Modbus_TCP_Client.SetCommunicationState(eRequestedState := DED.DEVICE_TRANSITION_STATE.STOP); // .RESET & .START The answer of this function when executed is "status_client = NOT_SUPPORTED" It seems that is a socket problem, but I do not understand if it is on the client or server side. I tried a modbus simulator called "ananas.exe" and the result is the same. Impossible to get a modbus reconnection. What is different at the first connection and at a reconnection attempt ? Thanks for your help
Last updated: 2025-03-14
Post by fmon on Modbus TCP client reconnection problem
CODESYS Forge
talk
(Post)
Hello, I am using codesys Modbus TCP client (4.4.0.0) to communicate with a python modbus server (package pyModbusTCP). I first start my python server on the distant machine. After a fresh codesys compilation, a plc connection/transfer and a PLC run, the modbus connection is OK. Every time in this context the connection is created correctly. When I shut down the server, the modbus connection falls, that is normal. When I restart the python server, impossible to recreate the modbus connexion. With the client autoreconnection, I see on my server that the client tries to connect but unsuccesfully. I have the following message : DEBUG:pyModbusTCP.server:accept new connection from ClientInfo(address='192.168.1.20', port=33476) DEBUG:pyModbusTCP.server:Exception during request handling: NetworkError('recv return null') I tried to stop the codesys client and to restart it with these commands : Modbus_TCP_Client.xStop := True; // Or False Modbus_TCP_Client.Enable := True; // Or False Modbus_TCP_Server.Enable := True; // Or False I tried to confirm the error manually to force reconnection with: Modbus_TCP_Server_Motors.xConfirmError := TRUE; I tried also this command to STOP/RESET/START client and server (codesys side) but nothing happens : status_client := Modbus_TCP_Client.SetCommunicationState(eRequestedState := DED.DEVICE_TRANSITION_STATE.STOP); // .RESET & .START The answer of this function when executed is "status_client = NOT_SUPPORTED". Is it normal ? It seems that is a socket problem, but I do not understand if it is on the client or server side. I tried a modbus simulator called "ananas.exe" and the result is the same. Impossible to get a modbus client reconnection. What is different at the first client connection and at a reconnection attempt ? Thanks for your help
Last updated: 2025-03-17
Post by edson-bueno on SysProcess Execute Command unable to run commands with special characters
CODESYS Forge
talk
(Post)
Hi, I found the same issue, and I fixed with this steps: 1st go to codesys .cfg file. sudo nano CODESYSControl.cfg Then insert this: [SysProcess] BasePriority=Realtime Command=AllowAll Now we need to grant codesys root rights on Linux. Step 1: Create or edit the systemd override configuration: sudo systemctl edit codesyscontrol In the editor that opens, insert: [Service] User=root Save and exit: Press Ctrl+O to save Press Ctrl+X to exit Step 2: Reload systemd and reboot To apply the override: sudo systemctl daemon-reexec sudo systemctl daemon-reload sudo reboot Step 3: Confirm CODESYS is running as root After reboot, open the terminal and run: ps aux | grep codesyscontrol You should see something like: root 1234 ... /opt/codesys/bin/codesyscontrol.bin ... If instead it shows admin or another user, the override was not applied correctly. Step 4: (Optional) Confirm from within CODESYS In your CODESYS project, insert this test code to run the Linux command whoami: Make sure the lib SysProcessImplementation, SysTypes, and CmpErrors is on the project. VAR sCommand : STRING := '/usr/bin/whoami'; sOutput : STRING(255); refCommand : REFERENCE TO STRING; refOutput : REFERENCE TO STRING; resultCmd : UDINT; END_VAR refCommand REF= sCommand; refOutput REF= sOutput; SysProcessExecuteCommand2( pszCommand := refCommand, pszStdOut := refOutput, udiStdOutLen := SIZEOF(sOutput), pResult := ADR(resultCmd) ); Notes & Warnings This method gives full system access to the CODESYS runtime — do not expose this system to the public network without protection. Do not use sudo in commands inside CODESYS when the runtime is already running as root. @tomas111, in case you want to read the temperatur, use this command: sCommand:STRING:='/usr/bin/vcgencmd measure_temp';
Last updated: 2025-05-20
Post by dnordenberg on BOOL variable in OPC UA and modbus simultaneously
CODESYS Forge
talk
(Post)
How can I have an BOOL variable that is read/write in both modbus TCP server and from OPC UA at the same time? Variables with %MX.n %QX.n addressing which is used for BOOL/coils in modbus server is not allowed in OPC UA symbol list on Schneider M251 controllers. I have already asked on Schneiders forum but there are not a lot of codesys experts over there :( So OPC symbols can't have a address and the modbus TCP server needs a address. So how should I do it then? It seems like an impossible combination. Since I want read/write access from both ways I can't just simply make a second variable and assign it the value of another since it would be a one way assignment. I also tried using VAR_CONFIG which then makes the address syntax %Q or %M in my main variable list and that fools OPC UA Symbol Configuration to allow it and then I add the complete %MX.n address of it in the VAR_CONFIG list. This semi works, I can toggle the BOOL from modbus but the OPC UA symbol seems to be read only. It displays symbol status but nothing happens when I toogle it. I think I need another way to have the variables and symbols pointing to the same bits in memory... My plan B is to write a BOOL array compare FB that saves a history bit from previous cycle and if that does not match current status at next cycle for either the OPC symbol or the modbus variable then it copies the new status over to the other. Maybe ugly but it's like my last alternative. Kind regards David
Last updated: 2025-07-14
Post by gunstr on CODESYS control on RPi starts, shortly runs, then exits
CODESYS Forge
talk
(Post)
Thank you very much for the quick answer! The RPi hw were delivered last week so it's probably a resent version. I found an old scrapped board (2 years or so) with a broken GPIO that I tested with the same SD card image and with that board Codeys can run for the full 2h demo period without issues. Then I went back the the new board to test the 4.17.0.0 release but the installation fails... [INFORMATION] Standard output: create and install daemon [INFORMATION] Standard output: codesyscontrol started [WARNING] Unable to detect Raspberry Pi Architecture. Setting armv6l as default, you can change this through the settings panel. [INFORMATION] Standard output: codesyscontrol stopped [ERROR] Expected exit value of command failed: expected 0, received 1 [WARNING] Error setting the Raspberry Pi Architecture [ERROR] Error installing codesyscontrol Do you have any idea what could be the reason? I'm using an OS image including Python drivers for LCD and some I/O:s from last year to keep things stable, but also after upgrading with sudo apt update && sudo apt upgrade I still get the same error Reverting back to 4.15.00 works fine: [INFORMATION] Standard output: create and install daemon [INFORMATION] Standard output: codesysedge started [INFORMATION] Successfully installed codesyscontrol [INFORMATION] Successfully started CODESYS Edge Gateway for Linux [INFORMATION] Successfully started CODESYS Control for Raspberry Pi SL but as before the Codesys services stops after 30 seconds. And I noticed that the Codesys control stops and is not reachable also if I have no application loaded.
Last updated: 2025-09-02
Post by gunstr on CODESYS control on RPi starts, shortly runs, then exits
CODESYS Forge
talk
(Post)
I now tried to install 4.17.0.0 on a fully clean installations: A brand new RPi 4B Install latest Raspberry Pi OS Lite, Release date 13 May 2025, 64bit run sudo apt update && sudo apt full-upgrade When asked - Select to install the updated version of '/etc/initramfs-tools/initramfs.conf' Install CODESYS Control for Raspberry Pi SL. version 4.17.0.0 Log output: [INFORMATION] Connected successfully! [INFORMATION] Successfully connected to target (192.168.0.197) [WARNING] No supported container engine found on device. You have to install a supported container engine to use Virtual Control. Please contact the Administrator of the device. [WARNING] Error detecting container architecture [INFORMATION] Finished reading device information [ERROR] Error output: /var/lib/dpkg/info/codesyscontrol.postinst: 88: check_legacy_extensionapi_folder: not found [INFORMATION] Standard output: Selecting previously unselected package codesyscontrol. [INFORMATION] Standard output: (Reading database ... 76772 files and directories currently installed.) [INFORMATION] Standard output: Preparing to unpack .../codesyscontrol_raspberry_4.17.0.0_all.deb ... [INFORMATION] Standard output: Unpacking codesyscontrol (4.17.0.0) ... [INFORMATION] Standard output: Setting up codesyscontrol (4.17.0.0) ... [INFORMATION] Standard output: make config rw [INFORMATION] Standard output: set folder permissions [INFORMATION] Standard output: prepare permissions [INFORMATION] Standard output: check legacy extension api folder [INFORMATION] Standard output: create and install daemon [INFORMATION] Standard output: codesyscontrol started [WARNING] Unable to detect Raspberry Pi Architecture. Setting armv6l as default, you can change this through the settings panel. [INFORMATION] Standard output: codesyscontrol stopped [ERROR] Expected exit value of command failed: expected 0, received 1 [WARNING] Error setting the Raspberry Pi Architecture [ERROR] Error installing codesyscontrol If I read the log correctly the control is actually installed but cannot start. I'm running out of ideas how to proceeed now, any suggestions are welcome.
Last updated: 2025-09-03
Post by testlogic on Sending Sequential Modbus TCP Packets
CODESYS Forge
talk
(Post)
I have a Modbus TCP slave device where I need to do sequential writes to the same register. The register I'm writing to is kind of like a command line, each packet is a command word encoded in Hexadecimal. I am having difficulty implementing this system in CoDeSys 3.5 SP19. I feel like the structure of the program should be something along the lines of (Pseudocode): ModbusTCPSend(Command Register, Command1) ModbusTCPSend(Command Register, Command2) ModbusTCPSend(Command Register, Command3) I have tried to implement this with a rising edge trigger wMot1OPCode := 16#E1; //Stop Motor & Kill Program xMot1SendOP := TRUE; //Send OP on rising edge xMot1SendOP := FALSE; //Reset wMot1OPCode := 16#9E; //Disable Motor xMot1SendOP :=TRUE; //Send OP on rising edge xMot1SendOP := FALSE; //Reset Where "wMot1OPCode" is the IO map for writing to the command register, and "xMot1SendOP" is the rising edge trigger for that modbus channel. However, this doesn't work. The device never responds to the modbus commands. It seems like the trigger variable is switched too quickly for modbus to send the packet. I know the modbus register is working, because I can set the channel to cyclic and the device will respond. However, I can't use this reliably because I need each command to be sent once, in order. Cyclic keeps re-sending the commands and seems like it could miss a command as well if one was sent in-between cycle time. I have also trying using the Application trigger as described by https://faq.codesys.com/pages/viewpage.action?pageId=24510480, but this is also not working for me. See attached picture for my FBD code. This seems like a simple function, I can't tell what I'm doing wrong here. Thanks for the help.
Last updated: 2024-03-06
Post by mp9876 on Device logon problem following fresh install
CODESYS Forge
talk
(Post)
All screenshots are enclosed in MS Word document. NOTE; 4 questions features at the end. Any assistance would be greatly appreciated! Hi everyone, I am still having device logon problem following a fresh install Here are the STEPS I went through following a FRESH CODESYS Win 64 3.5.19.60 install on a different computer. ‘Gateway not configured properly’ when attempting to REFRESH from the Users and Groups. Then did Gateway manage and confirmed it was ok: This message popped out as it was a fresh install: YES then: Initially went with Administrator twice (user & password) but it did not like it. No device is responding to …: Continued by setting a new user Pressed OK and obtained the following message: Stopped and Restarted PLC at that point: Obtained the following: Attempted to login a 2nd time without success. Then I scanned the network to see if matching device can be detected: Only way to get it detected was to remove the tick in the ‘Hide non-matching devices…’ Attempted a WINK but got a return like this one: REFRESH either @ Users and Groups or @ Access Rights returns: Then created user ‘mp’ at Project level ang logged in: Understandably device user mp9876 was not valid for the Project. Device information A few questions comes to my mind following these connection attempts: 1- Gateway not configured properly message pops out because of a non-compatible device ? 2- Why would the device not be compatible ? 3- Any way to make the device compatible ? 4- Any ideas on how to get this connectivity problem to the device solved ?
Last updated: 2024-03-17
Post by manuknecht on Maximum dynamic limits of MC_MoveLinearAbsolute and MC_MoveLinearRelative
CODESYS Forge
talk
(Post)
Hello all I use an axis group with two linear axes in a kinematic system. Using MC_MoveLinearAbsolute and MC_MoveLinearRelative, I am trying to realise dynamic movements (set velocity: 1 m/s, set acceleration: 30 m/s^2) with the highest possible jerk, as I want to compare this with a movement with a trapezoidal velocity profile. However, my measurements show that the axes accelerate with a maximum jerk of approx. 800 m/s^3, even if the value in the function block was set to 10,000 m/s^3. I have already increased the dynamic limits of the two axes accordingly (Velocity: 5000 u/s, Acc-/Deceleration: 250'000 u/s^2, Jerk: 100'000'000 u/s^3, where 1 u = 1 mm), but this had no effect. I have checked the input parameters of the function blocks and the VelFactor, AccFactor and JerkFactor factors are all set to the maximum (value 1). Reducing the cycle time has resulted in a higher jerk, but as we are already working with a cycle time of 1 ms, there is nothing more that can be done here. The axes themselves are still a long way from drawing the maximum current, which is why I assume that this should not be a problem either. Are there other factors or parameters that influence the dynamic limits and therefore allow faster movements? The PLC I am using is a Raspberry Pi 4 and Codesys V3.5 SP20 with all libraries up to date. Thanks in advance Manuel
Last updated: 2024-05-08
Post by xcqt on Oop best practice
CODESYS Forge
talk
(Post)
Hi all, I’m currently trying to improve my OOP structure in CODESYS and I’m looking for some input on how others approach this. I understand the basics like inheritance, interfaces, abstract FBs, methods, and properties, but I still struggle a bit with the overall architecture and what’s considered clean or scalable in bigger projects. As an example, I’m working on two different energy meter function blocks: FB_EnergyMeter_MQTT reads data from MQTT (strings) FB_EnergyMeter_Modbus reads data from Modbus (words) Both have their own Update() method and implement the same interface (something like IF_EnergyMeter). Later on, I’ll probably add more meter types, but they should all behave the same from the controller’s point of view. Now, there’s a FB_GridControl block that needs power data from these meters. I see two options here: Define the meter blocks inside FB_GridControl and call them directly (for example fbModbusMeter.UpdateModbus()). Keep the meter blocks outside and pass them into FB_GridControl as interface references, so the control block doesn’t know which specific type of meter it’s dealing with. Option 2 feels cleaner and more flexible to me, but I’m not entirely sure how to handle the data flow in that case. Should I pass the meter instance through an interface reference (REFERENCE TO IF_EnergyMeter)? Or is there a better way to link the external FBs to the control block? I’d like to hear how you structure this kind of setup or see an example from someone who has done something similar. EDIT: I think i need to do something like this fbModbusUpdateInput(wInput:= wWordValue); fbMqttUpdateInput(strInput:= strStringValue); IF bUseMqtt THEN Meter REF = fbMqttUpdateInput; ELSE Meter REF = fbModbusUpdateInput; END_IF fbControl.SetMeter(UsedMeter := Meter); Or am i thinking wrong? Thanks, Thomas
Last updated: 2025-10-16
Post by kishan on SMC_Interpolator / CNC Abort – ControlAxisByPos Sometimes Remains Busy and Axis Continues Moving
CODESYS Forge
talk
(Post)
Hello, I am working with CODESYS SoftMotion CNC using a preprocessing chain, SMC_Interpolator, and SMC_ControlAxisByPos for controlling the CNC axes. I have noticed an issue when aborting CNC execution. When I execute the Abort command, I abort both the CNC preprocessing blocks and SMC_Interpolator. Normally the CNC process stops correctly. However, occasionally after the abort: SMC_Interpolator has already been aborted. CNC preprocessing has also been aborted/reset. But SMC_ControlAxisByPos.bBusy remains TRUE. The corresponding physical axis can still continue moving for some distance. This does not happen every time. My basic sequence is similar to: G-code / CNC preprocessing ↓ SMC_Interpolator ↓ Transformation ↓ SMC_ControlAxisByPos ↓ SoftMotion Axis When the operator requests CNC Abort, I set the Abort inputs of the interpolator and preprocessing function blocks. My questions are: Does SMC_Interpolator.bAbort only stop/reset the interpolator, or should it also stop the motion currently being executed by SMC_ControlAxisByPos? Can SMC_ControlAxisByPos remain Busy after the interpolator has been aborted? If bAvoidGaps = TRUE, could SMC_ControlAxisByPos still be trying to reach the last fSetPosition using the fGapVelocity / fGapAcceleration / fGapDeceleration settings after the interpolator has stopped? What is the recommended sequence for safely aborting a running CNC program? For example, should the sequence be something like: Stop / Abort Interpolator Disable SMC_ControlAxisByPos Execute MC_Stop for all physical axes Wait until all axes reach standstill Abort/reset preprocessing blocks and CNC queues Reset CNC state Re-enable axis controllers Or is there a recommended CODESYS CNC method for performing a complete CNC Abort? I would especially like to understand why SMC_ControlAxisByPos.bBusy can occasionally remain TRUE and continue moving the axis even though the interpolator and preprocessing blocks have already been aborted. CODESYS SoftMotion CNC is being used with real EtherCAT servo axes. Any explanation about the correct abort/stop sequence would be appreciated.
Last updated: 2026-09-25
Post by hhermsen on Codesys v3.5.22.10 - Internal error:System.AggregateException...Value cannot be null...Parameter name: expLValue
CODESYS Forge
talk
(Post)
Internal compiler error in SP22.x (expLValue null) — SP21 unaffected — need help isolating root cause I'm hitting the same internal compiler crash reported here. Same exact stack trace (AssignmentStatementReplacer → CreateAssignmentExpression → ArgumentNullException: Value cannot be null. Parameter name: expLValue), but in my case triggered by a self-written library, not a Visualization object — so this looks like a general SP22.x codegen regression rather than something tied to one specific language feature. What I've confirmed: Reverting to SP21 — no issue, builds clean SP22.x — internal compiler crash during Build > Application, code generation phase, with zero useful location info (no POU, no line number) check_for_errors/precompile-only passes cleanly; the crash only happens on a real full build, meaning static analysis never sees it coming What I've tried to isolate the cause (without success): Removed a suspected pattern involving a VAR_IN_OUT parameter used directly as a FOR-loop control variable, combined with early RETURN inside the loop — crash persisted identically afterward Confirmed no Visualization Manager or similar object in the project (ruling out the trigger in the linked report) Used an AI coding assistant (Claude Code) to search for other suspicious patterns — no luck pinpointing the exact statement Question @codesys-com Since the compiler gives no location info at all, is there any way to get more diagnostic output from the SP22.x codegen phase (verbose/debug build flags, log level settings, etc.)? Without that, isolating this in a large library means blind bisection — cutting the codebase in half repeatedly until the crash disappears, which is slow and error-prone without official tooling support. Happy to share the affected project or a minimal repro if that helps CODESYS engineering track it down.
Last updated: 2026-07-11
Post by ihatemaryfisher on Sorting array of any-sized structure
CODESYS Forge
talk
(Post)
In my machine's operation, I need to display multiples tables containing arrays of structured variables. The arrays change during operation, and my supervisor has advised me to write a new bubble-sort for each array. I think I can make a function to sort an array of any data type. This was my own project, and I'm a relatively new coder. I want to know the weaknesses in my approach, and a better method, if one exists. As far as I can test, the function accepts an array of a structured variable of any size, and sort it by any VAR in that structure. But it relies heavily on pointers, which I've heard are bad practice? Function call: // SORT BY BYTE-SIZED VAR IF xDoIt[6] THEN FUNBubbleSortSansBuffer( IN_pbySourcePointer := ADR(astArray[1]), // address of first byte in first element of array IN_pbyComparePointer:= ADR(astArray[1].byCompByte), // points to first byte of the comparing variable (variable you sort by) IN_uiStructureSize := SIZEOF(TYPE_STRUCTURE), // size, in bytes, of the structured variable IN_uiCompareSize := SIZEOF(astArray[1].byCompByte), // size, in bytes, of the comparing variable (variable you sort by) diArrayElements := UPPER_BOUND(astArray,1), // number of elements in array IN_xSmallToLarge := xSortOrder // whether to sort by small2large or large2small ); END_IF Function: FUNCTION FUNBubbleSortSansBuffer : BOOL VAR_INPUT IN_pbySourcePointer : POINTER TO BYTE; // points to beginning of array (first byte of first element) IN_pbyComparePointer: POINTER TO BYTE; // points to first byte of the comparing variable (variable you sort by) IN_uiStructureSize : UINT; // size, in bytes, of the structured variable IN_uiCompareSize : UINT; // size, in bytes, of the comparing variable (variable you sort by) diArrayElements : DINT; // number of elements in array IN_xSmallToLarge : BOOL; // whether to sort by small2large or large2small END_VAR VAR j : DINT; // repeat iteration over array until array ends i : DINT; // iterarte over array, swapping when necesary k : DINT; // iterator from 1 to size of structure (stepping 'through' a single element in array) dwSize : DWORD; // internal var for use in MEMUtils.MemCpy(<size>) // FOR SORTING BY BYTE VAR pbySourcePointer : POINTER TO BYTE; pbySourcePointer2 : POINTER TO BYTE; pbyComparePointer : POINTER TO BYTE; pbyComparePointer2 : POINTER TO BYTE; pbyPointerToBuffer : POINTER TO BYTE; // pointer to single byte buffer byBufferByte : BYTE; // single byte buffer END_VAR dwSize := UINT_TO_DWORD(IN_uiStructureSize); // get structure size (number of bytes) pbyPointerToBuffer := ADR(byBufferByte); // assign pointer to address of buffer byte (because MEMUtils.MemCpy requires a pointer input) CASE IN_uiCompareSize OF // depending on the size of the VAR to sort by (current functionality for BYTE and WORD/INT 1: // BYTE (8 BIT) FOR j := 1 TO diArrayElements DO // for number of elements in array FOR i := 1 TO (diArrayElements-1) DO // same thing, but row[i+1] row is included in swap logic pbySourcePointer := IN_pbySourcePointer + dwSize*(i-1); // point at #1 byte in array element[i] pbySourcePointer2 := pbySourcePointer + dwSize; // point at #1 byte in array element[i+1] // NOTE: because of memory locations, each array element is offset from one another by a number of bytes equal to the size of the structure // We can "walk" from array[i] to array[i+1] via steps equal to the size of the structure // e.g., ADR(array[i+1]) == ADR(array[i]) + SIZEOF([array datatype]) pbyComparePointer := IN_pbyComparePointer + dwSize*(i-1); // point to sorting variable in array element[i] pbyComparePointer2 := pbyComparePointer + dwSize; // point to sorting variable in array element[i+1] // using sort order (small -> large/large -> small) IF SEL(IN_xSmallToLarge, (pbyComparePointer2^ > pbyComparePointer^),(pbyComparePointer2^ < pbyComparePointer^)) THEN // This is where it gets tricky. We've identified pointers for the starting bytes of aArray[i] and aArray[i+1] // and we know the size of aArray[i]. We are going to swap individual bytes, one at a time, from aArray[i] and aArray[i+1] // this allows us to use only a single byte var as a buffer or temporary data storage // e.g., consider a structure consisting of a word, a byte, and a string. it is stored like this // |------WORD-------| |--BYTE-| |STRING------...| // astArray[1] == 1000 0100 0010 0001 1100 0011 1010 1010.... etc // astArray[2] == 0001 0010 0100 1000 0011 1100 0101 0101.... etc // performing a single swap (copy into a buffer, etc.) of the first byte of each array element creates this // astArray[1] == 0001 0100 0010 0001 1100 0011 1010 1010.... etc // astArray[2] == 1000 0010 0100 1000 0011 1100 0101 0101.... etc // incrementing the pointer adresses for the swap by 1 and swapping again swaps the next byte in each array element // astArray[1] == 0001 0010 0010 0001 1100 0011 1010 1010.... etc // astArray[2] == 1000 0100 0100 1000 0011 1100 0101 0101.... etc // continuing this from k to SIZEOF(TYPE_STRUCTURE) results in a toally swapped row FOR k := 1 TO IN_uiStructureSize DO // copy single byte[k] of array element 1 to buffer MEMUtils.MemCpy(pbyDest := (pbyPointerToBuffer), pbySrc := (pbySourcePointer+k-1), dwSize := 1); // copy single byte[k] of array element 2 to 1 MEMUtils.MemCpy(pbyDest := pbySourcePointer+k-1, pbySrc := (pbySourcePointer2+k-1), dwSize := 1); // copy buffer to byte[k] array element 2 MEMUtils.MemCpy(pbyDest := (pbySourcePointer2+k-1), pbySrc := pbyPointerToBuffer, dwSize := 1); END_FOR END_IF END_FOR END_FOR
Last updated: 2023-08-17
Post by baltzer on ICertificateVerifier.VerifyCertificate doesn't appear to override an ERR_CERT_HAS_EXPIRED rejection in TCP_Client.Upgrade()
CODESYS Forge
talk
(Post)
Setup: Net Base Services (NBS), TCP_Client + TLSContext, ePurpose := CLIENT_SIDE, connecting to a third-party embedded device (Velux KLF200 home automation gateway) whose factory-installed TLS certificate expired on 2026-07-12 and has no user-facing renewal path. Tested on both CODESYS Control for Raspberry Pi SL (3.5.19.0) and CODESYS Control Win V3 x64, with identical results on both. Goal: Accept the peer's expired certificate via a custom ICertificateVerifier implementation, since the certificate itself can never be replaced. Implementation: FUNCTION_BLOCK FB_MyCertVerifier IMPLEMENTS NBS.ICertificateVerifier VAR udiCallCount : UDINT; aeCurStateLog : ARRAY[0..9] OF NBS.RTS_IEC_RESULT; END_VAR METHOD VerifyCertificate : UDINT VAR_INPUT hCert : NBS.RTS_IEC_HANDLE; eCurState : NBS.RTS_IEC_RESULT; END_VAR IF udiCallCount < 10 THEN aeCurStateLog[udiCallCount] := eCurState; END_IF udiCallCount := udiCallCount + 1; VerifyCertificate := 0; // also tried: VerifyCertificate := eCurState; Wired in via tlsContext.itfCertVerifer := certVerifier at declaration time, alongside itfTLSContext := tlsContext set at TCP_Client's own declaration (per an earlier forum thread here on ensuring inline start-values on FB-typed variables are actually honored when nested one level deep - that fix was needed and worked correctly for getting the handshake to start at all). Observed: - VerifyCertificate is confirmed called exactly once per connection attempt (via the call counter above). - eCurState passed in = 0x709 = ERR_CERT_HAS_EXPIRED - correctly reflecting the actual state of the peer's certificate. - Packet capture confirms the full TLS 1.2 handshake completes correctly at the wire level: ClientHello -> ServerHello -> Certificate -> ServerKeyExchange -> ServerHelloDone -> our ClientKeyExchange/ChangeCipherSpec/Finished -> server's ChangeCipherSpec/Finished. Both sides successfully complete the cryptographic handshake. - Despite this, TCP_Client.Upgrade() subsequently returns NBS.ERROR.CONNECTION_ERROR, and the connection is torn down with a plain TCP FIN (not a TLS alert) roughly 7-8ms after our side ACKs the server's Finished message. - TCP_Client.eErrorID (inherited from LCon) remains NO_ERROR throughout - the failure is only visible via Upgrade()'s return value. - Returning 0 (ERR_OK/ERR_CERT_OK - confirmed via the CmpErrors2 Interfaces documentation that these are literally the same value) makes no difference. - Echoing eCurState back unchanged (on the theory that this callback might work like mbedTLS's native verify callback, where the incoming state is a flags value to be cleared/modified and returned, rather than paired with a separate fixed "accept" sentinel) also makes no difference. - Identical result across udiVerificationMode := 0, 1, and 2. - Importing the peer's certificate into the runtime's trusted certificate store via cert-import trusted makes no difference either. - With itfCertVerifer left at its default (no custom verifier) and udiVerificationMode := 2, the underlying engine sends its own fatal TLS alert (handshake_failure) immediately after receiving the peer's Certificate message - suggesting built-in validation may reject at the record layer independent of whether a custom verifier is even present. Questions: 1. Is there an additional step required for a custom ICertificateVerifier to actually override an ERR_CERT_HAS_EXPIRED (or more generally, any ERR_CERT_*) rejection, or is certificate validation enforced unconditionally for a CLIENT_SIDE TLSContext regardless of what the verifier returns? 2. Is 0/ERR_OK genuinely the correct "accept" return value for VerifyCertificate, or does it expect something else (a specific bit pattern, a different constant, a value that must be computed from hCert itself)? 3. Is there a supported way to connect to a peer presenting a certificate that is both self-signed and expired, short of the peer issuing a new certificate? Happy to share the full packet captures or additional test combinations if useful. Also worth noting for anyone finding this thread later: a maintained Node-RED library for this same device (node-red-contrib-velux-klf200, forked to handle this exact expired-certificate situation) solves it by disabling built-in TLS validation entirely (rejectUnauthorized: false) and doing fingerprint-based pinning by hand after the handshake completes, rather than relying on a validation callback - which may be the more realistic approach here too if ICertificateVerifier genuinely can't override this class of rejection. Thanks in advance /Jørgen Disclosure: most of the CODESYS code and this investigation was developed with the help of Claude (Anthropic's AI assistant) - the state machine design, SLIP/checksum implementation, and debugging methodology (including the packet capture analysis) were worked through in an extended back-and-forth with it. Posting here because we've run out of self-serviceable options and this now looks like it needs input from someone with visibility into NBS's actual implementation.
Last updated: 2026-07-26
Post by thn-power on Updating OPC UA Core Nodeset on PLS
CODESYS Forge
talk
(Post)
Hi After much trail and error I think I found the root cause to my OPC UA problem. The problem is that I cannot manage to build and download a program with a a custom OPC UA Information model. We use a Weidmueller WL2000 PLS, but the problem also exsist on the Win V3 PLC. Our custom information model is based on the latest versions of the OPC UA Core Nodeset v 1.05.03 (2023-09-20) and UA/DI nodeset 1.04.0 (2022-11-03) Those nodesets are installed in the Codesys Information Model Repository (3.5.19.6) However, when trying to build I get the following error. [ERROR] Untitled1: Communication Manager [Device: PLC Logic: Application]: The information model http://opcfoundation.org/UA/ is required by http://bos.org/ with a minimal publication date from 15.12.2023 but the device has only a model from 15.09.2021 installed. Probably the information model from 15.09.2021 is missing in the information model repository. [ERROR] Untitled1: Communication Manager [Device: PLC Logic: Application]: The information model http://opcfoundation.org/UA/DI/ is required by http://bos.org/ with a minimal publication date from 03.11.2022 but the device has only a model from 09.03.2021 installed. Probably the information model from 09.03.2021 is missing in the information model repository. Build complete -- 2 errors, 1 warnings : No download possible I think the problem is that the UA Core nodeset is implemented in the PLC firmware (at least that in Siemens S7), and that only includes the "old" nodeset from 2021-09-21 etc. So the question is, how (or if?) can I transfer the new nodeset to the PLS? I have created separate Information models under Communication manager with the newer code nodesets (UA and DI). But it seems that the compiler does not recognize them being excising, neither in the Codesys IDE or on the PLC. Would have guessed that this is a common issue, sine many manufacturers use the latest versions of the OPC UA standard, and that it would be a solution to the problem.
Last updated: 2024-09-20
To search for an exact phrase, put it in quotes. Example: "getting started docs"
To exclude a word or phrase, put a dash in front of it. Example: docs -help
To search on specific fields, use these field names instead of a general text search. You can group with AND or OR.